Skip to content

Commit

Permalink
Improved errors if you don't have permission
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanratcliffe committed Oct 13, 2023
1 parent f31aa5d commit 0f043d5
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 21 deletions.
70 changes: 50 additions & 20 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ func Execute() {
}
}

// extracts custom claims from a JWT token. Note that this does not verify the
// signature of the token, it just extracts the claims from the payload
func extractClaims(token string) (*sdp.CustomClaims, error) {
// We aren't interested in checking the signature of the token since
// the server will do that. All we need to do is make sure it
// contains the right scopes. Therefore we just parse the payload
// directly
sections := strings.Split(token, ".")

if len(sections) != 3 {
return nil, errors.New("token is not a JWT")
}

// Decode the payload
decodedPayload, err := base64.RawURLEncoding.DecodeString(sections[1])

if err != nil {
return nil, fmt.Errorf("error decoding token payload: %w", err)
}

// Parse the payload
claims := new(sdp.CustomClaims)

err = json.Unmarshal(decodedPayload, claims)

if err != nil {
return nil, fmt.Errorf("error parsing token payload: %w", err)
}

return claims, nil
}

// reads the locally cached token if it exists and is valid returns the token,
// its scopes, and an error if any. The scopes are returned even if they are
// insufficient to allow cached tokens to be added to rather than constantly
Expand Down Expand Up @@ -91,30 +123,14 @@ func readLocalToken(homeDir string, expectedScopes []string) (string, []string,
return "", nil, errors.New("token is no longer valid")
}

// We aren't interested in checking the signature of the token since
// the server will do that. All we need to do is make sure it
// contains the right scopes. Therefore we just parse the payload
// directly
sections := strings.Split(token.AccessToken, ".")

if len(sections) != 3 {
return "", nil, errors.New("token is not a JWT")
}

// Decode the payload
decodedPayload, err := base64.RawURLEncoding.DecodeString(sections[1])
claims, err := extractClaims(token.AccessToken)

if err != nil {
return "", nil, fmt.Errorf("error decoding token payload: %w", err)
return "", nil, fmt.Errorf("error extracting claims from token: %w", err)
}

// Parse the payload
claims := new(sdp.CustomClaims)

err = json.Unmarshal(decodedPayload, claims)

if err != nil {
return "", nil, fmt.Errorf("error parsing token payload: %w", err)
if claims.Scope == "" {
return "", nil, errors.New("token does not have any scopes")
}

currentScopes := strings.Split(claims.Scope, " ")
Expand Down Expand Up @@ -275,6 +291,20 @@ func ensureToken(ctx context.Context, requiredScopes []string) (context.Context,
log.WithContext(ctx).WithError(err).Warn("failed to shutdown auth callback server, but continuing anyway")
}

// Check that we actually got the claims we asked for. If you don't have
// permission auth0 will just not assign those scopes rather than fail
claims, err := extractClaims(token.AccessToken)

if err != nil {
return ctx, fmt.Errorf("error extracting claims from token: %w", err)
}

for _, scope := range requiredScopes {
if !claims.HasScope(scope) {
return ctx, fmt.Errorf("authenticated successfully, but you don't have the required permission: '%v'", scope)
}
}

log.WithContext(ctx).Info("Authenticated successfully ✅")

// Save the token locally
Expand Down
4 changes: 3 additions & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cmd

import "testing"
import (
"testing"
)

func TestParseChangeUrl(t *testing.T) {
tests := []struct {
Expand Down

0 comments on commit 0f043d5

Please sign in to comment.