From c900913ec5abcd5398b9cc212f9478ffc01d6f66 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 11 Apr 2024 14:04:21 -0600 Subject: [PATCH] Added authentication logic --- internal/auth.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 internal/auth.go diff --git a/internal/auth.go b/internal/auth.go new file mode 100644 index 0000000..58aea04 --- /dev/null +++ b/internal/auth.go @@ -0,0 +1,114 @@ +package configurator + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "slices" + + "github.com/OpenCHAMI/jwtauth/v5" + "github.com/lestrrat-go/jwx/jwk" +) + +func VerifyClaims(testClaims []string, r *http.Request) (bool, error) { + // extract claims from JWT + _, claims, err := jwtauth.FromContext(r.Context()) + if err != nil { + return false, fmt.Errorf("failed to get claims(s) from token: %v", err) + } + + // verify that each one of the test claims are included + for _, testClaim := range testClaims { + _, ok := claims[testClaim] + if !ok { + return false, fmt.Errorf("failed to verify claim(s) from token: %s", testClaim) + } + } + return true, nil +} + +func VerifyScope(testScopes []string, r *http.Request) (bool, error) { + // extract the scopes from JWT + var scopes []string + _, claims, err := jwtauth.FromContext(r.Context()) + if err != nil { + return false, fmt.Errorf("failed to get claim(s) from token: %v", err) + } + + appendScopes := func(slice []string, scopeClaim any) []string { + switch scopeClaim.(type) { + case []any: + // convert all scopes to str and append + for _, s := range scopeClaim.([]any) { + switch s.(type) { + case string: + slice = append(slice, s.(string)) + } + } + case []string: + slice = append(slice, scopeClaim.([]string)...) + } + return slice + } + + // check for and append both "scp" and "scope" claims + v, ok := claims["scp"] + if ok { + scopes = appendScopes(scopes, v) + } + v, ok = claims["scope"] + if ok { + scopes = appendScopes(scopes, v) + } + + // check for both 'scp' and 'scope' claims for scope + scopeClaim, ok := claims["scp"] + if ok { + switch scopeClaim.(type) { + case []any: + // convert all scopes to str and append + for _, s := range scopeClaim.([]any) { + switch s.(type) { + case string: + scopes = append(scopes, s.(string)) + } + } + case []string: + scopes = append(scopes, scopeClaim.([]string)...) + } + } + scopeClaim, ok = claims["scope"] + if ok { + scopes = append(scopes, scopeClaim.([]string)...) + } + + // verify that each of the test scopes are included + for _, testScope := range testScopes { + index := slices.Index(scopes, testScope) + if index < 0 { + return false, fmt.Errorf("invalid or missing scope") + } + } + // NOTE: should this be ok if no scopes were found? + return true, nil +} + +func FetchPublicKeyFromURL(url string) (*jwtauth.JWTAuth, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + set, err := jwk.Fetch(ctx, url) + if err != nil { + return nil, fmt.Errorf("%v", err) + } + jwks, err := json.Marshal(set) + if err != nil { + return nil, fmt.Errorf("failed to marshal JWKS: %v", err) + } + tokenAuth, err := jwtauth.NewKeySet(jwks) + if err != nil { + return nil, fmt.Errorf("failed to initialize JWKS: %v", err) + } + + return tokenAuth, nil +}