Skip to content

Add role and permission handling in JWT token - #47

Closed
BrandtKruger wants to merge 7 commits into
kinde-oss:mainfrom
BrandtKruger:main
Closed

Add role and permission handling in JWT token#47
BrandtKruger wants to merge 7 commits into
kinde-oss:mainfrom
BrandtKruger:main

Conversation

@BrandtKruger

Copy link
Copy Markdown
Contributor
  • Implemented GetRoles and HasRoles methods to retrieve user roles from both standard and Hasura claims.
  • Enhanced GetPermissions, GetOrganizationCode, and GetFeatureFlags methods to support fallback between standard and Hasura claim formats.
  • Added comprehensive unit tests for role and permission retrieval, ensuring correct functionality across various scenarios.

This update improves the flexibility and robustness of token handling in the application.

Explain your changes

Suppose there is a related issue with enough detail for a reviewer to understand your changes fully. In that case, you can omit an explanation and instead include either “Fixes #XX” or “Updates #XX” where “XX” is the issue number.

Checklist

🛟 If you need help, consider asking for advice over in the Kinde community.

- Implemented `GetRoles` and `HasRoles` methods to retrieve user roles from both standard and Hasura claims.
- Enhanced `GetPermissions`, `GetOrganizationCode`, and `GetFeatureFlags` methods to support fallback between standard and Hasura claim formats.
- Added comprehensive unit tests for role and permission retrieval, ensuring correct functionality across various scenarios.

This update improves the flexibility and robustness of token handling in the application.
@BrandtKruger
BrandtKruger requested a review from a team as a code owner November 23, 2025 15:11
@coderabbitai

coderabbitai Bot commented Nov 23, 2025

Copy link
Copy Markdown

Walkthrough

Adds extensive tests across Gin and OAuth2 flows; implements a Kinde Account API client with pagination and merging; enriches JWT token parsing and claim helpers (roles, feature flags, profile, ParseIDTokenUnverified); adds token-to-Account-API helper methods and exposes Account API client creation from the authorization-code flow.

Changes

Cohort / File(s) Summary
Gin Kinde Tests
frameworks/gin_kinde/gin_kinde_test.go
Adds tests for session storage (code verifier, raw token, state, post-auth redirect, generic items) and UseKindeAuth middleware paths (redirects, error handling, client injection).
JWT Core & Token Enhancements
jwt/token.go, jwt/jwt.go, jwt/jwt_options.go
Adds Role and UserProfile types and many claim accessors (GetRoles, HasRoles, GetUserProfile, GetClaims, GetClaim, GetUserOrganizations, GetFeatureFlag helpers); supports Hasura x-hasura-* fallbacks; adds ParseIDTokenUnverified and new validation options.
JWT Tests
jwt/jwt_test.go, jwt/jwt_test.go*
Adds extensive unit tests covering roles, permissions, orgs, feature flags, profile extraction, token helpers, and Hasura-prefixed claim fallbacks.
JWT Account API Helpers & Tests
jwt/account_api_helpers.go, jwt/account_api_helpers_test.go
Adds Token methods to fetch permissions/roles/feature-flags (token vs Account API via force option) and entitlements via Account API; introduces types for permissions/entitlements and tests using mocked account API servers.
Kinde Account API Client & Tests
kinde/account_api/client.go, kinde/account_api/client_test.go
New account_api.Client with NewClient, WithHTTPClient, CallAccountAPI, CallAccountAPIPaginated; pagination metadata handling, array/object merging and deduplication; tests for auth header, error cases, pagination flows, and merge behavior.
OAuth2 Authorization Code: API Client & Interface
oauth2/authorization_code/authorization_code.go, oauth2/authorization_code/account_api.go, oauth2/authorization_code/account_api_test.go
Adds GetToken to IAuthorizationCodeFlow; AuthorizationCodeFlow.GetAccountAPIClient builds an account_api.Client using token issuer and access token; includes skipped test scaffold.
OAuth2 Authorization Code Tests & Mocks
oauth2/authorization_code/options_test.go, oauth2/authorization_code/token_source_test.go
Adds option tests (PKCE, prompts, audiences, token validation, state generator, session hooks) and token-source tests with MockSessionHooksForTokenSource implementing session hook methods.
OAuth2 Client Credentials Tests & Mocks
oauth2/client_credentials/options_test.go, oauth2/client_credentials/token_source_test.go
Adds option tests (WithKindeManagementAPI audience parsing, token validation) and token-source tests with MockSessionHooksForTokenSource (SetRawToken/GetRawToken) for caching/error scenarios.

*Note: multiple JWT test additions consolidated under "JWT Tests".

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Test/Caller
    participant Token as jwt.Token
    participant AccountAPI as account_api.Client
    participant HTTP as Account API Server

    Note over Caller,AccountAPI: Token.GetPermissionsWithAPI(forceAPI=true)
    Caller->>Token: GetPermissionsWithAPI(ctx, apiClient, ForceAPI=true)
    Token->>AccountAPI: CallAccountAPIPaginated("/organizations/:org/permissions")
    AccountAPI->>HTTP: GET /... (Authorization: Bearer <access_token>)
    HTTP-->>AccountAPI: 200 JSON page + metadata
    AccountAPI-->>Token: aggregated pages
    Token->>Token: merge/dedupe/transform pages -> PermissionsWithOrg
    Token-->>Caller: PermissionsWithOrg
Loading
sequenceDiagram
    participant Client as Browser
    participant Router as Gin Router
    participant Middleware as UseKindeAuth
    participant Session as Session Store
    participant Flow as AuthorizationCodeFlow
    participant Handler as Protected Handler

    Client->>Router: Request private route
    Router->>Middleware: invoke middleware
    Middleware->>Session: GetRawToken()
    alt token present
        Session-->>Middleware: oauth2.Token
        Middleware->>Flow: GetAccountAPIClient(ctx)
        Flow-->>Middleware: account_api.Client
        Middleware->>Router: inject client into context
        Router->>Handler: forward request
        Handler-->>Client: 200 OK
    else token missing/invalid
        Session-->>Middleware: nil/error
        Middleware->>Client: 302 Redirect to auth endpoint
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • Pagination, cursor handling, merging and deduplication logic in kinde/account_api/client.go.
    • Type assertions, pagination mapping, and error wrapping in jwt/account_api_helpers.go and its tests.
    • Claim-extraction precedence and Hasura fallback behavior in jwt/token.go and corresponding unit tests.
    • Interface change: addition of GetToken to IAuthorizationCodeFlow and interaction in GetAccountAPIClient.
    • Consistency of mock session hook interfaces and signatures across authorization_code and client_credentials tests.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add role and permission handling in JWT token' clearly and concisely describes the main changes in the PR, which focus on implementing role/permission retrieval methods and enhancing token claim handling.
