Skip to content

Commit

Permalink
feat: Add scopes api and some other utils
Browse files Browse the repository at this point in the history
  • Loading branch information
gfyrag committed Aug 2, 2022
1 parent cc82aaa commit d4fd70b
Show file tree
Hide file tree
Showing 14 changed files with 708 additions and 148 deletions.
3 changes: 0 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/pem"
"errors"
"fmt"
"strings"

"github.com/google/uuid"
auth "github.com/numary/auth/pkg"
Expand Down Expand Up @@ -98,7 +97,6 @@ var serveCmd = &cobra.Command{
oidc.GrantTypeClientCredentials,
},
"post_logout_redirect_uri": `["http://localhost:3000/"]`,
"scopes": fmt.Sprintf(`["%s"]`, strings.Join(auth.Scopes, `", "`)),
"access_token_type": op.AccessTokenTypeJWT,
"secrets": `[{"value": "1234"}]`,
}),
Expand All @@ -124,7 +122,6 @@ var serveCmd = &cobra.Command{
},
AccessTokenType: op.AccessTokenTypeJWT,
PostLogoutRedirectUris: auth.Array[string]{"http://localhost:3000/"},
Scopes: auth.Scopes,
}).Error
},
})
Expand Down
2 changes: 1 addition & 1 deletion demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const configuration = {
client_id: 'demo',
redirect_uri: 'http://localhost:3000/auth-callback',
silent_redirect_uri: 'http://localhost:3000/silent-auth-callback',
scope: 'openid offline_access email transactions:write accounts:write stats payments:write connectors:write',
scope: 'openid offline_access email',
authority: 'http://localhost:8080',
post_logout_redirect_uri: 'http://localhost:3000'
};
Expand Down
212 changes: 113 additions & 99 deletions pkg/api/clients.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@
package api

import (
"encoding/json"
"net/http"

"github.com/gorilla/mux"
auth "github.com/numary/auth/pkg"
"github.com/numary/go-libs/sharedapi"
_ "github.com/numary/go-libs/sharedapi"
"github.com/numary/go-libs/sharedlogging"
"github.com/zitadel/oidc/pkg/oidc"
"go.opentelemetry.io/otel/trace"
"gorm.io/gorm"
)