Description check ✅ Passed The description is directly related to the changeset, detailing the implementation of GetRoles/HasRoles methods, enhancements to GetPermissions/GetOrganizationCode/GetFeatureFlags with Hasura fallback support, and comprehensive unit tests added to verify the changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (12)
frameworks/gin_kinde/gin_kinde_test.go (2)

15-214: Suggest extracting test setup into a helper function.

The test setup pattern is repeated in every test case (creating store, router, configuring middleware, and extracting session). This duplication can be reduced by extracting a common helper function.

Consider adding a helper function:

func setupTestSession(t *testing.T) (sessions.Session, *httptest.ResponseRecorder) {
	t.Helper()
	store := cookie.NewStore([]byte("secret"))
	router := gin.New()
	router.Use(sessions.Sessions("test", store))
	
	var session sessions.Session
	router.GET("/", func(c *gin.Context) {
		session = sessions.Default(c)
	})
	
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	router.ServeHTTP(w, req)
	
	return session, w
}

Then simplify tests:

-	store := cookie.NewStore([]byte("secret"))
-	router := gin.New()
-	router.Use(sessions.Sessions("test", store))
-
-	var session sessions.Session
-	router.GET("/", func(c *gin.Context) {
-		session = sessions.Default(c)
-	})
-
-	w := httptest.NewRecorder()
-	req := httptest.NewRequest("GET", "/", nil)
-	router.ServeHTTP(w, req)
-
+	session, _ := setupTestSession(t)
 	storage := &SessionStorage{session: session}

221-243: Strengthen test assertion for kinde_client in context.

The test "creates kinde client in context" only verifies that the response recorder is not nil (line 242), which doesn't actually validate that the kinde_client was set in the context. Consider adding a test route that accesses the context value to verify the client was properly set.

 	privateGroup := router.Group("/private")
 	err := UseKindeAuth(
 		privateGroup,
 		"https://test.kinde.com",
 		"test_client_id",
 		"test_client_secret",
 		"http://localhost:8080",
 	)
 	assert.Nil(t, err)
+	
+	var clientFound bool
+	privateGroup.GET("/test", func(c *gin.Context) {
+		_, clientFound = c.Get("kinde_client")
+		c.String(200, "ok")
+	})

 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/private/test", nil)
 	router.ServeHTTP(w, req)

-	// The middleware should set the kinde_client in context
-	// This is tested indirectly through the middleware execution
-	assert.NotNil(t, w)
+	assert.True(t, clientFound, "kinde_client should be set in context")
oauth2/client_credentials/token_source_test.go (2)

34-58: Test name vs behavior for validateToken

The subtest is named "successfully validates token" but explicitly expects validation to fail due to missing JWKS, only asserting that err is non‑nil. Consider renaming the test (e.g. to “fails validation without JWKS”) or tightening the assertion (e.g. checking an error substring) so the intent matches the behavior under test.


60-109: Token source tests are structurally OK but only assert failure cases

Both subtests in TestSessionTokenSource_Token and the getValidatedToken test primarily assert that “something failed” (non‑nil error) under misconfigured or error conditions. That’s fine as smoke coverage, but if you want these to guard behavior more strongly, adding a happy‑path unit test (with a fake token source / stubbed JWKS) would better exercise caching and validation logic without depending on integration tests.

oauth2/authorization_code/token_source_test.go (1)

62-140: Authorization code token source tests focus only on error paths

Similar to the client‑credentials tests, these subtests validate error propagation when JWKS/config or session hooks fail, while names like "successfully validates token" might suggest a passing validation scenario. If/when you add more coverage, consider:

  • Renaming error‑path tests to reflect the expected failure.
  • Adding at least one happy‑path unit (with a stubbed JWKS/keyfunc) so validateToken, Token, and getValidatedToken are exercised under success conditions as well.
jwt/account_api_helpers.go (1)

61-95: Roles API helper returns normalized Role objects

GetRolesWithAPI mirrors GetRoles() semantics:

  • Uses token roles when forceAPI is false.
  • On forceAPI == true, calls account_api/v1/roles and converts each entry into the exported Role type.

If you expect org‑specific role information to matter, consider later extending the return type (or adding a parallel helper) to surface OrgCode as well, but as‑is this is coherent with how GetRoles() works.

jwt/token.go (1)

259-299: Feature flag extraction helper is a nice reuse point and filters malformed entries

The refactored GetFeatureFlags plus extractFeatureFlags:

  • Prefer the standard "feature_flags" claim, then fallback to "x-hasura-feature-flags".
  • Build a map[string]FeatureFlag only from entries that contain both "t" and "v".
  • Use toString for the type so non‑string type markers don’t panic.

This is consistent with existing feature flag usage and matches the new tests that ensure malformed flags are ignored.

kinde/account_api/client.go (5)

33-47: Consider adding input validation for required parameters.

The constructor doesn't validate that baseURL is non-empty or that getToken is non-nil. Adding early validation would provide clearer error messages at construction time rather than later during API calls.