func validationError(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(sharedapi.ErrorResponse{
ErrorCode: "VALIDATION",
ErrorMessage: err.Error(),
}); err != nil {
sharedlogging.GetLogger(r.Context()).Info("Error validating request: %s", err)
}
type client struct {
auth.ClientOptions
ID string `json:"id"`
Scopes []string `json:"scopes"`
}

func internalServerError(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
if err := json.NewEncoder(w).Encode(sharedapi.ErrorResponse{
ErrorCode: "INTERNAL",
ErrorMessage: err.Error(),
}); err != nil {
trace.SpanFromContext(r.Context()).RecordError(err)
func mapBusinessClient(c auth.Client) client {
public := true
for _, grantType := range c.GrantTypes {
if grantType == oidc.GrantTypeClientCredentials {
public = false
}
}
return client{
ClientOptions: auth.ClientOptions{
Public: public,
RedirectUris: c.RedirectURIs,
Description: c.Description,
Name: c.Name,
PostLogoutRedirectUris: c.PostLogoutRedirectUris,
},
ID: c.Id,
Scopes: func() []string {
ret := make([]string, 0)
for _, scope := range c.Scopes {
ret = append(ret, scope.ID)
}
return ret
}(),
}
}

func writeObject[T any](w http.ResponseWriter, r *http.Request, v T) {
if err := json.NewEncoder(w).Encode(sharedapi.BaseResponse[T]{
Data: &v,
}); err != nil {
trace.SpanFromContext(r.Context()).RecordError(err)
}
type secretCreate struct {
Name string `json:"name"`
}

type client struct {
auth.ClientOptions
ID string `json:"id"`
type secretCreateResult struct {
ID string `json:"id"`
LastDigits string `json:"lastDigits"`
Name string `json:"name"`
Clear string `json:"clear"`
}

func deleteSecret(db *gorm.DB) http.HandlerFunc {
Expand All @@ -65,53 +71,32 @@ func deleteSecret(db *gorm.DB) http.HandlerFunc {
return
}

if err := db.Save(client).Error; err != nil {
internalServerError(w, r, err)
if err := saveObject(w, r, db, client); err != nil {
return
}
w.WriteHeader(http.StatusNoContent)
}
}

type secretCreate struct {
Name string `json:"name"`
}

type secretCreateResult struct {
ID string `json:"id"`
LastDigits string `json:"lastDigits"`
Name string `json:"name"`
Clear string `json:"clear"`
}

func createSecret(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
client := &auth.Client{}
if err := db.Find(client, "id = ?", mux.Vars(r)["clientId"]).Error; err != nil {
switch err {
case gorm.ErrRecordNotFound:
w.WriteHeader(http.StatusNotFound)
default:
internalServerError(w, r, err)
}
client := findById[auth.Client](w, r, db, "clientId")
if client == nil {
return
}

sc := secretCreate{}
if err := json.NewDecoder(r.Body).Decode(&sc); err != nil {
validationError(w, r, err)
sc := readJSONObject[secretCreate](w, r)
if sc == nil {
return
}

secret, clear := client.GenerateNewSecret(sc.Name)

if err := db.Save(client).Error; err != nil {
internalServerError(w, r, err)
if err := saveObject(w, r, db, client); err != nil {
return
}

w.WriteHeader(http.StatusOK)

writeObject(w, r, secretCreateResult{
writeJSONObject(w, r, secretCreateResult{
ID: secret.ID,
LastDigits: secret.LastDigits,
Name: secret.Name,
Expand All @@ -122,72 +107,62 @@ func createSecret(db *gorm.DB) http.HandlerFunc {

func readClient(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
client := &auth.Client{}
if err := db.Find(client, "id = ?", mux.Vars(r)["clientId"]).Error; err != nil {
switch err {
case gorm.ErrRecordNotFound:
w.WriteHeader(http.StatusNotFound)
default:
internalServerError(w, r, err)
}
client := findById[auth.Client](w, r, db, "clientId")
if client == nil {
return
}
if err := loadAssociation(w, r, db, client, "Scopes", &client.Scopes); err != nil {
return
}
writeObject(w, r, client)
writeJSONObject(w, r, mapBusinessClient(*client))
}
}

func listClients(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
clients := make([]auth.Client, 0)
if err := db.Find(&clients).Error; err != nil {
if err := db.
WithContext(r.Context()).
Preload("Scopes").
Find(&clients).Error; err != nil {
internalServerError(w, r, err)
return
}
writeObject(w, r, clients)
writeJSONObject(w, r, mapList(clients, mapBusinessClient))
}
}

func updateClient(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

c := auth.Client{}
if err := db.First(&c, "id = ?", mux.Vars(r)["clientId"]).Error; err != nil {
switch err {
case gorm.ErrRecordNotFound:
w.WriteHeader(http.StatusNotFound)
default:
internalServerError(w, r, err)
}
c := findById[auth.Client](w, r, db, "clientId")
if c == nil {
return
}

opts := auth.ClientOptions{}
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
validationError(w, r, err)
opts := readJSONObject[auth.ClientOptions](w, r)
if opts == nil {
return
}

c.Update(opts)
c.Update(*opts)

if err := db.Save(c).Error; err != nil {
internalServerError(w, r, err)
if err := saveObject(w, r, db, c); err != nil {
return
}

w.WriteHeader(http.StatusOK)
if err := loadAssociation(w, r, db, c, "Scopes", &c.Scopes); err != nil {
return
}

writeObject(w, r, client{
ClientOptions: opts,
ID: c.Id,
})
writeJSONObject(w, r, mapBusinessClient(*c))
}
}

func createClient(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
opts := auth.ClientOptions{}
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
validationError(w, r, err)
opts := readJSONObject[auth.ClientOptions](w, r)
if opts == nil {
return
}

Expand All @@ -199,18 +174,57 @@ func createClient(db *gorm.DB) http.HandlerFunc {
grantTypes = append(grantTypes, oidc.GrantTypeClientCredentials)
}

c := auth.NewClient(opts)
if err := db.Create(c).Error; err != nil {
internalServerError(w, r, err)
c := auth.NewClient(*opts)
if err := createObject(w, r, db, c); err != nil {
return
}

w.WriteHeader(http.StatusCreated)
w.Header().Set("Location", "./"+c.Id)
writeCreatedJSONObject(w, r, mapBusinessClient(*c), c.Id)
}
}

writeObject(w, r, client{
ClientOptions: opts,
ID: c.Id,
})
func deleteScopeOfClient(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
client := findById[auth.Client](w, r, db, "clientId")
if client == nil {
return
}
scope := findById[auth.Scope](w, r, db, "scopeId")
if scope == nil {
return
}
if err := loadAssociation(w, r, db, client, "Scopes", &client.Scopes); err != nil {
return
}
if !client.HasScope(scope.ID) {
return
}
if err := removeFromAssociation(w, r, db, client, "Scopes", scope); err != nil {
return
}
w.WriteHeader(http.StatusNoContent)
}
}

func addScopeToClient(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
client := findById[auth.Client](w, r, db, "clientId")
if client == nil {
return
}
scope := findById[auth.Scope](w, r, db, "scopeId")
if scope == nil {
return
}
if err := loadAssociation(w, r, db, client, "Scopes", &client.Scopes); err != nil {
return
}
if client.HasScope(scope.ID) {
return
}
if err := appendToAssociation(w, r, db, client, "Scopes", scope); err != nil {
return
}
w.WriteHeader(http.StatusNoContent)
}
}
Loading

0 comments on commit d4fd70b

Please sign in to comment.