Apply this diff to add validation:

 func NewClient(baseURL string, getToken func(ctx context.Context) (string, error), opts ...ClientOption) (*Client, error) {
+	if baseURL == "" {
+		return nil, fmt.Errorf("baseURL cannot be empty")
+	}
+	if getToken == nil {
+		return nil, fmt.Errorf("getToken function cannot be nil")
+	}
+
 	// Remove trailing slash
 	baseURL = strings.TrimSuffix(baseURL, "/")

61-62: Handle leading slashes in route to prevent double slashes in URL.

If route starts with a /, the URL construction will produce a double slash (e.g., https://api.example.com//v1/users). While many servers tolerate this, it's not RFC-compliant and could cause issues.

Apply this diff to normalize the route:

 	// Build URL
-	apiURL := fmt.Sprintf("%s/%s", c.baseURL, route)
+	route = strings.TrimPrefix(route, "/")
+	apiURL := fmt.Sprintf("%s/%s", c.baseURL, route)

251-275: Deduplication relies on exact byte-matching of JSON.

The deduplication uses string(item) as the key, which means JSON objects with identical content but different key ordering would be treated as duplicates. While API responses typically have consistent key ordering, this approach is fragile.

For more robust deduplication, consider unmarshaling to a comparable format and using semantic comparison, or add a comment documenting this assumption:

 // mergeArrays merges two arrays and removes duplicates.
+// Note: Deduplication assumes identical JSON byte representation;
+// objects with the same content but different key order are treated as distinct.
 func mergeArrays(arr1, arr2 []json.RawMessage) []json.RawMessage {

294-302: Document the fallback behavior when inputs aren't maps.

When either obj1 or obj2 is not a map[string]interface{}, the function returns obj2, which discards obj1 entirely. For the pagination use case where both should be maps, this is acceptable, but it could be documented to clarify the expected behavior.

Add a comment explaining this:

 // deepMergeObjects deeply merges two objects.
+// If either input is not a map[string]interface{}, returns obj2.
 func deepMergeObjects(obj1, obj2 interface{}) interface{} {

332-354: Deduplication using fmt.Sprintf("%v", item) is fragile for complex types.

Using fmt.Sprintf("%v", item) to generate deduplication keys has limitations:

  • Maps with identical content but different iteration order produce different string representations
  • Complex nested structures may not format consistently
  • This can cause incorrect deduplication or failure to deduplicate semantically identical items

For more reliable deduplication, consider marshaling items to JSON and comparing the byte representation (similar to mergeArrays):

 func mergeInterfaceArrays(arr1, arr2 []interface{}) []interface{} {
 	seen := make(map[string]bool)
 	result := []interface{}{}
 
 	for _, item := range arr1 {
-		key := fmt.Sprintf("%v", item)
+		itemBytes, _ := json.Marshal(item)
+		key := string(itemBytes)
 		if !seen[key] {
 			seen[key] = true
 			result = append(result, item)
 		}
 	}
 
 	for _, item := range arr2 {
-		key := fmt.Sprintf("%v", item)
+		itemBytes, _ := json.Marshal(item)
+		key := string(itemBytes)
 		if !seen[key] {
 			seen[key] = true
 			result = append(result, item)
 		}
 	}
 
 	return result
 }

Note: This still has the JSON key-ordering limitation, but it's more consistent with the mergeArrays approach.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5dde31a and f569405.

📒 Files selected for processing (14)
  • frameworks/gin_kinde/gin_kinde_test.go (1 hunks)
  • jwt/account_api_helpers.go (1 hunks)
  • jwt/account_api_helpers_test.go (1 hunks)
  • jwt/jwt_test.go (1 hunks)
  • jwt/token.go (5 hunks)
  • kinde/account_api/client.go (1 hunks)
  • kinde/account_api/client_test.go (1 hunks)
  • oauth2/authorization_code/account_api.go (1 hunks)
  • oauth2/authorization_code/account_api_test.go (1 hunks)
  • oauth2/authorization_code/authorization_code.go (1 hunks)
  • oauth2/authorization_code/options_test.go (1 hunks)
  • oauth2/authorization_code/token_source_test.go (1 hunks)
  • oauth2/client_credentials/options_test.go (1 hunks)
  • oauth2/client_credentials/token_source_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
oauth2/authorization_code/account_api.go (2)
oauth2/authorization_code/authorization_code.go (1)
  • AuthorizationCodeFlow (78-90)
kinde/account_api/client.go (2)
  • Client (15-19)
  • NewClient (33-48)
jwt/account_api_helpers_test.go (2)
jwt/account_api_helpers.go (1)
  • GetPermissionsOptions (11-13)
jwt/jwt.go (1)
  • Token (22-27)
oauth2/client_credentials/token_source_test.go (2)
oauth2/client_credentials/client_credentials.go (1)
  • ClientCredentialsFlow (30-35)
jwt/jwt_options.go (1)
  • WillValidateWithJWKSUrl (28-53)
oauth2/authorization_code/authorization_code.go (1)
jwt/jwt.go (1)
  • Token (22-27)
frameworks/gin_kinde/gin_kinde_test.go (1)
frameworks/gin_kinde/gin_kinde.go (1)
  • UseKindeAuth (99-167)
oauth2/authorization_code/token_source_test.go (2)
oauth2/authorization_code/authorization_code.go (1)
  • AuthorizationCodeFlow (78-90)
jwt/jwt_options.go (1)
  • WillValidateWithJWKSUrl (28-53)
oauth2/client_credentials/options_test.go (3)
oauth2/client_credentials/client_credentials.go (1)
  • ClientCredentialsFlow (30-35)
oauth2/client_credentials/options.go (1)
  • WithKindeManagementAPI (41-60)
jwt/jwt_options.go (1)
  • WillValidateAlgorithm (77-85)
jwt/jwt_test.go (1)
jwt/jwt.go (1)
  • Token (22-27)
kinde/account_api/client_test.go (1)
kinde/account_api/client.go (5)
  • NewClient (33-48)
  • Client (15-19)
  • WithHTTPClient (25-29)
  • BaseAccountResponse (99-102)
  • Metadata (93-96)
jwt/token.go (1)
jwt/jwt.go (2)
  • Token (22-27)
  • ParseFromString (42-44)
jwt/account_api_helpers.go (4)
kinde/management_api/oas_schemas_gen.go (2)
  • Permissions (17566-17575)
  • Roles (18925-18936)
jwt/jwt.go (1)
  • Token (22-27)
kinde/account_api/client.go (1)
  • Client (15-19)
jwt/token.go (2)
  • Role (353-357)
  • FeatureFlag (254-257)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (20)
oauth2/authorization_code/account_api_test.go (1)

7-14: LGTM - Appropriate test placeholder for integration testing.

The skipped test appropriately recognizes that GetAccountAPIClient requires a full OAuth flow setup. This placeholder documents the need for integration tests while allowing the PR to proceed with unit-testable components.

oauth2/authorization_code/authorization_code.go (1)

57-58: LGTM - Interface extension aligns with PR objectives.

Adding GetToken to the IAuthorizationCodeFlow interface is a logical extension that supports the new Account API integration. The implementation already exists and is correctly implemented.

oauth2/authorization_code/account_api.go (1)

10-38: LGTM - Well-structured Account API client integration.

The implementation correctly:

  • Retrieves and validates the token
  • Extracts the issuer for the base URL
  • Creates a token provider function for API authentication
  • Handles all error paths appropriately

The getTokenFunc closure creates a fresh token on each API call, which ensures token validity but may re-validate unnecessarily. This is acceptable for the Chill review mode, as it prioritizes correctness over micro-optimizations.

oauth2/client_credentials/options_test.go (1)

1-213: LGTM - Comprehensive test coverage for option configurators.

The test suite thoroughly covers all option functions with both happy paths and edge cases. The tests are well-organized, use parallel execution appropriately, and handle error scenarios gracefully.

oauth2/authorization_code/options_test.go (1)

1-254: LGTM - Comprehensive authorization code flow options testing.

The test suite provides excellent coverage of all configuration options, including important security features like PKCE. The tests appropriately:

  • Verify deduplication of auth parameters
  • Test custom state generators
  • Validate PKCE setup with both S256 and plain methods
  • Ensure secure defaults (S256) for invalid challenge methods
kinde/account_api/client_test.go (1)

1-318: LGTM - Excellent test coverage for Account API client.

The test suite comprehensively covers:

  • Client initialization and configuration
  • Successful API calls with proper headers and authentication
  • Error handling for HTTP errors and missing tokens
  • Complex pagination scenarios with cursor-based navigation
  • Data merging utilities for both arrays and objects

The pagination tests are particularly thorough, validating multi-page flows and ensuring correct data aggregation.

jwt/account_api_helpers_test.go (1)

1-234: LGTM - Comprehensive JWT API helper tests.

The test suite thoroughly validates all API helper methods with:

  • Dual-path testing for token-based and API-based data retrieval
  • Proper mock server setup for API responses
  • Verification of complex data structures (permissions, roles, feature flags, entitlements)
  • Clean test helper function for token creation

The consistent test patterns and comprehensive coverage ensure the API helpers work correctly in both fallback and forced API modes.

oauth2/client_credentials/token_source_test.go (1)

16-32: MockSessionHooksForTokenSource implementation looks solid

The mock correctly wires SetRawToken and GetRawToken, and the nil check before type assertion in GetRawToken avoids panics. No changes needed here.

oauth2/authorization_code/token_source_test.go (1)

14-60: MockSessionHooksForTokenSource covers the full session hooks surface

The mock cleanly implements all required session hook methods and uses testify/mock idiomatically, including safe return value handling for getters. This is a good reusable test helper.

jwt/jwt_test.go (3)

1130-1329: Role and HasRoles tests cover the key scenarios well

The GetRoles and HasRoles tests exercise:

  • Standard vs Hasura role claims.
  • Object vs string role representations.
  • Precedence of standard over Hasura roles.
  • Presence/absence checks in HasRoles, including the empty‑argument behavior.

These align with the implementation in jwt/token.go and provide good coverage of the new role API.


1393-1463: GetClaim tests are clear and accurate

The new GetClaim tests correctly cover:

  • The parsed == nil case.
  • Existing claims (including custom ones).
  • Non‑existent claims.

They match the simple map‑lookup semantics and are sufficient for this helper.


1466-1593: Hasura claim fallback tests look good

The *_WithHasura tests for permissions, organization code, and feature flags correctly validate:

  • Fallback to Hasura claims when standard claims are absent.
  • Preference for standard claims when both formats are present.
  • For feature flags, the correct extraction of type/value and exclusion of Hasura flags when standard flags exist.

These align with the updated implementations in jwt/token.go and give good coverage of the precedence rules.

jwt/account_api_helpers.go (2)

10-59: Permissions API helper is consistent with existing token behavior

GetPermissionsWithAPI:

  • Sensibly returns token‑derived permissions/org_code when ForceAPI is false.
  • For ForceAPI == true, maps account_api/v1/permissions into PermissionsWithOrg, returning just the permission keys, which is consistent with GetPermissions().

This is a clean separation between token‑only and API‑backed behavior; no issues spotted here.


97-131: Feature flags API helper matches the in-token flag structure

GetFeatureFlagsWithAPI:

  • Respects the forceAPI flag similarly to roles/permissions.
  • Maps the API’s richer feature_flags entries down to your existing FeatureFlag type using the same Type/Value pattern.

That keeps the external API format decoupled from consumers. No functional issues noted.

jwt/token.go (6)

159-192: Hasura-aware permissions logic is correct and backward compatible

GetPermissions now:

  • Prefers the standard "permissions" claim when present.
  • Falls back to "x-hasura-permissions" only when the standard claim is absent or not of the expected type.
  • Continues to ignore non‑string entries.

This matches the tests and keeps existing behavior intact while adding Hasura support.


215-236: Organization code fallback to Hasura is implemented sensibly

GetOrganizationCode:

  • First attempts "org_code" and returns it if it’s a string.
  • Falls back to "x-hasura-org-code" when the standard claim is missing or of a wrong type.

This matches the new tests, maintains old behavior, and provides the Hasura compatibility you want.


352-418: Role type and extraction handle both object and string formats correctly

The new Role struct and GetRoles/extractRoles:

  • Support both full role objects (id, name, key) and simple string roles (key only).
  • Prefer "roles" over "x-hasura-roles", as tested.
  • Guard against malformed entries by requiring at least a key or ID before appending.

This provides a robust abstraction over the raw claims and is well covered by tests.


420-448: HasRoles implementation is straightforward and efficient

HasRoles:

  • Returns true when no role keys are passed (useful for guard logic).
  • Builds a map of user role keys for O(1) lookup.
  • Correctly returns false when there are no roles or no requested keys are present.

This is idiomatic and matches the test expectations.


508-519: GetClaim is a thin but useful wrapper around MapClaims

GetClaim correctly:

  • Short‑circuits when parsed or Claims is nil.
  • Performs a direct lookup on golangjwt.MapClaims.

The tests in jwt/jwt_test.go cover the main scenarios; no issues here.


556-576: extractStringArray is a safe, reusable helper

extractStringArray:

  • Handles []string and []interface{} (the two main cases from JSON/MapClaims).
  • Returns nil for everything else, avoiding panics.
  • Is used in GetUserOrganizations, which keeps that code clean.

Looks good and idiomatic.

Comment thread jwt/account_api_helpers.go Outdated
Comment thread jwt/jwt_test.go
Comment thread jwt/jwt_test.go
Comment thread jwt/token.go Outdated
Comment thread jwt/token.go Outdated
Fixes critical bugs and test coverage issues identified by CodeRabbit:

1. **Added ParseIDTokenUnverified() function** (jwt/jwt.go)
   - Parses ID tokens without signature validation
   - Safe for tokens already validated during OAuth flow
   - Uses jwt.ParseUnverified() to extract claims

2. **Fixed GetUserProfile()** (jwt/token.go)
   - Changed from ParseFromString() to ParseIDTokenUnverified()
   - Now correctly extracts user profile from ID tokens
   - Previously always returned nil due to missing keyfunc

3. **Fixed GetUserOrganizations()** (jwt/token.go)
   - Changed from ParseFromString() to ParseIDTokenUnverified()
   - Now correctly extracts org_codes from ID tokens
   - Previously always returned nil due to missing keyfunc

4. **Enhanced test coverage** (jwt/jwt_test.go)
   - Replaced incomplete test stubs with real assertions
   - Added comprehensive GetUserProfile tests with actual JWT tokens
   - Added comprehensive GetUserOrganizations tests
   - Tests both standard and Hasura claim formats
   - Tests fallback behavior and edge cases

5. **Enhanced GetEntitlements test** (jwt/account_api_helpers_test.go)
   - Added assertions for all 8 Entitlement fields
   - Added error case coverage for API failures
   - Validates Plans fields (Key and SubscribedOn)

Testing:
✅ All JWT tests pass (36 test cases)
✅ Code compiles without errors
✅ GetUserProfile now works correctly
✅ GetUserOrganizations now works correctly
✅ GetEntitlements has comprehensive coverage

Addresses CodeRabbit review comments:
- jwt/token.go lines 450-507 (GetUserProfile)
- jwt/token.go lines 521-554 (GetUserOrganizations)
- jwt/jwt_test.go lines 1331-1391 (GetUserProfile tests)
- jwt/jwt_test.go lines 1439-1497 (GetUserOrganizations tests)
- jwt/account_api_helpers_test.go lines 133-210 (GetEntitlements)
- Removed unnecessary blank lines in `account_api_helpers_test.go`, `jwt_test.go`, and `jwt.go` to improve code readability.
- Ensured consistent formatting across test cases and function implementations.

This change does not affect functionality but enhances code maintainability.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
jwt/account_api_helpers_test.go (4)

17-64: Good coverage of token vs API permissions paths; consider asserting NewClient never fails

The two GetPermissionsWithAPI subtests clearly exercise the token‑only and API code paths and validate both OrgCode and permissions. Since account_api.NewClient can, in principle, evolve to return an error, you may want to require.NoError(t, err) instead of discarding it to keep tests robust against future changes.


66-114: Avoid relying on role slice ordering from the API

The forceAPI=true roles test asserts result[0].Key == "admin" and result[1].Key == "user", which couples the test to the ordering behavior of GetRolesWithAPI/the API response. To make the test resilient if roles are later collected or merged in a different order (e.g., via maps or pagination), consider asserting on the set of keys (e.g., using a map or assert.ElementsMatch) instead of fixed indices.


116-167: Feature flag tests effectively cover token and API formats

The feature‑flag tests nicely validate both in‑token feature_flags and API‑provided flags, including type and value for a boolean flag. You might optionally add an assertion on the second flag’s value ("flag2" == "test") to fully exercise the string path, but current coverage is already solid.


169-245: Entitlements tests give strong coverage; pagination/metadata path could be added later

The entitlements tests thoroughly verify org code, plan fields, and all entitlement properties on the happy path, plus error handling for a 500 response with an informative error message. If Metadata.HasMore / pagination behavior in GetEntitlements is non‑trivial, a future test that exercises multiple pages (with HasMore=true and next_page_starting_after) would round out coverage, but it’s not a blocker.

jwt/token.go (3)

159-236: Hasura fallbacks in GetPermissions and GetOrganizationCode are correct; consider reusing extractStringArray

The fallbacks to "x-hasura-permissions" and "x-hasura-org-code" are implemented safely and only used when the standard claims are absent, which is the right precedence. The per‑function loops that turn []interface{} into []string mirror the new extractStringArray helper; if you ever want to support both []interface{} and []string for permissions as you already do for org codes, you could refactor to reuse extractStringArray here to reduce duplication and broaden type support.


352-418: Role parsing covers both string and object roles; align behavior with comment and consider empty‑arg HasRoles semantics

extractRoles correctly supports both simple string roles and richer role objects with id/name/key, ignoring malformed entries. Two minor points:

  • The GetRoles comment says “Returns an empty slice if no roles are found,” but the function currently returns nil; callers using len() are fine, but you may want either to return []Role{} explicitly or adjust the comment.
  • Since extractStringArray already handles []string and []interface{}, you could reuse a similar pattern for roles in the future if you ever accept other slice types.

420-448: HasRoles logic is efficient; double‑check the behavior when called with no role keys

Building a map[string]bool of role keys and checking requested keys against it is a good O(n+m) approach. The special case len(roleKeys) == 0 returning true is a design choice that effectively treats “no roles requested” as “no restriction”; ensure callers never rely on HasRoles() with no args to mean “user has any roles,” or consider returning false in that case if you want a stricter interpretation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f569405 and 4559e8b.

📒 Files selected for processing (4)
  • jwt/account_api_helpers_test.go (1 hunks)
  • jwt/jwt.go (1 hunks)
  • jwt/jwt_test.go (1 hunks)
  • jwt/token.go (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • jwt/jwt_test.go
🧰 Additional context used
🧬 Code graph analysis (2)
jwt/account_api_helpers_test.go (2)
jwt/account_api_helpers.go (1)
  • GetPermissionsOptions (11-13)
jwt/jwt.go (1)
  • Token (22-27)
jwt/token.go (1)
jwt/jwt.go (2)
  • Token (22-27)
  • ParseIDTokenUnverified (50-63)
🔇 Additional comments (5)
jwt/jwt.go (1)

46-63: Non‑validating ID token parse helper looks correct and resolves prior limitation

Using ParseUnverified with golangjwt.MapClaims{} and a defensive MapClaims type assertion cleanly supports extracting ID token claims without requiring a keyfunc, matching the documented “already validated in OAuth flow” assumption and unblocking callers like GetUserProfile/GetUserOrganizations.

jwt/account_api_helpers_test.go (1)

247-263: createTestToken helper is straightforward and appropriate for claim‑centric tests

Directly seeding processing.parsed with a MapClaims‑backed golangjwt.Token is a clean way to focus these tests on claim extraction logic without involving signing/parsing, and the minimal oauth2.Token stub is sufficient for the helpers under test.

jwt/token.go (3)

259-299: Feature flag extraction logic is robust and shared for standard/Hasura claims

Using a shared extractFeatureFlags helper and trying "feature_flags" before "x-hasura-feature-flags" gives clear, predictable behavior, and the helper gracefully skips malformed entries by requiring both t and v. Returning Type via toString ensures only string types are treated as valid flag‑types, which keeps downstream getters (GetFeatureFlagBool/String/Int) well‑guarded.


450-504: GetUserProfile now correctly uses non‑validating ID token parsing and enforces required sub

Switching GetUserProfile to use ParseIDTokenUnverified fixes the earlier issue where parsing always failed without a keyfunc, and the method now gracefully handles missing/invalid tokens and requires a non‑empty "sub" before returning a profile. Optional fields (given_name, family_name, email, picture) are populated only when present, which is a sensible, backwards‑compatible shape for UserProfile.


506-573: Generic claim access and organization extraction from ID token are well-factored

GetClaim provides a simple, nil‑safe way to fetch arbitrary access‑token claims, and GetUserOrganizations correctly mirrors GetUserProfile by using ParseIDTokenUnverified and supporting both "org_codes" and "x-hasura-org-codes". The shared extractStringArray helper cleanly handles both []string and []interface{} inputs and ignores non‑string elements, which should be robust against minor claim shape variations.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Nov 23, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
jwt/account_api_helpers_test.go (1)

229-244: Consider additional error scenarios for better coverage.

The error test currently only covers HTTP 500 responses. To improve test robustness, consider adding test cases for:

  • Malformed JSON responses
  • Missing required fields in the response
  • Network timeout scenarios (if applicable)

This would help ensure the error handling is resilient across various failure modes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4559e8b and 8f00580.

📒 Files selected for processing (3)
  • jwt/account_api_helpers_test.go (1 hunks)
  • jwt/jwt.go (1 hunks)
  • jwt/jwt_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • jwt/jwt.go
🧰 Additional context used
🧬 Code graph analysis (2)
jwt/jwt_test.go (1)
jwt/jwt.go (1)
  • Token (22-27)
jwt/account_api_helpers_test.go (3)
kinde/account_api/client.go (3)
  • NewClient (33-48)
  • BaseAccountResponse (99-102)
  • Metadata (93-96)
jwt/account_api_helpers.go (1)
  • GetPermissionsOptions (11-13)
jwt/jwt.go (1)
  • Token (22-27)
🔇 Additional comments (13)
jwt/account_api_helpers_test.go (5)

17-64: LGTM! Well-structured API integration tests.

The test coverage for both claim-based and API-based permission retrieval is solid. The mock server correctly simulates the API response structure.


66-114: LGTM! Consistent test pattern for roles.

The test appropriately validates both token-based and API-based role retrieval with proper assertions on the role structure.


116-167: LGTM! Comprehensive feature flag testing.

The test validates both boolean and string feature flag types, ensuring proper extraction of both Type and Value fields.


169-227: LGTM! Thorough validation of entitlement fields.

The success case comprehensively validates all entitlement fields including org_code, plans, and all entitlement properties. The assertions provide good regression protection.


247-263: LGTM! Clean test helper implementation.

The helper function properly uses t.Helper() and provides a convenient way to create test tokens with custom claims. The direct struct construction is appropriate for unit testing.

jwt/jwt_test.go (8)

1130-1251: LGTM! Comprehensive role extraction tests.

The test suite thoroughly covers role retrieval scenarios including standard and Hasura claim formats, string vs. object representations, and proper precedence handling. The coverage ensures robust role extraction functionality.


1253-1329: LGTM! Well-designed role authorization tests.

The tests cover all relevant scenarios for role checking, including the edge case where no roles are specified (returns true). This provides good validation of the authorization logic.


1331-1425: LGTM! Profile extraction tests now fully implemented.

The tests properly call GetUserProfile() and validate the returned profile data. The previous no-op issue has been successfully resolved. Edge cases for missing ID token, missing sub claim, and minimal profile data are well-covered.


1427-1471: LGTM! Clean generic claim accessor tests.

The tests validate the claim retrieval pattern with proper existence checks. Coverage is appropriate for a generic accessor method.


1473-1559: LGTM! Organization extraction tests now fully functional.

The tests properly call GetUserOrganizations() and validate the returned organization codes. The previous no-op issue has been successfully addressed. Tests cover standard claims, Hasura fallback, and proper precedence handling.


1561-1599: LGTM! Hasura permissions fallback properly tested.

The tests validate that Hasura-format permissions are used when standard claims are missing, and that standard claims take precedence when both are present. This ensures proper fallback behavior.


1601-1632: LGTM! Organization code Hasura fallback validated.

The tests ensure proper fallback to Hasura-format organization codes and correct precedence handling. The implementation follows the same pattern as other Hasura claim tests.


1634-1688: LGTM! Feature flags Hasura support thoroughly tested.

The tests validate Hasura-format feature flags as a fallback and ensure standard claims take precedence. The assertions correctly verify both the presence of expected flags and absence of Hasura flags when standard claims exist.

- Add comprehensive documentation to all exported types and functions
- Document struct fields with detailed descriptions
- Enhance function docs with parameters, return values, and examples
- Add context and use case information for critical functions
- Document internal helper functions for better code understanding

This addresses CodeRabbit's docstring coverage warning, improving coverage
from 25.40% towards the 80% threshold.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
kinde/account_api/client.go (1)

441-469: Consider JSON-based deduplication for better accuracy with complex objects.

The current implementation uses fmt.Sprintf("%v") for deduplication, which may produce identical strings for different complex objects. While the comment acknowledges this limitation and it works well for primitive types, consider using JSON marshaling for the deduplication key to handle nested structures more reliably.

Apply this diff for more robust deduplication:

 func mergeInterfaceArrays(arr1, arr2 []interface{}) []interface{} {
 	seen := make(map[string]bool)
 	result := []interface{}{}

 	for _, item := range arr1 {
-		key := fmt.Sprintf("%v", item)
+		keyBytes, _ := json.Marshal(item)
+		key := string(keyBytes)
 		if !seen[key] {
 			seen[key] = true
 			result = append(result, item)
 		}
 	}

 	for _, item := range arr2 {
-		key := fmt.Sprintf("%v", item)
+		keyBytes, _ := json.Marshal(item)
+		key := string(keyBytes)
 		if !seen[key] {
 			seen[key] = true
 			result = append(result, item)
 		}
 	}

 	return result
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f00580 and ff6c423.

📒 Files selected for processing (4)
  • jwt/account_api_helpers.go (1 hunks)
  • jwt/jwt.go (1 hunks)
  • jwt/token.go (5 hunks)
  • kinde/account_api/client.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
jwt/token.go (1)
jwt/jwt.go (2)
  • Token (22-27)
  • ParseIDTokenUnverified (67-80)
jwt/account_api_helpers.go (4)
kinde/management_api/oas_schemas_gen.go (2)
  • Permissions (17566-17575)
  • Roles (18925-18936)
jwt/jwt.go (1)
  • Token (22-27)
kinde/account_api/client.go (1)
  • Client (17-24)
jwt/token.go (2)
  • Role (358-362)
  • FeatureFlag (254-257)
🔇 Additional comments (6)
jwt/jwt.go (1)

46-80: Excellent implementation with clear security documentation.

The function correctly uses ParseUnverified for ID tokens that have already been validated during the OAuth flow. The comprehensive documentation clearly warns against misuse, specifying appropriate use cases and explicitly listing scenarios where this function should NOT be used.

jwt/account_api_helpers.go (1)

1-288: Clean API-backed helpers with consistent patterns.

The implementations follow a consistent pattern:

  • Check forceAPI flag to decide between token claims or API fetch
  • Use CallAccountAPIPaginated for automatic pagination
  • Define internal DTOs matching API response structure
  • Map to exported SDK types
  • Include contextual error messages

The separation between internal API DTOs and exported types is good practice, and error handling properly wraps errors with context.

jwt/token.go (4)

159-192: LGTM: Hasura fallback maintains backward compatibility.

The enhancement properly tries the standard "permissions" claim first, then falls back to "x-hasura-permissions". This maintains backward compatibility while adding Hasura support.


357-460: Well-designed role handling with flexible format support.

The implementation handles multiple role formats gracefully:

  • String arrays (simplified format with keys only)
  • Object arrays (detailed format with ID, name, and key)

HasRoles uses an efficient map-based lookup, and the Hasura fallback pattern is consistent with other methods. The validation that roles have at least a key or ID before adding them (line 424) is good defensive programming.


478-534: Correctly uses unverified parsing for already-validated ID tokens.

The implementation now properly uses ParseIDTokenUnverified to extract claims from the ID token without requiring a keyfunc. This is appropriate since the ID token was already validated during the OAuth flow. The required "sub" field validation and optional field handling are correct.


549-593: Correct implementation with consistent Hasura fallback pattern.

Like GetUserProfile, this method now correctly uses ParseIDTokenUnverified to extract organization codes from the validated ID token. The fallback from standard "org_codes" to Hasura "x-hasura-org-codes" follows the same pattern used throughout the codebase.

Comment thread kinde/account_api/client.go Outdated
- Read response body once before checking status code
- Fixes critical issue where second read would fail since HTTP response bodies can only be read once
- Addresses CodeRabbit review comment on PR kinde-oss#47
coderabbitai[bot]
coderabbitai Bot previously approved these changes Nov 25, 2025
- Expanded docstrings for JWT options, including detailed descriptions, parameters, return values, and examples for functions like WillValidateWithPublicKey, WillValidateWithJWKSUrl, and others.
- Improved documentation for parsing functions such as ParseFromAuthorizationHeader, ParseFromString, and ParseFromSessionStorage, providing clarity on usage and expected behavior.
- Added comprehensive comments for internal helper functions to facilitate better understanding of the codebase.

This update significantly improves the documentation coverage and usability of the JWT library, addressing previous coverage warnings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jwt/jwt_options.go (1)

320-338: newError doesn't handle nil primary error gracefully.

When newError is called with a non-empty message and nil err, the format string becomes "%w: %s" with args [nil, message]. Using %w with a nil error can produce unexpected behavior in Go's error wrapping. This becomes problematic when GetValidationErrors in token.go calls newError("token validation errors", nil, j.validationErrors...).

If j.validationErrors is empty, this creates an error message even when there are no errors, contradicting the documented behavior that the method should return nil when validation succeeds.

Consider this fix:

 func newError(message string, err error, more ...error) error {
+	// If no primary error and no additional errors, return nil
+	if err == nil && len(more) == 0 {
+		return nil
+	}
+	
+	// If no primary error but we have additional errors, use the first as primary
+	if err == nil && len(more) > 0 {
+		err = more[0]
+		more = more[1:]
+	}
+	
 	var format string
 	var args []any
 	if message != "" {
 		format = "%w: %s"
 		args = []any{err, message}
 	} else {
 		format = "%w"
 		args = []any{err}
 	}

 	for _, e := range more {
 		format += ": %w"
 		args = append(args, e)
 	}

 	err = fmt.Errorf(format, args...)
 	return err
 }
🧹 Nitpick comments (3)
jwt/token.go (3)

565-593: Document the vacuous truth behavior of HasRoles with empty input.

Line 569 returns true when len(roleKeys) == 0. This vacuous truth behavior (checking if the user has "any of zero roles" returns true) may be counterintuitive. Some developers might expect this to return false or to check whether the user has any roles at all.

Add a clear comment explaining this behavior:

 // HasRoles checks if the token contains any of the specified roles.
-// Returns true if the user has at least one of the provided role keys.
+// Returns true if the user has at least one of the provided role keys.
+// If no role keys are provided (empty input), returns true (vacuously true).
 func (j *Token) HasRoles(roleKeys ...string) bool {
 	if len(roleKeys) == 0 {
+		// Vacuously true: user has "any of zero roles"
 		return true
 	}

738-766: extractStringArray returns inconsistent nil vs empty slice.

The function returns nil for nil input (line 749) but an empty slice []string for empty arrays (e.g., []interface{}{}). This inconsistency could cause issues for callers that use len() checks vs nil checks.

Consider always returning an empty slice for clarity and consistency:

 func extractStringArray(value interface{}) []string {
 	if value == nil {
-		return nil
+		return []string{}
 	}

 	switch arr := value.(type) {
 	case []string:
+		if arr == nil {
+			return []string{}
+		}
 		return arr
 	case []interface{}:
 		result := make([]string, 0, len(arr))
 		for _, item := range arr {
 			if str, ok := item.(string); ok {
 				result = append(result, str)
 			}
 		}
 		return result
 	}

-	return nil
+	return []string{}
 }

626-736: Consider caching parsed ID token claims to avoid duplicate parsing.

Both GetUserProfile (line 633) and GetUserOrganizations (line 716) independently parse the ID token using ParseIDTokenUnverified. If both methods are called on the same token, the ID token is parsed twice, which is inefficient.

Consider caching the parsed ID token claims in the Token struct:

type Token struct {
    rawToken         *oauth2.Token
    processing       tokenProcessing
    isValid          bool
    validationErrors []error
    idTokenClaims    golangjwt.MapClaims  // Cache parsed ID token claims
}

func (j *Token) getIDTokenClaims() (golangjwt.MapClaims, error) {
    if j.idTokenClaims != nil {
        return j.idTokenClaims, nil
    }
    
    idTokenStr, exists := j.GetIdToken()
    if !exists || idTokenStr == "" {
        return nil, fmt.Errorf("ID token not available")
    }
    
    claims, err := ParseIDTokenUnverified(idTokenStr)
    if err != nil {
        return nil, err
    }
    
    j.idTokenClaims = claims
    return claims, nil
}

Then update both methods to use getIDTokenClaims() instead of parsing directly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c50a390 and b6bf5d4.

📒 Files selected for processing (3)
  • jwt/jwt.go (4 hunks)
  • jwt/jwt_options.go (6 hunks)
  • jwt/token.go (18 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
jwt/token.go (1)
jwt/jwt.go (2)
  • Token (29-34)
  • ParseIDTokenUnverified (110-123)
🔇 Additional comments (7)
jwt/jwt_options.go (1)

15-310: LGTM! Excellent documentation and validator additions.

The enhanced documentation for all options is comprehensive and provides clear examples. The three new validator options (WillValidateIssuer, WillValidateAudience, WillValidateClaims) follow consistent patterns and integrate well with the existing validation pipeline. The implementations correctly append validators to the token's processing configuration.

jwt/jwt.go (1)

14-239: LGTM! Comprehensive documentation and correct ParseIDTokenUnverified implementation.

The enhanced documentation across all parse functions is excellent, providing clear guidance on when and how to use each function. The new ParseIDTokenUnverified function correctly addresses the previous review comments by enabling ID token parsing without signature verification, which is appropriate since ID tokens are already validated during the OAuth2 flow. The implementation properly uses ParseUnverified and includes appropriate safety warnings in the documentation.

jwt/token.go (5)

238-271: Good Hasura fallback support for permissions.

The implementation correctly tries the standard "permissions" claim first, then falls back to "x-hasura-permissions". This pattern provides good compatibility with both standard and Hasura token formats.


301-322: Consistent Hasura fallback pattern across claims.

Good consistency in implementing Hasura fallback support for GetOrganizationCode and GetFeatureFlags. The extractFeatureFlags helper correctly handles the feature flag structure with type and value fields.

Also applies to: 352-397


626-667: Good fix for ID token parsing.

The implementation correctly uses ParseIDTokenUnverified to parse ID tokens without requiring validation, which addresses the previous review comments. The required subject check and optional field extraction logic is sound.


692-736: GetUserOrganizations correctly addresses previous review concerns.

Like GetUserProfile, this method now correctly uses ParseIDTokenUnverified to parse ID tokens without validation. The Hasura fallback support for org codes is properly implemented.


669-690: Useful generic claim accessors.

GetClaim and GetClaims provide helpful generic access to token claims, complementing the type-specific accessor methods. Good additions to the API surface.

Also applies to: 785-802

Comment thread jwt/token.go
Comment thread jwt/token.go
…dling

- Updated role extraction logic to only include roles with a Key, simplifying the condition for appending roles.
- Enhanced the GetValidationErrors method to return nil when there are no validation errors, improving clarity in error handling.

These changes improve code readability and maintainability while ensuring correct functionality in role management and validation error reporting.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
jwt/token.go (1)

568-570: Document the empty-input behavior of HasRoles.

When HasRoles() is called with no role keys, it returns true (lines 568-570). While this could be a deliberate design choice (vacuous truth), it may be surprising to callers. The documentation states "Returns true if the user has at least one of the provided role keys," which doesn't explicitly cover the zero-input case.

Consider either:

  1. Returning false for empty input (no roles to match = no match)
  2. Explicitly documenting this behavior in the function comment

The current behavior might hide bugs where HasRoles() is accidentally called with no arguments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6bf5d4 and 8808d0f.

📒 Files selected for processing (1)
  • jwt/token.go (18 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
jwt/token.go (1)
jwt/jwt.go (2)
  • Token (29-34)
  • ParseIDTokenUnverified (110-123)
🔇 Additional comments (9)
jwt/token.go (9)

10-79: Token accessor methods look good.

The methods for accessing the underlying OAuth2 token (GetRawToken, GetIdToken, GetAccessToken, GetRefreshToken, AsString) are well-documented and correctly implemented. Proper nil checks and consistent return patterns enhance safety.


81-236: Standard JWT claim accessors are well-implemented.

The validation and standard claim methods (IsValid, GetSubject, GetIssuer, GetAudience, GetExpiration, GetIssuedAt, GetJWTID) correctly implement RFC 7519 specifications. Handling both single-string and array formats for audience, and multiple numeric types for timestamps, demonstrates good flexibility for JSON unmarshaling variations.


238-344: Permissions and organization accessors with Hasura fallback work correctly.

The methods for retrieving permissions, scopes, organization code, and authorized party are well-structured. The fallback pattern (standard claim first, then Hasura format) is consistently applied and clearly documented.


346-488: Feature flag handling is comprehensive and type-safe.

The feature flag methods provide both generic access (GetFeatureFlags, GetFeatureFlag) and type-specific accessors (GetFeatureFlagBool, GetFeatureFlagString, GetFeatureFlagInt). The fallback to Hasura format and handling of multiple numeric types demonstrate good design. The extractFeatureFlags helper correctly parses the Kinde feature flag structure.


595-667: User profile extraction is correctly implemented.

The GetUserProfile method now properly uses ParseIDTokenUnverified to extract profile information without requiring validation (since the token was already validated in the OAuth flow). The method correctly requires the "sub" claim and treats other profile fields as optional, following OpenID Connect specifications.

The past review concern about this method has been successfully addressed.


669-690: Generic claim accessor is well-designed.

The GetClaim method provides flexible access to any claim in the token, including both standard and custom claims. The implementation is straightforward with appropriate nil checks.


692-736: Organization extraction is correctly implemented.

The GetUserOrganizations method now properly uses ParseIDTokenUnverified and supports both standard ("org_codes") and Hasura ("x-hasura-org-codes") formats. This follows the same pattern as GetUserProfile and addresses the past review concern.


738-783: Helper functions are robust and well-documented.

The internal helpers extractStringArray and toString handle type conversions safely with proper nil checks. The behavior of silently skipping non-string elements and returning empty strings for invalid types is documented and appropriate for claim parsing.


785-827: Claims and validation error accessors are correctly implemented.

The GetClaims method provides comprehensive access to all token claims. The GetValidationErrors method now correctly returns nil when there are no validation errors (lines 823-825), which addresses the past review concern and matches the documented behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant