-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat jwe #140
base: master
Are you sure you want to change the base?
Feat jwe #140
Conversation
Important Review skippedMore than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review. 32 files out of 114 files are above the max files limit of 75. Please upgrade to Pro plan to get higher limits. You can disable this status message by setting the WalkthroughThe pull request introduces significant updates to the Authelia OAuth 2.0 Framework, including a change in the module path, a new minimum Go version requirement, and various enhancements to the JWT handling and claims management. Key modifications include the renaming of the Changes
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- authorize_request_handler.go (5 hunks)
🔇 Additional comments (10)
authorize_request_handler.go (10)
12-12
: Importing the "time" package is appropriateThe
"time"
package is correctly imported and utilized for time-related functions in the code.
69-70
: Variablesalg
andalgAny
are properly declaredThe variables
alg
andalgAny
are declared to handle the request object signing algorithm logic effectively.
73-75
: Using assignment within the switch statementAssigning
alg
within theswitch
statement is acceptable and enhances code conciseness.
119-119
: Retrieving the issuer from configurationThe issuer is obtained using
f.Config.GetIDTokenIssuer(ctx)
, which is necessary for subsequent validations.
128-134
: Header validation options are correctly configuredThe JWT header validation options ensure that the token headers match the client's expected values, enhancing security.
136-138
: Proper validation of the JWT token headersThe
token.Valid(optsValidHeader...)
call effectively validates the token headers, and errors are appropriately handled.
140-144
: Appropriate handling of unexpected 'none' algorithm usageThe code correctly returns an error when an unregistered client attempts to use the
'none'
algorithm, ensuring compliance with security policies.
193-195
: Validation of issuer configurationChecking for an empty issuer and returning an error maintains the integrity of the authorization server configuration.
197-203
: Claims validation options are properly setThe claims validation includes time functions, issuer, and audience checks, which are essential for accurate JWT validation.
205-206
: Correct validation of JWT claimsValidating the JWT claims using
claims.Valid(optsValidClaims...)
ensures the token's integrity and authenticity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (7)
internal/test_helpers.go (1)
Line range hint
1-74
: Summary of changes and recommendationsThe changes in this file are part of a larger refactoring effort to use an internal JWT library. While these changes simplify the code, they also introduce potential security and behavior concerns that need to be addressed:
The switch to an internal JWT library (
authelia.com/provider/oauth2/token/jwt
) may have wide-ranging effects across the codebase. Ensure that all JWT-related code has been updated consistently.The use of
UnsafeParseSignedAny
in theExtractJwtExpClaim
function skips signature verification. While this might be acceptable for a test helper, it's crucial to ensure that this doesn't lead to any security vulnerabilities if used in non-test code.The change in checking the expiration claim (from
nil
toIsZero()
) might affect behavior in edge cases. Verify that this change doesn't introduce any regressions in test cases.The error handling in the
ExtractJwtExpClaim
function has been simplified, which could potentially mask certain types of parsing errors. Consider adding more granular error handling to catch and log specific parsing issues.Please review these points carefully and make any necessary adjustments to ensure the security and correctness of the JWT handling throughout the codebase.
token/jwt/claims_jwt.go (2)
229-239
: Approve changes with a minor suggestionThe modifications to the
toTime
function improve type handling and error checking. The use of the newtoInt64
helper function allows for handling more input types, and the conversion to UTC ensures consistent time representation.Consider adding a check for zero value to improve robustness:
func toTime(v any, def time.Time) (t time.Time, ok bool) { t = def var value int64 if value, ok = toInt64(v); ok { + if value == 0 { + return def, true + } t = time.Unix(value, 0).UTC() } return }This change would ensure that a zero value doesn't overwrite the default time unnecessarily.
241-263
: Approve changes with a suggestion for improved readabilityThe new
toInt64
function provides robust type conversion for various numeric types, with comprehensive error handling forjson.Number
. The function correctly returns false for unsupported types, which is a good practice.Consider using a type switch for improved readability:
func toInt64(v any) (val int64, ok bool) { var err error - switch t := v.(type) { - case float64: - return int64(t), true - case int64: - return t, true - case json.Number: + switch t := v.(type) { + case float64: + return int64(t), true + case int64: + return t, true + case json.Number: if val, err = t.Int64(); err == nil { return val, true } var valf float64 if valf, err = t.Float64(); err != nil { return 0, false } return int64(valf), true + default: + return 0, false } - - return 0, false }This change makes the function more idiomatic Go and slightly more efficient by eliminating the need for a separate return statement at the end.
token/jwt/claims_map.go (4)
77-80
: LGTM with a suggestion: GetAudience implementation is concise.The
GetAudience
method correctly retrieves the 'aud' claim using a helper functionStringSliceFromMap
. This is good for code reuse. However, it would be helpful to include or document theStringSliceFromMap
function for better understanding of the full behavior.Consider adding a comment explaining the behavior of
StringSliceFromMap
or including its implementation in this file if it's not used elsewhere.
171-233
: LGTM with a suggestion: Valid implementation is comprehensive and flexible.The
Valid
method provides a thorough validation of multiple claims based on provided options. It correctly uses the verification methods implemented earlier and accumulates errors in aValidationError
struct, which is a good practice. The use ofClaimValidationOptions
allows for flexible and customizable validation.Consider adding a comment explaining the purpose of the
ValidationError
struct and how it's used to accumulate errors. This would improve code readability and make it easier for other developers to understand the error handling mechanism.
257-259
: LGTM with a suggestion: toInt64 implementation is concise.The
toInt64
method correctly delegates the conversion to a helper function. This approach promotes code reuse and consistency.Consider adding a comment explaining the behavior of the
toInt64
helper function or including its implementation in this file if it's not used elsewhere. This would improve code readability and make it easier for other developers to understand the conversion logic.
Line range hint
1-392
: Overall assessment: Significant improvements to JWT claim handling and validation.This PR introduces substantial enhancements to the
MapClaims
type and associated functions:
- New methods for individual claim retrieval and validation have been added, providing more granular control.
- A flexible claim validation system has been implemented using the functional options pattern.
- Security has been improved with the use of constant-time comparisons for sensitive operations.
- The code maintains consistency in patterns and naming conventions throughout the file.
These changes significantly improve the robustness and flexibility of JWT claim handling and validation in the system. The new functionality allows for more precise control over claim verification, which can enhance security and reduce the likelihood of improper token acceptance.
Consider documenting the new claim validation system in the package documentation, including examples of how to use the new
Valid
method with various options. This will help other developers understand and correctly use the new functionality.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
go.mod
is excluded by!**/*.mod
go.sum
is excluded by!**/*.sum
,!**/*.sum
📒 Files selected for processing (9)
- handler/openid/flow_hybrid_test.go (4 hunks)
- internal/test_helpers.go (2 hunks)
- introspection_request_handler_test.go (0 hunks)
- token/jwt/claims_id_token.go (2 hunks)
- token/jwt/claims_jarm.go (1 hunks)
- token/jwt/claims_jwt.go (3 hunks)
- token/jwt/claims_map.go (2 hunks)
- token/jwt/jwt_strategy_opts.go (1 hunks)
- token/jwt/util.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (1)
- introspection_request_handler_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
- handler/openid/flow_hybrid_test.go
- token/jwt/jwt_strategy_opts.go
- token/jwt/util.go
🔇 Additional comments (17)
internal/test_helpers.go (1)
21-21
: Verify the impact of the JWT package change across the codebase.The JWT package import has been changed from an external library to an internal one. This change could have a significant impact on JWT-related operations throughout the codebase.
Run the following script to check for any remaining usages of the old JWT package and potential inconsistencies:
Ensure that all JWT-related code has been updated to use the new internal package consistently.
✅ Verification successful
JWT package import successfully updated to the internal package across the codebase. No remaining references to the old package were found.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any remaining usages of the old JWT package and potential inconsistencies. # Test 1: Search for any remaining imports of the old JWT package echo "Checking for any remaining imports of github.com/golang-jwt/jwt/v5:" rg --type go 'github.com/golang-jwt/jwt/v5' # Test 2: Search for any uses of the old JWT package types or functions echo "Checking for any uses of types or functions from github.com/golang-jwt/jwt/v5:" rg --type go 'jwt\.(NewWithClaims|Parse|ParseWithClaims|SigningMethodHS256|MapClaims|StandardClaims)' # Test 3: Check for consistency in JWT package usage echo "Checking for consistency in JWT package usage:" rg --type go 'authelia.com/provider/oauth2/token/jwt'Length of output: 15947
token/jwt/claims_jwt.go (1)
7-7
: LGTM: Import change is appropriateThe addition of the "encoding/json" import is necessary and correctly implemented to support the handling of
json.Number
in the newtoInt64
function.token/jwt/claims_map.go (15)
27-38
: LGTM: GetIssuer implementation is correct and robust.The
GetIssuer
method correctly retrieves the 'iss' claim, handling both the case where the claim is not present and where it's not a string. This implementation follows good practices for working with map claims.
40-50
: LGTM: VerifyIssuer implementation is correct and consistent.The
VerifyIssuer
method correctly verifies the 'iss' claim, handling the required flag appropriately. It reuses theGetIssuer
method, which ensures consistency in how the claim is retrieved and processed.
52-63
: LGTM: GetSubject implementation is correct and consistent.The
GetSubject
method correctly retrieves the 'sub' claim, following the same robust pattern asGetIssuer
. This consistency in implementation across different claims is a good practice.
65-75
: LGTM: VerifySubject implementation is correct and consistent.The
VerifySubject
method correctly verifies the 'sub' claim, following the same pattern asVerifyIssuer
. This consistency in implementation across different claim verifications is commendable.
84-92
: LGTM: VerifyAudience implementation is correct and consistent.The
VerifyAudience
method correctly verifies the 'aud' claim, handling the required flag appropriately. It reuses theGetAudience
method and uses a helper functionverifyAud
, which is consistent with the pattern used in other verification methods.
94-105
: LGTM: VerifyAudienceAll implementation is correct and consistent.The
VerifyAudienceAll
method correctly verifies all provided audience values against the 'aud' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, which is good for consistency.
107-118
: LGTM: VerifyAudienceAny implementation is correct and consistent.The
VerifyAudienceAny
method correctly verifies if any of the provided audience values match the 'aud' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.
120-123
: LGTM: GetExpiresAt implementation is concise and consistent.The
GetExpiresAt
method correctly retrieves the 'exp' claim using the helper methodtoInt64
. This approach is consistent with other getter methods and promotes code reuse.
127-135
: LGTM: VerifyExpiresAt implementation is correct and consistent.The
VerifyExpiresAt
method correctly verifies the 'exp' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.
137-140
: LGTM: GetIssuedAt implementation is concise and consistent.The
GetIssuedAt
method correctly retrieves the 'iat' claim using the helper methodtoInt64
. This approach is consistent with other getter methods and promotes code reuse.
144-151
: LGTM: VerifyIssuedAt implementation is correct and consistent.The
VerifyIssuedAt
method correctly verifies the 'iat' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.
154-157
: LGTM: GetNotBefore implementation is concise and consistent.The
GetNotBefore
method correctly retrieves the 'nbf' claim using the helper methodtoInt64
. This approach is consistent with other getter methods and promotes code reuse.
161-168
: LGTM: VerifyNotBefore implementation is correct and consistent.The
VerifyNotBefore
method correctly verifies the 'nbf' claim, handling the required flag appropriately. It follows the same pattern as other verification methods and reuses theverifyInt64Past
function, which is good for consistency and code reuse.
261-320
: LGTM: New types and functions for claim validation are well-designed.The addition of
ClaimValidationOption
,ClaimValidationOptions
, and the associated functions to create validation options is well-implemented. The use of the functional options pattern provides a flexible and extensible way to configure claim validation. Each option function is simple and focused, which is good for maintainability and readability.
321-392
: LGTM: Helper functions are well-implemented and improve security.The modifications and additions to the helper functions are well-implemented:
- The use of
subtle.ConstantTimeCompare
inverifyAud
is a good security practice to prevent timing attacks.- The new functions
verifyAudAny
andverifyAudAll
provide more granular control over audience verification.- The additional helper functions
verifyExp
,verifyInt64Past
, andverifyMapString
are simple, focused, and enhance the overall functionality of the claims validation process.These changes contribute to improved security and more flexible claim verification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (10)
token/jwt/claims_test.go (2)
30-69
: Well-structured test function with good coverage.The
TestFilter
function is well-organized using table-driven tests, which is a good practice for testing multiple scenarios. The use oft.Run
for each test case allows for clear reporting of individual test results.However, consider the following suggestions to enhance the test coverage:
- Add a test case for when the filter contains keys not present in the map.
- Include a test case with non-string map keys to ensure type safety.
Example:
{ "ShouldHandleNonExistentFilterKeys", map[string]any{"abc": 123}, []string{"xyz"}, map[string]any{"abc": 123}, }, { "ShouldHandleNonStringKeys", map[string]any{"abc": 123, 42: "answer"}, []string{"abc"}, map[string]any{42: "answer"}, },Also, consider adding a brief comment explaining the purpose of the
Filter
function being tested, as it's not visible in this file.
44-48
: Remove redundant test case.The test case "ShouldFilterNoneNil" appears to be redundant with "ShouldFilterNone". Both cases test the scenario where an empty filter is applied. Consider removing one of these cases to avoid duplication.
token/jwt/claims_map.go (4)
77-124
: LGTM: Comprehensive audience claim handling with a minor suggestionThe new methods for handling the 'aud' claim are well-implemented, providing flexibility for different audience verification scenarios. The code is DRY and handles edge cases appropriately.
Consider adding a comment explaining the purpose of the
StringSliceFromMap
function, as its implementation is not visible in this file.
177-243
: LGTM: Comprehensive claim validation with a suggestion for improvementThe new
Valid
method provides a flexible and thorough way to validate all claims based on provided options. It correctly handles different validation scenarios and uses aValidationError
struct for detailed error reporting.Consider refactoring this method to reduce its length and improve readability. You could extract the validation logic for each claim type into separate helper methods. For example:
func (m MapClaims) validateTimeClaims(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) { if !m.VerifyExpiresAt(now, vopts.expRequired) { vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } // ... (similar for iat and nbf) } func (m MapClaims) validateIssuer(vErr *ValidationError, vopts *ClaimValidationOptions) { if len(vopts.iss) != 0 && !m.VerifyIssuer(vopts.iss, true) { vErr.Inner = errors.New("Token has invalid issuer") vErr.Errors |= ValidationErrorIssuer } } // ... (similar methods for subject and audience) func (m MapClaims) Valid(opts ...ClaimValidationOption) error { vopts := &ClaimValidationOptions{} for _, opt := range opts { opt(vopts) } now := m.getNow(vopts) vErr := new(ValidationError) m.validateTimeClaims(vErr, now, vopts) m.validateIssuer(vErr, vopts) m.validateSubject(vErr, vopts) m.validateAudience(vErr, vopts) if vErr.valid() { return nil } return vErr }This refactoring would make the
Valid
method more readable and easier to maintain.
260-268
: LGTM: Useful helper method for numeric claimsThe
toInt64
method is a good addition, providing a convenient way to retrieve and convert numeric claims toint64
. It correctly handles cases where the claim is not present.Consider adding a brief comment explaining the purpose of the external
toInt64
function, as its implementation is not visible in this file. This would improve code readability and maintainability.
331-377
: LGTM: Comprehensive audience verification with a suggestionThe audience verification functions (
verifyAud
,verifyAudAny
,verifyAudAll
) are well-implemented and cover different verification scenarios. The use of constant-time comparison is good for security.In the
verifyAudAll
function, the use of a labeled outer loop is correct but might be a bit hard to follow. Consider adding a brief comment explaining the logic, or refactor to use a helper function for clarity. For example:func verifyAudAll(aud []string, cmp []string, required bool) bool { if len(aud) == 0 { return !required } return all(cmp, func(c string) bool { return any(aud, func(a string) bool { return subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 }) }) } func all(slice []string, predicate func(string) bool) bool { for _, item := range slice { if !predicate(item) { return false } } return true } func any(slice []string, predicate func(string) bool) bool { for _, item := range slice { if predicate(item) { return true } } return false }This refactoring might make the logic easier to understand at a glance.
token/jwt/claims_map_test.go (4)
987-993
: Simplify the conditional check for error handlingThe condition
if len(tc.err) == 0 && tc.err == ""
is redundant sincetc.err == ""
implies thatlen(tc.err) == 0
. Additionally, the nestedif tc.err != ""
is unnecessary because this block is only executed whentc.err != ""
.Apply this diff to simplify the condition and improve readability:
-if len(tc.err) == 0 && tc.err == "" { +if tc.err == "" { assert.NoError(t, actual) } else { - if tc.err != "" { assert.EqualError(t, actual, tc.err) - }
924-930
: Correct the validation option in the test caseIn the test case
"ShouldFailIssuer"
, theValidateTimeFunc(time.Now)
option seems unnecessary and may not impact the issuer validation. This could be confusing for future readers.Remove the unnecessary validation option to focus on the issuer validation:
[]ClaimValidationOption{ ValidateIssuer("abc2"), - ValidateTimeFunc(time.Now), },
983-1008
: Simplify error variable declarationThe variable
errs
is used to accumulate error codes, but could be declared and used more concisely.Apply this diff to simplify the error accumulation:
- var errs uint32 for _, err := range tc.errs { - errs |= err + errs := err assert.True(t, e.Has(err)) } - assert.Equal(t, errs, e.Errors)
473-545
: Improve consistency in test case structureIn
TestMapClaims_VerifySubject
, the test cases usetrue
andfalse
for therequired
field, but there's no explanation of whatrequired
signifies. Adding comments or using a type with named fields can enhance readability.Consider defining a type for the test cases or adding comments to clarify the purpose of each field.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (4)
- token/jwt/claims_jwt.go (3 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_map_test.go (1 hunks)
- token/jwt/claims_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- token/jwt/claims_jwt.go
🔇 Additional comments (8)
token/jwt/claims_map.go (6)
27-75
: LGTM: Well-implemented claim retrieval and verification methodsThe new methods for getting and verifying 'iss' and 'sub' claims are well-implemented. They follow a consistent pattern, use constant-time comparison for security, and provide flexibility with optional requirement checks. This is a good addition to the
MapClaims
type.
126-174
: LGTM: Improved time-based claim handlingThe modifications to the time-based claim methods (exp, iat, nbf) are well-implemented. The use of helper functions (
toInt64
,verifyInt64Future
,verifyInt64Past
) improves code consistency and readability. The methods correctly handle cases where claims are not present.
247-257
: LGTM: Improved JSON unmarshaling for claimsThe modification to
UnmarshalJSON
is a good improvement. Setting the number type toUnmarshalIntOrFloat
ensures that numeric claims are correctly parsed as either integers or floats, which is appropriate for JWT claims. The use oferrorsx.WithStack(err)
provides better error context for debugging.
270-329
: LGTM: Well-designed claim validation configurationThe new
ClaimValidationOption
type,ClaimValidationOptions
struct, and related functions provide a flexible and extensible way to configure claim validation. The use of the functional options pattern is a good choice, allowing for easy addition of new options in the future. Each option function is focused and easy to understand.This design will make it easy for users of the library to customize claim validation according to their needs.
380-403
: LGTM: Well-implemented helper functions for claim verificationThe new helper functions (
verifyInt64Future
,verifyInt64Past
,verifyMapString
) are well-implemented and consistent with the rest of the code. They correctly handle cases where the value is not set and respect therequired
parameter. The use of constant-time comparison inverifyMapString
is good for security.These functions contribute to the overall robustness and security of the claim verification process.
Line range hint
1-403
: Overall assessment: High-quality enhancements to JWT claim handlingThe changes to
token/jwt/claims_map.go
significantly improve the JWT claim handling and verification capabilities of the system. The new methods and functions are well-implemented, following good practices such as constant-time comparisons for security-sensitive operations and providing flexibility through functional options for claim validation.Key improvements include:
- Comprehensive methods for retrieving and verifying individual claims.
- A flexible
Valid
method for validating multiple claims with customizable options.- Enhanced audience verification with support for different scenarios.
- Improved JSON unmarshaling for claims.
While the overall quality is high, consider the suggested minor improvements:
- Adding comments for clarity in some areas, especially for external functions.
- Potential refactoring of the
Valid
method to improve readability.- Simplifying the
verifyAudAll
function for better clarity.These changes provide a robust foundation for JWT handling in the system.
token/jwt/claims_map_test.go (2)
921-930
: Ensure error messages match expected validation failuresIn the test cases
"ShouldFailIssuer"
and"ShouldFailIssuerAbsent"
, the expected error message is"Token has invalid issuer"
, which suggests a specific validation failure. Confirm that this message aligns with the actual error produced by theValid
method.Run the tests to ensure that the error messages match the expected output.
11-14
:⚠️ Potential issueVerify the correctness of the import path
The import statement is referencing
"authelia.com/provider/oauth2/internal/consts"
, which may not be intended in the context of this project under Ory Corp. This could introduce unintended dependencies or conflicts.Run the following script to check for unintended imports from
authelia.com
:Please confirm if the import path is correct and update it if necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (8)
authorize_request_handler.go (4)
69-75
: LGTM! Consider adding a comment for clarity.The changes improve the handling of the signing algorithm, especially for cases where it's not specified. The introduction of
algAny
is a good approach.Consider adding a brief comment explaining the purpose of
algAny
, e.g.:// algAny is true when no specific algorithm is set, allowing any algorithm
121-138
: Improved JWT decoding and validation. Consider adding debug logging.The changes significantly enhance the JWT decoding and validation process. The use of a strategy pattern and the introduction of header validation options improve the code's flexibility and security.
Consider adding debug logging before and after the token decoding and validation steps. This could help with troubleshooting in production environments. For example:
f.Config.GetLogger(ctx).Debug("Decoding JWT token") token, err := strategy.Decode(ctx, assertion, jwt.WithSigAlgorithm(jwt.SignatureAlgorithmsNone...), jwt.WithJARClient(client)) if err != nil { f.Config.GetLogger(ctx).WithError(err).Debug("Failed to decode JWT token") return errorsx.WithStack(fmtRequestObjectDecodeError(token, client, issuer, openid, err)) } f.Config.GetLogger(ctx).Debug("Successfully decoded JWT token")
193-206
: Robust issuer and claim validation. Consider extracting time function.The addition of issuer validation and the enhanced claim validation logic significantly improve the security and correctness of the JWT processing. The use of custom validation options is a good approach.
Consider extracting the time function to a separate, testable function. This would improve testability and make it easier to mock time in unit tests. For example:
func getNowUTC() time.Time { return time.Now().UTC() } // Then in the validation options: jwt.ValidateTimeFunc(getNowUTC),
522-596
: Comprehensive error handling for JWT validation. Consider breaking down the function.The
fmtRequestObjectDecodeError
function provides excellent, detailed error messages for various JWT validation scenarios. This will greatly improve debuggability and help in identifying specific issues with JWTs.While the function is comprehensive, its length and complexity might make it harder to maintain. Consider breaking it down into smaller, more focused functions. For example:
func fmtRequestObjectDecodeError(token *jwt.Token, client JARClient, issuer string, openid bool, inner error) (outer *RFC6749Error) { outer = ErrInvalidRequestObject.WithWrap(inner).WithHintf("%s request object could not be decoded or validated.", hintRequestObjectPrefix(openid)) if errJWTValidation := new(jwt.ValidationError); errors.As(inner, &errJWTValidation) { return handleJWTValidationError(errJWTValidation, token, client, issuer, openid) } else if errJWKLookup := new(jwt.JWKLookupError); errors.As(inner, &errJWKLookup) { return handleJWKLookupError(errJWKLookup, client, openid) } else { return handleGenericError(inner, client, openid) } } func handleJWTValidationError(errJWTValidation *jwt.ValidationError, token *jwt.Token, client JARClient, issuer string, openid bool) *RFC6749Error { // Handle different validation errors... } func handleJWKLookupError(errJWKLookup *jwt.JWKLookupError, client JARClient, openid bool) *RFC6749Error { // Handle JWK lookup errors... } func handleGenericError(err error, client JARClient, openid bool) *RFC6749Error { // Handle generic errors... }This approach would make the code more modular and easier to maintain.
authorize_request_handler_oidc_request_test.go (4)
31-31
: Consider increasing RSA key size for more realistic testing.The current RSA key size of 2048 bits is acceptable for testing purposes. However, for more realistic scenarios, consider using a 4096-bit key. This would better reflect current security recommendations for production environments.
Note: As per the retrieved learning, using a 1024-bit RSA key in test files is acceptable for specific scenarios or to keep tests running quickly. The current 2048-bit key is already an improvement over that.
Line range hint
167-491
: Consider grouping test cases for improved readability.The test cases are comprehensive and cover a wide range of scenarios, which is excellent. However, the large number of test cases in a single slice can make it challenging to understand and maintain. Consider the following suggestions:
- Group related test cases into separate slices or sub-functions based on the scenario they're testing (e.g., valid requests, invalid signatures, algorithm mismatches).
- Use a table-driven test helper function to reduce repetition in test case definitions.
- Add comments to separate and explain different groups of test cases.
These changes could improve the readability and maintainability of the test suite.
Line range hint
493-529
: Consider enhancing error assertions and result validation.The test execution is generally well-structured, using subtests for each case. However, consider the following improvements:
- Use
require.Equal
instead ofassert.Equal
for critical assertions to stop the test immediately if they fail.- Add more specific assertions for the error types, not just the error messages.
- Consider using a helper function to compare the expected and actual form values, which could provide more detailed output on mismatches.
- Add assertions to verify that no unexpected form values are present.
These changes could make the tests more robust and easier to debug when failures occur.
Line range hint
531-573
: Consider improving helper functions for better error handling and clarity.The helper functions are useful for generating test data, but consider the following improvements:
- Replace
mustGenerateX
functions with versions that return errors, and handle these errors in the test cases. This allows for more granular error handling and avoids panics in tests.- Rename
mangleSig
to something more descriptive, likecorruptSignature
.- Add comments to explain the purpose and behavior of each helper function.
- In
mangleSig
, consider using a more deterministic way to corrupt the signature for consistent test results.These changes would improve the maintainability and clarity of the test helpers.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
- authorize_request_handler.go (5 hunks)
- authorize_request_handler_oidc_request_test.go (9 hunks)
- token/jwt/jwt_strategy.go (1 hunks)
- token/jwt/jwt_strategy_test.go (1 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- token/jwt/jwt_strategy.go
- token/jwt/jwt_strategy_test.go
- token/jwt/util.go
🧰 Additional context used
📓 Learnings (2)
authorize_request_handler.go (1)
Learnt from: james-d-elliott PR: authelia/oauth2-provider#140 File: authorize_request_handler.go:121-125 Timestamp: 2024-09-27T07:36:36.699Z Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
authorize_request_handler_oidc_request_test.go (1)
Learnt from: james-d-elliott PR: authelia/oauth2-provider#66 File: authorize_request_handler_oidc_request_test.go:108-179 Timestamp: 2024-03-23T23:32:47.956Z Learning: In test files (with the _test.go suffix), using a 1024-bit RSA key is acceptable for testing specific scenarios, such as the acceptance of insecure keys, or to keep tests running quickly. This practice is not applicable to production code.
🔇 Additional comments (4)
authorize_request_handler.go (1)
140-144
: Excellent security improvement for 'none' algorithm handling.This new check prevents the use of the 'none' algorithm when it's not explicitly allowed, which is a crucial security improvement. It helps protect against potential vulnerabilities where an attacker might try to bypass signature verification.
authorize_request_handler_oidc_request_test.go (2)
Line range hint
1-27
: LGTM: Imports and package declaration are appropriate.The imports cover the necessary standard libraries and third-party packages required for testing OAuth2 and JWT functionality. The copyright notice is present, and the package declaration is correct.
Line range hint
1-573
: Overall, a comprehensive test suite with room for improvements.This test file provides extensive coverage for OpenID Connect request object handling, which is commendable. It tests various scenarios, including different signing algorithms, valid and invalid requests, and error conditions. However, there are several areas where the file could be improved:
- Organization: Group related test cases and consider breaking down the large test function into smaller, more focused tests.
- Error Handling: Improve error assertions and consider returning errors from helper functions instead of using
must
functions that panic.- Readability: Add more comments explaining the purpose of different test groups and helper functions.
- Consistency: Ensure consistent naming conventions and consider increasing key sizes for more realistic testing scenarios.
Addressing these points would enhance the maintainability, readability, and robustness of the test suite.
token/jwt/token.go (1)
538-541
: Verify Handling of Multiple Headers innewToken
FunctionThe check
if len(parsedToken.Headers) != 1
enforces that only one header is present, returning an error otherwise. Ensure that tokens with multiple headers are correctly handled or explicitly unsupported, and that this behavior aligns with JWT specifications and project requirements.
660d6e1
to
95370b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
🧹 Outside diff range and nitpick comments (29)
token/jwt/validation_error.go (2)
22-29
: Approved reordering and addition of ValidationErrorSubjectThe reordering of existing constants and the addition of
ValidationErrorSubject
maintain consistency with the error type structure. The new constant for subject claim validation is a valuable addition.For consistency, consider adding a period at the end of each constant's comment, similar to the format used for the new constants above.
32-32
: Improved comment clarity for ValidationError structThe updated comment provides a clearer explanation of the ValidationError struct's purpose.
Consider a minor grammatical improvement:
- // The ValidationError is an error implementation from Parse if token is not valid. + // ValidationError is an error implementation returned by Parse when a token is not valid.This removes the unnecessary article and slightly rephrases for better clarity.
handler/oauth2/strategy_jwt_profile_test.go (4)
58-85
: LGTM! Consider adding a comment explaining the purpose of this function.The new
jwtInvalidTypCase
function is well-structured and serves as a good test case for invalid JWT types. It correctly sets up a JWT with an invalid type in the header, which is crucial for testing error handling in the JWT validation process.Consider adding a brief comment above the function to explain its purpose and how it differs from
jwtValidCase
. This would enhance code readability and make the test case's intention clearer to other developers.
Line range hint
150-168
: LGTM! Consider using consistent time offsets for improved readability.The addition of the
now
parameter tojwtExpiredCase
is a great improvement. It allows for more deterministic testing of expiration logic by decoupling the test from the current system time.For improved readability and consistency, consider using the same time unit for all offsets. For example:
IssuedAt: now.UTC().Add(-time.Hour), NotBefore: now.UTC().Add(-time.Hour), ExpiresAt: now.UTC().Add(-time.Minute * 59),This makes it easier to understand the relative timing of each field at a glance.
195-195
: LGTM! Consider using a more readable timestamp format.The updates to
TestAccessToken
improve the test's determinism and configuration. The use of constants for header type checking is a good practice for maintainability.For improved readability, consider using
time.Date()
instead oftime.Unix()
for the specific timestamp. For example:r: jwtExpiredCase(oauth2.AccessToken, time.Date(2024, 9, 22, 12, 0, 0, 0, time.UTC)),This makes it easier for developers to understand the exact date and time being used in the test.
Also applies to: 214-219, 252-252
Line range hint
1-307
: Great improvements to test determinism and coverage!The changes in this file significantly enhance the robustness and maintainability of the JWT-related tests. The introduction of the
now
parameter for expired token tests, the newjwtInvalidTypCase
function, and the use of constants instead of hardcoded values are all excellent improvements.Consider adding a test case for a JWT with an expiration time very close to the current time (e.g., 1 second in the future). This edge case could help ensure that the system handles near-expiration scenarios correctly.
token/jwt/claims_map.go (2)
177-246
: LGTM: Valid implementation is comprehensive and flexible.The
Valid
function provides a thorough validation of all standard claims (exp, iat, nbf, iss, sub, aud) using the previously defined verification methods. This ensures consistency across the package. The use ofValidationError
allows for detailed error reporting, which is excellent for debugging and error handling.The function's flexibility through the use of options is a great design choice, allowing users to customize the validation process as needed.
Consider adding a comment explaining the purpose of the
ValidationError
struct and how it accumulates errors. This would improve code readability and maintainability.
270-329
: LGTM: Claim validation options are well-designed and comprehensive.The implementation of claim validation options using the functional options pattern is an excellent choice for API design. It provides flexibility and extensibility while keeping the code clean and easy to use. Each option function is focused on a single task, which adheres to the Single Responsibility Principle.
The options cover all standard claims (iss, sub, aud, exp, iat, nbf) and allow for customization of validation behavior, including the ability to set custom time functions and require specific claims.
Consider adding a brief comment for each validation option function explaining its purpose and any default behavior. This would improve the self-documentation of the code and make it easier for users to understand the available options without referring to external documentation.
client_authentication.go (8)
164-165
: LGTM. Consider adding documentation.The addition of
GetAuthSigningKeyID
method enhances the flexibility of client authentication. It would be beneficial to add a comment explaining the purpose and expected return value of this method.
169-174
: LGTM. Consider adding documentation for new methods.The addition of
GetAuthEncryptionKeyID
,GetAuthEncryptionAlg
, andGetAuthEncryptionEnc
methods suggests support for encrypted client authentication, which is a good security enhancement. It would be beneficial to add comments explaining the purpose and expected return values of these methods.
182-226
: LGTM. Consider implementing encryption-related methods.The
EndpointClientAuthJWTClient
struct is a good addition for handling JWT-based client authentication. However, some methods return empty strings or fixed values:
GetSigningKeyID
(line 204)GetEncryptionKeyID
(line 212)GetEncryptionAlg
(line 216)GetEncryptionEnc
(line 220)Consider implementing these methods to return meaningful values if JWT encryption is supported, or add comments explaining why they return empty strings if this is intentional.
233-236
: Consider implementingGetAuthSigningKeyID
or add a comment.The
GetAuthSigningKeyID
method currently returns an empty string. If a signing key ID is required for token endpoint authentication, consider implementing this method. If returning an empty string is intentional, please add a comment explaining the rationale.
241-252
: Consider implementing encryption-related methods or add comments.The following methods currently return empty strings:
GetAuthEncryptionKeyID
(line 242)GetAuthEncryptionAlg
(line 246)GetAuthEncryptionEnc
(line 250)If encryption is supported for token endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.
267-286
: Consider implementing authentication-related methods or add comments.The following methods currently return empty strings:
GetAuthSigningKeyID
(line 268)GetAuthEncryptionKeyID
(line 276)GetAuthEncryptionAlg
(line 280)GetAuthEncryptionEnc
(line 284)If signing and encryption are supported for introspection endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.
301-320
: Consider implementing authentication-related methods or add comments.The following methods currently return empty strings:
GetAuthSigningKeyID
(line 302)GetAuthEncryptionKeyID
(line 310)GetAuthEncryptionAlg
(line 314)GetAuthEncryptionEnc
(line 318)If signing and encryption are supported for revocation endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.
Line range hint
1-330
: Overall, good improvements to client authentication. Consider addressing the following:
- Add documentation to new methods in the
EndpointClientAuthHandler
interface (lines 164-174).- Implement or explain the rationale for methods returning empty strings in
EndpointClientAuthJWTClient
(lines 182-226).- For each endpoint handler (
TokenEndpointClientAuthHandler
,IntrospectionEndpointClientAuthHandler
,RevocationEndpointClientAuthHandler
), implement or explain the rationale for methods returning empty strings.These changes significantly enhance the flexibility of client authentication. Ensuring proper implementation or documentation for the methods currently returning empty strings will improve the overall quality and maintainability of the code.
client_authentication_strategy.go (3)
Line range hint
29-67
: LGTM! Improved flexibility in client authentication.The changes to the
AuthenticateClient
method significantly enhance its capabilities, particularly in handling JWT assertions. The switch fromresolver
tohandler
provides a more generalized approach to endpoint authentication.One minor suggestion:
Consider adding a comment explaining the purpose of the
handler
parameter, as it's a significant change from the previousresolver
. This would help future developers understand the rationale behind this change.-func (s *DefaultClientAuthenticationStrategy) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values, handler EndpointClientAuthHandler) (client Client, method string, err error) { +// AuthenticateClient authenticates a client using various methods including JWT assertions. +// The 'handler' parameter provides endpoint-specific authentication logic, replacing the previous 'resolver'. +func (s *DefaultClientAuthenticationStrategy) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values, handler EndpointClientAuthHandler) (client Client, method string, err error) {
137-186
: LGTM! Significant improvements to client assertion handling.The
NewClientAssertion
function has been greatly enhanced with more robust JWT validation and error handling. The use ofjwt.Strategy
provides flexibility in JWT processing, which is excellent for supporting various client authentication scenarios.One suggestion for improvement:
Consider adding more detailed error messages for different failure scenarios. This could help in troubleshooting client authentication issues. For example:
if token, err = strategy.Decode(ctx, assertion, jwt.WithAllowUnverified(), jwt.WithSigAlgorithm(jwt.SignatureAlgorithmsNone...)); err != nil { - return &ClientAssertion{Assertion: assertion, Type: assertionType}, resolveJWTErrorToRFCError(err) + return &ClientAssertion{Assertion: assertion, Type: assertionType}, resolveJWTErrorToRFCError(err).WithHintf("Failed to decode client assertion of type '%s'.", assertionType) }
389-464
: LGTM! Excellent addition for detailed JWT error reporting.The
fmtClientAssertionDecodeError
function is a valuable addition that provides comprehensive and detailed error reporting for JWT validation issues during client authentication. This will greatly assist in debugging and troubleshooting JWT-related problems.One minor suggestion for consistency:
Consider using consistent string formatting across all error messages. Some messages use
%s
for string interpolation while others use'%s'
. Standardizing on one format (preferably'%s'
for clearer distinction of interpolated values) would improve readability. For example:-return outer.WithDebugf("OAuth 2.0 client with id '%s' provided a client assertion that was malformed. %s.", client.GetID(), strings.TrimPrefix(errJWTValidation.Error(), "go-jose/go-jose: ")) +return outer.WithDebugf("OAuth 2.0 client with id '%s' provided a client assertion that was malformed. '%s'.", client.GetID(), strings.TrimPrefix(errJWTValidation.Error(), "go-jose/go-jose: "))Apply this change consistently across all error messages in the function for improved readability and maintainability.
authorize_request_handler_oidc_request_test.go (6)
32-41
: Improved key generation for test casesThe changes introduce new key generation for RSA and ECDSA, which is good for testing different scenarios. However, consider the following improvements:
- Use a constant key size for RSA to ensure consistency across tests.
- Consider using a named curve constant for ECDSA key generation for better readability.
Example:
-keyRSA, err := rsa.GenerateKey(rand.Reader, 2048) +const rsaKeySize = 2048 +keyRSA, err := rsa.GenerateKey(rand.Reader, rsaKeySize) -keyECDSA, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +keyECDSA, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
56-110
: Comprehensive JWK set definitionsThe addition of multiple JWK definitions for different algorithms (RSA, ECDSA) and uses (signing, encryption) is excellent for thorough testing. However, there's room for improvement in code organization:
- Consider grouping related JWKs together (e.g., all RSA keys, all ECDSA keys).
- Use constants for repeated values like key IDs and algorithms to improve maintainability.
Example:
const ( kidRS256Sig = "rs256-sig" kidES256Sig = "es256-sig" kidES256Enc = "es256-enc" ) jwkPublicSigRSA := &jose.JSONWebKey{ Key: keyRSA.Public(), KeyID: kidRS256Sig, Algorithm: string(jose.RS256), Use: consts.JSONWebTokenUseSignature, } jwkPrivateSigRSA := &jose.JSONWebKey{ Key: keyRSA, KeyID: kidRS256Sig, Algorithm: string(jose.RS256), Use: consts.JSONWebTokenUseSignature, } // ... (group other related JWKs)
112-129
: JWKS creation and debugging outputThe creation of
jwksPrivate
andjwksPublic
is good for organizing the keys. However, there's an unnecessary debug print statement:fmt.Println(jwksPrivate)This line should be removed as it's not needed in the test and may clutter the output.
Line range hint
150-179
: HTTP test server setupThe setup of the HTTP test server with various endpoints for different test scenarios is well-organized. However, consider the following improvements:
- Use a
const
block for the endpoint paths to improve maintainability.- Consider using a map to store the handlers, which can make it easier to add or modify endpoints in the future.
Example:
const ( endpointJWKS = "/jwks.json" endpointValidStandard = "/request-object/valid/standard.jwk" // ... (other endpoint constants) ) handlers := map[string]http.HandlerFunc{ endpointJWKS: handlerJWKS, endpointValidStandard: handleString(assertionRequestObjectValid), // ... (other handlers) } for path, handler := range handlers { mux.Handle(path, handler) }
Line range hint
181-566
: Comprehensive test casesThe test cases cover a wide range of scenarios, which is excellent for ensuring the robustness of the implementation. However, the large number of test cases makes the code harder to read and maintain. Consider the following improvements:
- Group related test cases into subtests using
t.Run()
.- Use table-driven tests with more descriptive struct fields.
- Consider moving the test case definitions to a separate function or even a separate file to improve readability of the main test function.
Example:
func TestAuthorizeRequestParametersFromOpenIDConnectRequestObject(t *testing.T) { testCases := getTestCases(t) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // ... (existing test logic) }) } } func getTestCases(t *testing.T) []struct { name string description string have url.Values par bool client Client expected url.Values wantErr bool errType error errString string errRegex *regexp.Regexp } { return []struct{ // ... (test case definitions) }{ { name: "ValidRequestObject", description: "Should pass with a valid request object", have: url.Values{consts.FormParameterScope: {"foo openid"}, consts.FormParameterClientID: {"foo"}, consts.FormParameterResponseType: {consts.ResponseTypeImplicitFlowToken}, consts.FormParameterRequest: {assertionRequestObjectValid}}, client: &DefaultJARClient{JSONWebKeys: jwksPublic, RequestObjectSigningAlg: "RS256", DefaultClient: &DefaultClient{ID: "foo"}}, expected: url.Values{consts.FormParameterScope: {"foo openid"}, consts.FormParameterClientID: {"foo"}, consts.FormParameterResponseType: {consts.ResponseTypeImplicitFlowToken}, consts.FormParameterResponseMode: {consts.ResponseModeFormPost}, consts.FormParameterRequest: {assertionRequestObjectValid}, "foo": {"bar"}, "baz": {"baz"}}, }, // ... (other test cases) } }
Line range hint
568-621
: Test execution logicThe test execution logic is sound, but there are a few areas for improvement:
- Consider using
require
instead ofassert
for critical checks that should stop the test if they fail.- The error checking logic could be simplified using a switch statement.
- The form parameter checking could be extracted into a helper function for better readability.
Example:
func TestAuthorizeRequestParametersFromOpenIDConnectRequestObject(t *testing.T) { // ... (existing setup code) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // ... (existing setup code) err := provider.authorizeRequestParametersFromOpenIDConnectRequestObject(context.Background(), r, tc.par) switch { case tc.wantErr: require.Error(t, err) if tc.errType != nil { assert.ErrorIs(t, err, tc.errType) } if tc.errString != "" { assert.EqualError(t, ErrorToDebugRFC6749Error(err), tc.errString) } if tc.errRegex != nil { assert.Regexp(t, tc.errRegex, ErrorToDebugRFC6749Error(err).Error()) } default: require.NoError(t, err) assertFormParameters(t, tc.expected, r.Form) } }) } } func assertFormParameters(t *testing.T, expected, actual url.Values) { assert.Equal(t, len(expected), len(actual)) for k, v := range expected { assert.EqualValues(t, v, actual[k], fmt.Sprintf("Parameter %s did not match", k)) } }token/jwt/util.go (3)
375-384
: Clarify Error Message When Handling Encrypted TokensIn the
getJWTSignature
function, when the token has five segments (indicating it's an encrypted JWT), the error message returned is "invalid token: the token is probably encrypted" (line 378~). Since encrypted tokens are valid JWTs, labeling them as "invalid" might be misleading.Consider updating the error message to more accurately reflect the situation:
-return "", errors.WithStack(errors.New("invalid token: the token is probably encrypted")) +return "", errors.WithStack(errors.New("cannot extract signature from an encrypted token"))This clarifies that the inability to extract a signature is due to the token being encrypted, not because the token is invalid.
417-427
: Caution Advisable When Using Unsafe JWT ParsingThe
UnsafeParseSignedAny
function parses JWTs without verifying signatures or validating claims (lines 422~). This could pose a security risk if used improperly, as it may allow malicious tokens to be processed without verification.Ensure that this function is only used in controlled scenarios where the tokens are trusted, and the lack of verification does not introduce vulnerabilities. Consider adding explicit documentation or comments to highlight the security implications and guide developers on safe usage.
96-145
: Validate Presence and Type of Essential JWE Header FieldsIn the
headerValidateJWE
function, the code correctly checks for the presence and type of thekid
,alg
, andenc
header fields. However, it might be beneficial to include additional validation for thecty
(Content Type) header if it's required in your use case.Consider verifying the
cty
header to ensure it matches expected values, which can help prevent token misinterpretation and enhance security.token/jwt/client.go (1)
7-70
: Add unit tests for client constructors and interfaces.To ensure the reliability of the new client implementations, please add unit tests for the constructors (e.g.,
NewJARClient
,NewIDTokenClient
) and verify that the decorated clients correctly implement theClient
interface.Would you like me to help generate the unit test code or open a GitHub issue to track this task?
Also applies to: 72-82
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
go.mod
is excluded by!**/*.mod
go.sum
is excluded by!**/*.sum
,!**/*.sum
📒 Files selected for processing (78)
- access_error_test.go (2 hunks)
- access_write_test.go (1 hunks)
- authorize_request_handler.go (5 hunks)
- authorize_request_handler_oidc_request_test.go (9 hunks)
- client.go (6 hunks)
- client_authentication.go (3 hunks)
- client_authentication_jwks_strategy.go (3 hunks)
- client_authentication_secret.go (1 hunks)
- client_authentication_secret_plaintext.go (1 hunks)
- client_authentication_strategy.go (8 hunks)
- client_authentication_test.go (27 hunks)
- compose/compose.go (1 hunks)
- compose/compose_oauth2.go (1 hunks)
- compose/compose_openid.go (3 hunks)
- compose/compose_strategy.go (2 hunks)
- config.go (3 hunks)
- config_default.go (8 hunks)
- fosite.go (2 hunks)
- generate-mocks.sh (1 hunks)
- generate.go (1 hunks)
- handler/oauth2/introspector_jwt.go (1 hunks)
- handler/oauth2/introspector_jwt_test.go (3 hunks)
- handler/oauth2/strategy.go (1 hunks)
- handler/oauth2/strategy_jwt_profile.go (4 hunks)
- handler/oauth2/strategy_jwt_profile_test.go (6 hunks)
- handler/oauth2/strategy_jwt_session.go (2 hunks)
- handler/openid/flow_device_authorization_test.go (2 hunks)
- handler/openid/flow_explicit_auth_test.go (1 hunks)
- handler/openid/flow_explicit_token_test.go (1 hunks)
- handler/openid/flow_hybrid.go (1 hunks)
- handler/openid/flow_hybrid_test.go (4 hunks)
- handler/openid/flow_implicit.go (1 hunks)
- handler/openid/flow_implicit_test.go (2 hunks)
- handler/openid/flow_refresh_token_test.go (2 hunks)
- handler/openid/helper_test.go (1 hunks)
- handler/openid/strategy_jwt.go (4 hunks)
- handler/openid/strategy_jwt_test.go (1 hunks)
- handler/openid/validator.go (2 hunks)
- handler/openid/validator_test.go (2 hunks)
- handler/rfc7523/handler.go (1 hunks)
- handler/rfc8693/custom_jwt_type_handler.go (3 hunks)
- handler/rfc8693/id_token_type_handler.go (2 hunks)
- handler/rfc8693/token_exchange_test.go (4 hunks)
- integration/helper_setup_test.go (1 hunks)
- integration/introspect_token_test.go (2 hunks)
- internal/consts/client_auth_method.go (1 hunks)
- internal/consts/const.go (1 hunks)
- internal/consts/jwt.go (1 hunks)
- internal/consts/spec.go (1 hunks)
- internal/mock/client_secret.go (1 hunks)
- internal/randx/sequence.go (3 hunks)
- internal/test_helpers.go (2 hunks)
- introspection_request_handler_test.go (0 hunks)
- introspection_response_writer.go (1 hunks)
- response_handler.go (1 hunks)
- testing/mock/client.go (1 hunks)
- testing/mock/pkce_storage.go (2 hunks)
- token/jarm/generate.go (1 hunks)
- token/jarm/types.go (1 hunks)
- token/jwt/claims_id_token.go (2 hunks)
- token/jwt/claims_jarm.go (1 hunks)
- token/jwt/claims_jwt.go (3 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_map_test.go (1 hunks)
- token/jwt/claims_test.go (1 hunks)
- token/jwt/client.go (1 hunks)
- token/jwt/client_test.go (1 hunks)
- token/jwt/consts.go (1 hunks)
- token/jwt/issuer.go (1 hunks)
- token/jwt/jwt.go (0 hunks)
- token/jwt/jwt_strategy.go (1 hunks)
- token/jwt/jwt_strategy_opts.go (1 hunks)
- token/jwt/jwt_strategy_test.go (1 hunks)
- token/jwt/jwt_test.go (0 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/token_test.go (6 hunks)
- token/jwt/util.go (1 hunks)
- token/jwt/validation_error.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (3)
- introspection_request_handler_test.go
- token/jwt/jwt.go
- token/jwt/jwt_test.go
✅ Files skipped from review due to trivial changes (1)
- internal/mock/client_secret.go
🚧 Files skipped from review as they are similar to previous changes (62)
- access_error_test.go
- access_write_test.go
- client.go
- client_authentication_jwks_strategy.go
- client_authentication_secret.go
- client_authentication_secret_plaintext.go
- compose/compose.go
- compose/compose_oauth2.go
- compose/compose_openid.go
- compose/compose_strategy.go
- config.go
- config_default.go
- fosite.go
- generate-mocks.sh
- generate.go
- handler/oauth2/introspector_jwt.go
- handler/oauth2/introspector_jwt_test.go
- handler/oauth2/strategy.go
- handler/oauth2/strategy_jwt_session.go
- handler/openid/flow_device_authorization_test.go
- handler/openid/flow_explicit_auth_test.go
- handler/openid/flow_explicit_token_test.go
- handler/openid/flow_hybrid.go
- handler/openid/flow_hybrid_test.go
- handler/openid/flow_implicit.go
- handler/openid/flow_implicit_test.go
- handler/openid/flow_refresh_token_test.go
- handler/openid/helper_test.go
- handler/openid/strategy_jwt.go
- handler/openid/strategy_jwt_test.go
- handler/openid/validator.go
- handler/openid/validator_test.go
- handler/rfc7523/handler.go
- handler/rfc8693/custom_jwt_type_handler.go
- handler/rfc8693/id_token_type_handler.go
- handler/rfc8693/token_exchange_test.go
- integration/helper_setup_test.go
- integration/introspect_token_test.go
- internal/consts/client_auth_method.go
- internal/consts/const.go
- internal/consts/jwt.go
- internal/consts/spec.go
- internal/randx/sequence.go
- internal/test_helpers.go
- introspection_response_writer.go
- response_handler.go
- testing/mock/client.go
- testing/mock/pkce_storage.go
- token/jarm/generate.go
- token/jarm/types.go
- token/jwt/claims_jarm.go
- token/jwt/claims_jwt.go
- token/jwt/claims_map_test.go
- token/jwt/claims_test.go
- token/jwt/client_test.go
- token/jwt/consts.go
- token/jwt/issuer.go
- token/jwt/jwt_strategy.go
- token/jwt/jwt_strategy_opts.go
- token/jwt/jwt_strategy_test.go
- token/jwt/token.go
- token/jwt/token_test.go
🧰 Additional context used
📓 Learnings (2)
authorize_request_handler.go (1)
Learnt from: james-d-elliott PR: authelia/oauth2-provider#140 File: authorize_request_handler.go:121-125 Timestamp: 2024-09-27T07:36:36.699Z Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
authorize_request_handler_oidc_request_test.go (1)
Learnt from: james-d-elliott PR: authelia/oauth2-provider#66 File: authorize_request_handler_oidc_request_test.go:108-179 Timestamp: 2024-03-23T23:32:47.956Z Learning: In test files (with the _test.go suffix), using a 1024-bit RSA key is acceptable for testing specific scenarios, such as the acceptance of insecure keys, or to keep tests running quickly. This practice is not applicable to production code.
🔇 Additional comments (34)
token/jwt/validation_error.go (1)
9-21
: Excellent addition of granular error types!The new constants for specific validation errors greatly enhance the error reporting capabilities. This granularity will be beneficial for debugging and more precise error handling. The use of bitwise flags with iota is maintained, allowing for efficient combination of multiple error types.
token/jwt/claims_map.go (17)
28-38
: LGTM: GetIssuer implementation is correct and safe.The function properly retrieves the 'iss' claim, handles the case where the claim is not present, and uses safe type assertion. Good job!
40-50
: LGTM: VerifyIssuer implementation is secure and flexible.The function correctly verifies the 'iss' claim, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyMapString
suggests a constant-time comparison, which is good for security.
52-63
: LGTM: GetSubject implementation is correct and consistent.The function properly retrieves the 'sub' claim, handles the case where the claim is not present, and uses safe type assertion. The implementation is consistent with
GetIssuer
, which is good for maintainability.
65-75
: LGTM: VerifySubject implementation is secure and consistent.The function correctly verifies the 'sub' claim, handles the case where the claim is not present, and respects the
required
parameter. The implementation is consistent withVerifyIssuer
, which is good for maintainability. The use ofverifyMapString
ensures a secure comparison.
77-86
: LGTM: GetAudience implementation is flexible and robust.The function properly retrieves the 'aud' claim and handles the case where the claim is not present. The use of
StringSliceFromMap
suggests proper handling of different audience formats (string or []string), which is a good practice for flexibility.
90-98
: LGTM: VerifyAudience implementation is correct and flexible.The function properly verifies the 'aud' claim, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyAud
suggests proper handling of audience verification.
100-111
: LGTM: VerifyAudienceAll implementation is comprehensive.The function properly verifies all values in the 'aud' claim against provided values, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyAudAll
ensures proper handling of multiple audience values, which is a good practice for flexibility.
113-124
: LGTM: VerifyAudienceAny implementation is flexible and correct.The function properly verifies any value in the 'aud' claim against provided values, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyAudAny
ensures proper handling of multiple audience values, providing flexibility in audience verification.
126-129
: LGTM: GetExpiresAt implementation is concise and correct.The function properly retrieves the 'exp' claim using the
toInt64
helper method, which ensures correct type conversion and error handling.
133-141
: LGTM: VerifyExpiresAt implementation is correct and flexible.The function properly verifies the 'exp' claim, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyInt64Future
ensures proper handling of expiration time verification.
143-146
: LGTM: GetIssuedAt implementation is concise and correct.The function properly retrieves the 'iat' claim using the
toInt64
helper method, ensuring correct type conversion and error handling.
150-158
: LGTM: VerifyIssuedAt implementation is correct and flexible.The function properly verifies the 'iat' claim, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyInt64Past
ensures proper handling of issued at time verification.
160-163
: LGTM: GetNotBefore implementation is concise and correct.The function properly retrieves the 'nbf' claim using the
toInt64
helper method, ensuring correct type conversion and error handling.
167-175
: LGTM: VerifyNotBefore implementation is correct and flexible.The function properly verifies the 'nbf' claim, handles the case where the claim is not present, and respects the
required
parameter. The use ofverifyInt64Past
ensures proper handling of not before time verification.
247-258
: LGTM: UnmarshalJSON implementation is robust and flexible.The function uses a custom JSON decoder to handle numbers as either int or float, which is a good practice for dealing with different number formats in JWT claims. The error handling is appropriate, wrapping errors with additional context for better debugging.
Line range hint
1-403
: Overall, excellent implementation with robust JWT claim handling.The changes to this file significantly enhance the JWT claim handling capabilities. The implementation is thorough, covering all standard claims with proper validation, error handling, and security considerations. The use of helper functions and the functional options pattern demonstrates good software design practices.
Key strengths:
- Comprehensive coverage of all standard JWT claims.
- Consistent error handling and validation across different claim types.
- Flexible API design using the functional options pattern.
- Security considerations, such as constant-time comparisons for sensitive data.
Minor suggestions for improvement:
- Add more inline documentation, especially for the validation option functions.
- Consider adding a comment explaining the
ValidationError
struct in theValid
function.Great job on these improvements!
260-268
: LGTM: toInt64 method is correctly implemented.The method properly handles the case where the claim is not present and delegates the actual conversion to a separate
toInt64
function. This separation of concerns is a good practice.Please ensure that the
toInt64
function (which is not visible in this file) correctly handles all possible number formats (int, float, string) that could be present in a JWT claim. You can verify this by checking its implementation or its unit tests.client_authentication_strategy.go (2)
Line range hint
14-26
: LGTM! Important updates to authentication strategy.The addition of the JWT import and the updates to the
DefaultClientAuthenticationStrategy
struct are significant improvements. These changes introduce JWT-based authentication capabilities and JWKS fetching, which are crucial for modern OAuth2 implementations.
Line range hint
70-136
: LGTM! Consistent updates to the authenticate method.The changes to the
authenticate
method align well with the updates made to theAuthenticateClient
method. The replacement ofresolver
withhandler
is consistent, and the core authentication logic remains intact. This approach maintains existing functionality while accommodating the new JWT-based authentication capabilities.authorize_request_handler_oidc_request_test.go (1)
42-54
: New JWK definitions enhance test coverageThe addition of various JWK (JSON Web Key) definitions, including
jwkNone
,clientSecretHS256
, andjwkEncAES256
, improves the test coverage by allowing for different signing and encryption scenarios. This is a positive change that will help ensure the robustness of the implementation.handler/oauth2/strategy_jwt_profile.go (2)
174-174
: Confirm the correctness of the 'Encode' method usageIn the
GenerateJWT
method, theEncode
function is called ons.Strategy
. Please verify that all the options passed toEncode
, such asjwt.WithClaims
,jwt.WithHeaders
, andjwt.WithJWTProfileAccessTokenClient
, are correctly set up and that they align with the expected parameters of the method.
60-62
: Validate proper use of the updated 'Strategy' in token validationIn the
ValidateAccessToken
method, thevalidateJWT
function is called withs.Strategy
. Ensure thats.Strategy
correctly implements the necessary interface expected byvalidateJWT
and that all dependent code has been updated to accommodate this change fromSigner
toStrategy
.Run the following script to ensure that
s.Strategy
satisfies the required interfaces:token/jwt/client.go (1)
172-174
: Verify the consistency ofIsClientSigned()
return values.The
IsClientSigned()
method returnstrue
indecoratedJARClient
butfalse
in other decorated client structs. Please confirm if this is intentional and reflects the expected behavior.Also applies to: 234-236, 296-298, 356-358, 416-418, 478-480
authorize_request_handler.go (10)
12-12
: LGTM!The import of
time
is necessary for time-related validations in the JWT claims.
Line range hint
69-82
: LGTM!The handling of the
alg
parameter and thealgAny
flag is logically correct. It ensures appropriate processing based on the client's configured request object signing algorithm.
119-119
: LGTM!Retrieving the issuer using
GetIDTokenIssuer
is properly implemented.
128-134
: LGTM!The JWT header validation options are correctly set using the client's configurations. This ensures that the token headers are validated against the expected values.
136-138
: LGTM!The token header validation is appropriately performed using
token.Valid(optsValidHeader...)
, and errors are correctly handled.
140-145
: LGTM!The additional check ensures that clients not explicitly allowing the 'none' algorithm cannot use it, which enhances security by preventing unauthorized use of unsecured tokens.
193-195
: LGTM!The code correctly handles the scenario where the issuer is not configured, returning a server error with a meaningful message.
197-203
: LGTM!The claim validation options are appropriately set, including the custom time function and validations for issuer and audience. This ensures the token claims are valid according to the expected values.
205-206
: LGTM!The claims are validated using
claims.Valid(optsValidClaims...)
, and errors are properly handled usingfmtRequestObjectDecodeError
.
521-598
: Refactoring suggestion forfmtRequestObjectDecodeError
remains applicable.This function is extensive and contains repetitive patterns. Consider refactoring to enhance readability and maintainability by:
- Eliminating repetitive code through helper functions.
- Utilizing structured error types to handle different error scenarios more robustly.
097e725
to
d31ada4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Outside diff range and nitpick comments (9)
client_authentication.go (1)
Line range hint
1-339
: Overall review: Significant enhancements to client authentication, but clarification and documentation needed.The changes to
client_authentication.go
represent a substantial enhancement to the client authentication capabilities, potentially supporting more advanced scenarios like JWE. However, there are several areas that need attention:
- Documentation: Add comprehensive comments explaining the purpose and expected behavior of new methods, especially those with empty implementations.
- Consistency: The pattern of empty implementations across different endpoint handlers suggests a deliberate design choice. Consider extracting this common behavior into a shared implementation or explaining the rationale behind this approach.
- Placeholder Implementations: If any of the empty implementations are placeholders, add TODO comments to indicate future work.
- Testing: Ensure that unit tests are updated or added to cover the new functionality and edge cases.
- Usage Examples: Consider adding usage examples or updating existing documentation to demonstrate how these new authentication capabilities should be used.
Given the significant changes to the authentication logic, it would be beneficial to provide a high-level architectural overview of the new authentication flow. This could be in the form of a comment at the package level or in a separate documentation file, explaining how the different components (EndpointClientAuthHandler, EndpointClientAuthJWTClient, and the various endpoint-specific handlers) interact and what scenarios they support.
token/jwt/claims_map_test.go (3)
17-127
: LGTM! Comprehensive test cases for VerifyAudience.The test cases for
TestMapClaims_VerifyAudience
are well-structured and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The table-driven test approach is a good practice for testing multiple scenarios efficiently.Consider adding a test case for an empty audience slice to ensure the method handles this edge case correctly:
{ "ShouldFailEmptyAudience", MapClaims{ consts.ClaimAudience: []string{}, }, "foo", true, false, },
550-640
: LGTM with a suggestion for improvement.The test cases for
TestMapClaims_VerifyExpiresAt
are well-structured and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained.Consider adding a test case for a future expiration time to ensure the method correctly handles tokens that have not yet expired:
{ "ShouldPassFutureExpiration", MapClaims{ consts.ClaimExpirationTime: int64(9999999999), // Far future timestamp }, int64(123), true, true, },This additional test case would improve the coverage by verifying that the method correctly handles tokens with future expiration times.
850-1011
: LGTM! Comprehensive test cases for the Valid method.The test cases for
TestMapClaims_Valid
are well-structured and cover a wide range of scenarios, including different validation options and error cases. The error checking logic is thorough, verifying both error messages and specific error types.Consider adding a nil check before accessing the
ValidationError
to improve robustness:errors.As(actual, &e) -require.NotNil(t, e) +if assert.NotNil(t, e) { var errs uint32 for _, err := range tc.errs { errs |= err assert.True(t, e.Has(err)) } assert.Equal(t, errs, e.Errors) +}This change ensures that the subsequent error checks are only performed if the error is not nil, preventing potential panics in case of unexpected error types.
token/jwt/client.go (5)
1-6
: Add package-level documentation.Consider adding a package comment to describe the purpose and usage of the
jwt
package. This improves readability and helps other developers understand the functionality provided.
3-5
: Remove unnecessary import if not used.Review whether the import of
"github.com/go-jose/go-jose/v4"
is required in this file. If it's not used, it can be removed to clean up the code.
80-91
: Clarify the purpose of theIsClientSigned
method.The
IsClientSigned()
method in theClient
interface returns a boolean, but it's not immediately clear what "client signed" signifies. Providing additional documentation or renaming the method to better reflect its purpose could enhance code clarity.
163-185
: Consistent receiver naming for methods.In
decoratedJARClient
, the receiver is namedr
. For consistency and clarity, consider renaming it toc
orclient
, matching the naming convention used elsewhere.
260-271
: Correct indentation and formatting in comments.Some comments have inconsistent indentation, making them harder to read. Ensure that all comments are properly indented and formatted for better readability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (9)
- client.go (8 hunks)
- client_authentication.go (3 hunks)
- handler/oauth2/strategy_jwt_profile.go (4 hunks)
- introspection_response_writer.go (1 hunks)
- token/jarm/generate.go (1 hunks)
- token/jarm/types.go (1 hunks)
- token/jwt/claims_map_test.go (1 hunks)
- token/jwt/client.go (1 hunks)
- token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- client.go
- introspection_response_writer.go
- token/jarm/generate.go
- token/jarm/types.go
- token/jwt/util.go
🔇 Additional comments (17)
client_authentication.go (6)
164-166
: LGTM. Consider adding documentation for the new method.The addition of
GetAuthSigningKeyID
method to theEndpointClientAuthHandler
interface is a good enhancement for flexible client authentication.Could you provide more context on how this method will be used in the authentication flow? Also, consider adding a comment to explain the purpose and expected return value of this method.
170-178
: LGTM. Consider enhancing documentation for the new encryption-related methods.The addition of
GetAuthEncryptionKeyID
,GetAuthEncryptionAlg
, andGetAuthEncryptionEnc
methods to theEndpointClientAuthHandler
interface is a good enhancement for supporting encrypted client authentication.To improve clarity and maintainability:
- Add comments explaining the purpose and expected return values for each new method.
- Consider providing a brief overview of how these methods fit into the overall client authentication flow, possibly in a comment block at the interface level.
305-324
: Clarify the purpose of new method implementations in RevocationEndpointClientAuthHandler and explain the pattern across handlers.The newly added methods in
RevocationEndpointClientAuthHandler
follow the same pattern as inIntrospectionEndpointClientAuthHandler
:
GetAuthSigningKeyID
,GetAuthEncryptionKeyID
,GetAuthEncryptionAlg
, andGetAuthEncryptionEnc
return empty strings.GetAuthSigningAlg
delegates to the client's method.
- Please clarify if this consistent pattern across different endpoint handlers is intentional. If so, consider extracting this common behavior into a shared implementation to reduce code duplication.
- Add comments explaining the rationale behind the empty string returns and the delegation of
GetAuthSigningAlg
.- If these are placeholder implementations, please add TODO comments indicating the need for future implementation.
- Consider adding a comment at the package level explaining the overall authentication strategy across different endpoints and why certain methods are implemented differently.
To verify the consistency of implementations across different handlers, you can run the following script:
#!/bin/bash # Description: Compare implementations of new methods across different endpoint handlers echo "Comparing implementations of new methods across different endpoint handlers:" rg --type go "(TokenEndpointClientAuthHandler|IntrospectionEndpointClientAuthHandler|RevocationEndpointClientAuthHandler).*(GetAuthSigningKeyID|GetAuthSigningAlg|GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"
245-256
: Clarify the purpose of empty encryption-related method implementations.The newly added methods
GetAuthEncryptionKeyID
,GetAuthEncryptionAlg
, andGetAuthEncryptionEnc
inTokenEndpointClientAuthHandler
all return empty strings.
- Is this intentional? If so, please add comments explaining why these methods return empty strings for the token endpoint.
- If encryption is not supported or not applicable for the token endpoint, consider adding a comment at the struct level explaining this.
- If these are placeholder implementations, please add TODO comments indicating the need for future implementation.
To verify the usage of these methods, you can run the following script:
#!/bin/bash # Description: Check for usage of encryption-related methods in TokenEndpointClientAuthHandler echo "Searching for usage of encryption-related methods in TokenEndpointClientAuthHandler:" rg --type go "TokenEndpointClientAuthHandler.*(GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"
271-290
: Clarify the purpose of new method implementations in IntrospectionEndpointClientAuthHandler.The newly added methods in
IntrospectionEndpointClientAuthHandler
have varying implementations:
GetAuthSigningKeyID
,GetAuthEncryptionKeyID
,GetAuthEncryptionAlg
, andGetAuthEncryptionEnc
return empty strings.GetAuthSigningAlg
delegates to the client's method.
- Please clarify if the empty string returns are intentional. If so, add comments explaining the rationale.
- Explain why
GetAuthSigningAlg
has a different implementation compared to other methods.- If these are placeholder implementations, please add TODO comments indicating the need for future implementation.
- Consider adding a comment at the struct level explaining the overall authentication strategy for the introspection endpoint.
To verify the usage and implementation of these methods, you can run the following script:
#!/bin/bash # Description: Check for usage and implementation of new methods in IntrospectionEndpointClientAuthHandler echo "Searching for usage and implementation of new methods in IntrospectionEndpointClientAuthHandler:" rg --type go "IntrospectionEndpointClientAuthHandler.*(GetAuthSigningKeyID|GetAuthSigningAlg|GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"
186-230
: LGTM with suggestions. Consider refining the implementation of EndpointClientAuthJWTClient.The addition of
EndpointClientAuthJWTClient
is a good step towards supporting JWT-based client authentication. However, there are a few points to consider:
- Some methods (GetSigningKeyID, GetEncryptionKeyID, GetEncryptionAlg, GetEncryptionEnc) return empty strings. Is this intentional or are these placeholder implementations?
- The IsClientSigned method always returns true. This might be an oversimplification. Consider implementing a more nuanced check.
- Add comments to explain the purpose of this struct and its methods, especially for methods with non-obvious implementations.
To verify the usage and implementation of this new struct, you can run the following script:
✅ Verification successful
Apologies for the error in the previous script. Here's the corrected script to continue verification:
[/run_scripts]
Apologies for the error in the previous script. Here's the corrected script to continue verification:
[/run_scripts]
Verification Successful: EndpointClientAuthJWTClient Implementation is Intentional
The implementation of
EndpointClientAuthJWTClient
aligns with existing patterns in the codebase. The methods that return empty strings (GetSigningKeyID
,GetEncryptionKeyID
,GetEncryptionAlg
,GetEncryptionEnc
) are consistently implemented across various client handlers, indicating intentional design choices. Additionally, theIsClientSigned
method is uniformly used, supporting its current implementation.Recommendations:
- Add Comments: Include explanatory comments within
EndpointClientAuthJWTClient
to clarify the purpose of methods returning empty strings and the rationale behind always returningtrue
forIsClientSigned
.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for usage of EndpointClientAuthJWTClient and its methods # Search for usage of EndpointClientAuthJWTClient echo "Searching for usage of EndpointClientAuthJWTClient:" rg --type go "EndpointClientAuthJWTClient" # Search for implementations of methods that return empty strings echo "\nSearching for implementations of methods that return empty strings:" rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc" # Search for usage of IsClientSigned echo "\nSearching for usage of IsClientSigned:" rg --type go "IsClientSigned"Length of output: 2512
Script:
#!/bin/bash # Description: Check for usage of EndpointClientAuthJWTClient and its methods # Search for usage of EndpointClientAuthJWTClient echo "Searching for usage of EndpointClientAuthJWTClient:" rg --type go "EndpointClientAuthJWTClient" # Search for implementations of methods that return empty strings echo "Searching for implementations of methods that return empty strings:" rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc" # Search for usage of IsClientSigned echo "Searching for usage of IsClientSigned:" rg --type go "IsClientSigned"Length of output: 6262
Script:
#!/bin/bash # Description: Check for usage of EndpointClientAuthJWTClient and its methods # Search for usage of EndpointClientAuthJWTClient echo "Searching for usage of EndpointClientAuthJWTClient:" rg --type go "EndpointClientAuthJWTClient" # Search for implementations of methods that return empty strings echo "Searching for implementations of methods that return empty strings:" rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc" # Search for usage of IsClientSigned echo "Searching for usage of IsClientSigned:" rg --type go "IsClientSigned"Length of output: 6262
token/jwt/claims_map_test.go (6)
129-256
: LGTM! Thorough test cases for VerifyAudienceAll.The test cases for
TestMapClaims_VerifyAudienceAll
are well-designed and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is commendable.
389-470
: LGTM! Comprehensive test cases for VerifyIssuer.The test cases for
TestMapClaims_VerifyIssuer
are well-designed and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained, which is excellent for readability and maintainability.
474-546
: LGTM! Well-structured test cases for VerifySubject.The test cases for
TestMapClaims_VerifySubject
are thorough and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained, which is excellent for readability and maintainability.
644-743
: LGTM! Excellent test coverage for VerifyIssuedAt.The test cases for
TestMapClaims_VerifyIssuedAt
are comprehensive and well-structured. They cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The inclusion of tests for both past and future issued at times is particularly noteworthy and ensures robust validation of the VerifyIssuedAt method.
747-846
: LGTM! Thorough test coverage for VerifyNotBefore.The test cases for
TestMapClaims_VerifyNotBefore
are well-designed and comprehensive. They cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The inclusion of tests for both past and future "not before" times ensures robust validation of the VerifyNotBefore method.
Line range hint
1-1011
: Excellent test coverage and structure for MapClaims.The
token/jwt/claims_map_test.go
file demonstrates high-quality test coverage for the MapClaims structure. Key strengths include:
- Consistent use of table-driven tests across all functions.
- Comprehensive coverage of various scenarios, including edge cases and error conditions.
- Clear and descriptive test case names.
- Thorough error checking, including verification of specific error types and bitwise error flags.
The minor suggestions provided in the review comments will further enhance the robustness and completeness of the test suite. Overall, this test file serves as an excellent example of thorough and well-structured unit testing.
handler/oauth2/strategy_jwt_profile.go (2)
177-187
: Handle potential nil pointer dereference invalidateJWT
The
validateJWT
function may cause a nil pointer dereference ifstrategy.Decode
returns an error and a niltoken
. Whentoken
is nil, passing it tofmtValidateJWTError
can lead to a panic when the function tries to access fields oftoken
.
189-273
: RefactorfmtValidateJWTError
for improved maintainabilityThe
fmtValidateJWTError
function is lengthy and contains deeply nested conditional logic, which can make it difficult to read and maintain. Consider refactoring the function to enhance readability and maintainability. This could involve extracting repetitive code into helper functions or mapping error cases to corresponding error messages.token/jwt/client.go (3)
159-185
: Avoid code duplication in decorated client structs.The
decorated*Client
structs and their methods largely duplicate code by delegating to underlying client interfaces. Consider refactoring to reduce repetition, possibly by using embedded types or a generic decorator pattern for shared behaviors.
221-248
: Verify theIsClientSigned
return value indecoratedIDTokenClient
.The
IsClientSigned()
method returnsfalse
indecoratedIDTokenClient
, while it returnstrue
indecoratedJARClient
. Ensure that this behavior is intentional and aligns with the desired functionality.
471-475
: Fix potential typos in documentation comments.Double-check the comments for
GetIntrospectionSignedResponseAlg()
to ensure accuracy and correctness, as there may be residual typos or formatting errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- handler/oauth2/strategy_jwt_profile.go (4 hunks)
- token/jwt/claims_map_test.go (1 hunks)
- token/jwt/client.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- handler/oauth2/strategy_jwt_profile.go
🔇 Additional comments (5)
token/jwt/claims_map_test.go (5)
129-256
: LGTM! Comprehensive test cases for VerifyAudienceAll.The test cases for
TestMapClaims_VerifyAudienceAll
are well-structured and cover a wide range of scenarios, including edge cases. Good job on maintaining consistency with the previous test function.
389-470
: LGTM! Comprehensive test cases for VerifyIssuer.The test cases for
TestMapClaims_VerifyIssuer
are well-structured and cover a wide range of scenarios, including edge cases such as empty strings, nil values, and wrong types. Good job on maintaining consistency with the previous test functions.
644-743
: LGTM! Comprehensive test cases for VerifyIssuedAt.The test cases for
TestMapClaims_VerifyIssuedAt
are well-structured and cover a wide range of scenarios, including edge cases such as future and past issued times, different integer types, and nil values. The time-based comparisons are appropriately tested. Good job on maintaining consistency with the previous test functions and addressing time-related edge cases.
747-846
: LGTM! Comprehensive test cases for VerifyNotBefore.The test cases for
TestMapClaims_VerifyNotBefore
are well-structured and cover a wide range of scenarios, including edge cases such as future and past "not before" times, different integer types, and nil values. The time-based comparisons are appropriately tested. Good job on maintaining consistency with the previous test functions and addressing time-related edge cases.
Line range hint
1-1011
: Overall, excellent test coverage with minor suggestions for improvement.The
claims_map_test.go
file provides a comprehensive set of test cases for JWT claim validations. The tests are well-structured, consistent, and cover a wide range of scenarios including edge cases.A few minor improvements have been suggested:
- Adding test cases for multiple audience values in
TestMapClaims_VerifyAudience
.- Improving the "ShouldPassMultipleAny" test case in
TestMapClaims_VerifyAudienceAny
.- Adding a test case for empty subject in
TestMapClaims_VerifySubject
.- Adding test cases for time-based comparisons in
TestMapClaims_VerifyExpiresAt
.- Improving error checking in
TestMapClaims_Valid
.- Adding a test case for VerifyAudienceAny validation in
TestMapClaims_Valid
.Implementing these suggestions will further enhance the robustness and coverage of the test suite. Great job on maintaining consistency and thoroughness throughout the test file!
token/jwt/claims_map_test.go
Outdated
consts.ClaimAudience: []string{"foo"}, | ||
}, | ||
"foo", | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassMultiple", | ||
MapClaims{ | ||
consts.ClaimAudience: []string{"foo", "bar"}, | ||
}, | ||
"foo", | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoClaim", | ||
MapClaims{}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailNoMatch", | ||
MapClaims{ | ||
consts.ClaimAudience: []string{"bar"}, | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNoClaim", | ||
MapClaims{}, | ||
"foo", | ||
false, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassTypeAny", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"foo"}, | ||
}, | ||
"foo", | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassTypeString", | ||
MapClaims{ | ||
consts.ClaimAudience: "foo", | ||
}, | ||
"foo", | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailTypeString", | ||
MapClaims{ | ||
consts.ClaimAudience: "bar", | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeNil", | ||
MapClaims{ | ||
consts.ClaimAudience: nil, | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeSliceAnyInt", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{1, 2, 3}, | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeInt", | ||
MapClaims{ | ||
consts.ClaimAudience: 1, | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
assert.Equal(t, tc.expected, tc.have.VerifyAudience(tc.cmp, tc.required)) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding a test case for multiple audience values.
The test cases for TestMapClaims_VerifyAudience
are comprehensive. However, to improve coverage, consider adding a test case where the audience claim contains multiple values, and the verification should pass if any of them match. This would test the behavior when the audience claim is a slice of strings.
Add a new test case:
{
"ShouldPassMultipleAudiences",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar", "baz"},
},
"bar",
true,
true,
},
token/jwt/claims_map_test.go
Outdated
func TestMapClaims_VerifySubject(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
have MapClaims | ||
cmp string | ||
required bool | ||
expected bool | ||
}{ | ||
{ | ||
"ShouldPass", | ||
MapClaims{ | ||
consts.ClaimSubject: "foo", | ||
}, | ||
"foo", | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoClaim", | ||
MapClaims{}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNoClaim", | ||
MapClaims{}, | ||
"foo", | ||
false, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoMatch", | ||
MapClaims{ | ||
consts.ClaimSubject: "bar", | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailWrongType", | ||
MapClaims{ | ||
consts.ClaimSubject: 5, | ||
}, | ||
"5", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailNil", | ||
MapClaims{ | ||
consts.ClaimSubject: nil, | ||
}, | ||
"foo", | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNil", | ||
MapClaims{ | ||
consts.ClaimSubject: nil, | ||
}, | ||
"foo", | ||
false, | ||
true, | ||
}, | ||
} | ||
want := true | ||
got := mapClaims.VerifyAudience("foo", true) | ||
|
||
if want != got { | ||
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) | ||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
assert.Equal(t, tc.expected, tc.have.VerifySubject(tc.cmp, tc.required)) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding a test case for empty subject.
The test cases for TestMapClaims_VerifySubject
are comprehensive and well-structured. However, to improve coverage, consider adding a test case where the subject claim is an empty string. This would test the behavior when the subject is present but empty.
Add a new test case:
{
"ShouldFailEmptyString",
MapClaims{
consts.ClaimSubject: "",
},
"foo",
true,
false,
},
This test case would ensure that an empty string is not considered a valid subject when a specific subject is required.
token/jwt/claims_map_test.go
Outdated
func TestMapClaims_VerifyExpiresAt(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
have MapClaims | ||
cmp int64 | ||
required bool | ||
expected bool | ||
}{ | ||
{ | ||
"ShouldPass", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: int64(123), | ||
}, | ||
int64(123), | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassStandardInt", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: 123, | ||
}, | ||
int64(123), | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassStandardInt32", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: int32(123), | ||
}, | ||
int64(123), | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoClaim", | ||
MapClaims{}, | ||
int64(123), | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNoClaim", | ||
MapClaims{}, | ||
int64(123), | ||
false, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoMatch", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: 4, | ||
}, | ||
int64(123), | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailWrongType", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: true, | ||
}, | ||
int64(123), | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailNil", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: nil, | ||
}, | ||
int64(123), | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNil", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: nil, | ||
}, | ||
int64(123), | ||
false, | ||
true, | ||
}, | ||
} | ||
want := false | ||
got := mapClaims.VerifyAudience("foo", true) | ||
|
||
if want != got { | ||
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) | ||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
assert.Equal(t, tc.expected, tc.have.VerifyExpiresAt(tc.cmp, tc.required)) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding test cases for time-based comparisons.
The test cases for TestMapClaims_VerifyExpiresAt
are well-structured and cover various scenarios. However, to improve coverage for time-based comparisons, consider adding the following test cases:
Add these new test cases:
{
"ShouldPassFuture",
MapClaims{
consts.ClaimExpirationTime: int64(200),
},
int64(100),
true,
true,
},
{
"ShouldFailPast",
MapClaims{
consts.ClaimExpirationTime: int64(100),
},
int64(200),
true,
false,
},
These additional test cases would ensure that the VerifyExpiresAt
method correctly handles future and past expiration times relative to the comparison time.
"ShouldFailIssuedFuture", | ||
MapClaims{ | ||
consts.ClaimIssuedAt: 999999999999999, | ||
}, | ||
nil, | ||
[]uint32{ValidationErrorIssuedAt}, | ||
"Token used before issued", | ||
}, | ||
{ | ||
"ShouldFailMultiple", | ||
MapClaims{ | ||
consts.ClaimExpirationTime: 1, | ||
consts.ClaimIssuedAt: 999999999999999, | ||
}, | ||
nil, | ||
[]uint32{ValidationErrorIssuedAt, ValidationErrorExpired}, | ||
"Token used before issued", | ||
}, | ||
{ | ||
"ShouldPassIssuer", | ||
MapClaims{ | ||
consts.ClaimIssuer: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateIssuer("abc")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailIssuer", | ||
MapClaims{ | ||
consts.ClaimIssuer: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateIssuer("abc2"), ValidateTimeFunc(time.Now)}, | ||
[]uint32{ValidationErrorIssuer}, | ||
"Token has invalid issuer", | ||
}, | ||
{ | ||
"ShouldFailIssuerAbsent", | ||
MapClaims{}, | ||
[]ClaimValidationOption{ValidateIssuer("abc2")}, | ||
[]uint32{ValidationErrorIssuer}, | ||
"Token has invalid issuer", | ||
}, | ||
{ | ||
"ShouldPassSubject", | ||
MapClaims{ | ||
consts.ClaimSubject: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateSubject("abc")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailSubject", | ||
MapClaims{ | ||
consts.ClaimSubject: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateSubject("abc2")}, | ||
[]uint32{ValidationErrorSubject}, | ||
"Token has invalid subject", | ||
}, | ||
{ | ||
"ShouldFailSubjectAbsent", | ||
MapClaims{}, | ||
[]ClaimValidationOption{ValidateSubject("abc2")}, | ||
[]uint32{ValidationErrorSubject}, | ||
"Token has invalid subject", | ||
}, | ||
{ | ||
"ShouldPassAudienceAll", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"abc", "123"}, | ||
}, | ||
[]ClaimValidationOption{ValidateAudienceAll("abc", "123")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailAudienceAll", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"abc", "123"}, | ||
}, | ||
[]ClaimValidationOption{ValidateAudienceAll("abc", "123", "456")}, | ||
[]uint32{ValidationErrorAudience}, | ||
"Token has invalid audience", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actual := tc.have.Valid(tc.opts...) | ||
|
||
if len(tc.err) == 0 && tc.err == "" { | ||
assert.NoError(t, actual) | ||
} else { | ||
if tc.err != "" { | ||
assert.EqualError(t, actual, tc.err) | ||
} | ||
|
||
var e *ValidationError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error checking in TestMapClaims_Valid.
The error checking in the TestMapClaims_Valid
function can be improved. Currently, the code doesn't check if the error type assertion was successful before accessing the ValidationError
fields.
Update the error checking code as follows:
var e *ValidationError
if errors.As(actual, &e) {
if tc.err != "" {
assert.EqualError(t, actual, tc.err)
}
var errs uint32
for _, err := range tc.errs {
errs |= err
assert.True(t, e.Has(err))
}
assert.Equal(t, errs, e.Errors)
} else {
t.Errorf("Expected ValidationError, but got a different error type")
}
This change ensures that the code only accesses the ValidationError
fields if the type assertion was successful, preventing potential nil pointer dereferences.
{ | ||
"ShouldPassIssuer", | ||
MapClaims{ | ||
consts.ClaimIssuer: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateIssuer("abc")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailIssuer", | ||
MapClaims{ | ||
consts.ClaimIssuer: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateIssuer("abc2"), ValidateTimeFunc(time.Now)}, | ||
[]uint32{ValidationErrorIssuer}, | ||
"Token has invalid issuer", | ||
}, | ||
{ | ||
"ShouldFailIssuerAbsent", | ||
MapClaims{}, | ||
[]ClaimValidationOption{ValidateIssuer("abc2")}, | ||
[]uint32{ValidationErrorIssuer}, | ||
"Token has invalid issuer", | ||
}, | ||
{ | ||
"ShouldPassSubject", | ||
MapClaims{ | ||
consts.ClaimSubject: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateSubject("abc")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailSubject", | ||
MapClaims{ | ||
consts.ClaimSubject: "abc", | ||
}, | ||
[]ClaimValidationOption{ValidateSubject("abc2")}, | ||
[]uint32{ValidationErrorSubject}, | ||
"Token has invalid subject", | ||
}, | ||
{ | ||
"ShouldFailSubjectAbsent", | ||
MapClaims{}, | ||
[]ClaimValidationOption{ValidateSubject("abc2")}, | ||
[]uint32{ValidationErrorSubject}, | ||
"Token has invalid subject", | ||
}, | ||
{ | ||
"ShouldPassAudienceAll", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"abc", "123"}, | ||
}, | ||
[]ClaimValidationOption{ValidateAudienceAll("abc", "123")}, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"ShouldFailAudienceAll", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"abc", "123"}, | ||
}, | ||
[]ClaimValidationOption{ValidateAudienceAll("abc", "123", "456")}, | ||
[]uint32{ValidationErrorAudience}, | ||
"Token has invalid audience", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actual := tc.have.Valid(tc.opts...) | ||
|
||
if len(tc.err) == 0 && tc.err == "" { | ||
assert.NoError(t, actual) | ||
} else { | ||
if tc.err != "" { | ||
assert.EqualError(t, actual, tc.err) | ||
} | ||
|
||
var e *ValidationError | ||
|
||
errors.As(actual, &e) | ||
|
||
require.NotNil(t, e) | ||
|
||
var errs uint32 | ||
|
||
for _, err := range tc.errs { | ||
errs |= err | ||
|
||
assert.True(t, e.Has(err)) | ||
} | ||
|
||
if want != got { | ||
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) | ||
assert.Equal(t, errs, e.Errors) | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add a test case for VerifyAudienceAny validation.
The TestMapClaims_Valid
function covers various validation scenarios, but it's missing a test case for the VerifyAudienceAny
validation. Adding this case would improve the overall test coverage.
Add the following test case to the testCases
slice:
{
"ShouldPassAudienceAny",
MapClaims{
consts.ClaimAudience: []any{"abc", "123", "xyz"},
},
[]ClaimValidationOption{ValidateAudienceAny("123", "456")},
nil,
"",
},
This test case verifies that the Valid
method correctly handles the VerifyAudienceAny
validation when at least one of the provided audiences matches.
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) { | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modify IsClientSigned
to reflect GetEnableJWTProfileOAuthAccessTokens
in decoratedJWTProfileAccessTokenClient
The IsClientSigned
method in decoratedJWTProfileAccessTokenClient
always returns false
. Considering that JWT Profile Access Tokens may be signed based on the client's configuration (GetEnableJWTProfileOAuthAccessTokens
), it's advisable to have IsClientSigned
reflect this.
Apply this diff to update the IsClientSigned
method:
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) {
- return false
+ return r.GetEnableJWTProfileOAuthAccessTokens()
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) { | |
return false | |
} | |
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) { | |
return r.GetEnableJWTProfileOAuthAccessTokens() | |
} |
// GetClientSecretPlainText returns the ClientSecret as plaintext if available. The semantics of this function | ||
// return values are important. | ||
// If the client is not configured with a secret the return should be: | ||
// - secret with value nil, ok with value false, and err with value of nil | ||
// If the client is configured with a secret but is hashed or otherwise not a plaintext value: | ||
// - secret with value nil, ok with value true, and err with value of nil | ||
// If an error occurs retrieving the secret other than this: | ||
// - secret with value nil, ok with value true, and err with value of the error | ||
// If the plaintext secret is successful: | ||
// - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil | ||
GetClientSecretPlainText() (secret []byte, ok bool, err error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify the return semantics of GetClientSecretPlainText
The method GetClientSecretPlainText
has complex return semantics involving secret
, ok
, and err
. This can lead to confusion and misuse. Consider simplifying the method to return only (secret []byte, err error)
, using a nil
secret
to indicate absence, and an error when appropriate. This aligns with Go conventions and improves clarity.
Apply this diff to simplify the method signature and adjust the documentation:
// GetClientSecretPlainText returns the ClientSecret as plaintext if available.
-// The semantics of this function return values are important.
-// If the client is not configured with a secret the return should be:
-// - secret with value nil, ok with value false, and err with value of nil
-// If the client is configured with a secret but is hashed or otherwise not a plaintext value:
-// - secret with value nil, ok with value true, and err with value of nil
-// If an error occurs retrieving the secret other than this:
-// - secret with value nil, ok with value true, and err with value of the error
-// If the plaintext secret is successful:
-// - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil
-GetClientSecretPlainText() (secret []byte, ok bool, err error)
+// Returns nil and an error if the secret cannot be retrieved.
+GetClientSecretPlainText() (secret []byte, err error)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// GetClientSecretPlainText returns the ClientSecret as plaintext if available. The semantics of this function | |
// return values are important. | |
// If the client is not configured with a secret the return should be: | |
// - secret with value nil, ok with value false, and err with value of nil | |
// If the client is configured with a secret but is hashed or otherwise not a plaintext value: | |
// - secret with value nil, ok with value true, and err with value of nil | |
// If an error occurs retrieving the secret other than this: | |
// - secret with value nil, ok with value true, and err with value of the error | |
// If the plaintext secret is successful: | |
// - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil | |
GetClientSecretPlainText() (secret []byte, ok bool, err error) | |
// GetClientSecretPlainText returns the ClientSecret as plaintext if available. | |
// Returns nil and an error if the secret cannot be retrieved. | |
GetClientSecretPlainText() (secret []byte, err error) |
func (r *decoratedJARMClient) IsClientSigned() (is bool) { | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider setting IsClientSigned
to true
for decoratedJARMClient
The IsClientSigned
method in decoratedJARMClient
returns false
. However, per the JWT Secured Authorization Response Mode (JARM) specifications, authorization responses are expected to be signed, with RS256
as the default signing algorithm if not specified. Modifying IsClientSigned
to return true
will reflect the standard behavior.
Apply this diff to adjust the IsClientSigned
method:
func (r *decoratedJARMClient) IsClientSigned() (is bool) {
- return false
+ return true
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (r *decoratedJARMClient) IsClientSigned() (is bool) { | |
return false | |
} | |
func (r *decoratedJARMClient) IsClientSigned() (is bool) { | |
return true | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsClientSigned returns true if the signature is generated by the client, in the instance of JARM the signature is generated by the provider. Without this there is no way to know which JWK source to check for the key to verify the signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@james-d-elliott, thank you for the clarification. That makes sense—since in JARM, the signature is generated by the provider, setting IsClientSigned
to false
ensures that the correct JWK source is used for verification.
(・_・?)
✏️ Learnings added
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#140
File: token/jwt/client.go:307-309
Timestamp: 2024-09-28T13:55:18.497Z
Learning: For `decoratedJARMClient`, the `IsClientSigned` method should return `false` because, in JARM, the signature is generated by the provider, not the client. This ensures the correct JWK source is used for verifying the signature.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) { | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modify IsClientSigned
to reflect client configuration in decoratedIntrospectionClient
In decoratedIntrospectionClient
, the IsClientSigned
method returns false
. Since clients can specify a signing algorithm for introspection responses via GetIntrospectionSignedResponseAlg
, consider modifying IsClientSigned
to return true
when a signing algorithm is set.
Apply this diff to adjust IsClientSigned
accordingly:
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) {
- return false
+ return r.GetIntrospectionSignedResponseAlg() != ""
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) { | |
return false | |
} | |
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) { | |
return r.GetIntrospectionSignedResponseAlg() != "" | |
} |
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) { | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modify IsClientSigned
to reflect client configuration in decoratedUserInfoClient
Currently, IsClientSigned
in decoratedUserInfoClient
always returns false
. Since clients may specify a signing algorithm for UserInfo responses via GetUserinfoSignedResponseAlg
, consider altering IsClientSigned
to return true
when a signing algorithm is configured.
Apply this diff to conditionally set IsClientSigned
:
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) {
- return false
+ return r.GetUserinfoSignedResponseAlg() != ""
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) { | |
return false | |
} | |
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) { | |
return r.GetUserinfoSignedResponseAlg() != "" | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 17
🧹 Outside diff range and nitpick comments (37)
token/jwt/variables.go (4)
6-6
: Add documentation forMarshalSingleStringAsArray
.While the variable name is descriptive, its purpose and potential impact on JWT operations are not immediately clear. Consider adding a comment explaining:
- The specific use case for this variable.
- The implications of setting it to
true
orfalse
.- Any compatibility considerations with other JWT libraries or standards.
This documentation will help other developers understand when and how to use or modify this setting.
7-7
: LGTM:TimePrecision
set totime.Second
.Setting
TimePrecision
totime.Second
is a reasonable default for most JWT use cases. It aligns well with the standard practice of using Unix timestamps in JWT claims.Consider adding a brief comment explaining the purpose of this variable and noting that it can be modified if higher precision is required for specific use cases.
8-8
: LGTM:TimeFunc
set totime.Now
.Setting
TimeFunc
totime.Now
is a good practice. It allows for easy customization and mocking of time-related functionality, which is particularly useful for testing.Consider adding a brief comment explaining the purpose of this variable and how it can be used in testing scenarios or for custom time handling.
5-9
: Consider alternative configuration approaches and add comprehensive documentation.The use of package-level variables for configuration is a common pattern in Go. However, for a JWT library, consider if this approach provides the best flexibility and clarity for users of the package. Some alternatives to consider:
- Use a configuration struct that can be passed to relevant functions or methods.
- Implement these as fields in a JWT handler or processor struct.
These approaches could provide more explicit configuration and potentially better support for concurrent usage with different settings.
Regardless of the chosen approach, it's crucial to add comprehensive documentation for these configuration options. This documentation should include:
- The purpose of each variable.
- The impact of changing these variables on JWT processing.
- Any interactions between these variables and other parts of the JWT system.
- Examples of when and how to modify these variables.
Clear documentation will greatly improve the usability and maintainability of the JWT package.
token/jwt/claims_jarm_test.go (1)
Inconsistency Found:
consts.Claim*
references still present across the codebase.The verification revealed that
consts.Claim*
references remain in multiple files, indicating that the refactoring is not fully consistent. Please ensure that all instances ofconsts.Claim*
are replaced with directClaim*
constants throughout the codebase to maintain clarity and reduce dependencies.🔗 Analysis chain
Line range hint
28-43
: Overall LGTM! Consistent removal ofconsts
package dependency.The changes in this file consistently replace
consts.Claim*
references with directClaim*
constants. This improves code clarity and reduces external dependencies without altering functionality. Great job!To ensure consistency across the codebase, please run the following script:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that `consts.Claim*` references have been removed throughout the codebase. # Test: Search for any remaining `consts.Claim` references. Expect: No results. rg --type go 'consts\.Claim' # Test: Search for direct `Claim` constant usage. Expect: Multiple results. rg --type go '\bClaim(Issuer|Audience|JWTID|IssuedAt|ExpirationTime)\b'Length of output: 68335
token/jwt/claims.go (2)
10-19
: LGTM! Consider minor enhancements to the Claims interface.The
Claims
interface is well-designed and covers all standard JWT claims. It provides methods for accessing individual claims, converting to map claims, and validating the claims.Consider the following enhancements:
Add a method to retrieve all claims at once, which could be useful in some scenarios:
GetAllClaims() (MapClaims, error)Improve documentation for the
Valid
method, especially regarding the purpose and usage ofClaimValidationOption
:// Valid validates the claims according to the JWT specification and any provided options. // ClaimValidationOption allows for custom validation logic to be applied. Valid(opts ...ClaimValidationOption) errorThese suggestions aim to improve the interface's usability and clarity.
Line range hint
22-121
: Consider improving error handling in utility functions.While not part of the current changes, the existing utility functions (
ToString
,ToTime
,Filter
,Copy
, andStringSliceFromMap
) could benefit from improved error handling.Consider the following improvements:
In
ToTime
, add error return for invalid input types:func ToTime(i any) (time.Time, error) { // ... existing code ... return time.Time{}, fmt.Errorf("unsupported type for time conversion: %T", i) }In
StringSliceFromMap
, consider returning an error instead of a boolean for invalid types:func StringSliceFromMap(value any) ([]string, error) { // ... existing code ... return nil, fmt.Errorf("unsupported type for string slice conversion: %T", value) }These changes would make the functions more robust and easier to use, as callers would have more information about why a conversion failed.
token/jwt/claims_id_token_test.go (1)
Line range hint
58-80
: Consider completing the refactoring for consistency.While the earlier part of this test has been updated to use direct constant references, this segment still uses the
consts.
prefix for claim constants. For consistency and to complete the refactoring:
- Replace all remaining
consts.Claim*
references with their directClaim*
counterparts.- Update the
consts.ClaimNonce
reference similarly.This will ensure consistency throughout the test and align with the changes made in the rest of the file.
Here's a suggested diff to complete the refactoring:
- consts.ClaimJWTID: idTokenClaims.JTI, - consts.ClaimSubject: idTokenClaims.Subject, - consts.ClaimIssuedAt: idTokenClaims.IssuedAt.Unix(), - consts.ClaimIssuer: idTokenClaims.Issuer, - consts.ClaimAudience: idTokenClaims.Audience, - consts.ClaimExpirationTime: idTokenClaims.ExpiresAt.Unix(), + ClaimJWTID: idTokenClaims.JTI, + ClaimSubject: idTokenClaims.Subject, + ClaimIssuedAt: idTokenClaims.IssuedAt.Unix(), + ClaimIssuer: idTokenClaims.Issuer, + ClaimAudience: idTokenClaims.Audience, + ClaimExpirationTime: idTokenClaims.ExpiresAt.Unix(), "foo": idTokenClaims.Extra["foo"], "baz": idTokenClaims.Extra["baz"], - consts.ClaimAccessTokenHash: idTokenClaims.AccessTokenHash, - consts.ClaimCodeHash: idTokenClaims.CodeHash, - consts.ClaimStateHash: idTokenClaims.StateHash, - consts.ClaimAuthenticationTime: idTokenClaims.AuthTime.Unix(), - consts.ClaimAuthenticationContextClassReference: idTokenClaims.AuthenticationContextClassReference, - consts.ClaimAuthenticationMethodsReference: idTokenClaims.AuthenticationMethodsReferences, - consts.ClaimNonce: idTokenClaims.Nonce, + ClaimAccessTokenHash: idTokenClaims.AccessTokenHash, + ClaimCodeHash: idTokenClaims.CodeHash, + ClaimStateHash: idTokenClaims.StateHash, + ClaimAuthenticationTime: idTokenClaims.AuthTime.Unix(), + ClaimAuthenticationContextClassReference: idTokenClaims.AuthenticationContextClassReference, + ClaimAuthenticationMethodsReference: idTokenClaims.AuthenticationMethodsReferences, + ClaimNonce: idTokenClaims.Nonce,token/hmac/hmacsha_test.go (1)
Line range hint
68-76
: Approved with a suggestion for improved error reporting.The simplification of the loop is good for code cleanliness. However, to maintain ease of debugging, consider using
t.Run
with subtests for each case. This approach provides better error reporting without adding noise to the test output.Here's a suggested refactoring to improve error reporting:
-for _, c := range []string{ - "", - " ", - "foo.bar", - "foo.", - ".foo", -} { - err = cg.Validate(context.Background(), c) - assert.Error(t, err) -} +testCases := []string{"", " ", "foo.bar", "foo.", ".foo"} +for i, c := range testCases { + t.Run(fmt.Sprintf("Case%d", i), func(t *testing.T) { + err = cg.Validate(context.Background(), c) + assert.Error(t, err) + }) +}This change will provide more detailed error output if a test case fails, without adding unnecessary logging for successful cases.
token/jwt/claims_jwt.go (3)
104-155
: LGTM with a minor suggestion for consistencyThe changes in the
ToMap
method look good. The switch fromconsts.Claim*
toClaim*
is consistent throughout the method.For consistency, consider updating line 118 to use
ClaimJWTID
instead of a string literal:- ret[ClaimJWTID] = uuid.New().String() + ret[ClaimJWTID] = uuid.New().String()This change would align with the usage of
Claim*
constants in the rest of the method.
229-239
: LGTM with a minor suggestionThe changes to the
toTime
function improve its robustness by handling more input types and providing a success indicator. This is a good improvement.Consider adding a comment explaining the meaning of the
ok
return value:// toTime converts the input to a time.Time value. // It returns the converted time and a boolean indicating whether the conversion was successful. func toTime(v any, def time.Time) (t time.Time, ok bool) { // ... (rest of the function) }This will make the function's behavior clearer to other developers.
270-302
: LGTM with suggestions for improved error handlingThe
toNumericDate
function is a good addition, providing consistent handling of various numeric types for date conversion. The handling of zero values andjson.Number
is particularly noteworthy.Consider the following improvements for more consistent error handling:
- Return an error for unsupported types instead of using a catch-all error at the end.
- Handle potential errors from
json.Number.Float64()
.Here's a suggested refactor:
func toNumericDate(v any) (*NumericDate, error) { switch value := v.(type) { case float64, int64, int32, int: if value == 0 { return nil, nil } return newNumericDateFromSeconds(float64(value)), nil case json.Number: f, err := value.Float64() if err != nil { return nil, fmt.Errorf("invalid json.Number: %w", err) } if f == 0 { return nil, nil } return newNumericDateFromSeconds(f), nil default: return nil, fmt.Errorf("unsupported type for NumericDate: %T", v) } }This refactored version provides more specific error messages and handles potential errors from
json.Number.Float64()
.token/jwt/claims_map.go (6)
69-131
: LGTM: Comprehensive handling of audience claims with a minor optimization opportunityThe new methods for handling audience claims are well-implemented, covering various scenarios (single audience, any audience, all audiences). The use of constant-time comparison in the helper functions is a good security practice.
Consider combining the
VerifyAudienceAll
andVerifyAudienceAny
methods into a single method with a boolean parameter to determine the verification type. This could reduce code duplication and improve maintainability. For example:func (m MapClaims) VerifyAudiences(cmp []string, required bool, allRequired bool) bool { // ... implementation ... }This suggestion is optional and depends on the specific use cases in your project.
133-202
: LGTM: Consistent handling of time-based claims with a minor improvement suggestionThe new methods for handling time-based claims (expiration time, issued at, and not before) are well-implemented. They correctly handle cases where claims are not set and use appropriate helper functions for verification.
For consistency with other claim verification methods, consider renaming the helper functions to match the naming pattern:
-func verifyInt64Future(value, now int64, required bool) bool { +func verifyExpirationTime(value, now int64, required bool) bool { // ... implementation ... } -func verifyInt64Past(value, now int64, required bool) bool { +func verifyIssuedAtOrNotBefore(value, now int64, required bool) bool { // ... implementation ... }This change would make the helper function names more descriptive and consistent with the claim they're verifying.
217-286
: LGTM: Comprehensive claim validation with a suggestion for improved error handlingThe new
Valid
method provides a thorough and flexible approach to validating all claims. It correctly handles various validation scenarios and accumulates multiple validation errors.Consider enhancing the error handling to provide more detailed information about which claims failed validation. This could be achieved by modifying the
ValidationError
struct to include a map of claim-specific error messages. For example:type ValidationError struct { Errors uint32 Inner error ClaimErrors map[string]string } // In the Valid method: if !m.VerifyExpirationTime(now, vopts.expRequired) { vErr.ClaimErrors["exp"] = "Token is expired" vErr.Errors |= ValidationErrorExpired } // ... similar for other claims ... // Modify the Error() method of ValidationError to include claim-specific messagesThis change would provide more detailed error information, making it easier for users of this library to understand and handle validation failures.
365-424
: LGTM: Well-implemented claim validation options with a suggestion for improved documentationThe new
ClaimValidationOption
type and related functions provide a flexible and extensible way to configure claim validation. The use of the functional options pattern is a good choice for this scenario.Consider adding godoc comments to each
ValidateX
function to provide more context on their usage. For example:// ValidateTimeFunc sets a custom time function for claim validation. // If not set, the default time.Now will be used. func ValidateTimeFunc(timef func() time.Time) ClaimValidationOption { // ... implementation ... } // ValidateIssuer sets the required issuer for claim validation. func ValidateIssuer(iss string) ClaimValidationOption { // ... implementation ... } // ... similar for other functions ...This addition would improve the discoverability and usability of these options for users of your library.
425-498
: LGTM: Well-implemented helper functions with a suggestion for improved organizationThe helper functions for claim verification are correctly implemented. The use of constant-time comparison in audience verification functions is a good security practice. The time-based verification functions are simple and handle the case where the claim is not set.
Consider grouping related helper functions together for better code organization. For example:
// Audience verification helpers func verifyAud(aud []string, cmp string, required bool) bool { // ... implementation ... } func verifyAudAny(aud []string, cmp []string, required bool) bool { // ... implementation ... } func verifyAudAll(aud []string, cmp []string, required bool) bool { // ... implementation ... } // Time-based verification helpers func verifyInt64Future(value, now int64, required bool) bool { // ... implementation ... } func verifyInt64Past(value, now int64, required bool) bool { // ... implementation ... } // String verification helper func verifyString(value, cmp string, required bool) bool { // ... implementation ... }This organization would improve the readability and maintainability of the code.
Line range hint
1-498
: Excellent enhancements to JWT claim handling and validationThe changes in this file significantly improve the functionality and flexibility of the
MapClaims
type for JWT handling. Key improvements include:
- Comprehensive methods for getting and verifying individual claims.
- A flexible
Valid
method for validating multiple claims with customizable options.- Secure implementation using constant-time comparisons where necessary.
- Well-structured code following consistent patterns.
These enhancements will make it easier and safer for users of this library to work with JWTs, providing both fine-grained control over individual claims and high-level validation of the entire token.
As the JWT functionality grows, consider splitting this file into multiple files for better maintainability. For example:
claims_map.go
: Core MapClaims type and methodsclaims_validation.go
: Validation logic and optionsclaims_helpers.go
: Helper functions for claim verificationThis separation would improve code organization and make it easier to navigate and maintain the codebase as it continues to evolve.
token/jwt/claims_map_test.go (4)
389-461
: LGTM: Comprehensive test cases for VerifyIssuerThe test cases for
TestMapClaims_VerifyIssuer
are well-structured and cover a wide range of scenarios, including:
- Valid and invalid inputs
- Different data types
- Edge cases (nil values, wrong types)
- Required and non-required issuer checks
Consider adding a test case for an empty string issuer:
{ "ShouldFailEmptyString", MapClaims{ consts.ClaimIssuer: "", }, "foo", true, false, },This additional test case would ensure that an empty string is not considered a valid issuer when a specific issuer is required.
465-528
: LGTM: Thorough test cases for VerifySubjectThe test cases for
TestMapClaims_VerifySubject
are well-designed and cover a wide range of scenarios, including:
- Valid and invalid inputs
- Different data types
- Edge cases (nil values, wrong types)
- Required and non-required subject checks
Consider adding a test case for an empty string subject:
{ "ShouldFailEmptyString", MapClaims{ consts.ClaimSubject: "", }, "foo", true, false, },This additional test case would ensure that an empty string is not considered a valid subject when a specific subject is required.
532-613
: LGTM: Comprehensive test cases for VerifyExpiresAtThe test cases for
TestMapClaims_VerifyExpiresAt
are well-structured and cover a wide range of scenarios, including:
- Valid and invalid inputs
- Different data types (int64, int, int32)
- Edge cases (nil values, wrong types)
- Required and non-required expiration checks
Consider adding test cases for time-based comparisons:
{ "ShouldPassFuture", MapClaims{ consts.ClaimExpirationTime: int64(200), }, int64(100), true, true, }, { "ShouldFailPast", MapClaims{ consts.ClaimExpirationTime: int64(100), }, int64(200), true, false, },These additional test cases would ensure that the
VerifyExpiresAt
method correctly handles future and past expiration times relative to the comparison time.
805-966
: LGTM with a suggestion for improved error checkingThe test cases for
TestMapClaims_Valid
are comprehensive and cover a wide range of scenarios, including:
- Various claim validations (expiration, issuance, not before, issuer, subject, audience)
- Different combinations of claims and validation options
- Error cases and expected error messages
Consider improving the error checking in the test function:
var e *ValidationError -errors.As(actual, &e) +if errors.As(actual, &e) { var errs uint32 for _, err := range tc.errs { errs |= err assert.True(t, e.Has(err)) } assert.Equal(t, errs, e.Errors) +} else { + t.Errorf("Expected ValidationError, but got a different error type") +}This change ensures that the code only accesses the
ValidationError
fields if the type assertion was successful, preventing potential nil pointer dereferences.client_authentication_strategy.go (5)
Line range hint
29-67
: LGTM! Enhanced client authentication flow.The refactoring of the AuthenticateClient method improves the overall structure and readability of the authentication process. The introduction of the ClientAssertion type and the use of a handler instead of a resolver align well with the new JWT-based approach.
Consider adding a comment explaining the purpose of the ClientAssertion type for better code documentation.
137-184
: LGTM! Enhanced client assertion handling.The refactoring of NewClientAssertion significantly improves the handling of client assertions. The introduction of the jwt.Strategy parameter and the comprehensive JWT validation logic enhance the security and flexibility of the authentication process.
Consider adding error wrapping to provide more context when returning errors, especially for the error returned on line 161.
247-307
: LGTM! Significantly enhanced JWT bearer token authentication.The refactoring of doAuthenticateAssertionJWTBearer greatly improves the security and compliance of JWT-based client authentication. The additional checks for the AuthenticationMethodClient interface and parsed assertion, along with more detailed claim validation, are excellent improvements.
Consider extracting the claim validation logic (lines 277-282) into a separate method for better readability and maintainability.
310-343
: LGTM! Comprehensive improvements to JWT parsing and validation.The refactoring of doAuthenticateAssertionParseAssertionJWTBearer significantly enhances the security and compliance of JWT parsing and validation. The additional checks for audience, expiration, and various header validations are excellent improvements that align with best practices for JWT handling in OAuth 2.0 and OpenID Connect.
Consider enhancing the error handling to provide more context-specific error messages. This could be achieved by wrapping the errors returned by the JWT library with more descriptive messages related to client authentication.
387-462
: LGTM! Detailed client assertion error formatting.The new fmtClientAssertionDecodeError function significantly enhances error reporting for client assertion decoding issues. It provides very detailed and context-specific error messages for a wide range of JWT validation failure scenarios, which will greatly aid in diagnosing and troubleshooting client assertion problems.
Consider adding comments to group related error cases (e.g., header-related errors, claim-related errors) to improve readability of this large switch statement.
token/jwt/jwt_strategy_test.go (2)
514-558
: Well-structured test for decoding encrypted tokens with room for improvement.This test function is well-designed:
- It uses table-driven tests, which is a good practice for testing multiple scenarios efficiently.
- It covers different encryption algorithms (RS256 and ES256).
- It includes validation of various JWT claims.
Suggestions for improvement:
- Consider adding more comprehensive error checking. Currently, the test only checks if there's no error and if the token is not nil. It would be beneficial to add specific checks for expected errors in failure scenarios.
- Add test cases for invalid tokens or incorrect keys to ensure the decoding fails as expected in these cases.
Example of additional error checking:
if err != nil { t.Fatalf("Failed to decode token: %v", err) } if token == nil { t.Fatal("Decoded token is nil") }Adding these improvements will make the tests more robust and comprehensive.
1-724
: Overall assessment: Comprehensive test suite with room for improvementThis test file for the JWT strategy implementation is thorough and covers many important scenarios. Here's a summary of the strengths and areas for improvement:
Strengths:
- Comprehensive coverage of various JWT operations (encoding, decoding, encryption).
- Good use of table-driven tests in some functions.
- Proper setup of test keys and helper structures.
Areas for improvement:
- Some test functions (e.g., TestDefaultStrategy, TestNestedJWTEncodeDecode) are very long and could be broken down into smaller, more focused tests.
- The TestIniit function needs significant rework to follow proper testing practices.
- Some tests could benefit from more comprehensive error checking and edge case coverage.
Recommendations:
- Refactor long test functions into smaller, more focused tests.
- Improve the TestIniit function by fixing the name, adding proper assertions, and clarifying its purpose.
- Add more error checking and edge cases to existing tests where appropriate.
- Consider adding more comments to explain the purpose and expected outcomes of complex test scenarios.
Implementing these improvements will enhance the maintainability and effectiveness of the test suite.
token/jwt/date.go (1)
47-47
: Typo in error message: "NumericData" should be "NumericDate"There's a typo in the error message. It should read "could not parse NumericDate" instead of "could not parse NumericData".
Apply this diff to fix the typo:
func (date *NumericDate) UnmarshalJSON(b []byte) (err error) { var ( number json.Number f float64 ) if err = json.Unmarshal(b, &number); err != nil { - return fmt.Errorf("could not parse NumericData: %w", err) + return fmt.Errorf("could not parse NumericDate: %w", err) }token/jwt/consts.go (6)
24-25
: Clarify variable nameSignatureAlgorithmsNone
for better understanding.The variable
SignatureAlgorithmsNone
includes all algorithms including'none'
, as per the comment. The name may be misleading. Consider renaming it toSignatureAlgorithmsIncludingNone
orAllSignatureAlgorithms
for clarity.
36-68
: Add documentation comments for exported constants.The constants from lines 37 to 68 are exported but lack documentation comments. According to Go conventions, exported identifiers should have comments to explain their purpose. Consider adding comments to these constants for better code readability and maintainability.
71-79
: Add documentation comments for exported constants.The constants from lines 71 to 79 are exported but missing documentation comments. To adhere to Go best practices, please add comments to these constants.
82-84
: Add documentation comments for exported constants.The exported constants on lines 82 to 84 lack documentation comments. Adding comments will improve code clarity and maintain Go documentation standards.
87-91
: Add documentation comments for exported constants.The constants from lines 87 to 91 are exported without accompanying documentation. Please provide comments to describe their purpose.
94-94
: Add documentation comment for exported constantJSONWebTokenAlgNone
.The exported constant
JSONWebTokenAlgNone
lacks a documentation comment. Adding a comment will enhance code readability and adhere to Go best practices.authorize_request_handler.go (1)
75-75
: Unnecessarybreak
Statement in Switch CaseIn Go,
break
statements at the end of a switch case are not necessary because control flow does not fall through by default. Removing thebreak
statement can simplify the code.Apply this diff to remove the unnecessary
break
statement:case consts.JSONWebTokenAlgNone: - break
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (51)
- arguments_test.go (0 hunks)
- authorize_helper_test.go (0 hunks)
- authorize_request_handler.go (6 hunks)
- authorize_request_handler_oidc_request_test.go (9 hunks)
- authorize_response_writer_test.go (0 hunks)
- client_authentication_strategy.go (8 hunks)
- handler/oauth2/introspector_jwt.go (1 hunks)
- handler/oauth2/strategy_jwt_profile.go (4 hunks)
- handler/openid/flow_explicit_token_test.go (3 hunks)
- handler/openid/flow_refresh_token_test.go (4 hunks)
- handler/openid/helper_test.go (1 hunks)
- handler/openid/strategy_jwt.go (4 hunks)
- handler/openid/validator.go (2 hunks)
- handler/openid/validator_test.go (2 hunks)
- handler/rfc8628/token_endpoint_handler_test.go (0 hunks)
- handler/rfc8693/custom_jwt_type_handler.go (4 hunks)
- handler/rfc8693/token_exchange_test.go (4 hunks)
- helper_test.go (0 hunks)
- integration/authorize_code_grant_public_client_pkce_test.go (0 hunks)
- integration/client_credentials_grant_test.go (0 hunks)
- integration/introspect_token_test.go (2 hunks)
- integration/resource_owner_password_credentials_grant_test.go (0 hunks)
- introspection_response_writer.go (2 hunks)
- pushed_authorize_response_writer_test.go (0 hunks)
- rfc8628_device_authorize_response_writer_test.go (0 hunks)
- rfc8628_user_authorize_request_handler_test.go (0 hunks)
- rfc8628_user_authorize_response_writer_test.go (0 hunks)
- session.go (1 hunks)
- testing/mock/client.go (2 hunks)
- token/hmac/hmacsha_test.go (1 hunks)
- token/jarm/generate.go (1 hunks)
- token/jwt/claims.go (1 hunks)
- token/jwt/claims_id_token.go (2 hunks)
- token/jwt/claims_id_token_test.go (2 hunks)
- token/jwt/claims_jarm.go (2 hunks)
- token/jwt/claims_jarm_test.go (2 hunks)
- token/jwt/claims_jwt.go (4 hunks)
- token/jwt/claims_jwt_test.go (4 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_map_test.go (1 hunks)
- token/jwt/consts.go (1 hunks)
- token/jwt/date.go (1 hunks)
- token/jwt/header.go (1 hunks)
- token/jwt/issuer.go (1 hunks)
- token/jwt/jwt_strategy.go (1 hunks)
- token/jwt/jwt_strategy_opts.go (1 hunks)
- token/jwt/jwt_strategy_test.go (1 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/token_test.go (8 hunks)
- token/jwt/util.go (1 hunks)
- token/jwt/variables.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (12)
- arguments_test.go
- authorize_helper_test.go
- authorize_response_writer_test.go
- handler/rfc8628/token_endpoint_handler_test.go
- helper_test.go
- integration/authorize_code_grant_public_client_pkce_test.go
- integration/client_credentials_grant_test.go
- integration/resource_owner_password_credentials_grant_test.go
- pushed_authorize_response_writer_test.go
- rfc8628_device_authorize_response_writer_test.go
- rfc8628_user_authorize_request_handler_test.go
- rfc8628_user_authorize_response_writer_test.go
🚧 Files skipped from review as they are similar to previous changes (21)
- authorize_request_handler_oidc_request_test.go
- handler/oauth2/introspector_jwt.go
- handler/oauth2/strategy_jwt_profile.go
- handler/openid/flow_explicit_token_test.go
- handler/openid/flow_refresh_token_test.go
- handler/openid/helper_test.go
- handler/openid/strategy_jwt.go
- handler/openid/validator.go
- handler/openid/validator_test.go
- handler/rfc8693/custom_jwt_type_handler.go
- handler/rfc8693/token_exchange_test.go
- integration/introspect_token_test.go
- introspection_response_writer.go
- testing/mock/client.go
- token/jarm/generate.go
- token/jwt/claims_jarm.go
- token/jwt/issuer.go
- token/jwt/jwt_strategy.go
- token/jwt/jwt_strategy_opts.go
- token/jwt/token_test.go
- token/jwt/util.go
🧰 Additional context used
📓 Learnings (1)
authorize_request_handler.go (1)
Learnt from: james-d-elliott PR: authelia/oauth2-provider#140 File: authorize_request_handler.go:121-125 Timestamp: 2024-09-27T07:36:36.699Z Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
🔇 Additional comments (36)
token/jwt/variables.go (1)
1-3
: LGTM: Package declaration and import.The package declaration and import statement are correct and appropriate for the file's purpose.
token/jwt/claims_jarm_test.go (2)
28-34
: LGTM! Improved code clarity by removingconsts
package dependency.The direct use of claim constants (
ClaimIssuer
,ClaimAudience
, etc.) instead ofconsts.Claim*
improves code readability and reduces external dependencies. This change is consistent and doesn't alter the functionality.
43-43
: LGTM! Consistent change in test assertion.The replacement of
consts.ClaimJWTID
withClaimJWTID
is consistent with the earlier changes. This modification maintains the test's functionality while improving code clarity.session.go (2)
22-23
: Review impact of interface method renamingThe inconsistency between the documentation and the method signature in the
Session
interface could have broader implications:
If the rename to
GetExpiresTimeX
was intended:
- All implementations of the
Session
interface need to be updated.- All usages of this method throughout the codebase need to be modified.
- This change would likely cause compilation errors until all occurrences are updated.
If the rename was unintended:
- The documentation comment should be reverted to maintain consistency.
Recommendation:
- Clarify the intention behind this change.
- If the rename is desired, ensure a comprehensive update across the entire codebase.
- If not intended, revert the documentation change.
- Consider using an IDE or tool-assisted refactoring to ensure all occurrences are updated consistently if proceeding with the rename.
To assess the scope of this change, run the following script to find all implementations and usages of the
Session
interface:#!/bin/bash echo "Searching for Session interface implementations:" rg --type go 'type\s+\w+\s+struct\s*{[\s\S]*?}\s*func\s*\(\w+\s*\*?\w+\)\s*GetExpiresAt' echo "Searching for GetExpiresAt method calls:" rg --type go '\.GetExpiresAt\s*\('This will help in understanding the full impact of the potential rename and ensure no usages are overlooked.
22-23
:⚠️ Potential issueInconsistency between documentation and method signature
There's a mismatch between the documentation comment and the actual method signature. The comment refers to
GetExpiresTimeX
, but the method is still namedGetExpiresAt
.To resolve this, either:
- Update the method signature to match the new name in the comment:
- GetExpiresAt(key TokenType) time.Time + GetExpiresTimeX(key TokenType) time.Time
- Or, revert the comment to use the original method name:
- // session.GetExpiresTimeX(oauth2.AccessToken) + // session.GetExpiresAt(oauth2.AccessToken)Please clarify the intended change and update both the comment and method signature consistently.
To verify the usage of this method across the codebase, run:
This will help determine which method name is being used in other parts of the codebase.
token/jwt/claims_jwt_test.go (4)
33-42
: LGTM: Improved readability by removingconsts.
prefixesThe changes to
jwtClaimsMap
look good. Removing theconsts.
prefixes from claim constants improves code readability without affecting functionality. This change is consistent across all claim constants and aligns with the expected modifications mentioned in the AI-generated summary.
51-51
: LGTM: Consistent removal ofconsts.
prefixThe change in the
TestClaimsToMapSetsID
function is consistent with the modifications made tojwtClaimsMap
. Removing theconsts.
prefix fromClaimJWTID
maintains code consistency without altering the test's functionality.
81-82
: LGTM: Consistent changes across test functionsThe modifications in
TestScopeFieldString
andTestScopeFieldBoth
functions are consistent with the earlier changes. Removing theconsts.
prefix from claim constants maintains code consistency and improves readability without altering the tests' functionalities. These changes align well with the overall refactoring described in the AI-generated summary.Also applies to: 93-93
Line range hint
1-98
: Overall assessment: Consistent and beneficial refactoringThe changes made to this file consistently remove the
consts.
prefix from claim constants throughout the code. This refactoring improves readability and reduces verbosity without altering the functionality of the tests. The modifications align well with the description provided in the AI-generated summary and contribute to a more streamlined codebase.token/jwt/claims_id_token_test.go (2)
46-57
: LGTM! Verify all constant origins.The changes from
consts.Claim*
toClaim*
are consistent with the refactoring described in the summary. The test logic and expected values remain unchanged, and the readability is improved by reducing prefix usage.To ensure all constants are correctly defined, run the following script:
#!/bin/bash # Description: Verify the origin of all Claim* constants used in this test # Test: Search for all Claim* constant definitions rg --type go -A 5 'const\s+(Claim\w+)'
22-22
: LGTM! Verify constant origin.The change from
consts.ClaimJWTID
toClaimJWTID
is consistent with the refactoring described in the summary. The test logic remains valid.To ensure the constant is correctly defined, run the following script:
✅ Verification successful
Constant
ClaimJWTID
correctly defined and imported.The refactoring from
consts.ClaimJWTID
toClaimJWTID
is valid. The constant is properly defined ininternal/consts/claim.go
and correctly imported in the test file through the dot import of thejwt
package. The test logic remains intact and functions as expected.
internal/consts/claim.go
: DefinesClaimJWTID = "jti"
token/jwt/consts.go
: AliasesClaimJWTID = consts.ClaimJWTID
token/jwt/claims_id_token_test.go
: UsesClaimJWTID
directly🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the origin of the ClaimJWTID constant # Test: Search for the ClaimJWTID constant definition rg --type go -A 5 'const\s+ClaimJWTID'Length of output: 40
Script:
#!/bin/bash # Description: Broadly search for the ClaimJWTID identifier across the codebase # Test: Search for any occurrence of ClaimJWTID in Go files rg --type go 'ClaimJWTID'Length of output: 3073
token/jwt/claims_id_token.go (2)
7-8
: LGTM: New imports and method declaration look good.The new imports are appropriate for the
UnmarshalJSON
method implementation. The method signature correctly follows thejson.Unmarshaler
interface.Also applies to: 11-11, 15-15, 37-48
123-123
: LGTM: Minor updates toToMap
method look good.The changes to use
ClaimSubject
andClaimIssuer
constants instead ofconsts.ClaimSubject
andconsts.ClaimIssuer
are consistent with the rest of the code and may improve readability.Also applies to: 125-125, 129-129
token/jwt/claims_jwt.go (2)
241-268
: LGTM: Robust helper function for numeric conversionsThe
toInt64
function is a well-implemented helper that handles various numeric types and provides proper error handling. This addition enhances the overall robustness of the time conversion process.Good job on including handling for
json.Number
, which can be particularly tricky. The function correctly attempts bothInt64()
andFloat64()
conversions for this type.
187-191
:⚠️ Potential issueHandle the success/failure status returned by
toTime
The current implementation ignores the second return value from
toTime
, which indicates whether the conversion was successful. This could lead to silent failures if the time conversion fails.Consider handling the success status to ensure data integrity:
-c.IssuedAt, _ = toTime(v, c.IssuedAt) +c.IssuedAt, ok := toTime(v, c.IssuedAt) +if !ok { + // Handle conversion failure (e.g., log warning, set to default value, etc.) +} -c.NotBefore, _ = toTime(v, c.NotBefore) +c.NotBefore, ok := toTime(v, c.NotBefore) +if !ok { + // Handle conversion failure +} -c.ExpiresAt, _ = toTime(v, c.ExpiresAt) +c.ExpiresAt, ok := toTime(v, c.ExpiresAt) +if !ok { + // Handle conversion failure +}This approach will allow you to handle cases where time conversion fails, improving the robustness of your code.
token/jwt/claims_map.go (1)
21-67
: LGTM: Consistent and secure implementation of issuer and subject claim methodsThe new methods for getting and verifying issuer and subject claims are well-implemented. They follow a consistent pattern, handle cases where claims are not set, and use constant-time comparison for string verification, which is a good security practice to prevent timing attacks.
token/jwt/claims_map_test.go (4)
17-127
: LGTM: Comprehensive test cases for VerifyAudienceThe test cases for
TestMapClaims_VerifyAudience
are well-structured and cover a wide range of scenarios, including:
- Valid and invalid inputs
- Different data types (string, slice of strings, any)
- Edge cases (empty claims, nil values)
- Required and non-required audience checks
This comprehensive approach ensures robust testing of the
VerifyAudience
method.
129-257
: LGTM: Thorough test cases for VerifyAudienceAllThe test cases for
TestMapClaims_VerifyAudienceAll
are well-designed and cover a wide range of scenarios, including:
- Single and multiple audience values
- Different data types (string, slice of strings, any)
- Edge cases (empty claims, nil values)
- Required and non-required audience checks
This thorough approach ensures comprehensive testing of the
VerifyAudienceAll
method.
617-707
: LGTM: Thorough test cases for VerifyIssuedAtThe test cases for
TestMapClaims_VerifyIssuedAt
are well-designed and cover a wide range of scenarios, including:
- Valid and invalid inputs
- Different data types (int64, int, int32)
- Edge cases (nil values, wrong types)
- Required and non-required issuance time checks
- Future and past issuance times
This comprehensive approach ensures robust testing of the
VerifyIssuedAt
method.
Line range hint
1-966
: Overall: Well-structured and comprehensive test suiteThe
claims_map_test.go
file contains a robust and comprehensive set of unit tests for JWT claim verification methods. The test cases cover a wide range of scenarios, including valid and invalid inputs, different data types, and edge cases for various claim types (audience, issuer, subject, expiration time, issued at, and not before).Key strengths:
- Thorough coverage of different claim types and validation scenarios
- Consistent structure across test functions
- Good use of table-driven tests for readability and maintainability
While some minor improvements were suggested, the overall quality of the test suite is high and provides strong confidence in the correctness of the JWT claim verification implementation.
client_authentication_strategy.go (6)
Line range hint
14-26
: LGTM! Improved JWT handling and configuration.The addition of the custom JWT package and the new methods in the Config interface enhance the JWT handling capabilities of the client authentication strategy. This change aligns well with the overall refactoring towards a more robust JWT management system.
Line range hint
70-134
: LGTM! Improved flexibility in authentication methods.The updates to the authenticate method, including the new assertion parameter and the switch case for handling assertions, enhance the method's ability to handle various authentication scenarios. This aligns well with the overall shift towards a more robust JWT-based authentication system.
Line range hint
186-193
: LGTM! Improved ClientAssertion structure.The updates to the ClientAssertion struct enhance its utility by providing more detailed information about the assertion. The new fields (Parsed, ID, Method, Algorithm) allow for better tracking and validation of the assertion throughout the authentication process.
346-349
: LGTM! Simplified client credentials retrieval.The refactoring of getClientCredentialsSecretPost simplifies the method and improves its readability. This change makes the code more maintainable and easier to understand.
352-384
: LGTM! Comprehensive JWT error handling.The new resolveJWTErrorToRFCError function greatly enhances the error handling for JWT-related issues. It provides detailed and context-specific error messages for various JWT validation scenarios, which will significantly improve debugging and troubleshooting of JWT validation problems.
Line range hint
1-462
: Excellent refactoring of the client authentication strategy!The changes in this file represent a significant improvement in the handling of JWT-based client authentication. Key improvements include:
- Enhanced JWT validation and parsing.
- More robust error handling with detailed, context-specific error messages.
- Better separation of concerns with the introduction of the jwt.Strategy.
- Improved security with constant-time comparisons for sensitive data.
- More comprehensive claim and header validations.
These changes align well with best practices for OAuth 2.0 and OpenID Connect, and should significantly improve the security, maintainability, and debuggability of the client authentication process.
token/jwt/jwt_strategy_test.go (4)
1-19
: Imports and package declaration look good.The package declaration and imports are appropriate for a JWT strategy test file. The necessary cryptographic and testing libraries are included.
291-326
: Well-structured test for rejecting non-compact serialized JWTs.This test function is well-designed:
- It uses table-driven tests, which is a good practice for testing multiple scenarios efficiently.
- It covers various edge cases, including empty strings and partially formed JWTs.
- The error message assertion is consistent across all test cases.
Good job on this test implementation.
560-596
: Helper structures and functions are appropriate for testing.The
testConfig
andtestFetcher
structures and their implementations are well-suited for the testing purposes of this file:
testConfig
provides a simple implementation of theGetJWKSFetcherStrategy
method.testFetcher
implements a basic JWKS fetcher, which is sufficient for testing scenarios.- The
Resolve
method intestFetcher
includes proper error handling and context usage.These helper structures contribute to making the tests more manageable and focused on the JWT strategy implementation.
598-710
: Global variables and init function are well-structured for test key setup.The global variables and init function are appropriately used for setting up test keys:
- The use of global variables for test keys is acceptable in this testing context.
- The init function properly handles the parsing and setup of RSA and ECDSA keys for both signing and encryption.
- Error handling is implemented, with panics used appropriately for setup failures.
- The keys are set up with correct attributes (KeyID, Use, Algorithm) for both private and public keys.
This setup ensures that the necessary test keys are available for all test functions, promoting consistency across tests.
token/jwt/token.go (5)
616-631
: SimplifyisUnsafeNoneMagicConstant
FunctionThe
isUnsafeNoneMagicConstant
function can be simplified by reducing nested type assertions, improving readability and maintainability. Currently, the function includes multiple type switches that can be consolidated.
350-440
: Potential Overwriting of Errors in Header ValidationIn the
Valid
method, thevErr.Inner
field is overwritten each time a validation error is detected in different header fields. This means that if multiple validation errors occur, only the last error is reported, and earlier errors are lost. To provide comprehensive feedback, consider accumulating errors or appending messages tovErr.Inner
.
490-518
: Reduce Duplication in Header Validation Option FunctionsThe functions
ValidateKeyID
,ValidateAlgorithm
,ValidateEncryptionKeyID
, etc., have similar structures that could be refactored to reduce code duplication. Consolidating these functions can enhance maintainability.
583-591
:⚠️ Potential issuePossible Panic in
pointer
Function with Nil InputThe
pointer
function can cause a panic if the inputv
isnil
. Callingreflect.ValueOf(v).Kind()
whenv
isnil
will result in a runtime panic becausereflect.ValueOf(nil)
returns a zeroValue
, and callingKind()
on it is invalid. To prevent this, consider adding a nil check at the beginning of the function:func pointer(v any) any { + if v == nil { + return nil + } if reflect.ValueOf(v).Kind() != reflect.Ptr { value := reflect.New(reflect.ValueOf(v).Type()) value.Elem().Set(reflect.ValueOf(v)) return value.Interface() } return v }This ensures that the function safely handles
nil
inputs without causing a panic.Likely invalid or redundant comment.
209-215
: EnsureSetJWE
Handles Empty Parameters AppropriatelyIn the
SetJWE
method, ensure that it correctly handles cases where optional parameters likezip
(compression algorithm) may be empty or unset. If any of these parameters are required for subsequent encryption processes, consider adding validation or default values to prevent potential runtime errors.To verify that the
SetJWE
method correctly handles cases with empty parameters, you can run the following shell script to search for usages ofSetJWE
and check if it's being called with appropriate parameters throughout the codebase.✅ Verification successful
Verified
SetJWE
Parameter HandlingThe
SetJWE
method is consistently called with validzip
parameters across the codebase. Currently, all usages provide a defined compression algorithm, ensuring proper handling without runtime errors.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that `SetJWE` is called with appropriate parameters. # Test: Search for usages of `SetJWE` in the codebase. # Expect: Calls to `SetJWE` should provide valid arguments. rg --type go --no-heading --line-number 'SetJWE\('Length of output: 324
Script:
#!/bin/bash # Description: Extract the implementation of the `SetJWE` method for analysis. ast-grep --lang go --pattern 'func (t \*Token) SetJWE($_) { $$$ }' token/jwt/token.goLength of output: 87
Script:
#!/bin/bash # Description: Extract the implementation of the `SetJWE` method for analysis using a more flexible pattern. ast-grep --lang go --pattern 'func (t \*Token) SetJWE($_) { $$$ }' token/jwt/token.go --debugLength of output: 342
Script:
#!/bin/bash # Description: Extract the implementation of the `SetJWE` method for analysis without using the unsupported --debug flag. ast-grep --lang go --pattern 'func (t *Token) SetJWE($_) { $$$ }' token/jwt/token.goLength of output: 86
Script:
#!/bin/bash # Description: Extract the full implementation of the `SetJWE` method for analysis using `rg`. # Use ripgrep to find the function definition and include the function body. rg -A 20 -B 5 'func \(t \*Token\) SetJWE\(' token/jwt/token.goLength of output: 756
authorize_request_handler.go (1)
522-598
: Refactored Error Handling Enhances MaintainabilityGreat job extracting
fmtRequestObjectDecodeError
into a separate function. This refactoring centralizes error formatting for JWT decoding issues, improving readability and maintainability of the code.
@@ -18,7 +14,7 @@ func NewHeaders() *Headers { | |||
|
|||
// ToMap will transform the headers to a map structure | |||
func (h *Headers) ToMap() map[string]any { | |||
var filter = map[string]bool{consts.JSONWebTokenHeaderAlgorithm: true} | |||
var filter = map[string]bool{JSONWebTokenHeaderAlgorithm: true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Issue Detected with JSONWebTokenHeaderAlgorithm
Usage
The JSONWebTokenHeaderAlgorithm
constant is used in token/jwt/header.go
without importing the consts
package. This will lead to an undefined variable error.
Action Required:
- Import the
consts
package intoken/jwt/header.go
or define theJSONWebTokenHeaderAlgorithm
constant within the file.
🔗 Analysis chain
LGTM, but verify JSONWebTokenHeaderAlgorithm
definition.
The change simplifies the reference to the algorithm constant by removing the consts
package prefix. This is a good practice for improving code readability.
To ensure that JSONWebTokenHeaderAlgorithm
is properly defined or imported, please run the following verification script:
This script will help verify that JSONWebTokenHeaderAlgorithm
is properly defined or imported, ensuring the change doesn't introduce any undefined variable issues.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the definition or import of JSONWebTokenHeaderAlgorithm
# Test 1: Check if JSONWebTokenHeaderAlgorithm is defined in this file
echo "Checking for JSONWebTokenHeaderAlgorithm definition in header.go:"
rg --type go "^const\s+JSONWebTokenHeaderAlgorithm\s*=" token/jwt/header.go
# Test 2: Check if JSONWebTokenHeaderAlgorithm is imported from another file
echo "Checking for JSONWebTokenHeaderAlgorithm import:"
rg --type go "^import\s+.*JSONWebTokenHeaderAlgorithm" token/jwt/header.go
# Test 3: Search for JSONWebTokenHeaderAlgorithm definition in other files within the token/jwt directory
echo "Searching for JSONWebTokenHeaderAlgorithm definition in other files:"
rg --type go "^const\s+JSONWebTokenHeaderAlgorithm\s*=" token/jwt/
Length of output: 618
Script:
#!/bin/bash
# Description: Comprehensive search for JSONWebTokenHeaderAlgorithm across the entire codebase
# Test 1: Search for any occurrence of JSONWebTokenHeaderAlgorithm
echo "Searching for any occurrences of JSONWebTokenHeaderAlgorithm in the codebase:"
rg "JSONWebTokenHeaderAlgorithm"
# Test 2: Check if JSONWebTokenHeaderAlgorithm is defined as a variable
echo "Checking if JSONWebTokenHeaderAlgorithm is defined as a variable:"
rg --type go "^var\s+JSONWebTokenHeaderAlgorithm\s*="
# Test 3: Search for import statements that might import JSONWebTokenHeaderAlgorithm from external packages
echo "Looking for import statements that include JSONWebTokenHeaderAlgorithm:"
rg --type go "JSONWebTokenHeaderAlgorithm"
Length of output: 2340
token/jwt/claims_id_token.go
Outdated
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { | ||
claims := MapClaims{} | ||
|
||
decoder := jjson.NewDecoder(bytes.NewReader(data)) | ||
decoder.SetNumberType(jjson.UnmarshalIntOrFloat) | ||
|
||
if err := decoder.Decode(&claims); err != nil { | ||
return errorsx.WithStack(err) | ||
} | ||
|
||
var ok bool | ||
|
||
for claim, value := range claims { | ||
ok = false | ||
|
||
switch claim { | ||
case ClaimJWTID: | ||
c.JTI, ok = value.(string) | ||
case ClaimIssuer: | ||
c.Issuer, ok = value.(string) | ||
case ClaimSubject: | ||
c.Subject, ok = value.(string) | ||
case ClaimAudience: | ||
switch aud := value.(type) { | ||
case string: | ||
ok = true | ||
|
||
c.Audience = []string{aud} | ||
case []string: | ||
ok = true | ||
|
||
c.Audience = aud | ||
case []any: | ||
ok = true | ||
|
||
loop: | ||
for _, av := range aud { | ||
switch a := av.(type) { | ||
case string: | ||
c.Audience = append(c.Audience, a) | ||
default: | ||
ok = false | ||
|
||
break loop | ||
} | ||
} | ||
} | ||
case ClaimNonce: | ||
c.Nonce, ok = value.(string) | ||
case ClaimExpirationTime: | ||
c.ExpiresAt, ok = toTime(value, c.ExpiresAt) | ||
case ClaimIssuedAt: | ||
c.IssuedAt, ok = toTime(value, c.IssuedAt) | ||
case ClaimRequestedAt: | ||
c.RequestedAt, ok = toTime(value, c.RequestedAt) | ||
case ClaimAuthenticationTime: | ||
c.AuthTime, ok = toTime(value, c.AuthTime) | ||
case ClaimCodeHash: | ||
c.CodeHash, ok = value.(string) | ||
case ClaimStateHash: | ||
c.StateHash, ok = value.(string) | ||
case ClaimAuthenticationContextClassReference: | ||
c.AuthenticationContextClassReference, ok = value.(string) | ||
default: | ||
if c.Extra == nil { | ||
c.Extra = make(map[string]any) | ||
} | ||
|
||
c.Extra[claim] = value | ||
|
||
continue | ||
} | ||
|
||
if !ok { | ||
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) | ||
} | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider refactoring for improved maintainability
The UnmarshalJSON
method is quite long and handles multiple responsibilities. Consider refactoring it into smaller, more focused functions to improve readability and maintainability.
Here's a suggested approach:
- Create helper functions for handling specific claim types (e.g.,
handleAudienceClaim
,handleTimeClaim
). - Use a map of claim handlers to reduce the size of the switch statement.
Example refactoring:
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
claims := MapClaims{}
decoder := jjson.NewDecoder(bytes.NewReader(data))
decoder.SetNumberType(jjson.UnmarshalIntOrFloat)
if err := decoder.Decode(&claims); err != nil {
return errorsx.WithStack(err)
}
handlers := map[string]func(any) error{
consts.ClaimJWTID: c.handleStringClaim(&c.JTI),
consts.ClaimIssuer: c.handleStringClaim(&c.Issuer),
consts.ClaimSubject: c.handleStringClaim(&c.Subject),
consts.ClaimAudience: c.handleAudienceClaim,
consts.ClaimNonce: c.handleStringClaim(&c.Nonce),
consts.ClaimExpirationTime: c.handleTimeClaim(&c.ExpiresAt),
// ... other claims ...
}
for claim, value := range claims {
if handler, ok := handlers[claim]; ok {
if err := handler(value); err != nil {
return fmt.Errorf("claim %s could not be decoded", claim)
}
} else {
c.handleExtraClaim(claim, value)
}
}
return nil
}
func (c *IDTokenClaims) handleStringClaim(target *string) func(any) error {
return func(value any) error {
if s, ok := value.(string); ok {
*target = s
return nil
}
return fmt.Errorf("expected string value")
}
}
func (c *IDTokenClaims) handleAudienceClaim(value any) error {
// Implementation of audience claim handling
}
// ... other helper functions ...
This refactoring improves the code's structure and makes it easier to maintain and extend in the future.
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential exposure of sensitive data in error messages
The error message at line 111 includes the claim value, which could potentially expose sensitive information. This issue was previously flagged in a past review comment.
Apply this diff to address the issue:
- return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+ return fmt.Errorf("claim %s could not be decoded", claim)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) | |
} | |
return fmt.Errorf("claim %s could not be decoded", claim) | |
} |
[]string{"foo"}, | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassMultipleAll", | ||
MapClaims{ | ||
consts.ClaimAudience: []string{"foo", "bar"}, | ||
}, | ||
[]string{"foo", "bar"}, | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailNoClaim", | ||
MapClaims{}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailNoMatch", | ||
MapClaims{ | ||
consts.ClaimAudience: []string{"bar"}, | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldPassNoClaim", | ||
MapClaims{}, | ||
[]string{"foo"}, | ||
false, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassTypeAny", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{"foo"}, | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldPassTypeString", | ||
MapClaims{ | ||
consts.ClaimAudience: "foo", | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
true, | ||
}, | ||
{ | ||
"ShouldFailTypeString", | ||
MapClaims{ | ||
consts.ClaimAudience: "bar", | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeNil", | ||
MapClaims{ | ||
consts.ClaimAudience: nil, | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeSliceAnyInt", | ||
MapClaims{ | ||
consts.ClaimAudience: []any{1, 2, 3}, | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
{ | ||
"ShouldFailTypeInt", | ||
MapClaims{ | ||
consts.ClaimAudience: 1, | ||
}, | ||
[]string{"foo"}, | ||
true, | ||
false, | ||
}, | ||
} | ||
want := true | ||
got := mapClaims.VerifyAudience("foo", false) | ||
|
||
if want != got { | ||
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) | ||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required)) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving the "ShouldPassMultipleAny" test case
The test cases for TestMapClaims_VerifyAudienceAny
are well-structured and cover a wide range of scenarios. However, the "ShouldPassMultipleAny" test case could be improved to better demonstrate the "Any" behavior.
Consider updating the "ShouldPassMultipleAny" test case as follows:
{
"ShouldPassMultipleAny",
MapClaims{
- consts.ClaimAudience: []string{"foo", "baz"},
+ consts.ClaimAudience: []string{"foo", "baz", "qux"},
},
- []string{"bar", "baz"},
+ []string{"bar", "baz", "quux"},
true,
true,
},
This change would better demonstrate that the function passes when any of the audiences match, not just when the first one matches.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func TestMapClaims_VerifyAudienceAny(t *testing.T) { | |
testCases := []struct { | |
name string | |
have MapClaims | |
cmp []string | |
required bool | |
expected bool | |
}{ | |
{ | |
"ShouldPass", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultipleAny", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "baz"}, | |
}, | |
[]string{"bar", "baz"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultiple", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "bar"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultipleAll", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "bar"}, | |
}, | |
[]string{"foo", "bar"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldFailNoClaim", | |
MapClaims{}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailNoMatch", | |
MapClaims{ | |
consts.ClaimAudience: []string{"bar"}, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldPassNoClaim", | |
MapClaims{}, | |
[]string{"foo"}, | |
false, | |
true, | |
}, | |
{ | |
"ShouldPassTypeAny", | |
MapClaims{ | |
consts.ClaimAudience: []any{"foo"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassTypeString", | |
MapClaims{ | |
consts.ClaimAudience: "foo", | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldFailTypeString", | |
MapClaims{ | |
consts.ClaimAudience: "bar", | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeNil", | |
MapClaims{ | |
consts.ClaimAudience: nil, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeSliceAnyInt", | |
MapClaims{ | |
consts.ClaimAudience: []any{1, 2, 3}, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeInt", | |
MapClaims{ | |
consts.ClaimAudience: 1, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
} | |
want := true | |
got := mapClaims.VerifyAudience("foo", false) | |
if want != got { | |
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) | |
for _, tc := range testCases { | |
t.Run(tc.name, func(t *testing.T) { | |
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required)) | |
}) | |
func TestMapClaims_VerifyAudienceAny(t *testing.T) { | |
testCases := []struct { | |
name string | |
have MapClaims | |
cmp []string | |
required bool | |
expected bool | |
}{ | |
{ | |
"ShouldPass", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultipleAny", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "baz", "qux"}, | |
}, | |
[]string{"bar", "baz", "quux"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultiple", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "bar"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassMultipleAll", | |
MapClaims{ | |
consts.ClaimAudience: []string{"foo", "bar"}, | |
}, | |
[]string{"foo", "bar"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldFailNoClaim", | |
MapClaims{}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailNoMatch", | |
MapClaims{ | |
consts.ClaimAudience: []string{"bar"}, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldPassNoClaim", | |
MapClaims{}, | |
[]string{"foo"}, | |
false, | |
true, | |
}, | |
{ | |
"ShouldPassTypeAny", | |
MapClaims{ | |
consts.ClaimAudience: []any{"foo"}, | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldPassTypeString", | |
MapClaims{ | |
consts.ClaimAudience: "foo", | |
}, | |
[]string{"foo"}, | |
true, | |
true, | |
}, | |
{ | |
"ShouldFailTypeString", | |
MapClaims{ | |
consts.ClaimAudience: "bar", | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeNil", | |
MapClaims{ | |
consts.ClaimAudience: nil, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeSliceAnyInt", | |
MapClaims{ | |
consts.ClaimAudience: []any{1, 2, 3}, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
{ | |
"ShouldFailTypeInt", | |
MapClaims{ | |
consts.ClaimAudience: 1, | |
}, | |
[]string{"foo"}, | |
true, | |
false, | |
}, | |
} | |
for _, tc := range testCases { | |
t.Run(tc.name, func(t *testing.T) { | |
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required)) | |
}) |
encAlg: string(jose.PBES2_HS256_A128KW), | ||
enc: string(jose.A256GCM), | ||
csigned: true, | ||
secret: key128, | ||
jwks: issuerJWKSenc, | ||
jwksURI: "", | ||
} | ||
|
||
strategy := &DefaultStrategy{ | ||
Config: config, | ||
Issuer: issuer, | ||
} | ||
|
||
claims := MapClaims{ | ||
"value": 1, | ||
} | ||
|
||
headers1 := &Headers{ | ||
Extra: map[string]any{ | ||
JSONWebTokenHeaderType: JSONWebTokenTypeAccessToken, | ||
}, | ||
} | ||
|
||
var headersEnc *Headers | ||
|
||
var ( | ||
token1, signature1 string | ||
) | ||
|
||
token1, signature1, err = strategy.Encode(ctx, claims, WithHeaders(headers1), WithClient(client)) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, signature1) | ||
|
||
require.True(t, IsSignedJWT(token1)) | ||
|
||
headersEnc = &Headers{} | ||
|
||
var ( | ||
token2, signature2 string | ||
) | ||
|
||
headers2 := &Headers{ | ||
Extra: map[string]any{ | ||
JSONWebTokenHeaderType: JSONWebTokenTypeJWT, | ||
}, | ||
} | ||
|
||
token2, signature2, err = strategy.Encode(ctx, claims, WithHeaders(headers2), WithHeadersJWE(headersEnc), WithClient(clientEnc)) | ||
require.NoError(t, err) | ||
require.True(t, IsEncryptedJWT(token2)) | ||
require.NotEmpty(t, signature2) | ||
|
||
var ( | ||
token3, signature3 string | ||
) | ||
|
||
token3, signature3, err = strategy.Encode(ctx, claims, WithHeaders(headers1), WithHeadersJWE(headersEnc), WithClient(clientEncAsymmetric)) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, signature3) | ||
|
||
clientIssuer := &DefaultIssuer{ | ||
jwks: clientIssuerJWKS, | ||
} | ||
|
||
clientStrategy := &DefaultStrategy{ | ||
Config: config, | ||
Issuer: clientIssuer, | ||
} | ||
|
||
issuerClient := &testClient{ | ||
kid: "es512-sig", | ||
alg: "ES512", | ||
encKID: "", | ||
encAlg: "", | ||
enc: "", | ||
csigned: true, | ||
jwks: issuerClientJWKS, | ||
jwksURI: "", | ||
} | ||
|
||
tokenString, signature, jwe, err := clientStrategy.Decrypt(ctx, token2, WithClient(clientEncAsymmetric)) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, signature) | ||
assert.NotEmpty(t, tokenString) | ||
assert.NotNil(t, jwe) | ||
|
||
tokenString, signature, jwe, err = clientStrategy.Decrypt(ctx, token3, WithClient(clientEncAsymmetric)) | ||
require.NoError(t, err) | ||
|
||
tok, err := clientStrategy.Decode(ctx, token1, WithClient(issuerClient)) | ||
require.NoError(t, err) | ||
require.NotNil(t, tok) | ||
|
||
tok, err = clientStrategy.Decode(ctx, token2, WithClient(issuerClient)) | ||
require.NoError(t, err) | ||
|
||
tok, err = clientStrategy.Decode(ctx, token3, WithClient(clientEncAsymmetric)) | ||
require.NoError(t, err) | ||
require.NotNil(t, tok) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider breaking down TestDefaultStrategy into smaller, focused test cases.
While this test function is comprehensive, its length and complexity make it difficult to understand and maintain. Consider refactoring it into smaller, more focused test cases. This would improve readability and make it easier to identify the purpose of each test.
For example, you could create separate test functions for:
- JWT encoding
- JWT decoding
- Encrypted JWT handling
- Different key types (RSA, ECDSA)
This refactoring will make the tests more manageable and easier to update in the future.
case string: | ||
aud = append(aud, v) | ||
case []string: | ||
aud = ClaimStrings(v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable type assertion case []string
In the UnmarshalJSON
method for ClaimStrings
, the case []string:
might never be executed because JSON unmarshalling into an interface{}
will not produce a []string
directly. It will produce a []interface{}
instead. Consider removing this case or adjusting the unmarshalling logic.
Apply this diff to remove the unreachable case:
switch v := value.(type) {
case string:
aud = append(aud, v)
-case []string:
- aud = ClaimStrings(v)
case []interface{}:
for _, vv := range v {
Committable suggestion was skipped due to low confidence.
SigningMethodNone = jose.SignatureAlgorithm(JSONWebTokenAlgNone) | ||
|
||
// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens. | ||
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure security implications of allowing 'none' signature algorithm are addressed.
Including SigningMethodNone
and UnsafeAllowNoneSignatureType
enables the use of the 'none' signature algorithm, which effectively disables signature verification. This poses significant security risks. Please ensure this is intentional and that appropriate safeguards are in place to prevent misuse in production environments.
// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens. | ||
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix grammatical error in comment.
The comment in line 12 contains a grammatical error. It should say "should be used" instead of "should be use".
Apply this diff to correct the comment:
-// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens.
+// UnsafeAllowNoneSignatureType is unsafe to use and should be used to correctly sign and verify alg:none JWT tokens.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens. | |
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" | |
// UnsafeAllowNoneSignatureType is unsafe to use and should be used to correctly sign and verify alg:none JWT tokens. | |
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" |
// SignatureAlgorithms contain all algorithms excluding 'none'. | ||
SignatureAlgorithms = []jose.SignatureAlgorithm{jose.HS256, jose.HS384, jose.HS512, jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512, jose.ES256, jose.ES384, jose.ES512} | ||
|
||
// EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct pluralization in comment for accuracy.
The comment mentions "all valid JWE's". The plural form should be "JWEs" without an apostrophe.
Apply this diff to correct the comment:
- // EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0.
+ // EncryptionKeyAlgorithms contains all valid JWEs for OAuth 2.0 and OpenID Connect 1.0.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0. | |
// EncryptionKeyAlgorithms contains all valid JWEs for OAuth 2.0 and OpenID Connect 1.0. |
if err = claims.Valid(); err != nil { | ||
if e, ok := err.(*ValidationError); !ok { | ||
err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid} | ||
} | ||
|
||
return token, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect Error Wrapping in Claims Validation
There is a potential issue in the error handling when validating claims. In the following code:
if err = claims.Valid(); err != nil {
if e, ok := err.(*ValidationError); !ok {
err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
}
return token, err
}
If the type assertion e, ok := err.(*ValidationError)
fails (i.e., ok
is false
), e
will be nil
. However, you're attempting to use e
(which is nil
) as the Inner
error in a new ValidationError
, potentially losing the original error information stored in err
.
Consider modifying the code to properly wrap the original error err
when it's not a *ValidationError
:
if err = claims.Valid(); err != nil {
- if e, ok := err.(*ValidationError); !ok {
- err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
+ if _, ok := err.(*ValidationError); !ok {
+ err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
}
return token, err
}
This change ensures that the original err
is preserved and properly wrapped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🧹 Outside diff range and nitpick comments (17)
rfc8628_device_authorize_write_test.go (1)
Line range hint
1-58
: Summary of changes and recommendationsThe modifications in this file focus on standardizing time precision for JWT operations by using
Truncate(jwt.TimePrecision)
. This change improves consistency in time handling across the codebase.However, there are a couple of points to address:
- Verify that the change from
Round
toTruncate
doesn't introduce any unexpected behavior in the token polling interval or code lifespan calculations.- Check and correct the potential typo where
Second()
is used instead ofSeconds()
in the expiration time calculation.After addressing these points, consider adding comments to explain the rationale behind using
Truncate(jwt.TimePrecision)
for better code maintainability.handler/openid/flow_refresh_token.go (1)
Line range hint
1-110
: Overall improvement in JWT handling with a note on ExpirationTimeThe changes in this file generally improve JWT handling in the refresh token flow by aligning with JWT standards and best practices. The use of
jwt.Now()
forIssuedAt
is particularly good.However, there's a potential issue with setting
ExpirationTime
to an empty time, which needs to be addressed to ensure proper token expiration handling.Consider implementing a consistent method for setting token expiration times across the codebase. This could involve:
- Creating a helper function that calculates the appropriate expiration time based on the token type and configuration.
- Ensuring that all token creation and refresh operations use this helper function to set expiration times.
This approach would improve consistency and reduce the risk of issues related to token expiration.
token/jwt/claims_jarm.go (1)
10-21
: LGTM! Consider adding validation for thelifespan
parameter.The
NewJARMClaims
function is well-implemented and provides a convenient way to create a newJARMClaims
instance with sensible defaults. The use ofuuid.NewString()
for JTI ensures uniqueness, which is good.Consider adding a check to ensure that the
lifespan
parameter is positive. This would prevent the creation of claims with an expiration time in the past:if lifespan <= 0 { return nil, fmt.Errorf("lifespan must be positive") }token/jwt/claims_jwt.go (2)
241-268
: LGTM with suggestion: RobusttoInt64
functionThe
toInt64
function provides comprehensive type checking and conversion, handling various numeric types andjson.Number
. The error handling is thorough, especially forjson.Number
conversion.Consider adding a check for potential precision loss when converting
float64
toint64
. For large float values, this conversion might lead to unexpected results. You could add a warning or error for float values that exceedmath.MaxInt64
or are less thanmath.MinInt64
.Example:
case float64: if t > math.MaxInt64 || t < math.MinInt64 { return 0, false // or handle this case as appropriate } return int64(t), true
270-302
: LGTM with suggestion: Well-implementedtoNumericDate
functionThe
toNumericDate
function provides comprehensive type checking and conversion for various numeric types andjson.Number
. It properly handles the case where the input is 0, returning nil.Consider adding a check for negative values, as they might not be valid for dates. You could either return an error or handle them in a way that's consistent with your application's requirements.
Example:
func toNumericDate(v any) (date *NumericDate, err error) { // ... existing code ... // Add this check after converting to float64 if vv < 0 { return nil, newError("negative values are not allowed for dates", ErrInvalidValue) } return newNumericDateFromSeconds(vv), nil }README.md (2)
107-107
: Minor style suggestion: Consider adding a period after "etc"In American English, it's common to add a period after abbreviations like "etc.". However, given the technical nature of this document and the consistency with other bullet points, this change is optional and up to your preference.
If you choose to make this change, here's the suggested modification:
- - [x] Testing Package (mocks, etc) + - [x] Testing Package (mocks, etc.)🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
Documentation Updates Missing
While some test cases related to the fixes have been implemented, there are no additional documentation updates beyond the existing README.md. Please ensure that all changes are thoroughly documented to maintain clarity and completeness.
🔗 Analysis chain
Line range hint
30-52
: Comprehensive fixes implemented across various flowsGreat job on addressing multiple issues across different flows and functionalities. The inclusion of commit references for each fix is helpful for tracking changes.
To ensure the effectiveness of these fixes:
- Consider adding or updating relevant unit tests for each fix.
- Perform integration testing to verify that the fixes work correctly in the context of the entire system.
- Update any related documentation or API references to reflect these changes.
To verify the implementation of these fixes, you can run the following script:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the implementation of fixes by checking for relevant test files and documentation updates. # Check for test files related to the fixes echo "Checking for relevant test files:" rg --type go -l "(TestRefreshFlow|TestPKCEFlow|TestOpenIDFlow|TestWriteRevocationResponse)" # Check for documentation updates echo "Checking for documentation updates:" rg --type markdown -l "(Refresh Flow|PKCE Flow|OpenID Flow|Revocation Response)"Length of output: 3041
Script:
#!/bin/bash # Description: Verify the presence of test cases and documentation updates for the implemented fixes. # Search for any test files that include the fix descriptions echo "Searching for test cases related to the fixes:" fd -t f -e go . | xargs rg -l "(TestRefreshFlow|TestPKCEFlow|TestOpenIDFlow|TestWriteRevocationResponse)" # Search for documentation files that mention the fixes echo "Searching for documentation updates related to the fixes:" rg -l "(Refresh Flow|PKCE Flow|OpenID Flow|Revocation Response)" --type mdLength of output: 481
token/jwt/claims_map.go (2)
43-64
: LGTM: GetSubject and VerifySubject functions are well-implementedBoth
GetSubject
andVerifySubject
functions are correctly implemented and consistent with their 'iss' counterparts. They provide the necessary functionality for handling the 'sub' claim.Consider refactoring the
VerifyIssuer
andVerifySubject
functions to reduce code duplication. You could create a generic verification function that takes the claim key and comparison function as parameters. For example:func (m MapClaims) verifyStringClaim(claimKey string, cmp string, required bool, getClaim func() (string, error)) bool { claim, err := getClaim() if err != nil { return false } if claim == "" { return !required } return validString(claim, cmp, required) } // Then use it like this: func (m MapClaims) VerifyIssuer(cmp string, required bool) bool { return m.verifyStringClaim(ClaimIssuer, cmp, required, m.GetIssuer) } func (m MapClaims) VerifySubject(cmp string, required bool) bool { return m.verifyStringClaim(ClaimSubject, cmp, required, m.GetSubject) }This approach would make the code more DRY and easier to maintain.
217-282
: LGTM: Comprehensive Valid function with minor improvement suggestionThe new
Valid
function provides a thorough and flexible mechanism for validating JWT claims. It correctly utilizes individual verification functions and accumulates errors using theValidationError
struct.Consider improving the error messages to be more specific. Instead of generic messages like "Token is expired", you could include more details. For example:
if !m.VerifyExpirationTime(now, vopts.expRequired) { expTime, _ := m.GetExpirationTime() vErr.Inner = fmt.Errorf("Token is expired. Expiration time: %v, Current time: %v", expTime, now) vErr.Errors |= ValidationErrorExpired }This would provide more context in the error messages, making debugging easier for users of this library.
handler/openid/strategy_jwt.go (1)
171-171
: LGTM: Improved prompt validation with better error handlingThe updates to the prompt validation logic are excellent improvements:
- The code now handles potential nil values in
claims.AuthTime
, preventing nil pointer dereferences.- Error messages have been enhanced to provide more detailed information about validation failures.
- The use of
GetAuthTimeSafe()
andGetRequestedAtSafe()
methods ensures safer time comparisons.These changes significantly improve the robustness and debuggability of the prompt validation process.
Consider extracting the error messages into constants to improve maintainability and consistency. For example:
const ( ErrPromptNoneReauthentication = "Failed to generate id token because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed." ErrPromptLoginNoReauthentication = "Failed to generate id token because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden." )Then use these constants in the error messages:
WithDebugf(ErrPromptNoneReauthentication, claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe())Also applies to: 178-180, 183-185
integration/oidc_explicit_test.go (1)
161-162
: Consistent JWT claim updates and refactoring opportunityThe changes here continue the pattern of updating
RequestedAt
andAuthTime
fields consistently across test cases. While this consistency is commendable, the repetition of these changes across multiple test cases suggests a potential opportunity for refactoring.Consider creating a helper function to generate the
IDTokenClaims
with the updated time fields. This could reduce duplication and make future updates easier to manage.Here's a suggested helper function:
func newTestIDTokenClaims(subject string) *jwt.IDTokenClaims { return &jwt.IDTokenClaims{ Subject: subject, RequestedAt: jwt.Now(), AuthTime: jwt.NewNumericDate(time.Now().Add(time.Second)), } }You could then use this helper function in your test cases:
session: newIDSession(newTestIDTokenClaims("peter")),handler/oauth2/flow_authorize_code_token.go (2)
114-114
: LGTM. Consider refactoring for DRY principle.The change to use
Truncate(jwt.TimePrecision)
for the refresh token expiration time is consistent with the access token change and improves overall precision consistency.To reduce code duplication and improve maintainability, consider extracting the time calculation into a helper function. Here's a suggested refactoring:
+func calculateExpirationTime(duration time.Duration) time.Time { + return time.Now().UTC().Add(duration).Truncate(jwt.TimePrecision) +} // In HandleTokenEndpointRequest method -request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Truncate(jwt.TimePrecision)) +request.GetSession().SetExpiresAt(oauth2.AccessToken, calculateExpirationTime(atLifespan)) if rtLifespan > -1 { - request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Truncate(jwt.TimePrecision)) + request.GetSession().SetExpiresAt(oauth2.RefreshToken, calculateExpirationTime(rtLifespan)) }This refactoring will make the code more maintainable and reduce the risk of inconsistencies in future modifications.
Line range hint
1-214
: Summary: Time precision improvements enhance token handling consistency.The changes in this file improve the consistency of time precision handling for both access and refresh tokens. This is a positive change that aligns with best practices for JWT handling.
To ensure the changes don't have any unintended consequences:
- Verify that all token validation logic (both client-side and server-side) is compatible with this new precision.
- Update any documentation or API specifications to reflect the precise time handling for token expiration.
- Consider adding a test case that specifically checks for the correct precision of token expiration times.
These steps will help maintain the integrity of the authentication system while benefiting from the improved precision.
token/jwt/claims_id_token.go (1)
38-60
: LGTM: Well-implemented getter methods for claimsThe new getter methods for claims are implemented consistently and adhere to the
Claims
interface. They provide a clean way to access claim data.However, there's a minor inconsistency in the
GetNotBefore
method:Consider updating the
GetNotBefore
method to match the pattern of other getters:func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { return nil, nil // or implement actual logic if needed }This change would make the method consistent with others and avoid relying on an unseen
toNumericDate
function.handler/oauth2/flow_refresh.go (2)
154-154
: Approve change with refactoring suggestion.The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
for setting the refresh token expiration time is consistent with the previous change for the access token. This ensures uniform time precision handling across different token types.To improve code maintainability and reduce duplication, consider refactoring the time adjustment logic into a separate function. This would centralize the logic and make future changes easier. Here's a suggested implementation:
func adjustTokenExpiration(t time.Time) time.Time { return t.Truncate(jwt.TimePrecision) }Then, you can use this function for both access and refresh token expiration times:
request.GetSession().SetExpiresAt(oauth2.AccessToken, adjustTokenExpiration(time.Now().UTC().Add(atLifespan))) // ... request.GetSession().SetExpiresAt(oauth2.RefreshToken, adjustTokenExpiration(time.Now().UTC().Add(rtLifespan)))This refactoring would make the code more DRY (Don't Repeat Yourself) and easier to maintain if the time adjustment logic needs to change in the future.
Line range hint
1-300
: Summary of changes in flow_refresh.goThe changes in this file improve the precision of token expiration times by using
Truncate(jwt.TimePrecision)
instead ofRound(time.Second)
for both access and refresh tokens. These changes are consistent and align with best practices for JWT handling.To further improve the code:
- Verify the impact of using
jwt.TimePrecision
on dependent systems.- Consider refactoring the time adjustment logic into a separate function to reduce code duplication and improve maintainability.
- Ensure that the value of
jwt.TimePrecision
is appropriate for your use case and consistent across the entire authentication system.token/jwt/validate.go (1)
21-67
: Add documentation comments for exported functionsThe exported functions in this file lack documentation comments. Adding comments to exported functions improves code clarity and helps users understand their purpose and usage.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (41)
- README.md (3 hunks)
- handler/oauth2/flow_authorize_code_token.go (2 hunks)
- handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
- handler/oauth2/flow_authorize_implicit.go (2 hunks)
- handler/oauth2/flow_generic_code_token.go (2 hunks)
- handler/oauth2/flow_refresh.go (2 hunks)
- handler/oauth2/flow_refresh_test.go (11 hunks)
- handler/oauth2/flow_resource_owner.go (2 hunks)
- handler/oauth2/flow_resource_owner_test.go (2 hunks)
- handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
- handler/openid/flow_device_authorization_test.go (4 hunks)
- handler/openid/flow_hybrid.go (2 hunks)
- handler/openid/flow_hybrid_test.go (4 hunks)
- handler/openid/flow_refresh_token.go (3 hunks)
- handler/openid/strategy_jwt.go (6 hunks)
- handler/openid/strategy_jwt_test.go (13 hunks)
- handler/openid/validator.go (3 hunks)
- handler/openid/validator_test.go (15 hunks)
- handler/rfc8628/device_authorize_handler.go (2 hunks)
- handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
- handler/rfc8628/user_authorize_handler_test.go (5 hunks)
- handler/rfc8693/access_token_type_handler.go (2 hunks)
- handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
- handler/rfc8693/refresh_token_type_handler.go (2 hunks)
- integration/oidc_explicit_test.go (6 hunks)
- internal/test_helpers.go (2 hunks)
- introspection_response_writer_test.go (1 hunks)
- rfc8628_device_authorize_write_test.go (2 hunks)
- token/jarm/generate.go (2 hunks)
- token/jwt/claims_id_token.go (7 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/claims_jarm.go (2 hunks)
- token/jwt/claims_jarm_test.go (2 hunks)
- token/jwt/claims_jwt.go (5 hunks)
- token/jwt/claims_jwt_test.go (5 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_test.go (1 hunks)
- token/jwt/date.go (1 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/token_test.go (8 hunks)
- token/jwt/validate.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
- introspection_response_writer_test.go
🚧 Files skipped from review as they are similar to previous changes (17)
- handler/oauth2/strategy_jwt_profile_test.go
- handler/openid/flow_device_authorization_test.go
- handler/openid/flow_hybrid.go
- handler/openid/flow_hybrid_test.go
- handler/openid/strategy_jwt_test.go
- handler/openid/validator.go
- handler/openid/validator_test.go
- handler/rfc8628/token_endpoint_handler_test.go
- handler/rfc8693/custom_jwt_type_handler.go
- internal/test_helpers.go
- token/jarm/generate.go
- token/jwt/claims_id_token_test.go
- token/jwt/claims_jarm_test.go
- token/jwt/claims_jwt_test.go
- token/jwt/claims_test.go
- token/jwt/date.go
- token/jwt/token_test.go
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
🔇 Additional comments (74)
handler/rfc8628/device_authorize_handler.go (2)
4-4
: LGTM: Import statement addition is appropriate.The addition of the
jwt
package import is necessary and consistent with the changes made in the expiration time calculation.
Line range hint
1-78
: Summary: Improved consistency in JWT time precision handling.The changes made to this file enhance the consistency of time precision handling in JWT operations. The modification is minimal but significant, affecting only the expiration time calculation for device and user codes. The overall logic and structure of the
HandleRFC8628DeviceAuthorizeEndpointRequest
function remain intact, with the change focused on aligning the time precision with the JWT package's standards.These changes should lead to more consistent behavior in token expiration across the application. However, it's crucial to ensure that this new approach is applied consistently throughout the codebase to maintain uniformity in JWT handling.
handler/oauth2/flow_authorize_implicit.go (1)
7-7
: Verify the impact of changing token expiration time calculationThe modification in how the access token expiration time is calculated could have significant implications:
- The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
may alter the exact expiration time of access tokens.- It introduces a dependency on the
jwt
package for time precision.While this change likely aims to standardize time precision across the application, ensuring consistency in token validation, there are a few points to consider:
- The value of
jwt.TimePrecision
is not immediately clear. Could you please clarify what this value is set to and why it was chosen?- This change might affect existing systems that depend on the previous rounding behavior. Have you considered the potential impact on token validation in other parts of the system or in client applications?
- It would be helpful to add a comment explaining the rationale behind this change, especially if it's part of a larger effort to standardize time handling in JWT operations.
To ensure this change doesn't introduce unexpected behavior, please run the following verification:
This will help us understand the consistency of TimePrecision usage across the codebase.
Also applies to: 74-74
✅ Verification successful
Verified the impact of changing token expiration time calculation
jwt.TimePrecision
is defined astime.Second
.- All usages of
Truncate(jwt.TimePrecision)
are consistent with this definition.- The change standardizes time precision across the application, ensuring consistency in token validation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check the definition and usage of jwt.TimePrecision # Test 1: Find the definition of TimePrecision echo "TimePrecision definition:" rg --type go "TimePrecision.*=.*" -A 3 # Test 2: Check for other usages of TimePrecision in the codebase echo "\nOther usages of TimePrecision:" rg --type go "Truncate\(.*TimePrecision\)" -A 3Length of output: 13263
handler/openid/flow_refresh_token.go (1)
96-96
: Approved: Improved JWT timestamp handlingThe change from
time.Now().Truncate(time.Second)
tojwt.Now()
for setting theIssuedAt
claim is a good improvement. It ensures consistency with the JWT library's time handling and aligns with best practices for JWT timestamp management.token/jwt/claims_jarm.go (7)
25-31
: LGTM! Good use of pointers andomitempty
tag.The changes to the
JARMClaims
struct are well-thought-out:
- Using pointers for
IssuedAt
andExpirationTime
allows for nil checks and better represents optional fields.- The addition of the
omitempty
tag ensures that these fields are omitted from JSON output when they are nil, which is more efficient and cleaner.These changes improve the flexibility and JSON representation of the struct.
83-92
: LGTM! Good handling of nil values and consistent date representation.The updates to the
ToMap
method are well-implemented:
- The nil checks for
IssuedAt
andExpirationTime
are necessary and correctly implemented due to the change to pointer types.- Using the
Unix()
method to convertNumericDate
to integer ensures a consistent representation of dates.These changes improve the robustness of the method and ensure correct handling of optional date fields.
98-101
: LGTM! Simple and effective implementation.The
ToMapClaims
method is a straightforward and effective wrapper aroundToMap
. It provides a convenient way to convertJARMClaims
toMapClaims
, which enhances interoperability with other components that expectMapClaims
.
130-133
: LGTM! Simple and effective implementation.The
FromMapClaims
method is a straightforward and effective wrapper aroundFromMap
. It provides a convenient way to populateJARMClaims
fromMapClaims
, which enhances interoperability with other components that useMapClaims
.
149-160
: LGTM! Good handling of optional date fields.The
toNumericDate
method is well-implemented:
- It correctly handles the case where the key is not found in
Extra
by returning nil.- The error handling is appropriate, allowing the caller to decide how to handle conversion errors.
This method provides a convenient and robust way to handle optional date fields in the claims.
162-176
: LGTM! Good error handling and type checking.The
toString
method is well-implemented:
- It correctly handles the case where the key is not found in
Extra
by returning an empty string.- The type assertion and error handling are appropriate, providing informative errors when the value is not a string.
This method provides a robust way to handle optional string fields in the claims, with good error reporting.
179-181
: LGTM! Good use of compile-time interface check.The type assertion
_ Claims = (*JARMClaims)(nil)
is a great practice. It ensures at compile-time thatJARMClaims
implements theClaims
interface, which helps catch any interface implementation errors early in the development process.handler/oauth2/flow_resource_owner.go (1)
74-74
: Verify impact of precision change on access token expiration.The modification to use
Truncate(jwt.TimePrecision)
instead of the previous rounding method aligns the access token expiration time with a specific precision defined in the JWT package. This change could potentially affect token validation and consistency across the system.Please run the following script to check for any discrepancies in token validation logic:
Consider updating any relevant documentation or API specifications to reflect this change in token expiration precision. Also, ensure that all components dealing with token expiration are using the same precision to maintain consistency.
handler/rfc8693/refresh_token_type_handler.go (1)
4-4
: LGTM: Import statement added for jwt packageThe addition of the import statement for the jwt package is necessary and consistent with the changes made in the
issue
method.token/jwt/claims_jwt.go (3)
104-142
: LGTM: Improved constant usage inToMap
methodThe refactoring to use direct claim constants (e.g.,
ClaimSubject
,ClaimIssuer
) instead of accessing them through theconsts
package improves code readability and reduces package dependencies. The logic remains unchanged, ensuring the method's functionality is preserved.
229-239
: LGTM: Well-implementedtoTime
functionThe new
toTime
function provides a robust way to convert various input types to atime.Time
value. It properly handles conversion failures and ensures consistency by using UTC. Good job on improving the time-related claim processing.
Line range hint
1-302
: Overall assessment: Significant improvements in JWT claim handlingThe changes in this file substantially enhance the robustness and flexibility of JWT claim handling. The introduction of new utility functions (
toTime
,toInt64
,toNumericDate
) provides better support for various data types and improves type safety. The refactoring in theToMap
method improves code readability.While the changes are generally very good, there are a few areas where additional checks could be implemented to handle edge cases (e.g., precision loss in float to int conversion, negative date values). Additionally, as noted in a previous review comment, the
FromMap
method could benefit from handling the success status returned bytoTime
.These enhancements significantly improve the overall quality and reliability of the JWT claim processing in your application.
handler/rfc8693/access_token_type_handler.go (1)
165-165
: Approve the change in refresh token expiration time calculation.The modification from
Round(time.Second)
toTruncate(jwt.TimePrecision)
improves consistency with JWT standards and likely aligns with other parts of the codebase. This change standardizes the timestamp precision for JWT tokens.To ensure the change doesn't unexpectedly affect token lifetimes, please verify the value of
jwt.TimePrecision
:If
jwt.TimePrecision
is less than 1 second, consider adding a comment explaining the rationale for the increased precision.✅ Verification successful
Verified that
jwt.TimePrecision
is set totime.Second
, ensuring the change to useTruncate(jwt.TimePrecision)
correctly standardizes timestamp precision without affecting token lifespans.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check the value of jwt.TimePrecision # Test: Search for the definition of jwt.TimePrecision rg --type go -g '!*_test.go' 'const\s+TimePrecision\s*='Length of output: 58
Script:
#!/bin/bash # Description: Search for all occurrences of TimePrecision in Go files excluding test files rg --type go -g '!*_test.go' 'TimePrecision'Length of output: 2903
README.md (4)
25-27
: LGTM: JWT and JWKS logic improvements completedThe completion of simplifying internal JWT logic and implementing internal JWKS logic aligns well with the project's goals. These improvements should enhance the overall JWT management system.
106-112
: Significant feature enhancements implementedExcellent progress on implementing various features, particularly:
- JWE support for Client Authentication and Issuance
- Key Management
- Support for
s_hash
These additions greatly enhance the library's capabilities and security features.
To ensure these new features are well-documented and easily adoptable:
- Update the user documentation to include usage examples for the new features, especially JWE support and Key Management.
- Consider creating or updating API reference documentation for any new public interfaces related to these features.
- If not already done, add integration tests that demonstrate the correct usage and behavior of these new features in real-world scenarios.
To verify the implementation and documentation of these features, you can run the following script:
#!/bin/bash # Description: Verify the implementation and documentation of new features # Check for implementation files echo "Checking for implementation files:" rg --type go -l "(JWE.*Client Authentication|JWE.*Issuance|Key Management|s_hash)" # Check for test files echo "Checking for test files:" rg --type go -l "Test.*(JWE|KeyManagement|SHash)" # Check for documentation updates echo "Checking for documentation updates:" rg --type markdown -l "(JWE|\"Key Management\"|s_hash)"🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
128-128
: Verification needed for JWT dependency removalThe removal of the
github.com/golang-jwt/jwt
dependency is noted. This change aligns with the earlier simplification of internal JWT logic.To ensure this removal doesn't negatively impact the project:
- Verify that all functionality previously provided by this dependency has been adequately replaced or is no longer needed.
- Update any documentation or code comments that may have referenced this dependency.
- Run a full test suite to ensure no regressions have been introduced by this removal.
To verify the impact of this dependency removal, you can run the following script:
#!/bin/bash # Description: Verify the removal of the github.com/golang-jwt/jwt dependency # Check for any remaining imports of the removed dependency echo "Checking for any remaining imports of github.com/golang-jwt/jwt:" rg --type go "github.com/golang-jwt/jwt" # Check for any mentions in documentation echo "Checking for any mentions in documentation:" rg --type markdown "github.com/golang-jwt/jwt" # Check go.mod for the dependency echo "Checking go.mod for the dependency:" grep "github.com/golang-jwt/jwt" go.mod
129-131
: Dependency migrations require thorough testingThe migration of dependencies to newer versions is a positive step for maintaining the project's security and leveraging new features. Specifically:
github.com/go-jose/go-jose/v3
togithub.com/go-jose/go-jose/v4
github.com/golang/mock
togithub.com/uber-go/mock
github.com/cristalhq/jwt/v4
togithub.com/golang-jwt/jwt/v5
To ensure these migrations don't introduce any issues:
- Review the changelogs of each updated dependency to understand any breaking changes or new features.
- Update any code that may be affected by these changes, particularly focusing on areas that use these dependencies directly.
- Run a comprehensive test suite, including integration tests, to catch any potential regressions or compatibility issues.
- Update any documentation that references these dependencies or their usage.
To verify the impact of these dependency migrations, you can run the following script:
#!/bin/bash # Description: Verify the migration of dependencies # Check for updated imports echo "Checking for updated imports:" rg --type go "github.com/go-jose/go-jose/v4" rg --type go "github.com/uber-go/mock" rg --type go "github.com/golang-jwt/jwt/v5" # Check go.mod for the updated dependencies echo "Checking go.mod for updated dependencies:" grep -E "github.com/go-jose/go-jose/v4|github.com/uber-go/mock|github.com/golang-jwt/jwt/v5" go.mod # Check for any remaining old imports echo "Checking for any remaining old imports:" rg --type go "github.com/go-jose/go-jose/v3" rg --type go "github.com/golang/mock" rg --type go "github.com/cristalhq/jwt/v4"handler/oauth2/flow_resource_owner_test.go (2)
7-7
: LGTM: New import for JWT package.The addition of the
jwt
package import is appropriate and necessary for the changes made in the test assertions.
93-94
: Improved time precision in test assertions.The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
in the time assertions is a good improvement. This modification ensures that the test comparisons are consistent with the actual JWT token generation precision, leading to more accurate and reliable test results.token/jwt/claims_map.go (7)
19-21
: LGTM: New GetIssuer function looks goodThe
GetIssuer
function is a well-implemented addition for retrieving the 'iss' claim. It correctly uses thetoString
method to handle the conversion.
25-40
: LGTM: VerifyIssuer function is well-implementedThe
VerifyIssuer
function provides a robust mechanism for verifying the 'iss' claim. It correctly handles cases where the claim is unset and not required, and properly uses theGetIssuer
function.
67-127
: LGTM: Comprehensive audience verification functionsThe additions and modifications to the audience-related functions (
GetAudience
,VerifyAudience
,VerifyAudienceAll
,VerifyAudienceAny
) provide a robust and flexible system for handling the 'aud' claim. The implementation is consistent across all functions and makes good use of helper functions for code reuse.These changes significantly enhance the JWT validation capabilities, allowing for more precise audience verification scenarios.
130-199
: LGTM: Improved handling of time-based claimsThe changes to the time-based claim functions (
GetExpirationTime
,VerifyExpirationTime
,GetIssuedAt
,VerifyIssuedAt
,GetNotBefore
,VerifyNotBefore
) significantly improve the structure and consistency of the code. The new getter functions and the modified verification functions work together seamlessly to provide robust claim validation.Key improvements:
- Consistent structure across all time-based claims.
- Proper handling of unset and not required cases.
- Use of
validInt64Future
andvalidInt64Past
for accurate time comparisons.These changes enhance the reliability and maintainability of the JWT validation process.
284-294
: LGTM: Improved JSON unmarshalingThe modification to the
UnmarshalJSON
function enhances the flexibility of number parsing in JSON. By usingjjson.UnmarshalIntOrFloat
, the function can now handle numbers as either integers or floats, which is particularly useful for JWT claims that may contain different number formats.This change improves the robustness of the JWT parsing process, reducing the likelihood of errors due to unexpected number formats in claims.
297-359
: LGTM: Well-implemented helper functions for claim type conversionsThe new helper functions (
toInt64
,toNumericDate
,toString
, andtoClaimsString
) are well-implemented and provide robust type conversion capabilities for different claim types. They include proper error handling and clear error messages for invalid types.These functions enhance the overall reliability of the claim parsing process and promote code reuse across the package. The consistent approach to handling different claim types will make the code easier to maintain and extend in the future.
Line range hint
1-359
: Overall assessment: Significant improvements to JWT claim handlingThis PR introduces substantial enhancements to the JWT claim handling capabilities in the
claims_map.go
file. The changes include:
- New getter functions for various claim types.
- Improved verification functions with consistent error handling.
- A comprehensive
Valid
function for holistic claim validation.- Enhanced JSON unmarshaling to handle different number formats.
- New helper functions for type conversions.
These changes significantly improve the flexibility, reliability, and maintainability of the JWT validation process. The consistent structure across different claim types and the use of helper functions promote code reuse and reduce the likelihood of errors.
While the implementation is generally solid, there are a few minor opportunities for further improvement, such as reducing code duplication in some verification functions and enhancing error messages for more detailed debugging.
Overall, this refactoring aligns well with the PR objectives and represents a substantial step forward in the JWT management system.
handler/openid/strategy_jwt.go (10)
46-46
: LGTM: Consistent time handlingThe change from
time.Now().UTC()
tojwt.Now()
for initializingRequestedAt
is a good improvement. It ensures consistency in time handling across JWT-related operations in the codebase.
144-145
: LGTM: Improved abstraction for ID token operationsThe introduction of
jwtClient
usingjwt.NewIDTokenClient(requester.GetClient())
is a good improvement. It provides a dedicated abstraction for ID token operations, which can lead to better separation of concerns and increased flexibility in token handling.
147-149
: LGTM: Improved error handling for maxAgeThe changes in
maxAge
parsing enhance the robustness of the code. By explicitly handling potential parsing errors and defaulting to 0, the code now gracefully handles cases wheremaxAge
is not provided or is invalid.
154-154
: LGTM: Practical adjustment to auth_time validationThe addition of a 5-second buffer when checking if
auth_time
is in the future is a pragmatic improvement. This change accounts for potential minor timing discrepancies that could occur in distributed systems, making the validation more robust in real-world scenarios.
160-164
: LGTM: Enhanced nil-safety in maxAge validationThe updates to the
maxAge
validation logic significantly improve the robustness of the code. By explicitly checking for nil or zero values inclaims.AuthTime
andclaims.RequestedAt
, the code now prevents potential nil pointer dereferences and provides more informative error messages.
196-199
: LGTM: Updated ID token hint decodingThe modification to use
h.Strategy.Decode
for decoding the ID token hint is consistent with the earlier change toDefaultStrategy
. Passing thejwtClient
to theDecode
method is a good practice, as it may provide necessary context for the decoding process. This change aligns well with the new strategy-based approach for JWT operations.
207-211
: LGTM: Enhanced subject validation for ID token hintThe updates to the subject extraction and validation process for the ID token hint are valuable improvements:
- The code now explicitly handles potential errors when extracting the subject from token claims.
- It provides more detailed error messages for missing subjects or subject mismatches.
These changes enhance the robustness and debuggability of the ID token hint validation process.
217-218
: LGTM: Improved token generation processThe updates to the token generation logic are excellent improvements:
- Using
jwt.NewNumericDate
for the expiration time ensures consistency with JWT standards.- The use of
jwt.Now()
for authentication and issuance times aligns with earlier changes, maintaining consistency throughout the codebase.- Including the
jwtClient
in the token encoding process is consistent with the new strategy-based approach.These changes enhance the consistency and correctness of the token generation process.
Also applies to: 221-221, 225-226, 243-245
Line range hint
1-248
: Overall assessment: Significant improvements to JWT handlingThe changes in this file represent a substantial improvement to the JWT handling process:
- The shift to a strategy-based approach (
jwt.Strategy
) provides more flexibility and potentially additional functionality.- Consistent use of
jwt.Now()
andjwt.NewNumericDate()
ensures standardized time handling across JWT operations.- Enhanced error handling and validation processes improve the robustness of the code.
- The introduction of
jwtClient
provides better abstraction for ID token operations.These changes collectively result in a more maintainable, consistent, and robust implementation of OpenID Connect token handling.
To ensure the changes haven't introduced any regressions, please run the existing test suite for this package and verify that all tests pass.
113-113
: Approve change to strategy-based approachThe modification of
DefaultStrategy
to embedjwt.Strategy
instead ofjwt.Signer
is a good architectural decision. It likely provides more flexibility and potentially additional functionality.Please verify that all methods previously available through
jwt.Signer
are still accessible or have been appropriately replaced in the newjwt.Strategy
. Run the following script to check for any breaking changes:integration/oidc_explicit_test.go (5)
108-109
: Improved JWT claim handlingThe changes to
RequestedAt
andAuthTime
fields are beneficial:
- Using
jwt.Now()
forRequestedAt
provides a standardized way to set the current time for JWT claims.- Using
jwt.NewNumericDate()
forAuthTime
ensures proper formatting of the time value as a numeric date in the JWT claim.These modifications align with best practices for working with JWT claims and improve consistency across the codebase.
133-134
: Consistent application of JWT claim updatesThe changes here are identical to those in the previous test case, demonstrating consistent application of the new JWT claim handling methods across multiple test scenarios. This consistency is commendable and helps maintain the integrity of the test suite.
146-147
: Continued consistency in JWT claim updatesThe changes in this test case maintain the same pattern of updates to
RequestedAt
andAuthTime
fields. This ongoing consistency across different test scenarios is excellent, as it ensures that all test cases are updated to use the new JWT claim handling methods uniformly.
176-177
: Appropriate JWT claim updates with intentional time differenceThe changes in these test cases maintain consistency with the new JWT claim handling methods:
RequestedAt
is set usingjwt.Now()
, consistent with previous updates.AuthTime
is set usingjwt.NewNumericDate(time.Now().Add(-time.Minute))
, which intentionally sets the authentication time to one minute in the past.This variation in
AuthTime
is appropriate for these specific test cases, which appear to be testing scenarios where the authentication occurred in the past. The use of JWT-specific methods continues to align with best practices while allowing for different time scenarios in the tests.Also applies to: 190-191
Line range hint
1-238
: Summary of JWT claim handling improvementsThe changes in this file consistently update the handling of JWT claims across multiple test cases in the
TestOpenIDConnectExplicitFlow
function. These updates offer several benefits:
- Standardization: Using
jwt.Now()
andjwt.NewNumericDate()
provides a more standardized approach to handling time-related JWT claims.- Consistency: The changes are applied uniformly across different test scenarios, ensuring consistent behavior.
- Improved readability: The use of JWT-specific methods makes the intention of the code clearer.
While these changes are positive, there's an opportunity to further improve the code by refactoring the repeated claim creation into a helper function, as suggested earlier. This refactoring would reduce duplication and make future updates easier to manage.
Overall, these changes represent a significant improvement in the handling of JWT claims in the test suite, enhancing both the quality and maintainability of the tests.
token/jwt/claims_id_token.go (5)
26-29
: LGTM: Improved type consistency for time-related fieldsThe change from
time.Time
to*NumericDate
forExpirationTime
,IssuedAt
,RequestedAt
, andAuthTime
fields improves JSON compatibility and allows for explicit nil checks. This change aligns well with JWT standards and enhances the flexibility of theIDTokenClaims
struct.
141-171
: LGTM: Well-implemented safe getter methods for time valuesThe new "Safe" getter methods (
GetExpirationTimeSafe
,GetIssuedAtSafe
,GetAuthTimeSafe
, andGetRequestedAtSafe
) provide a convenient and safe way to access time values without the need for nil checks in the calling code. The consistent implementation and the use oftime.Unix(0, 0).UTC()
as a default value for unset times is a good practice.
Line range hint
266-348
: LGTM: Well-updated ToMap methodThe
ToMap
method has been successfully updated to handle the new*NumericDate
types and now uses constants for claim keys. These changes improve consistency with the struct field updates and enhance maintainability. The method correctly handles nil cases for time-related fields, ensuring proper JSON serialization.
350-353
: LGTM: Useful addition of ToMapClaims methodThe new
ToMapClaims
method provides a convenient way to get a jwt-go MapClaims representation of the IDTokenClaims. Its implementation as a wrapper aroundToMap
is appropriate and maintains consistency with the existing codebase.
Line range hint
1-384
: Overall: Significant improvements to JWT claim handling with some refactoring opportunitiesThe changes to
token/jwt/claims_id_token.go
greatly enhance the robustness and flexibility of JWT claim handling. The introduction of*NumericDate
types, new validation methods, and safe getters improve the overall functionality and type safety of the package.Key improvements:
- Updated
IDTokenClaims
struct with*NumericDate
types- New getter methods implementing the
Claims
interface- Comprehensive
Valid
method for claim validation- Safe getter methods for time-related fields
- Implementation of
UnmarshalJSON
for flexible JSON parsing- Updates to
ToMap
method and addition ofToMapClaims
Recommendations:
- Refactor the
Valid
method to improve readability and maintainability- Refactor the
UnmarshalJSON
method to reduce complexity and improve error handling- Address the potential security issue in error messages containing claim values
These changes significantly improve the JWT handling capabilities of the package while maintaining compatibility with existing interfaces.
handler/oauth2/flow_generic_code_token.go (3)
134-134
: Improved precision in access token expiration time.The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
enhances the consistency of token expiration times. This adjustment ensures that the expiration time aligns precisely with the JWT library's time precision, potentially improving token validation accuracy.
138-138
: Consistent precision applied to refresh token expiration time.This change mirrors the modification made to the access token expiration time, ensuring consistency in how both token types are handled. The use of
Truncate(jwt.TimePrecision)
aligns the refresh token's expiration time with the JWT library's time precision, maintaining a uniform approach to token management.
134-138
: Standardized time precision for JWT token expiration.The changes in this file consistently apply
Truncate(jwt.TimePrecision)
to both access and refresh token expiration times. This standardization improves the precision and consistency of token handling, aligning with the JWT library's time precision. The modifications enhance the overall robustness of the token management system without introducing any apparent risks or inconsistencies.handler/oauth2/flow_refresh.go (1)
150-150
: Approve change with verification request.The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
for setting the access token expiration time is appropriate. It ensures consistent time precision across the JWT handling.To ensure this change doesn't introduce any unexpected behavior, please verify:
- The value of
jwt.TimePrecision
and its impact on token expiration.- Any dependent systems that might be affected by this change in precision.
Run the following script to check the
jwt.TimePrecision
value:handler/rfc8628/user_authorize_handler_test.go (5)
41-41
: Improved precision control for JWT expiration timesThe change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
is a good improvement. It ensures consistent behavior by always rounding down to the nearestjwt.TimePrecision
and allows for more flexible precision control. This aligns with best practices for handling JWT expiration times.
247-247
: Consistent improvement in JWT expiration time handlingThis change mirrors the improvement made earlier, replacing
Round(time.Second)
withTruncate(jwt.TimePrecision)
. It's good to see this consistency maintained across different test setup functions, ensuring uniform behavior in JWT expiration time handling throughout the test suite.
404-404
: Consistent JWT time handling in error case testingThe change to use
Truncate(jwt.TimePrecision)
is consistently applied here as well. It's particularly important to maintain this consistency in error case testing, as it ensures that the precise time handling doesn't inadvertently affect the outcome of these edge case tests.
447-447
: Critical consistency in expired token testingThe use of
Truncate(jwt.TimePrecision)
is correctly applied in this test case for expired user codes. This consistency is particularly crucial here, as precise time handling directly impacts the behavior of expiration checks. This change ensures that the expiration logic is tested with the same precision as it would be in production code.
Line range hint
41-447
: Comprehensive improvement in JWT time precision across test suiteThe changes made throughout this file represent a systematic improvement in handling JWT expiration times. By consistently using
Truncate(jwt.TimePrecision)
instead ofRound(time.Second)
, the test suite now more accurately reflects the behavior of the production code in terms of time precision. This modification enhances the reliability of tests across various scenarios, including normal operations, error cases, and expiration checks.These changes contribute to more robust and precise testing of the RFC8628 user authorization handler, which should lead to increased confidence in the correctness of the JWT-related functionality.
handler/oauth2/flow_authorize_code_token_test.go (1)
466-467
: LGTM! Improved precision in token expiration time assertions.The change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
improves the accuracy of the token expiration time assertions. This modification aligns the test with the precision used in the JWT package, ensuring more consistent and precise time comparisons in the tests.handler/oauth2/flow_refresh_test.go (7)
7-7
: LGTM: JWT package import addedThe addition of the JWT package import is consistent with the changes described in the summary, indicating the use of JWT-related functionality in the tests.
118-118
: Improved timestamp precision handlingThe change from
Round(time.Hour)
toTruncate(time.Hour)
for theRequestedAt
field improves consistency in timestamp handling. This modification ensures that the timestamp is always rounded down to the nearest hour, which can prevent potential issues with time comparisons in tests.
143-143
: Consistent timestamp handling across test casesThe change from
Round(time.Hour)
toTruncate(time.Hour)
for theRequestedAt
field is consistent with the previous modification. This ensures uniform timestamp handling across different test cases, improving the overall reliability of the tests.
169-169
: Standardized timestamp handling throughout the test suiteThe consistent change from
Round(time.Hour)
toTruncate(time.Hour)
for theRequestedAt
field across multiple test cases demonstrates a standardized approach to timestamp handling. This uniformity enhances the reliability and predictability of the test suite.
179-179
: Improved JWT expiration time assertionThe change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
for asserting the access token expiration time aligns the test with the standardized JWT time precision. This modification ensures that the expected expiration time is consistent with the actual token generation, improving the accuracy of JWT-related tests.
180-180
: Consistent JWT expiration time handling for refresh tokensThe change from
Round(time.Second)
toTruncate(jwt.TimePrecision)
for asserting the refresh token expiration time is consistent with the previous modification for access tokens. This ensures uniform handling of JWT time precision across different token types, enhancing the overall consistency and reliability of the JWT-related tests.
7-7
: Overall improvement in timestamp handling and JWT assertionsThe changes in this file consistently enhance the handling of timestamps and JWT-related assertions across multiple test cases. The standardization of using
Truncate
instead ofRound
for timestamps and the adoption ofjwt.TimePrecision
for token expiration assertions improve the accuracy and reliability of the test suite. These modifications align well with the goal of refining JWT management and ensure more consistent and predictable test behavior.Also applies to: 118-118, 143-143, 169-169, 179-180
token/jwt/validate.go (6)
8-9
: LGTM!The type definition for
ClaimValidationOption
correctly establishes a functional option type for configuring claim validation options.
10-19
: LGTM!The
ClaimValidationOptions
struct is well-defined and includes all necessary fields for JWT claim validation.
69-81
: LGTM!The
verifyAud
function correctly verifies the audience claim against the expected value using constant-time comparison to prevent timing attacks.
83-97
: LGTM!The
verifyAudAny
function properly checks if any of the expected audience values match the audience claim, using constant-time comparison.
118-126
: LGTM!The
validInt64Future
function correctly checks if a given time value is in the future relative to the current time.
128-135
: LGTM!The
validInt64Past
function correctly checks if a given time value is in the past or present relative to the current time.token/jwt/token.go (1)
20-36
: New constructorsNew
andNewWithClaims
are implemented correctlyThe
New
andNewWithClaims
functions properly initialize theToken
struct with appropriate default values and provided parameters, ensuring that all fields are correctly set up for further use.
)) | ||
resp.SetExpiresIn(int64( | ||
time.Now().Round(time.Second).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(), | ||
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approve Truncate(jwt.TimePrecision)
usage, but check Second()
method
The use of Truncate(jwt.TimePrecision)
aligns with the standardization of time precision for JWT operations, which is good. However, there's a potential issue at the end of the line.
The method Second()
is used instead of Seconds()
. This might be a typo and could lead to unexpected behavior. Please verify and correct if necessary:
- time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(),
+ time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Seconds(),
Also, consider adding a comment explaining the rationale behind using Truncate(jwt.TimePrecision)
to improve code maintainability.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(), | |
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Seconds(), |
@@ -25,10 +26,10 @@ | |||
resp.SetUserCode("AAAA") | |||
resp.SetDeviceCode("BBBB") | |||
resp.SetInterval(int( | |||
oauth2.Config.GetRFC8628TokenPollingInterval(context.TODO()).Round(time.Second).Seconds(), | |||
oauth2.Config.GetRFC8628TokenPollingInterval(context.TODO()).Truncate(jwt.TimePrecision).Seconds(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Update remaining time calculation to use Truncate(jwt.TimePrecision)
There is still an instance where Round(time.Second)
is used in handler/rfc7523/handler.go
. To maintain consistency in time precision across the codebase, please update this to use Truncate(jwt.TimePrecision)
as well.
- File:
handler/rfc7523/handler.go
- Line: SetExpiresAt for
oauth2.AccessToken
🔗 Analysis chain
Approve change to use Truncate(jwt.TimePrecision)
The modification from Round(time.Second)
to Truncate(jwt.TimePrecision)
aligns with standardizing time precision for JWT operations. This change is likely part of a broader effort to ensure consistent time handling across the codebase.
To ensure this change doesn't introduce unexpected behavior, please verify:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent use of jwt.TimePrecision in time calculations
# Test: Search for other occurrences of jwt.TimePrecision
rg --type go "jwt\.TimePrecision"
# Test: Check for any remaining uses of Round(time.Second) that might need updating
rg --type go "Round\(time\.Second\)"
Length of output: 5292
@@ -38,7 +39,7 @@ func (d *DeviceAuthorizeHandler) HandleRFC8628DeviceAuthorizeEndpointRequest(ctx | |||
dar.SetDeviceCodeSignature(deviceCodeSignature) | |||
dar.SetUserCodeSignature(userCodeSignature) | |||
|
|||
expireAt := time.Now().UTC().Add(d.Config.GetRFC8628CodeLifespan(ctx)).Round(time.Second) | |||
expireAt := time.Now().UTC().Add(d.Config.GetRFC8628CodeLifespan(ctx)).Truncate(jwt.TimePrecision) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Issue Detected: Remaining usage of .Round(time.Second)
found in handler/rfc7523/handler.go
.
- Line 42 still uses
.Round(time.Second)
instead of.Truncate(jwt.TimePrecision)
.
Please update this to ensure consistent expiration time calculations across the codebase.
🔗 Analysis chain
LGTM: Expiration time calculation change improves consistency.
The modification from Round(time.Second)
to Truncate(jwt.TimePrecision)
aligns the expiration time calculation with the JWT package's standard time precision. This change enhances consistency in token handling across the application.
To ensure this change is consistently applied throughout the codebase, please run the following verification script:
This script will help identify any remaining instances of the old pattern and confirm the usage of the new pattern across the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent usage of jwt.TimePrecision in expiration time calculations
# Search for time.Now().UTC().Add(...).Round(time.Second) pattern
echo "Searching for remaining instances of .Round(time.Second) in time calculations:"
rg --type go 'time\.Now\(\)\.UTC\(\)\.Add\([^)]+\)\.Round\(time\.Second\)'
# Search for time.Now().UTC().Add(...).Truncate(jwt.TimePrecision) pattern
echo "Verifying usage of .Truncate(jwt.TimePrecision) in time calculations:"
rg --type go 'time\.Now\(\)\.UTC\(\)\.Add\([^)]+\)\.Truncate\(jwt\.TimePrecision\)'
Length of output: 2566
@@ -49,7 +50,7 @@ func (c *OpenIDConnectRefreshHandler) HandleTokenEndpointRequest(ctx context.Con | |||
} | |||
|
|||
// We need to reset the expires at value as this would be the previous expiry. | |||
sess.IDTokenClaims().ExpiresAt = time.Time{} | |||
sess.IDTokenClaims().ExpirationTime = jwt.NewNumericDate(time.Time{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Setting ExpirationTime
to zero will cause token validation to fail as it is marked as mandatory.
Consider assigning a valid future time to ExpirationTime
to ensure tokens are valid upon issuance.
🔗 Analysis chain
Verify the intention of setting ExpirationTime to an empty time.
The change from ExpiresAt
to ExpirationTime
aligns with the JWT standard claims, which is good. However, setting ExpirationTime
to jwt.NewNumericDate(time.Time{})
will result in a token that is immediately expired or invalid.
Is this intentional? If not, consider setting it to a future time based on the token's intended lifespan.
To confirm the impact of this change, let's check how ExpirationTime
is used elsewhere in the codebase:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check usage of ExpirationTime in ID token claims
# Search for ExpirationTime usage in ID token claims
rg --type go -i 'IDTokenClaims.*ExpirationTime'
Length of output: 937
Script:
#!/bin/bash
# Description: Check how ExpirationTime is used in token validation and expiration logic
# Search for usages of ExpirationTime in validation or comparison contexts
rg --type go -i 'ExpirationTime.*(After|Before|IsZero|Validate|Check)'
Length of output: 505
|
||
rtLifespan := oauth2.GetEffectiveLifespan(request.GetClient(), oauth2.GrantTypePassword, oauth2.RefreshToken, c.Config.GetRefreshTokenLifespan(ctx)) | ||
if rtLifespan > -1 { | ||
request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Round(time.Second)) | ||
request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Truncate(jwt.TimePrecision)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consistent precision change for refresh token expiration.
The modification to use Truncate(jwt.TimePrecision)
for the refresh token expiration aligns with the change made for the access token. This ensures consistency in how token expiration times are calculated across different token types.
Consider refactoring the expiration time calculation into a separate function to reduce code duplication and ensure consistent behavior. For example:
func calculateTokenExpiration(baseTime time.Time, lifespan time.Duration) time.Time {
return baseTime.Add(lifespan).Truncate(jwt.TimePrecision)
}
Then, you can use this function for both access and refresh token expiration calculations:
request.GetSession().SetExpiresAt(oauth2.AccessToken, calculateTokenExpiration(time.Now().UTC(), atLifespan))
request.GetSession().SetExpiresAt(oauth2.RefreshToken, calculateTokenExpiration(time.Now().UTC(), rtLifespan))
This refactoring would make the code more maintainable and reduce the risk of inconsistencies in future modifications.
@@ -106,11 +107,11 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C | |||
request.SetID(authorizeRequest.GetID()) | |||
|
|||
atLifespan := oauth2.GetEffectiveLifespan(request.GetClient(), oauth2.GrantTypeAuthorizationCode, oauth2.AccessToken, c.Config.GetAccessTokenLifespan(ctx)) | |||
request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Round(time.Second)) | |||
request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Truncate(jwt.TimePrecision)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Inconsistent usage of time rounding detected.
Found Round(time.Second)
in handler/rfc7523/handler.go
. Please replace it with Truncate(jwt.TimePrecision)
to ensure consistent time precision for token expiration across the codebase.
🔗 Analysis chain
LGTM. Verify consistent usage of jwt.TimePrecision
.
The change from Round
to Truncate(jwt.TimePrecision)
for setting the access token expiration time is a good improvement for consistency. It ensures that all token expiration times are set with the same precision.
To ensure this change is applied consistently, please run the following script to check for any remaining usage of Round
method for token expiration times:
This will help identify any inconsistencies in time precision handling across the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining usage of Round method for token expiration times
# Test: Search for Round method usage in time-related operations
rg --type go 'Round\(time\.Second\)' -g '!vendor'
Length of output: 173
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) { | ||
vopts := &ClaimValidationOptions{} | ||
|
||
for _, opt := range opts { | ||
opt(vopts) | ||
} | ||
|
||
var now int64 | ||
|
||
if vopts.timef != nil { | ||
now = vopts.timef().UTC().Unix() | ||
} else { | ||
now = TimeFunc().UTC().Unix() | ||
} | ||
|
||
vErr := new(ValidationError) | ||
|
||
var date *NumericDate | ||
|
||
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token is expired") | ||
vErr.Errors |= ValidationErrorExpired | ||
} | ||
|
||
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token used before issued") | ||
vErr.Errors |= ValidationErrorIssuedAt | ||
} | ||
|
||
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token is not valid yet") | ||
vErr.Errors |= ValidationErrorNotValidYet | ||
} | ||
|
||
var str string | ||
|
||
if len(vopts.iss) != 0 { | ||
if str, err = c.GetIssuer(); err != nil { | ||
vErr.Inner = errors.New("Token has invalid issuer") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} else if !validString(str, vopts.iss, true) { | ||
vErr.Inner = errors.New("Token has invalid issuer") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} | ||
} | ||
|
||
if len(vopts.sub) != 0 { | ||
if str, err = c.GetSubject(); err != nil { | ||
vErr.Inner = errors.New("Token has invalid subject") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} else if !validString(str, vopts.sub, true) { | ||
vErr.Inner = errors.New("Token has invalid subject") | ||
vErr.Errors |= ValidationErrorSubject | ||
} | ||
} | ||
|
||
var aud ClaimStrings | ||
|
||
if len(vopts.aud) != 0 { | ||
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) { | ||
vErr.Inner = errors.New("Token has invalid audience") | ||
vErr.Errors |= ValidationErrorAudience | ||
} | ||
} | ||
|
||
if len(vopts.audAll) != 0 { | ||
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) { | ||
vErr.Inner = errors.New("Token has invalid audience") | ||
vErr.Errors |= ValidationErrorAudience | ||
} | ||
} | ||
|
||
if vErr.valid() { | ||
return nil | ||
} | ||
|
||
return vErr | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
LGTM: Comprehensive claim validation implementation
The Valid
method provides a thorough validation of claims, covering expiration, issuance, audience, and other important checks. The use of a ValidationError
struct for accumulating errors is a good practice.
Consider refactoring this method to improve readability:
- Extract the validation logic for each claim type into separate methods.
- Use a slice of validation functions to iterate over, reducing the method's complexity.
Example refactoring:
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error {
vopts := &ClaimValidationOptions{}
for _, opt := range opts {
opt(vopts)
}
now := getValidationTime(vopts)
vErr := new(ValidationError)
validations := []func(*ValidationError, int64, *ClaimValidationOptions){
c.validateExpiration,
c.validateIssuedAt,
c.validateNotBefore,
c.validateIssuer,
c.validateSubject,
c.validateAudience,
}
for _, validate := range validations {
validate(vErr, now, vopts)
}
if vErr.valid() {
return nil
}
return vErr
}
func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) {
if date, err := c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
}
// Implement other validation methods similarly...
This refactoring would make the Valid
method more modular and easier to maintain.
func verifyAudAll(aud []string, cmp []string, required bool) bool { | ||
if len(aud) == 0 { | ||
return !required | ||
} | ||
|
||
outer: | ||
for _, c := range cmp { | ||
for _, a := range aud { | ||
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 { | ||
continue outer | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor 'verifyAudAll' to improve readability
The use of the label outer
and the continue
statement in verifyAudAll
can be avoided to enhance code readability. Consider refactoring the function to eliminate the label and use more straightforward control flow.
Apply this diff to refactor the function:
func verifyAudAll(aud []string, cmp []string, required bool) bool {
if len(aud) == 0 {
return !required
}
-outer:
- for _, c := range cmp {
- for _, a := range aud {
- if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
- continue outer
- }
- }
-
- return false
- }
+ for _, c := range cmp {
+ matched := false
+ for _, a := range aud {
+ if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
+ matched = true
+ break
+ }
+ }
+ if !matched {
+ return false
+ }
+ }
return true
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func verifyAudAll(aud []string, cmp []string, required bool) bool { | |
if len(aud) == 0 { | |
return !required | |
} | |
outer: | |
for _, c := range cmp { | |
for _, a := range aud { | |
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 { | |
continue outer | |
} | |
} | |
return false | |
} | |
return true | |
} | |
func verifyAudAll(aud []string, cmp []string, required bool) bool { | |
if len(aud) == 0 { | |
return !required | |
} | |
for _, c := range cmp { | |
matched := false | |
for _, a := range aud { | |
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 { | |
matched = true | |
break | |
} | |
} | |
if !matched { | |
return false | |
} | |
} | |
return true | |
} |
} | ||
return v | ||
} | ||
|
||
func validateTokenType(values []string, header map[string]any) bool { | ||
var ( | ||
raw any | ||
ok bool | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor suggestion: Simplify the pointer
function to avoid reflection
The pointer
function currently uses reflection to convert non-pointer values to pointers, which can impact performance and readability.
Consider using a type switch or leveraging Go generics (if using Go 1.18 or later) to avoid reflection:
Option 1: Use a type switch
func pointer(v interface{}) interface{} {
switch val := v.(type) {
case *DesiredType:
return val
case DesiredType:
return &val
default:
return val
}
}
Option 2: Use generics (Go 1.18+)
func pointer[T any](v T) *T {
return &v
}
This refactor improves performance by eliminating reflection and enhances code clarity.
9f810e6
to
1373d68
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (9)
README.md (5)
25-27
: Excellent progress on implementing key features!Great job on completing several important features:
- Simplifying internal JWT logic using
github.com/golang-jwt/jwt/v5
- Implementing internal JWKS logic
- Adding JWE support for client authentication and issuance
- Implementing key management
These additions significantly enhance the project's capabilities and security.
Consider updating the project's main documentation or creating separate documentation pages to explain how to use these new features, especially the JWE support and key management functionality. This will help users understand and leverage these new capabilities effectively.
Also applies to: 106-106, 109-109
Line range hint
30-54
: Comprehensive fixes improve overall reliabilityExcellent work on addressing various issues across different flows and functionalities. The fixes cover important areas such as:
- RFC9068 compliance
- Argument case-insensitivity
- Improvements in Refresh, PKCE, and OpenID flows
- Error handling and response writing
The inclusion of commit references for each fix is very helpful for tracking changes.
To improve readability and organization, consider grouping the fixes by category (e.g., "Authentication Flows", "Error Handling", "Standards Compliance") in the README. This could make it easier for contributors and users to understand the areas of improvement.
Line range hint
56-113
: Impressive feature additions enhancing standards compliance and securityCongratulations on implementing a wide range of new features, including:
- Support for multiple RFCs (8628, 8693, 9101, 9207)
- JWT Secured Authorization Response Mode (JARM)
- Improvements in client authentication and revocation flows
- Support for
s_hash
These additions greatly enhance the project's compliance with OAuth 2.0 and OpenID Connect standards, as well as improve overall security and flexibility.
Given the substantial number of new features, consider the following next steps:
- Prioritize the implementation of remaining features, especially those related to security (e.g., RFC8705 for Mutual-TLS Client Authentication).
- Create a roadmap or project board to track the progress of remaining features and make it visible to the community.
- Consider writing migration guides for users upgrading from previous versions, as these new features may require configuration changes.
🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
Line range hint
115-131
: Significant improvements in dependency managementGreat job on streamlining the project's dependencies:
- Removal of multiple unnecessary dependencies
- Migration to newer versions of critical dependencies
These changes should lead to a more maintainable codebase with potentially improved performance and security.
To further improve dependency management:
- Consider adding a
go.mod
file to the repository (if not already present) to explicitly define and version the project's dependencies.- Document the rationale behind removing each dependency in the commit messages or in a separate document. This will help future maintainers understand the decisions made.
- Implement a process for regularly reviewing and updating dependencies to ensure the project stays current with the latest security patches and improvements.
107-107
: Minor style improvement suggestionThe README is well-structured and easy to read. However, there's a small style improvement that can be made:
In American English, the abbreviation "etc" should include a period. Please update line 107 to read:
- - [x] Testing Package (mocks, etc) + - [x] Testing Package (mocks, etc.)This change will ensure consistency with American English style guidelines throughout the document.
🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
token/jwt/claims_map.go (3)
67-128
: LGTM: Comprehensive audience claim handling with a minor suggestionThe new audience-related methods (
GetAudience
,VerifyAudience
,VerifyAudienceAll
, andVerifyAudienceAny
) provide comprehensive and flexible options for audience verification. The use ofClaimStrings
type allows for proper handling of multiple audience values.Consider adding a brief comment explaining the difference between
VerifyAudienceAll
andVerifyAudienceAny
to improve code documentation.
215-283
: LGTM: Comprehensive claim validation method with a suggestionThe new
Valid
method provides a flexible and comprehensive way to validate multiple claims in one call. It properly handles time-based claims and optional validations, and the use of aValidationError
struct allows for detailed error reporting.Consider adding a comment explaining the purpose and usage of the
ClaimValidationOption
type and its related functions to improve code documentation.
308-360
: LGTM: Well-implemented helper methods for claim extraction and conversionThe new helper methods (
toNumericDate
,toString
, andtoClaimsString
) provide type-safe ways to extract and convert different types of claims. The error handling is appropriate, using custom error types. ThetoClaimsString
method handles multiple possible types for the audience claim, which provides good flexibility.In the
toClaimsString
method, consider using a type switch instead of multiple type assertions to improve readability and potentially performance.token/jwt/token.go (1)
247-295
: Ensure Consistent Error Wrapping inCompactEncrypted
MethodIn the
CompactEncrypted
method, errors are wrapped usingerrorsx.WithStack
, but this pattern is not consistently applied throughout the codebase. For consistency and clarity, consider standardizing error handling practices across all methods.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (42)
- README.md (3 hunks)
- handler/oauth2/flow_authorize_code_token.go (2 hunks)
- handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
- handler/oauth2/flow_authorize_implicit.go (2 hunks)
- handler/oauth2/flow_generic_code_token.go (2 hunks)
- handler/oauth2/flow_refresh.go (2 hunks)
- handler/oauth2/flow_refresh_test.go (11 hunks)
- handler/oauth2/flow_resource_owner.go (2 hunks)
- handler/oauth2/flow_resource_owner_test.go (2 hunks)
- handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
- handler/openid/flow_device_authorization_test.go (4 hunks)
- handler/openid/flow_hybrid.go (2 hunks)
- handler/openid/flow_hybrid_test.go (4 hunks)
- handler/openid/flow_refresh_token.go (3 hunks)
- handler/openid/strategy_jwt.go (6 hunks)
- handler/openid/strategy_jwt_test.go (13 hunks)
- handler/openid/validator.go (3 hunks)
- handler/openid/validator_test.go (15 hunks)
- handler/rfc8628/device_authorize_handler.go (2 hunks)
- handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
- handler/rfc8628/user_authorize_handler_test.go (5 hunks)
- handler/rfc8693/access_token_type_handler.go (2 hunks)
- handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
- handler/rfc8693/refresh_token_type_handler.go (2 hunks)
- integration/oidc_explicit_test.go (6 hunks)
- internal/test_helpers.go (2 hunks)
- introspection_response_writer_test.go (1 hunks)
- rfc8628_device_authorize_write_test.go (2 hunks)
- testing/mock/client.go (1 hunks)
- token/jarm/generate.go (2 hunks)
- token/jwt/claims_id_token.go (7 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/claims_jarm.go (2 hunks)
- token/jwt/claims_jarm_test.go (2 hunks)
- token/jwt/claims_jwt.go (5 hunks)
- token/jwt/claims_jwt_test.go (5 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_test.go (1 hunks)
- token/jwt/date.go (1 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/token_test.go (8 hunks)
- token/jwt/validate.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (39)
- handler/oauth2/flow_authorize_code_token.go
- handler/oauth2/flow_authorize_code_token_test.go
- handler/oauth2/flow_authorize_implicit.go
- handler/oauth2/flow_generic_code_token.go
- handler/oauth2/flow_refresh.go
- handler/oauth2/flow_refresh_test.go
- handler/oauth2/flow_resource_owner.go
- handler/oauth2/flow_resource_owner_test.go
- handler/oauth2/strategy_jwt_profile_test.go
- handler/openid/flow_device_authorization_test.go
- handler/openid/flow_hybrid.go
- handler/openid/flow_hybrid_test.go
- handler/openid/flow_refresh_token.go
- handler/openid/strategy_jwt.go
- handler/openid/strategy_jwt_test.go
- handler/openid/validator.go
- handler/openid/validator_test.go
- handler/rfc8628/device_authorize_handler.go
- handler/rfc8628/token_endpoint_handler_test.go
- handler/rfc8628/user_authorize_handler_test.go
- handler/rfc8693/access_token_type_handler.go
- handler/rfc8693/custom_jwt_type_handler.go
- handler/rfc8693/refresh_token_type_handler.go
- integration/oidc_explicit_test.go
- internal/test_helpers.go
- introspection_response_writer_test.go
- rfc8628_device_authorize_write_test.go
- testing/mock/client.go
- token/jarm/generate.go
- token/jwt/claims_id_token.go
- token/jwt/claims_id_token_test.go
- token/jwt/claims_jarm.go
- token/jwt/claims_jarm_test.go
- token/jwt/claims_jwt.go
- token/jwt/claims_jwt_test.go
- token/jwt/claims_test.go
- token/jwt/date.go
- token/jwt/token_test.go
- token/jwt/validate.go
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
🔇 Additional comments (11)
token/jwt/claims_map.go (9)
19-41
: LGTM: Well-implemented issuer claim handlingThe new
GetIssuer
andVerifyIssuer
methods are well-implemented. TheVerifyIssuer
method'srequired
parameter allows for flexible validation, which is a good practice for handling optional claims.
43-65
: LGTM: Consistent implementation of subject claim handlingThe
GetSubject
andVerifySubject
methods are implemented consistently with the issuer claim methods. This consistency enhances code maintainability and readability.
131-153
: LGTM: Well-implemented expiration time claim handlingThe
GetExpirationTime
andVerifyExpirationTime
methods are implemented correctly and consistently with other claim handling methods. The use ofNumericDate
type andvalidInt64Future
function for verification is appropriate for the expiration time claim.
155-177
: LGTM: Consistent implementation of issued at claim handlingThe
GetIssuedAt
andVerifyIssuedAt
methods are implemented consistently with other time-based claim methods. The use ofvalidInt64Past
for verification is appropriate for the issued at claim.
179-201
: LGTM: Consistent implementation of not before claim handlingThe
GetNotBefore
andVerifyNotBefore
methods are implemented consistently with other time-based claim methods. The use ofvalidInt64Past
for verification is appropriate for the not before claim.
203-213
: LGTM: Useful type conversion methods addedThe
ToMapClaims
andToMap
methods provide simple and useful type conversions. The nil check inToMapClaims
is a good practice to prevent nil pointer dereferences.
285-297
: LGTM: Improved JSON unmarshalingThe modified
UnmarshalJSON
method uses a custom JSON decoder withjjson.UnmarshalIntOrFloat
, allowing for more flexible number parsing. This improves interoperability. The use oferrorsx.WithStack
for error handling is a good practice for maintaining the error stack trace.
298-306
: LGTM: Safe int64 conversion method addedThe new
toInt64
method provides a safe way to extract and convert int64 values from claims. It properly handles the case where the claim doesn't exist.
Line range hint
1-360
: Overall: Excellent improvements to JWT claim handlingThe changes in this file significantly enhance the JWT claim handling capabilities. The new methods provide a comprehensive and flexible way to retrieve, verify, and validate various claim types. The implementation is consistent across different claim types, which improves maintainability and readability.
Key improvements include:
- Consistent methods for handling standard claims (iss, sub, aud, exp, iat, nbf).
- Flexible audience verification options.
- A comprehensive
Valid
method for validating multiple claims.- Improved JSON unmarshaling for better interoperability.
- Type-safe helper methods for claim extraction and conversion.
These changes will likely improve the robustness and usability of the JWT handling in the system.
token/jwt/token.go (2)
117-123
:⚠️ Potential issueCorrect Error Wrapping in Claims Validation
In the error handling for claims validation, there's a logical issue. If the type assertion
e, ok := err.(*ValidationError)
fails (i.e.,ok
isfalse
),e
will benil
. This results in wrapping anil
error, potentially losing the original error information stored inerr
.Apply this diff to properly wrap the original error:
if err = claims.Valid(); err != nil { - if e, ok := err.(*ValidationError); !ok { - err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid} + if _, ok := err.(*ValidationError); !ok { + err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} } return token, err }Likely invalid or redundant comment.
611-626
: 🛠️ Refactor suggestionSimplify
isUnsafeNoneMagicConstant
Function for ReadabilityThe
isUnsafeNoneMagicConstant
function can be simplified to reduce nested type assertions and improve readability.Consider refactoring the function as follows:
func isUnsafeNoneMagicConstant(k any) bool { - switch key := k.(type) { - case unsafeNoneMagicConstant: - return true - case jose.JSONWebKey: - if _, ok := key.Key.(unsafeNoneMagicConstant); ok { - return true - } - case *jose.JSONWebKey: - if _, ok := key.Key.(unsafeNoneMagicConstant); ok { - return true - } - } + if _, ok := k.(unsafeNoneMagicConstant); ok { + return true + } + if key, ok := k.(jose.JSONWebKey); ok { + _, ok := key.Key.(unsafeNoneMagicConstant) + return ok + } + if key, ok := k.(*jose.JSONWebKey); ok { + _, ok := key.Key.(unsafeNoneMagicConstant) + return ok + } return false }Likely invalid or redundant comment.
if key, err = keyFunc(token); err != nil { | ||
// keyFunc returned an error | ||
var ve *ValidationError | ||
|
||
if errors.As(err, &ve) { | ||
return token, ve | ||
} | ||
|
||
return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure Proper Error Handling in Key Function
In the ParseCustomWithClaims
function, when keyFunc(token)
returns an error, the current logic may not preserve all error information, especially if the error is a custom type implementing error
but not *ValidationError
.
Consider adjusting the error handling to properly handle all error types:
if key, err = keyFunc(token); err != nil {
- var ve *ValidationError
-
- if errors.As(err, &ve) {
- return token, ve
- }
-
- return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
+ if ve, ok := err.(*ValidationError); ok {
+ return token, ve
+ }
+ return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
}
This change ensures that if the error is a *ValidationError
, it is returned as is; otherwise, it's wrapped appropriately.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if key, err = keyFunc(token); err != nil { | |
// keyFunc returned an error | |
var ve *ValidationError | |
if errors.As(err, &ve) { | |
return token, ve | |
} | |
return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err} | |
} | |
if key, err = keyFunc(token); err != nil { | |
if ve, ok := err.(*ValidationError); ok { | |
return token, ve | |
} | |
return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err} | |
} |
func validateTokenTypeValue(values []string, raw any) bool { | ||
var ( | ||
typ string | ||
ok bool | ||
) | ||
|
||
if typ, ok = raw.(string); !ok { | ||
return false | ||
} | ||
|
||
for _, t := range values { | ||
if t == typ { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify validateTokenTypeValue
Function
The validateTokenTypeValue
function can be simplified for better readability by returning early upon finding a match.
Refactor the function as follows:
func validateTokenTypeValue(values []string, raw any) bool {
- var (
- typ string
- ok bool
- )
-
- if typ, ok = raw.(string); !ok {
+ typ, ok := raw.(string)
+ if !ok {
return false
}
- for _, t := range values {
- if t == typ {
- return true
- }
- }
-
- return false
+ return containsString(values, typ)
}
+func containsString(slice []string, str string) bool {
+ for _, v := range slice {
+ if v == str {
+ return true
+ }
+ }
+ return false
+}
This refactor improves readability and efficiency by eliminating unnecessary variables and simplifying the control flow.
Committable suggestion was skipped due to low confidence.
vErr.Inner = errors.New("token has an invalid or unverified signature") | ||
vErr.Errors |= ValidationErrorSignatureInvalid | ||
} | ||
h := base64.RawURLEncoding.EncodeToString(hbytes) | ||
b := base64.RawURLEncoding.EncodeToString(bbytes) | ||
return fmt.Sprintf("%v.%v.", h, b), nil | ||
} | ||
|
||
func newToken(parsedToken *jwt.JSONWebToken, claims MapClaims) (*Token, error) { | ||
token := &Token{Claims: claims} | ||
if len(parsedToken.Headers) != 1 { | ||
return nil, &ValidationError{text: fmt.Sprintf("only one header supported, got %v", len(parsedToken.Headers)), Errors: ValidationErrorMalformed} | ||
if t.HeaderJWE != nil && (t.KeyAlgorithm != "" || t.ContentEncryption != "") { | ||
var ( | ||
typ any | ||
ok bool | ||
) | ||
|
||
if typ, ok = t.HeaderJWE[JSONWebTokenHeaderType]; !ok || typ != JSONWebTokenTypeJWT { | ||
vErr.Inner = errors.New("token was encrypted with invalid typ") | ||
vErr.Errors |= ValidationErrorHeaderEncryptionTypeInvalid | ||
} | ||
|
||
ttyp := t.Header[JSONWebTokenHeaderType] | ||
cty := t.HeaderJWE[JSONWebTokenHeaderContentType] | ||
|
||
if cty != ttyp { | ||
vErr.Inner = errors.New("token was encrypted with a cty value that doesn't match the typ value") | ||
vErr.Errors |= ValidationErrorHeaderContentTypeInvalidMismatch | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid Overwriting Errors in Header Validation
In the Valid
method, errors are being assigned to vErr.Inner
multiple times, which may overwrite previous errors and lead to loss of information. This could make debugging difficult as only the last error is retained.
Consider accumulating errors or appending messages to provide comprehensive feedback:
if !t.valid {
- vErr.Inner = errors.New("token has an invalid or unverified signature")
+ vErr = vErr.Append("token has an invalid or unverified signature")
vErr.Errors |= ValidationErrorSignatureInvalid
}
// ...
if condition {
- vErr.Inner = errors.New("another validation error")
+ vErr = vErr.Append("another validation error")
vErr.Errors |= ValidationErrorSomeOtherError
}
Ensure that ValidationError
supports error accumulation, perhaps by maintaining a slice of errors.
Committable suggestion was skipped due to low confidence.
// Token represets a JWT Token. | ||
type Token struct { | ||
KeyID string | ||
SignatureAlgorithm jose.SignatureAlgorithm // alg (JWS) | ||
EncryptionKeyID string | ||
KeyAlgorithm jose.KeyAlgorithm // alg (JWE) | ||
ContentEncryption jose.ContentEncryption // enc (JWE) | ||
CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE) | ||
|
||
Header map[string]any | ||
HeaderJWE map[string]any | ||
|
||
Claims Claims | ||
|
||
// Valid informs if the token was verified against a given verification key | ||
parsedToken *jwt.JSONWebToken | ||
|
||
valid bool | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSON Tags to Token
Struct Fields
The fields in the Token
struct lack JSON struct tags. If serialization to JSON is required for logging or other purposes, adding explicit JSON tags ensures correct field names and can help avoid accidental exposure of sensitive information.
Consider adding JSON tags to the struct fields:
type Token struct {
- KeyID string
- SignatureAlgorithm jose.SignatureAlgorithm // alg (JWS)
- EncryptionKeyID string
- KeyAlgorithm jose.KeyAlgorithm // alg (JWE)
- ContentEncryption jose.ContentEncryption // enc (JWE)
- CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE)
+ KeyID string `json:"kid,omitempty"`
+ SignatureAlgorithm jose.SignatureAlgorithm `json:"alg,omitempty"` // alg (JWS)
+ EncryptionKeyID string `json:"enc_kid,omitempty"`
+ KeyAlgorithm jose.KeyAlgorithm `json:"enc_alg,omitempty"` // alg (JWE)
+ ContentEncryption jose.ContentEncryption `json:"enc,omitempty"` // enc (JWE)
+ CompressionAlgorithm jose.CompressionAlgorithm `json:"zip,omitempty"` // zip (JWE)
Header map[string]any `json:"header,omitempty"`
HeaderJWE map[string]any `json:"header_jwe,omitempty"`
Claims Claims `json:"claims,omitempty"`
parsedToken *jwt.JSONWebToken `json:"-"`
valid bool `json:"-"`
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Token represets a JWT Token. | |
type Token struct { | |
KeyID string | |
SignatureAlgorithm jose.SignatureAlgorithm // alg (JWS) | |
EncryptionKeyID string | |
KeyAlgorithm jose.KeyAlgorithm // alg (JWE) | |
ContentEncryption jose.ContentEncryption // enc (JWE) | |
CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE) | |
Header map[string]any | |
HeaderJWE map[string]any | |
Claims Claims | |
// Valid informs if the token was verified against a given verification key | |
parsedToken *jwt.JSONWebToken | |
valid bool | |
} | |
// Token represets a JWT Token. | |
type Token struct { | |
KeyID string `json:"kid,omitempty"` | |
SignatureAlgorithm jose.SignatureAlgorithm `json:"alg,omitempty"` // alg (JWS) | |
EncryptionKeyID string `json:"enc_kid,omitempty"` | |
KeyAlgorithm jose.KeyAlgorithm `json:"enc_alg,omitempty"` // alg (JWE) | |
ContentEncryption jose.ContentEncryption `json:"enc,omitempty"` // enc (JWE) | |
CompressionAlgorithm jose.CompressionAlgorithm `json:"zip,omitempty"` // zip (JWE) | |
Header map[string]any `json:"header,omitempty"` | |
HeaderJWE map[string]any `json:"header_jwe,omitempty"` | |
Claims Claims `json:"claims,omitempty"` | |
parsedToken *jwt.JSONWebToken `json:"-"` | |
valid bool `json:"-"` | |
} |
0c03ce9
to
7fac454
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post
Actionable comments posted: 22
🧹 Outside diff range and nitpick comments (32)
token/jwt/claims_jarm_test.go (2)
16-20
: LGTM! Consider adding a comment for clarity.The changes to
IssuedAt
andExpirationTime
fields improve consistency in time handling. The use ofNow()
andNewNumericDate()
functions suggests a standardized approach to managing time precision in JWT claims.Consider adding a brief comment explaining the rationale behind using
Now()
andNewNumericDate()
, especially if these are custom functions. This would help other developers understand the importance of consistent time handling in JWT claims.
47-49
: LGTM! Consider enhancing test readability.The changes to use
ExpirationTime
andNewNumericDate()
are consistent with earlier modifications, ensuring standardized time handling in JWT claims.To improve test readability, consider extracting the time calculations into named variables. For example:
validTime := NewNumericDate(time.Now().Add(time.Hour)) invalidTime := NewNumericDate(time.Now().Add(-2 * time.Hour)) assert.Nil(t, (&JARMClaims{ExpirationTime: validTime}).ToMapClaims().Valid()) assert.NotNil(t, (&JARMClaims{ExpirationTime: invalidTime}).ToMapClaims().Valid())This change would make the test's intent clearer and easier to maintain.
token/jwt/date.go (1)
68-76
: Consider a more descriptive method name for Int64.While the
Int64
method is well-implemented and handles edge cases correctly, its name might not fully convey its purpose. Consider renaming it to something more descriptive, such asUnixTimestamp
orSecondsSinceEpoch
.Here's a suggested improvement:
-func (date *NumericDate) Int64() (val int64) { +func (date *NumericDate) UnixTimestamp() (val int64) { if date == nil { return 0 } return date.UTC().Truncate(TimePrecision).Unix() }token/jwt/claims_jwt.go (3)
229-239
: LGTM: Improved time conversion with error handlingThe new
toTime
function is a good addition. It centralizes time conversion logic and improves type safety by handling various input types. The use oftoInt64
further abstracts the conversion process.One minor suggestion:
Consider using a more descriptive variable name than
v
for the input parameter to improve readability. For example:-func toTime(v any, def time.Time) (t time.Time, ok bool) { +func toTime(value any, def time.Time) (t time.Time, ok bool) { // ... rest of the function - if value, ok = toInt64(v); ok { + if value, ok = toInt64(value); ok { // ... rest of the function } }
241-268
: LGTM: Robust int64 conversion functionThe
toInt64
function is a valuable addition. It centralizes int64 conversion logic and handles various numeric types, includingjson.Number
, which is particularly useful for JSON-based token claims. The boolean return value allows callers to handle conversion failures effectively.One minor suggestion for improvement:
Consider adding a case for
uint64
to handle unsigned integers:func toInt64(v any) (val int64, ok bool) { var err error switch t := v.(type) { // ... existing cases ... + case uint64: + if t <= math.MaxInt64 { + return int64(t), true + } + return 0, false } return 0, false }This addition would make the function more comprehensive in handling different integer types.
270-304
: LGTM: Comprehensive NumericDate conversion functionThe
toNumericDate
function is a well-implemented addition. It centralizes NumericDate conversion logic, handles various types, and consistently treats nil and zero values. The use ofnewNumericDateFromSeconds
suggests that this type represents Unix timestamps, which is appropriate for JWT claims.A minor suggestion for improvement:
Consider adding a case for
time.Time
to directly handle time objects:func toNumericDate(v any) (date *NumericDate, err error) { switch value := v.(type) { // ... existing cases ... + case time.Time: + return newNumericDateFromSeconds(float64(value.Unix())), nil } return nil, newError("value has invalid type", ErrInvalidType) }This addition would make the function more versatile by directly accepting
time.Time
objects, which are commonly used in Go for time representation.README.md (5)
Line range hint
11-24
: Consider adding version numbers for clarity.To help users understand when certain changes were implemented, consider adding version numbers next to completed items. For example:
- [x] Module path changed from `github.com/ory/fosite` to `authelia.com/provider/oauth2`. (v1.0.0)This would make it easier for users to track which version introduced specific changes.
25-27
: Clarify the status of JWT and JWKS implementations.The items for simplifying internal JWT logic and implementing internal JWKS logic are marked as completed. Consider adding brief notes on what libraries or methods are now being used, or link to relevant documentation for these implementations.
Line range hint
80-106
: Add links to RFCs for better reference.For the RFC implementations listed (e.g., RFC8628, RFC8693, RFC8705, etc.), consider adding direct links to the RFC documents. This would make it easier for users to reference the specific standards being implemented. For example:
- [x] [RFC8628: OAuth 2.0 Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628) support🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
Line range hint
115-128
: Fix formatting inconsistency in removed dependencies list.There's a minor formatting inconsistency in the list of removed dependencies. The last item has an extra space at the end of the package name:
- [x] `github.com/golang-jwt/jwt `Remove the extra space to maintain consistency with the other items in the list.
Line range hint
134-150
: LGTM: "Thanks" section provides clear context and acknowledgments.The "Thanks" section effectively explains the project's relationship to ORY Fosite and provides important clarifications about licensing and maintenance. This transparency is valuable for users and contributors.
Additionally, there's a minor style improvement to make:
In the line:
- Plan to continue to contribute back to te ORY fosite and related projects.
Correct "te" to "the" and consider adding a period at the end of each bullet point for consistency with American English style:
- Plan to continue to contribute back to the ORY fosite and related projects.
handler/oauth2/strategy_jwt_profile_test.go (2)
58-85
: LGTM: Well-structured test case for invalid JWT typeThe new
jwtInvalidTypCase
function is a valuable addition for testing error handling with invalid JWT types. It follows the established pattern of other test case functions in this file.Consider adding a comment explaining that this function intentionally sets an invalid "typ" claim in the JWT header for testing purposes.
var jwtInvalidTypCase = func(tokenType oauth2.TokenType) *oauth2.Request { + // This function intentionally sets an invalid "typ" claim in the JWT header for testing purposes r := &oauth2.Request{ // ... (rest of the function remains unchanged) } return r }
147-150
: LGTM: Improved test determinism with parameterized timeThe addition of the
now
parameter tojwtExpiredCase
and the use of relative time offsets significantly improve test determinism and flexibility. This change eliminates potential flakiness caused by using the current time in tests.Consider adding a comment explaining the purpose of this change to help future maintainers understand the rationale.
+// jwtExpiredCase generates a request with an expired JWT token. +// The 'now' parameter allows for deterministic testing of expiration logic. var jwtExpiredCase = func(tokenType oauth2.TokenType, now time.Time) *oauth2.Request { // ... (rest of the function remains unchanged) }Also applies to: 159-161, 168-168
token/jwt/claims_map.go (6)
26-41
: LGTM with suggestion: VerifyIssuer method implementationThe
VerifyIssuer
method is well-implemented, correctly handling cases where the issuer is unset and not required. It uses theGetIssuer
method andvalidString
helper function for consistency.However, consider improving error handling:
func (m MapClaims) VerifyIssuer(cmp string, required bool) (ok bool) { var ( iss string err error ) if iss, err = m.GetIssuer(); err != nil { - return false + return false // Consider logging the error or returning it } if iss == "" { return !required } return validString(iss, cmp, required) }This change would allow for better error tracking and debugging if needed.
44-65
: LGTM with suggestion: GetSubject and VerifySubject methodsBoth
GetSubject
andVerifySubject
methods are well-implemented and consistent with their issuer counterparts. They correctly use helper methods for string conversion and validation.For
VerifySubject
, consider the same error handling improvement as suggested forVerifyIssuer
:func (m MapClaims) VerifySubject(cmp string, required bool) (ok bool) { var ( sub string err error ) if sub, err = m.GetSubject(); err != nil { - return false + return false // Consider logging the error or returning it } if sub == "" { return !required } return validString(sub, cmp, required) }This change would allow for better error tracking and debugging if needed.
68-128
: LGTM with suggestions: Audience-related methodsThe implementation of
GetAudience
,VerifyAudience
,VerifyAudienceAll
, andVerifyAudienceAny
methods is well-structured and provides great flexibility for various audience validation scenarios. The use of helper methods for claim retrieval and validation ensures consistency.Suggestions for improvement:
- Consider improving error handling in all verification methods, similar to previous suggestions:
func (m MapClaims) VerifyAudience(cmp string, required bool) (ok bool) { var ( aud ClaimStrings err error ) if aud, err = m.GetAudience(); err != nil { - return false + return false // Consider logging the error or returning it } // ... rest of the method }Apply similar changes to
VerifyAudienceAll
andVerifyAudienceAny
.
- Consider adding comments to explain the difference between the three verification methods, especially
VerifyAudienceAll
andVerifyAudienceAny
, to make it clearer for developers using these methods.Great job on providing flexible options for audience verification!
131-200
: LGTM with suggestions: Time-based claim methodsThe implementation of methods for getting and verifying time-based claims ('exp', 'iat', 'nbf') is consistent and well-structured. The use of the
toNumericDate
helper and specific validation functions (validInt64Future
andvalidInt64Past
) enhances clarity and maintainability.Suggestions for improvement:
- Consider improving error handling in all verification methods, similar to previous suggestions:
func (m MapClaims) VerifyExpirationTime(cmp int64, required bool) (ok bool) { var ( exp *NumericDate err error ) if exp, err = m.GetExpirationTime(); err != nil { - return false + return false // Consider logging the error or returning it } // ... rest of the method }Apply similar changes to
VerifyIssuedAt
andVerifyNotBefore
.
- Consider adding a comment explaining the difference between
validInt64Future
andvalidInt64Past
to make it clear why different functions are used for different time-based claims.Great job on maintaining consistency across these time-based claim methods!
218-283
: LGTM with suggestions: Valid method implementationThe
Valid
method is well-implemented, providing a comprehensive validation of all standard JWT claims. The use ofValidationError
to accumulate multiple validation errors is a good practice, and the flexibility provided throughClaimValidationOption
is commendable.Suggestions for improvement:
- Consider adding more descriptive error messages, possibly including the actual and expected values:
if !m.VerifyExpirationTime(now, vopts.expRequired) { - vErr.Inner = errors.New("Token is expired") + vErr.Inner = fmt.Errorf("Token is expired (current time: %d)", now) vErr.Errors |= ValidationErrorExpired }
Consider adding a comment explaining the purpose and usage of the
ClaimValidationOption
for better documentation.You might want to consider returning the
ValidationError
even if it's not valid, to provide more information about which validations passed and which failed. This could be useful for debugging purposes.Great job on implementing this comprehensive validation method!
298-360
: LGTM with minor suggestion: Helper methods implementationThe helper methods
toInt64
,toNumericDate
,toString
, andtoClaimsString
are well-implemented. They handle various edge cases and provide consistent error handling and type checking. ThetoClaimsString
method, in particular, does a great job handling the complexity of the audience claim.Suggestion for improvement:
Consider using a consistent error creation method across all helper functions. For example, in
toString
:if value, ok = raw.(string); !ok { - return "", newError(fmt.Sprintf("%s is invalid", key), ErrInvalidType) + return "", fmt.Errorf("%s is invalid: %w", key, ErrInvalidType) }This would make it consistent with the error creation in
toClaimsString
.Great job on these thorough and useful helper methods!
handler/openid/strategy_jwt.go (4)
154-164
: LGTM: Enhanced claim validation with minor suggestionThe updates to the authentication time and requested time claim checks are good improvements. They add robustness by ensuring these claims are not nil or zero, and use the new
GetAuthTimeSafe()
andGetRequestedAtSafe()
methods for comparisons.Consider standardizing the error messages for consistency. For example:
- return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because authentication time claim is required when max_age is set.")) + return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: authentication time claim is required when max_age is set.")) - return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because requested at claim is required when max_age is set.")) + return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: requested at claim is required when max_age is set."))
171-185
: LGTM: Improved prompt parameter handling with minor suggestionThe updates to the
prompt
parameter checks and related time comparisons are good improvements. They add robustness by ensuring theauth_time
claim is not nil or zero, and use the newGetAuthTimeSafe()
andGetRequestedAtSafe()
methods for comparisons.Consider improving the formatting of the error messages for better readability:
- WithDebugf("Failed to generate id token because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe())) + WithDebugf("Failed to generate ID token: prompt was set to 'none' but auth_time (%s) is after the authorization request (%s), indicating user login during this request which is not allowed.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe())) - WithDebugf("Failed to generate id token because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe())) + WithDebugf("Failed to generate ID token: prompt was set to 'login' but auth_time (%s) is before the authorization request (%s), indicating the user was not re-authenticated which is forbidden.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe()))
196-211
: LGTM: Enhanced id_token_hint handling with suggestionThe updates to the
id_token_hint
parameter handling are good improvements. They add better decoding and validation of the hint token, including proper handling of expired tokens and subject claim comparison.Consider simplifying the error handling for the subject extraction:
- if subHint, err = tokenHint.Claims.GetSubject(); subHint == "" || err != nil { - return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject.")) - } else if subHint != claims.Subject { - return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'.")) - } + subHint, err := tokenHint.Claims.GetSubject() + if err != nil { + return "", errorsx.WithStack(oauth2.ErrServerError.WithWrap(err).WithDebug("Failed to extract subject from 'id_token_hint'.")) + } + if subHint == "" { + return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject.")) + } + if subHint != claims.Subject { + return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'.")) + }This change separates the error handling for extraction failure and empty subject, providing more specific error messages.
Line range hint
217-245
: LGTM: Improved time-related claim handling and token encodingThe updates to the handling of
ExpirationTime
,AuthTime
, andIssuedAt
claims, as well as the new token encoding process, are good improvements. They add important validations and align with the new strategy-based approach.Consider standardizing the error message for consistency with earlier suggestions:
- return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because expiry claim can not be in the past.")) + return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: expiry claim cannot be in the past."))token/jwt/claims_id_token.go (5)
38-60
: LGTM: Consistent claim retrieval methods addedThe new methods for claim retrieval provide a consistent interface and allow for future extensibility by returning both the claim value and an error. This is a good practice.
However, there's a minor inconsistency in the
GetNotBefore
method:func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { return toNumericDate(ClaimNotBefore) }This method uses a
toNumericDate
function, which is different from the direct field access used in other methods. Consider aligning this with the other methods ifNotBefore
is intended to be a field ofIDTokenClaims
.
62-139
: LGTM: Comprehensive claim validation with room for improvementThe
Valid
method provides thorough validation of all standard JWT claims, which is excellent. The use of aValidationError
struct to accumulate errors is a good practice, allowing for multiple validation issues to be reported at once.However, the method is quite long and could benefit from some refactoring to improve readability and maintainability. Consider breaking it down into smaller, focused functions for each claim type. For example:
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error { vErr := new(ValidationError) now := getNow(vopts) c.validateExpiration(vErr, now, vopts) c.validateIssuedAt(vErr, now, vopts) c.validateNotBefore(vErr, now, vopts) c.validateIssuer(vErr, vopts) c.validateSubject(vErr, vopts) c.validateAudience(vErr, vopts) if vErr.valid() { return nil } return vErr } func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) { // Expiration validation logic } // Similar methods for other claim validationsThis refactoring would make the
Valid
method more modular and easier to maintain.
141-171
: LGTM: Safe getter methods for time-related claimsThe new "Safe" getter methods (
GetExpirationTimeSafe
,GetIssuedAtSafe
,GetAuthTimeSafe
, andGetRequestedAtSafe
) are a great addition. They provide a safe way to access time-related claims without the risk of nil pointer dereferences, defaulting to the Unix epoch when the field is nil. The consistent use of UTC is also commendable.For improved consistency, consider extracting the default time creation to a constant or helper function:
var defaultTime = time.Unix(0, 0).UTC() func (c *IDTokenClaims) GetExpirationTimeSafe() time.Time { if c.ExpirationTime == nil { return defaultTime } return c.ExpirationTime.UTC() } // Apply similar changes to other Safe getter methodsThis small change would make the code more DRY and easier to maintain if the default time needs to be changed in the future.
350-353
: LGTM: New ToMapClaims method addedThe addition of the
ToMapClaims
method is a good way to provide aMapClaims
representation of theIDTokenClaims
. It's currently a simple wrapper aroundToMap
, which is fine ifMapClaims
is just an alias formap[string]any
.However, if
MapClaims
is a different type, consider updating the method to perform the necessary type conversion:func (c IDTokenClaims) ToMapClaims() MapClaims { return MapClaims(c.ToMap()) }This ensures that the method will work correctly even if
MapClaims
is not just an alias but a distinct type.
369-379
: LGTM: New toNumericDate method for handling additional claimsThe
toNumericDate
method is a good addition for converting additional claims toNumericDate
types. It correctly handles the case where the claim doesn't exist in theExtra
map.Consider adding some error handling for the case where the conversion fails:
func (c IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) { v, ok := c.Extra[key] if !ok { return nil, nil } date, err = toNumericDate(v) if err != nil { return nil, fmt.Errorf("failed to convert claim %s to NumericDate: %w", key, err) } return date, nil }This change would provide more context if the conversion fails, making debugging easier.
token/jwt/token_test.go (2)
51-51
: LGTM! Consider adding a comment for clarity.The changes to use
CompactSignedString
,jwt.ParseSigned
, andJSONWebTokenHeaderType
are consistent with the updated JWT management system. These modifications improve standardization and potentially leverage more robust parsing from the go-jose library.Consider adding a brief comment explaining the reason for using
CompactSignedString
instead ofSignedString
, to provide context for future developers.Also applies to: 57-57, 60-60
323-323
: LGTM! Consider applying this change consistently.The replacement of
iat
withClaimIssuedAt
improves code readability and consistency. This change aligns with best practices for using descriptive names in code.Consider reviewing the entire codebase to ensure all instances of
iat
and other abbreviated claim names are replaced with their full, descriptive counterparts (e.g.,exp
toClaimExpiresAt
,nbf
toClaimNotBefore
, etc.) for consistency.Also applies to: 328-328
token/jwt/token.go (2)
130-147
: Add comments to new fields inToken
structThe
Token
struct has been expanded to include fields for both JWS and JWE, which is good. However, to improve code readability and maintainability, consider adding comments to explain the purpose of each new field, especially for those that might not be immediately clear to other developers.For example:
type Token struct { KeyID string // Identifier for the key used to sign the token SignatureAlgorithm jose.SignatureAlgorithm // Algorithm used for signing (JWS) EncryptionKeyID string // Identifier for the key used to encrypt the token KeyAlgorithm jose.KeyAlgorithm // Algorithm used for key encryption (JWE) ContentEncryption jose.ContentEncryption // Algorithm used for content encryption (JWE) CompressionAlgorithm jose.CompressionAlgorithm // Algorithm used for compression (JWE) // ... (rest of the fields) }Adding these comments will make the purpose of each field clearer and improve the overall documentation of the struct.
Line range hint
537-626
: Improve error handling and type safety in helper functionsThe helper functions are generally well-implemented, but there are a few areas where they could be improved:
In
newToken()
, consider returning an error instead of aValidationError
when the number of headers is invalid. This would make the error handling more consistent with Go conventions.The
pointer()
function uses reflection, which can be slow and unsafe. Consider using generics (if using Go 1.18+) or creating type-specific functions for common types.In
validateTokenTypeValue()
, the type assertion could be combined with the initial check:func validateTokenTypeValue(values []string, raw any) bool { typ, ok := raw.(string) if !ok { return false } for _, t := range values { if t == typ { return true } } return false }
- The
isUnsafeNoneMagicConstant()
function could be simplified:func isUnsafeNoneMagicConstant(k any) bool { switch key := k.(type) { case unsafeNoneMagicConstant: return true case jose.JSONWebKey, *jose.JSONWebKey: _, ok := key.Key.(unsafeNoneMagicConstant) return ok } return false }These changes would improve the performance, readability, and maintainability of these helper functions.
🛑 Comments failed to post (22)
token/jwt/claims_jarm_test.go (1)
28-34: 💡 Codebase verification
Inconsistent usage of claim constants found.
The
consts
package is still being utilized in multiple locations across the codebase. This inconsistency contradicts the recent changes and reintroduces external dependencies that were previously removed.
- Numerous files reference
consts.ClaimXYZ
, indicating that the refactor is incomplete.Please ensure all instances of
consts.ClaimXYZ
are replaced with the direct claim constants to maintain consistency and improve code maintainability.🔗 Analysis chain
LGTM! Verify consistent usage of claim constants.
The changes improve code clarity by using direct claim constants and aligning with the standardized
ClaimExpirationTime
. This reduces external dependencies and enhances maintainability.To ensure consistency across the codebase, please run the following script:
This script will help identify any inconsistencies in claim constant usage and ensure that the
consts
package has been fully removed from JWT-related code.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify consistent usage of claim constants and absence of 'consts' package # Test 1: Check for any remaining usage of 'consts' package echo "Checking for remaining 'consts' package usage:" rg --type go 'consts\.Claim' # Test 2: Verify consistent usage of claim constants echo "Verifying consistent usage of claim constants:" rg --type go 'Claim(Issuer|Audience|JWTID|IssuedAt|ExpirationTime)'Length of output: 68439
token/jwt/date.go (2)
129-160: 🛠️ Refactor suggestion
Consider removing unnecessary case in UnmarshalJSON.
The
UnmarshalJSON
method forClaimStrings
is well-implemented and handles various input types flexibly. However, the case for[]string
might be unnecessary, as JSON unmarshaling typically produces[]interface{}
for arrays.Consider removing the unnecessary case:
switch v := value.(type) { case string: aud = append(aud, v) -case []string: - aud = ClaimStrings(v) case []interface{}: for _, vv := range v { vs, ok := vv.(string) if !ok { return ErrInvalidType } aud = append(aud, vs) } case nil: return nil default: return ErrInvalidType }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { var value interface{} if err = json.Unmarshal(data, &value); err != nil { return err } var aud []string switch v := value.(type) { case string: aud = append(aud, v) case []interface{}: for _, vv := range v { vs, ok := vv.(string) if !ok { return ErrInvalidType } aud = append(aud, vs) } case nil: return nil default: return ErrInvalidType } *s = aud return }
31-46: 🛠️ Refactor suggestion
Consider simplifying the MarshalJSON method.
The current implementation is correct, but it can be simplified by using string concatenation instead of byte slice operations.
Here's a suggested improvement:
func (date NumericDate) MarshalJSON() (b []byte, err error) { var prec int if TimePrecision < time.Second { prec = int(math.Log10(float64(time.Second) / float64(TimePrecision))) } truncatedDate := date.UTC().Truncate(TimePrecision) seconds := strconv.FormatInt(truncatedDate.Unix(), 10) nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64) - output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...) - return output, nil + output := seconds + nanosecondsOffset[1:] + return []byte(output), nil }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.func (date NumericDate) MarshalJSON() (b []byte, err error) { var prec int if TimePrecision < time.Second { prec = int(math.Log10(float64(time.Second) / float64(TimePrecision))) } truncatedDate := date.UTC().Truncate(TimePrecision) seconds := strconv.FormatInt(truncatedDate.Unix(), 10) nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64) output := seconds + nanosecondsOffset[1:] return []byte(output), nil }
token/jwt/claims_jarm.go (4)
33-55:
⚠️ Potential issueLGTM! But there's a potential issue in
GetSubject
.The new getter methods provide a consistent interface for accessing claims, which is great for usability and maintainability.
However, there's a potential issue in the
GetSubject
method:func (c *JARMClaims) GetSubject() (sub string, err error) { return c.toString(ClaimIssuer) }It seems to be using
ClaimIssuer
instead ofClaimSubject
. This should be corrected to:func (c *JARMClaims) GetSubject() (sub string, err error) { return c.toString(ClaimSubject) }
57-58:
⚠️ Potential issueImplement validation logic in the
Valid
method.The current implementation of the
Valid
method always returns nil, which means it's not performing any actual validation. This could lead to invalid claims being accepted.Consider implementing actual validation logic. If this is intended to be a placeholder for future implementation, add a TODO comment to make it clear:
func (c *JARMClaims) Valid(opts ...ClaimValidationOption) (err error) { // TODO: Implement validation logic return nil }Alternatively, implement basic validation checks, such as verifying that required fields are not empty and that the token is not expired.
120-123:
⚠️ Potential issueConsider handling errors from
toNumericDate
.The update to use
toNumericDate
forIssuedAt
andExpirationTime
is correct given the change to pointer types. However, the error return values fromtoNumericDate
are currently being discarded.Consider handling these errors to prevent silent failures. Here's a suggested improvement:
var err error if c.IssuedAt, err = toNumericDate(v); err != nil { // Handle or log the error } if c.ExpirationTime, err = toNumericDate(v); err != nil { // Handle or log the error }This way, any issues during the conversion process can be properly addressed or logged.
25-31: 💡 Codebase verification
Potential Nil Dereference Issues with
IssuedAt
andExpirationTime
The recent changes to
IssuedAt
andExpirationTime
fields as pointer types (*NumericDate
) introduce potential risks. Multiple instances have been found where these fields are dereferenced without priornil
checks, which can lead to runtime panics if the fields arenil
.Affected Locations:
token/jwt/claims_id_token.go: return c.IssuedAt.UTC()
token/jwt/claims_id_token.go: ret[consts.ClaimIssuedAt] = c.IssuedAt.Unix()
handler/rfc7523/handler.go: issuedDate = claims.IssuedAt.Time()
handler/rfc7523/handler_test.go
(multiple lines)token/jwt/claims_jwt.go: if c.IssuedAt.IsZero()
token/jwt/claims_jwt.go: ret[ClaimIssuedAt] = c.IssuedAt.Unix()
handler/openid/strategy_jwt.go: if claims.ExpirationTime.Before(time.Now().UTC())
Recommendation:
- Add
nil
checks before dereferencingIssuedAt
andExpirationTime
in all affected locations to ensure safe access.- Review and update tests to handle
nil
scenarios appropriately.🔗 Analysis chain
LGTM! Consider verifying usage across the codebase.
The change of
IssuedAt
andExpirationTime
to pointer types (*NumericDate
) is a good improvement. It allows for more explicit handling of optional values. However, ensure that this change is reflected consistently across the codebase where these fields are used.To verify the impact of this change, run the following script:
Further Verification Required: Ensure all usages handle pointer types appropriately.
To confirm that the changes to
IssuedAt
andExpirationTime
as pointer types (*NumericDate
) are correctly handled throughout the codebase, please run the following scripts:🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for usages of IssuedAt and ExpirationTime fields # Search for direct accesses to these fields echo "Checking direct field accesses:" rg --type go -n "\.IssuedAt|\.ExpirationTime" --glob '!token/jwt/claims_jarm.go' # Search for method calls that might be affected echo "Checking potentially affected method calls:" rg --type go -n "GetExpirationTime|GetIssuedAt" --glob '!token/jwt/claims_jarm.go'Length of output: 7114
Script:
#!/bin/bash # Description: Check for dereferences of IssuedAt and ExpirationTime without nil checks # Search for dereferences without preceding nil checks echo "Checking for dereferences without nil checks:" rg --type go '\.IssuedAt\.' --glob '!token/jwt/claims_jarm.go' | rg -v 'if.*\.IssuedAt.*== nil' rg --type go '\.ExpirationTime\.' --glob '!token/jwt/claims_jarm.go' | rg -v 'if.*\.ExpirationTime.*== nil'Length of output: 1669
Script:
#!/bin/bash # Description: Check for assignments to IssuedAt and ExpirationTime ensuring they are initialized as pointers echo "Checking for assignments to IssuedAt:" rg --type go 'claims\.IssuedAt\s*=' --glob '!token/jwt/claims_jarm.go' echo "Checking for assignments to ExpirationTime:" rg --type go 'claims\.ExpirationTime\s*=' --glob '!token/jwt/claims_jarm.go'Length of output: 1139
token/jwt/claims_id_token.go (1)
173-263:
⚠️ Potential issueLGTM: Comprehensive JSON unmarshaling with room for improvement
The
UnmarshalJSON
method provides a thorough implementation for decoding JSON data into theIDTokenClaims
struct. It handles various claim types well and correctly populates theExtra
field for additional claims.Consider refactoring this method to improve readability and maintainability:
- Extract the claim handling logic into separate methods for each claim type.
- Use a map of claim handlers to reduce the size of the switch statement.
Example refactoring:
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { claims := MapClaims{} if err := jjson.NewDecoder(bytes.NewReader(data)).Decode(&claims); err != nil { return errorsx.WithStack(err) } handlers := map[string]func(any) error{ ClaimJWTID: c.handleStringClaim(&c.JTI), ClaimIssuer: c.handleStringClaim(&c.Issuer), ClaimSubject: c.handleStringClaim(&c.Subject), ClaimAudience: c.handleAudienceClaim, ClaimNonce: c.handleStringClaim(&c.Nonce), ClaimExpirationTime: c.handleNumericDateClaim(&c.ExpirationTime), // ... other claims ... } for claim, value := range claims { if handler, ok := handlers[claim]; ok { if err := handler(value); err != nil { return fmt.Errorf("claim %s could not be decoded", claim) } } else { c.handleExtraClaim(claim, value) } } return nil } func (c *IDTokenClaims) handleStringClaim(target *string) func(any) error { return func(value any) error { if s, ok := value.(string); ok { *target = s return nil } return fmt.Errorf("expected string value") } } // Implement other handler methods...Additionally, there's a potential security issue in the error message on line 258. It includes the claim value, which could expose sensitive information. Apply this diff to address the issue:
- return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) + return fmt.Errorf("claim %s could not be decoded", claim)These changes will improve the method's readability, maintainability, and security.
token/jwt/token_test.go (1)
420-420: 💡 Codebase verification
Incomplete Replacement of
Valid()
withIsSignatureValid()
Multiple instances of
.Valid()
are still present in the codebase. Please ensure all necessary instances have been updated toIsSignatureValid()
to maintain consistency and improve token validation reliability.🔗 Analysis chain
LGTM! Please clarify the implications of these changes.
The updates to use
token.Claims.ToMapClaims()
andtoken.IsSignatureValid()
appear to be part of a broader refactoring of the JWT handling system. These changes likely improve type safety and provide more specific validity checks.Could you please clarify:
- The implications of using
ToMapClaims()
instead of directly accessingClaims
?- The difference between
IsSignatureValid()
and the previousValid()
method?To help verify the extent of these changes, please run the following script:
Also applies to: 429-429
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for other occurrences of ToMapClaims and IsSignatureValid echo "Occurrences of ToMapClaims:" rg --type go "ToMapClaims\(\)" echo "Occurrences of IsSignatureValid:" rg --type go "IsSignatureValid\(\)" echo "Occurrences of Valid (possible candidates for replacement):" rg --type go "\.Valid\(\)"Length of output: 1734
Script:
#!/bin/bash # Description: Re-run the search for ToMapClaims, IsSignatureValid, and Valid in Go files echo "Occurrences of ToMapClaims():" rg --type go "ToMapClaims\(\)" echo "Occurrences of IsSignatureValid():" rg --type go "IsSignatureValid\(\)" echo "Occurrences of Valid():" rg --type go "\.Valid\(\)"Length of output: 4576
token/jwt/token.go (7)
192-244: 🛠️ Refactor suggestion
Consider adding error handling to
AssignJWE()
The
SetJWS()
,SetJWE()
, andAssignJWE()
methods look good overall. However,AssignJWE()
could benefit from some additional error handling or validation. For instance:
- Check if the required fields in the
jwe.Header
are present before accessing them.- Return an error if any of the type assertions fail.
Here's a suggestion for improving
AssignJWE()
:func (t *Token) AssignJWE(jwe *jose.JSONWebEncryption) error { if jwe == nil { return errors.New("jwe is nil") } t.HeaderJWE = map[string]any{ JSONWebTokenHeaderAlgorithm: jwe.Header.Algorithm, } if jwe.Header.KeyID != "" { t.HeaderJWE[JSONWebTokenHeaderKeyIdentifier] = jwe.Header.KeyID t.EncryptionKeyID = jwe.Header.KeyID } for header, value := range jwe.Header.ExtraHeaders { h := string(header) t.HeaderJWE[h] = value switch h { case JSONWebTokenHeaderEncryptionAlgorithm: if v, ok := value.(string); ok { t.ContentEncryption = jose.ContentEncryption(v) } else { return fmt.Errorf("invalid type for %s", h) } case JSONWebTokenHeaderCompressionAlgorithm: if v, ok := value.(string); ok { t.CompressionAlgorithm = jose.CompressionAlgorithm(v) } else { return fmt.Errorf("invalid type for %s", h) } } } t.KeyAlgorithm = jose.KeyAlgorithm(jwe.Header.Algorithm) return nil }This version adds error handling and returns an error if any issues are encountered during the assignment process.
247-309:
⚠️ Potential issueSecurity consideration in
CompactEncrypted()
The
CompactEncrypted()
andCompactSigned()
methods are well-implemented overall. However, there's a potential security consideration inCompactEncrypted()
:On line 272, the method is using the
typ
header from the JWS token as thecty
(Content Type) header for the JWE token. This could potentially lead to information leakage or other security issues if the JWStyp
header contains sensitive or unexpected values.Consider either:
- Using a fixed value for the
cty
header instead of copying it from the JWStyp
.- Implementing a whitelist of allowed values for the
cty
header.For example:
if _, ok := opts.ExtraHeaders[JSONWebTokenHeaderContentType]; !ok { opts.ExtraHeaders[JSONWebTokenHeaderContentType] = JSONWebTokenTypeJWT }This ensures that the
cty
header is always set to a known, safe value.
345-435: 🛠️ Refactor suggestion
Simplify and improve maintainability of
Valid()
methodThe
Valid()
method is comprehensive in its checks, which is good. However, it could be simplified and made more maintainable:
Consider breaking down the validation logic into smaller, focused functions. This would improve readability and make the code easier to maintain.
Use a slice of validation functions instead of a long series of if statements. This would make it easier to add or remove validation checks in the future.
Consolidate error handling to reduce repetition.
Here's a suggestion for refactoring:
func (t *Token) Valid(opts ...HeaderValidationOption) error { vopts := &HeaderValidationOptions{ types: []string{JSONWebTokenTypeJWT}, } for _, opt := range opts { opt(vopts) } validations := []func() error{ t.validateSignature, t.validateEncryptionHeaders(vopts), t.validateType(vopts), t.validateAlgorithm(vopts), t.validateKeyID(vopts), t.validateKeyAlgorithm(vopts), t.validateContentEncryption(vopts), t.validateEncryptionKeyID(vopts), } var vErr ValidationError for _, validation := range validations { if err := validation(); err != nil { vErr.Errors |= err.(*ValidationError).Errors vErr.Inner = err } } if vErr.valid() { return nil } return &vErr }This approach makes the
Valid()
method more modular and easier to extend in the future.
438-467: 🛠️ Refactor suggestion
Simplify and improve robustness of
IsJWTProfileAccessToken()
The
IsJWTProfileAccessToken()
method is functional, but it can be simplified and made more robust:
- Combine the checks for
JSONWebTokenTypeAccessToken
andJSONWebTokenTypeAccessTokenAlternative
to reduce repetition.- Use a helper function to check for valid token types.
- Simplify the logic flow to reduce nesting.
Here's a suggested refactoring:
func (t *Token) IsJWTProfileAccessToken() bool { isValidTokenType := func(tokenType string) bool { return tokenType == JSONWebTokenTypeAccessToken || tokenType == JSONWebTokenTypeAccessTokenAlternative } if t.HeaderJWE != nil { if cty, ok := t.HeaderJWE[JSONWebTokenHeaderContentType].(string); ok && isValidTokenType(cty) { return true } } if typ, ok := t.Header[JSONWebTokenHeaderType].(string); ok && isValidTokenType(typ) { return true } return false }This refactored version is more concise, easier to read, and less prone to errors. It also handles type assertions more safely.
468-513: 🛠️ Refactor suggestion
Enhance type safety with constants for predefined values
The
HeaderValidationOption
andHeaderValidationOptions
types, along with their associated functions, provide a flexible way to configure header validation. To further improve type safety and reduce the likelihood of errors, consider using constants for predefined values.For example:
const ( DefaultJWTType = "JWT" // Add other common types here ) func ValidateTypes(types ...string) HeaderValidationOption { return func(validator *HeaderValidationOptions) { if len(types) == 0 { validator.types = []string{DefaultJWTType} } else { validator.types = types } } }This approach has several benefits:
- It prevents typos in string literals.
- It makes it easier to change values across the codebase if needed.
- It provides better autocompletion in IDEs.
Consider applying this pattern to other validation options where appropriate.
515-535:
⚠️ Potential issueSecurity consideration in
unsignedToken()
The
unsignedToken()
function correctly creates an unsigned token. However, there's an important security consideration to keep in mind:Unsigned tokens (with
alg: none
) are inherently insecure and should be used with extreme caution, if at all. They can lead to serious vulnerabilities if not handled properly in the verification process.Consider adding a prominent comment warning about the security implications:
// unsignedToken creates an unsigned token (alg: none). // WARNING: Unsigned tokens are inherently insecure and should only be used // in controlled environments where the risks are well understood and mitigated. // Never accept unsigned tokens from untrusted sources or use them for sensitive operations. func unsignedToken(token *Token) (tokenString string, err error) { // ... (existing implementation) }Also, ensure that your token verification logic explicitly checks for and rejects unsigned tokens in production environments unless there's a very specific, controlled use case for them.
38-128: 🛠️ Refactor suggestion
Consider improving error handling in
ParseCustomWithClaims()
The implementation of the parsing functions looks good overall. However, in
ParseCustomWithClaims()
, there's an opportunity to improve error handling:
- When creating a new token after a parsing error (line 60), consider returning a nil token instead of an empty one.
- In the claims validation section (lines 117-123), the error wrapping could be simplified.
Here's a suggested improvement for the claims validation error handling:
if err = claims.Valid(); err != nil { - if e, ok := err.(*ValidationError); !ok { - err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid} + if _, ok := err.(*ValidationError); !ok { + err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} } return token, err }This change ensures that the original error is always preserved in the
Inner
field ofValidationError
.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.func ParseCustomWithClaims(tokenString string, claims MapClaims, keyFunc Keyfunc, algs ...jose.SignatureAlgorithm) (token *Token, err error) { var parsed *jwt.JSONWebToken if parsed, err = jwt.ParseSigned(tokenString, algs); err != nil { return &Token{Claims: MapClaims(nil)}, &ValidationError{Errors: ValidationErrorMalformed, Inner: err} } // fill unverified claims // This conversion is required because go-jose supports // only marshalling structs or maps but not alias types from maps // // The KeyFunc(*Token) function requires the claims to be set into the // Token, that is an unverified token, therefore an UnsafeClaimsWithoutVerification is done first // then with the returned key, the claims gets verified. if err = parsed.UnsafeClaimsWithoutVerification(&claims); err != nil { return &Token{Claims: MapClaims(nil)}, &ValidationError{Errors: ValidationErrorClaimsInvalid, Inner: err} } // creates an unsafe token if token, err = newToken(parsed, claims); err != nil { return &Token{Claims: MapClaims(nil)}, err } if keyFunc == nil { return token, &ValidationError{Errors: ValidationErrorUnverifiable, text: "no Keyfunc was provided."} } var key any if key, err = keyFunc(token); err != nil { // keyFunc returned an error var ve *ValidationError if errors.As(err, &ve) { return token, ve } return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err} } if key == nil { return token, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: "keyfunc returned a nil verification key"} } // To verify signature go-jose requires a pointer to // public key instead of the public key value. // The pointer values provides that pointer. // E.g. transform rsa.PublicKey -> *rsa.PublicKey key = pointer(key) // verify signature with returned key _, validNoneKey := key.(*unsafeNoneMagicConstant) isSignedToken := !(token.SignatureAlgorithm == SigningMethodNone && validNoneKey) if isSignedToken { if err = parsed.Claims(key, &claims); err != nil { return token, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: err.Error()} } } // Validate claims // This validation is performed to be backwards compatible // with jwt-go library behavior if err = claims.Valid(); err != nil { if _, ok := err.(*ValidationError); !ok { err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} } return token, err } token.valid = true return token, nil }
token/jwt/validate.go (3)
136-143: 🛠️ Refactor suggestion
Assess the necessity of constant-time string comparison in
validString
Using
subtle.ConstantTimeCompare
for string comparison may not be necessary unless the comparison is sensitive to timing attacks. If not required, a standard string comparison can improve performance.Apply this diff if constant-time comparison is unnecessary:
func validString(value, cmp string, required bool) bool { if value == "" { return !required } - return subtle.ConstantTimeCompare([]byte(value), []byte(cmp)) == 1 + return value == cmp }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.func validString(value, cmp string, required bool) bool { if value == "" { return !required } return value == cmp }
146-164: 🛠️ Refactor suggestion
Refactor
validDate
to streamline error handlingPassing an error as a parameter to
validDate
is unconventional. Consider handling the error before invokingvalidDate
and simplify the function signature accordingly.Apply this diff to refactor the function:
-func validDate(valid validDateFunc, now int64, required bool, date *NumericDate, err error) bool { - if err != nil || valid == nil { +func validDate(valid validDateFunc, now int64, required bool, date *NumericDate) bool { + if valid == nil { return false } if date == nil { if required { return false } return true } if valid(date.Int64(), now, required) { return true } return false }Handle any errors related to
date
before callingvalidDate
to maintain clear separation of concerns.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.func validDate(valid validDateFunc, now int64, required bool, date *NumericDate) bool { if valid == nil { return false } if date == nil { if required { return false } return true } if valid(date.Int64(), now, required) { return true } return false }
83-97: 🛠️ Refactor suggestion
Optimize
verifyAudAny
by reducing time complexityThe nested loops in
verifyAudAny
can be optimized to improve performance, especially with large audiences. Consider using a map to reduce the time complexity from O(n*m) to O(n+m).Apply this diff to optimize the function:
func verifyAudAny(aud []string, cmp []string, required bool) bool { if len(aud) == 0 { return !required } - for _, c := range cmp { - for _, a := range aud { - if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 { - return true - } - } - } + audMap := make(map[string]struct{}, len(aud)) + for _, a := range aud { + audMap[a] = struct{}{} + } + for _, c := range cmp { + if _, exists := audMap[c]; exists { + // Note: If constant-time comparison is required, retain it here. + return true + } + } return false }Please ensure that using a map does not compromise any security requirements related to constant-time comparisons.
Committable suggestion was skipped due to low confidence.
handler/rfc8693/refresh_token_type_handler.go (1)
152-152: 🛠️ Refactor suggestion
Consider refactoring token expiration logic into a helper function
The expression
time.Now().UTC().Add(c.RefreshTokenLifespan).Truncate(jwt.TimePrecision)
is used to set the token expiration time. If this pattern is used in multiple places across the codebase, extracting it into a helper function can improve maintainability and ensure consistent handling of token expiration times.handler/openid/flow_hybrid_test.go (2)
192-194:
⚠️ Potential issueAvoid using 'jwt.UnsafeParseSignedAny' without signature verification
Using
jwt.UnsafeParseSignedAny
skips signature verification of the JWT token, which can lead to security vulnerabilities by accepting tampered tokens. It's recommended to use a method that parses and verifies the token's signature to ensure its integrity.Consider replacing with a secure parsing method that verifies the signature:
- _, err := jwt.UnsafeParseSignedAny(idToken, claims) + token, err := jwt.ParseSigned(idToken) + if err != nil { + require.NoError(t, err) + } + err = token.Claims(publicKey, claims) + require.NoError(t, err)Make sure to provide the correct
publicKey
for signature verification.Also applies to: 337-339
432-433:
⚠️ Potential issueUndefined variable 'key' in 'jwt.NewDefaultIssuerRS256Unverified'
The variable
key
used injwt.NewDefaultIssuerRS256Unverified(key)
is not defined within the scope of themakeOpenIDConnectHybridHandler
function. This will result in a compilation error.Apply this diff to define the
key
variable:+ key := []byte("your-key-here") // Replace with the appropriate key material jwtStrategy := &jwt.DefaultStrategy{ Config: config, Issuer: jwt.NewDefaultIssuerRS256Unverified(key), }
Ensure that
key
is properly initialized with the correct key material required for token signing.Committable suggestion was skipped due to low confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 24
🧹 Outside diff range and nitpick comments (17)
token/jwt/claims_jarm_test.go (1)
16-20
: LGTM with a minor suggestion.The changes look good and align with standard JWT claim names. The use of
Now()
andNewNumericDate()
likely ensures consistent time handling across the codebase.Consider extracting the duration
time.Hour
into a named constant for better readability and easier maintenance, especially if this duration is used in multiple places.+const defaultTokenExpiration = time.Hour var jarmClaims = &JARMClaims{ Issuer: "authelia", Audience: []string{"tests"}, JTI: "abcdef", IssuedAt: Now(), - ExpirationTime: NewNumericDate(time.Now().Add(time.Hour)), + ExpirationTime: NewNumericDate(time.Now().Add(defaultTokenExpiration)), // ... rest of the struct }handler/openid/validator_test.go (1)
38-40
: Simplified ID token generationThe changes to the ID token generation function are appropriate and align well with the new strategy-based approach. The use of
c.ToMapClaims()
provides a more standardized way of handling claims.Consider adding a comment explaining the purpose of this function for better code documentation.
var genIDToken = func(c jwt.IDTokenClaims) string { + // Generate an ID token string from the given claims s, _, err := j.Encode(context.TODO(), c.ToMapClaims()) require.NoError(t, err) return s }
token/jwt/claims_jwt.go (3)
229-239
: LGTM: ImprovedtoTime
function.The new implementation of
toTime
is more robust, handling various input types throughtoInt64
and providing a boolean success indicator. This change improves error handling capabilities.Minor suggestion: Consider using a named return for
ok
to make the function's intent clearer:func toTime(v any, def time.Time) (t time.Time, ok bool) { // ... existing implementation ... }This change would make the function's signature more self-documenting.
241-268
: LGTM: Well-implementedtoInt64
function.The
toInt64
function is robust, handling various numeric types andjson.Number
. The error handling is thorough, and the use of a boolean return value is consistent withtoTime
.Minor optimization suggestion: Consider using a type switch for cleaner code and potentially better performance:
func toInt64(v any) (val int64, ok bool) { switch t := v.(type) { case int64: return t, true case int32, int: return int64(t), true case float64: return int64(t), true case json.Number: if val, err := t.Int64(); err == nil { return val, true } if valf, err := t.Float64(); err == nil { return int64(valf), true } } return 0, false }This approach reduces the number of type assertions and may be more efficient.
270-304
: LGTM: Well-implementedtoNumericDate
function.The
toNumericDate
function is robust, handling various numeric types andjson.Number
. It consistently handles nil and zero values across different types, which is good for preventing unexpected behavior.Suggestion for consistency: Consider refactoring the repeated code for handling zero values and creating
NumericDate
instances:func toNumericDate(v any) (*NumericDate, error) { var value float64 var ok bool switch val := v.(type) { case nil: return nil, nil case float64: value, ok = val, true case int64, int32, int: value, ok = float64(val), true case json.Number: value, _ = val.Float64() ok = true default: return nil, newError("value has invalid type", ErrInvalidType) } if !ok || value == 0 { return nil, nil } return newNumericDateFromSeconds(value), nil }This refactoring reduces code duplication and makes the function easier to maintain.
README.md (4)
Line range hint
25-106
: Comprehensive improvements and new feature supportThe README outlines numerous improvements and new features:
- Implementation of internal JWKS logic and JWE support for client authentication and issuance.
- Various fixes across different OAuth 2.0 flows (Refresh, PKCE, OpenID).
- Support for new RFCs:
- RFC8628: OAuth 2.0 Device Authorization Grant
- RFC8693: OAuth 2.0 Token Exchange
- RFC9101: OAuth 2.0 JWT-Secured Authorization Requests
These enhancements significantly improve the library's functionality and standards compliance.
Consider updating or creating separate documentation for each major feature or RFC implementation. This would help users understand how to leverage these new capabilities effectively.
Would you like assistance in creating a documentation structure for these new features?
Line range hint
134-149
: Appropriate acknowledgment and licensing clarificationThe "Thanks" section properly acknowledges the original ORY Fosite project and clarifies the relationship between this fork and the original project. This transparency is commendable and aligns with open-source best practices.
Consider adding a link to the original ORY Fosite repository (https://github.com/ory/fosite) in the first paragraph of the "Thanks" section. This would make it easier for readers to reference the original project.
-This is a hard fork of [ORY Fosite](https://github.com/ory/fosite) under the +This is a hard fork of [ORY Fosite](https://github.com/ory/fosite) (original repository [here](https://github.com/ory/fosite)) under the
107-107
: Minor style correction neededIn American English, the abbreviation "etc" should be followed by a period. Please update the line as follows:
-- [x] Testing Package (mocks, etc) ++ [x] Testing Package (mocks, etc.)This change will improve the document's consistency with standard American English style.
🧰 Tools
🪛 LanguageTool
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
Remaining References to Deprecated Dependencies
The following dependencies listed as removed in the README are still referenced in the codebase:
go.opentelemetry.io/otel/trace
intoken/jwt/token.go
Please update or remove these references to align with the documented changes.
🔗 Analysis chain
Line range hint
1-24
: Important changes to module path and Go version requirementThe README reflects two significant changes that may impact existing implementations:
- The module path has changed from
github.com/ory/fosite
toauthelia.com/provider/oauth2
.- The minimum Go version requirement has been updated to 1.21.
To ensure these changes are consistently applied throughout the codebase, run the following script:
Please review the script output to ensure all necessary updates have been made.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify module path changes and Go version requirement # Check for old module path echo "Checking for old module path references:" rg "github.com/ory/fosite" --type go # Check go.mod for new module path and Go version echo "Verifying go.mod contents:" cat go.mod # Check for Go version declarations in GitHub workflows echo "Checking Go version in GitHub workflows:" rg "go-version" .github/workflows/*.ymlLength of output: 8578
token/jwt/claims_map.go (2)
67-128
: LGTM: Comprehensive audience claim handling with a minor suggestionThe new methods for handling audience claims are well-implemented and provide comprehensive functionality:
- Correct handling of audience claims as either a string or an array of strings
- Flexible verification options with VerifyAudienceAll and VerifyAudienceAny
- Consistent error handling and logic flow
These additions significantly enhance the JWT audience claim verification capabilities.
Consider adding a brief comment explaining the difference between VerifyAudience, VerifyAudienceAll, and VerifyAudienceAny for better clarity to users of this library.
131-200
: LGTM: Well-implemented time-based claim handling with a minor suggestionThe new methods for handling time-based claims (exp, iat, nbf) are well-implemented:
- Correct handling of each time-based claim type
- Appropriate comparison logic in verification methods
- Consistent error handling and logic flow
These additions provide robust functionality for time-based JWT claim verification.
For consistency, consider using a common helper function for the time comparison logic in VerifyExpirationTime, VerifyIssuedAt, and VerifyNotBefore. This could reduce code duplication and make future maintenance easier.
token/jwt/token.go (1)
Line range hint
568-577
: Consider using generics for the pointer functionThe
pointer
function uses reflection to create a pointer to a value if it's not already a pointer. While this works, it could be simplified and made more type-safe using generics if you're using Go 1.18 or later.Consider refactoring the
pointer
function to use generics:func pointer[T any](v T) *T { return &v }This version is more type-safe and doesn't require reflection, which could improve performance.
token/jwt/validate.go (2)
118-125
: Clarify the documentation forvalidInt64Future
The comment for
validInt64Future
mentions that it ensures the given value is in the future, but the implementation allows the value to be equal tonow
. If this behavior is intentional, consider updating the comment to accurately reflect the function's behavior.Apply this change to clarify the comment:
-// validInt64Future ensures the given value is in the future. +// validInt64Future ensures the given value is in the future or equal to the current time.
127-134
: Align the documentation forvalidInt64Past
Similarly, the comment for
validInt64Past
should reflect that the value can be in the past or equal tonow
for consistency and clarity.Apply this change to update the comment:
-// validInt64Past ensures the given value is in the past or the current value. +// validInt64Past ensures the given value is in the past or equal to the current time.handler/openid/strategy_jwt.go (3)
154-154
: Consider making the 5-second time buffer configurableThe use of a 5-second buffer when validating
auth_time
accounts for minor time discrepancies. Making this buffer configurable can enhance flexibility for different deployment environments with varying time synchronization tolerances.
196-199
: Properly handle errors duringid_token_hint
decodingWhen decoding the
id_token_hint
, ensure that all potential errors are appropriately handled. Consider logging or differentiating errors to provide more context in failure scenarios.
217-221
: Usejwt.Now()
consistently for time calculationsTo maintain consistency and ensure precise time claims, consider using
jwt.Now()
instead oftime.Now()
ortime.Now().UTC()
when working with JWT time calculations. For example, in lines 218 and 221, replacetime.Now()
andtime.Now().UTC()
withjwt.Now()
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (42)
- README.md (3 hunks)
- handler/oauth2/flow_authorize_code_token.go (2 hunks)
- handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
- handler/oauth2/flow_authorize_implicit.go (2 hunks)
- handler/oauth2/flow_generic_code_token.go (2 hunks)
- handler/oauth2/flow_refresh.go (2 hunks)
- handler/oauth2/flow_refresh_test.go (11 hunks)
- handler/oauth2/flow_resource_owner.go (2 hunks)
- handler/oauth2/flow_resource_owner_test.go (2 hunks)
- handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
- handler/openid/flow_device_authorization_test.go (4 hunks)
- handler/openid/flow_hybrid.go (2 hunks)
- handler/openid/flow_hybrid_test.go (4 hunks)
- handler/openid/flow_refresh_token.go (3 hunks)
- handler/openid/strategy_jwt.go (6 hunks)
- handler/openid/strategy_jwt_test.go (13 hunks)
- handler/openid/validator.go (3 hunks)
- handler/openid/validator_test.go (15 hunks)
- handler/rfc8628/device_authorize_handler.go (2 hunks)
- handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
- handler/rfc8628/user_authorize_handler_test.go (5 hunks)
- handler/rfc8693/access_token_type_handler.go (2 hunks)
- handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
- handler/rfc8693/refresh_token_type_handler.go (2 hunks)
- integration/oidc_explicit_test.go (6 hunks)
- internal/test_helpers.go (2 hunks)
- introspection_response_writer_test.go (1 hunks)
- rfc8628_device_authorize_write_test.go (2 hunks)
- testing/mock/client.go (1 hunks)
- token/jarm/generate.go (2 hunks)
- token/jwt/claims_id_token.go (7 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/claims_jarm.go (2 hunks)
- token/jwt/claims_jarm_test.go (2 hunks)
- token/jwt/claims_jwt.go (5 hunks)
- token/jwt/claims_jwt_test.go (5 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/claims_test.go (1 hunks)
- token/jwt/date.go (1 hunks)
- token/jwt/token.go (3 hunks)
- token/jwt/token_test.go (8 hunks)
- token/jwt/validate.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (28)
- handler/oauth2/flow_authorize_code_token.go
- handler/oauth2/flow_authorize_code_token_test.go
- handler/oauth2/flow_authorize_implicit.go
- handler/oauth2/flow_generic_code_token.go
- handler/oauth2/flow_refresh.go
- handler/oauth2/flow_refresh_test.go
- handler/oauth2/flow_resource_owner.go
- handler/oauth2/flow_resource_owner_test.go
- handler/oauth2/strategy_jwt_profile_test.go
- handler/openid/flow_device_authorization_test.go
- handler/openid/flow_hybrid.go
- handler/openid/flow_refresh_token.go
- handler/openid/strategy_jwt_test.go
- handler/openid/validator.go
- handler/rfc8628/device_authorize_handler.go
- handler/rfc8628/token_endpoint_handler_test.go
- handler/rfc8628/user_authorize_handler_test.go
- handler/rfc8693/access_token_type_handler.go
- handler/rfc8693/custom_jwt_type_handler.go
- handler/rfc8693/refresh_token_type_handler.go
- integration/oidc_explicit_test.go
- internal/test_helpers.go
- introspection_response_writer_test.go
- rfc8628_device_authorize_write_test.go
- testing/mock/client.go
- token/jwt/claims_id_token_test.go
- token/jwt/claims_jwt_test.go
- token/jwt/token_test.go
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...(ETC_PERIOD)
🔇 Additional comments (66)
token/jwt/claims_test.go (3)
Line range hint
1-69
: Overall improvements in JWT claims handling and testing.The changes in this file contribute positively to the Authelia OAuth 2.0 Framework:
- Improved time precision handling in
TestToTime
.- Comprehensive test coverage for the
Filter
function.These modifications align well with the PR objectives of enhancing token handling and improving the testing framework.
30-69
: Well-structured test function forFilter
.The new
TestFilter
function is well-implemented:
- It uses table-driven tests, which is a good practice in Go.
- The test cases cover important scenarios: no filtering, filtering everything, and partial filtering.
- The function is easy to read and maintain.
Let's verify the
Filter
function implementation:#!/bin/bash # Description: Verify the implementation of the Filter function # Test: Search for the Filter function definition rg --type go -A 10 'func\s+Filter'
24-24
: Improved time precision handling.The change from
Round(time.Second)
toTruncate(TimePrecision)
likely provides more precise time handling for JWT claims, which aligns with the overall improvements in token expiration handling mentioned in the PR summary.Let's verify the
TimePrecision
constant:✅ Verification successful
TimePrecision definition verified.
The
TimePrecision
variable is defined astime.Second
intoken/jwt/variables.go
, confirming that the change toTruncate(TimePrecision)
ensures precise time handling for JWT claims.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the definition of TimePrecision constant # Test: Search for the TimePrecision constant definition rg --type go -A 5 'const\s+TimePrecision'Length of output: 1170
Script:
#!/bin/bash # Description: Verify the definition of TimePrecision constant, variable, or type # Search for constant, variable, or type definitions of TimePrecision rg --type go -e 'const\s+TimePrecision' -e 'var\s+TimePrecision' -e 'type\s+TimePrecision'Length of output: 92
Script:
#!/bin/bash # Description: Search for all occurrences of TimePrecision in the codebase rg 'TimePrecision' --type goLength of output: 6001
token/jwt/claims_jarm_test.go (3)
43-43
: LGTM!The change from
consts.ClaimJWTID
toClaimJWTID
is consistent with the removal of theconsts
package dependency. The functionality of the test remains unchanged.
47-48
: LGTM!The changes are consistent with the updates made earlier in the file:
- Using
ExpirationTime
instead ofExpiresAt
.- Utilizing
NewNumericDate()
for time handling.These modifications ensure consistency across the codebase and likely improve the precision of time-related operations in JWT claims.
49-50
: LGTM!The changes in this segment are consistent with the previous updates:
- Using
ExpirationTime
instead ofExpiresAt
.- Utilizing
NewNumericDate()
for time handling.These modifications maintain consistency throughout the test function while preserving its original functionality.
token/jarm/generate.go (2)
22-22
: Improved function signature clarityThe parameter name change from
in
toparameters
enhances readability and self-documentation of the code. This is a good practice that makes the function's purpose clearer.
60-60
: Efficient claims creation withjwt.NewJARMClaims
The refactoring to use
jwt.NewJARMClaims
is a significant improvement. It simplifies the claims creation process and reduces the potential for errors by handling the initialization of standard claims (JTI
,IssuedAt
,ExpiresAt
) internally.token/jwt/date.go (8)
1-11
: LGTM: Package declaration and imports.The package name
jwt
is appropriate for JWT-related functionality, and the imported packages are relevant to the implementation.
13-23
: LGTM:NumericDate
struct and related functions.The implementation of
NumericDate
,Now()
, andNewNumericDate()
functions is correct and consistent with the use ofTimePrecision
.
31-46
: LGTM:MarshalJSON
method forNumericDate
.The implementation correctly formats the date as seconds since Unix epoch with fractional nanoseconds, consistently using
TimePrecision
for truncation. The manual construction of the JSON output is appropriate for this use case.
48-66
: LGTM:UnmarshalJSON
method forNumericDate
.The method correctly parses JSON numbers into
NumericDate
instances, with appropriate error handling.
68-76
: LGTM:Int64
method forNumericDate
.The method correctly returns the time value as seconds since Unix epoch, handling nil pointers and consistently using
TimePrecision
for truncation.
78-127
: LGTM:ClaimStrings
type and validation methods.The implementation of
ClaimStrings
and its validation methods (Valid
,ValidAny
,ValidAll
) is correct and secure. The use ofsubtle.ConstantTimeCompare
prevents timing attacks in claim validation.
162-168
: LGTM:MarshalJSON
method forClaimStrings
.The method correctly handles both single string and array serialization based on the
MarshalSingleStringAsArray
flag.
25-29
:⚠️ Potential issueConsider improving precision in
newNumericDateFromSeconds()
.The current implementation might lose precision when converting the fractional part of seconds to nanoseconds. Consider using
math.Round()
to minimize precision loss.Apply this diff to improve precision:
func newNumericDateFromSeconds(f float64) *NumericDate { round, frac := math.Modf(f) - return NewNumericDate(time.Unix(int64(round), int64(frac*1e9))) + return NewNumericDate(time.Unix(int64(round), int64(math.Round(frac*1e9)))) }Likely invalid or redundant comment.
token/jwt/claims_jarm.go (8)
10-21
: LGTM! Well-implemented constructor function.The
NewJARMClaims
function provides a clean and efficient way to create a newJARMClaims
instance. It correctly initializes all required fields, including generating a unique JTI using UUID, and setting time-based fields using the provided lifespan.
24-31
: LGTM! Improved struct definition with added flexibility.The changes to the
JARMClaims
struct are well-thought-out:
- Using
*NumericDate
forIssuedAt
andExpirationTime
allows for nil checks, improving robustness.- The addition of the
Extra
field provides flexibility for storing non-standard claims.These modifications enhance the struct's versatility and align well with the new constructor function.
62-96
: LGTM! ImprovedToMap
method with better handling of field types.The updates to the
ToMap
method are well-implemented:
- Proper handling of nil values for
IssuedAt
andExpirationTime
fields.- Consistent representation of the audience field, using an empty slice when no audience is set.
- Correct mapping of all fields to their corresponding claim keys.
These changes improve the robustness and consistency of the method.
98-101
: LGTM! Simple and effectiveToMapClaims
method.The
ToMapClaims
method provides a convenient way to convertJARMClaims
toMapClaims
. Its implementation is straightforward and correct, leveraging the existingToMap
method.
130-133
: LGTM! Simple and effectiveFromMapClaims
method.The
FromMapClaims
method provides a convenient way to populateJARMClaims
fromMapClaims
. Its implementation is straightforward and correct, leveraging the existingFromMap
method.
149-160
: LGTM! Well-implementedtoNumericDate
helper method.The
toNumericDate
method is a useful helper function for converting claim values toNumericDate
. It correctly handles cases where the key is not present in theExtra
map and delegates the actual conversion to thetoNumericDate
function. This implementation aligns well with the changes in theFromMap
method and provides a consistent way to handle numeric date claims.
162-176
: LGTM! Well-implementedtoString
helper method.The
toString
method is a useful helper function for converting claim values to strings. It correctly handles cases where the key is not present in theExtra
map and provides proper error handling when the value is not a string. This implementation enhances type safety and provides a consistent way to handle string claims.
179-181
: LGTM! Good practice to include type assertion.The addition of the type assertion
var _ Claims = (*JARMClaims)(nil)
is a great practice. It ensures at compile-time thatJARMClaims
correctly implements theClaims
interface. This helps catch any interface implementation issues early in the development process.handler/openid/validator_test.go (9)
24-29
: Improved JWT handling with strategy-based approachThe refactoring of the
DefaultStrategy
struct to use aStrategy
field withjwt.DefaultStrategy
is a positive change. This approach allows for more flexible and customizable JWT handling, which can improve testability and make it easier to swap out JWT implementations if needed in the future.
62-63
: Standardized time representation in testsThe use of
jwt.Now()
andjwt.NewNumericDate(...)
for time-related fields is a good practice. It ensures consistency in time representation across the tests and aligns with JWT standards for handling time.
77-78
: Consistent time representationThe standardization of time-related fields using
jwt.Now()
andjwt.NewNumericDate(...)
is maintained here, which is good for consistency across test cases.
92-93
: Maintained consistency in time representationThe continued use of
jwt.Now()
andjwt.NewNumericDate(...)
for time-related fields maintains consistency across test cases, which is commendable.
106-107
: Consistent RequestedAt field, AuthTime field missingThe use of
jwt.Now()
for the RequestedAt field maintains consistency with previous changes. However, I noticed that the AuthTime field is not present in this test case. Can you confirm if this is intentional for testing purposes?
119-120
: Time fields swapped intentionally?The use of JWT-specific time functions is maintained, which is good. However, I noticed that the time values for RequestedAt and AuthTime are swapped compared to previous test cases. Is this intentional to test a specific scenario where the auth time is more recent than the requested time?
133-134
: Consistent time representation restoredThe use of
jwt.Now()
for RequestedAt andjwt.NewNumericDate(time.Now().Add(-time.Minute))
for AuthTime is consistent with the standardized time representation used in earlier test cases. This maintains the correct order of AuthTime being in the past relative to RequestedAt.
147-148
: Consistent time representation across all test casesThe changes in time-related fields across all remaining test cases consistently use
jwt.Now()
,jwt.NewNumericDate()
, and relative time adjustments. This standardized approach to time representation significantly improves the consistency and readability of the entire test suite.These changes will make it easier to maintain and update the tests in the future, as well as reduce the likelihood of time-related inconsistencies in test results.
Also applies to: 161-162, 175-176, 189-190, 203-204, 208-210, 222-223, 227-229, 240-243, 247-249
Line range hint
1-274
: Summary of changes: Improved JWT handling and test consistencyThe modifications in this file are part of a larger refactoring effort to standardize JWT handling and improve the flexibility of the OpenID Connect implementation. Key improvements include:
- Adoption of a strategy-based approach for JWT handling, allowing for more flexible and customizable implementations.
- Simplified ID token generation process.
- Standardized time representation across all test cases using JWT-specific time functions.
These changes enhance the maintainability, readability, and consistency of the test suite, which will likely lead to more robust and reliable tests in the future.
token/jwt/claims_jwt.go (2)
7-7
: LGTM: Import ofjson
package.The addition of the
json
package is appropriate given the new functionality for handlingjson.Number
types in thetoInt64
function.
Line range hint
1-304
: Overall assessment: Improved JWT claims handlingThe changes in this file enhance the robustness and type-safety of JWT claims handling. Key improvements include:
- More comprehensive type handling in
toTime
,toInt64
, andtoNumericDate
functions.- Consistent error handling and success status reporting.
- Refactoring of claim constant references.
While the changes are generally good, consider addressing the following points:
- Fully handle the success status in the
FromMap
method forIssuedAt
,NotBefore
, andExpiresAt
.- Implement the suggested optimizations for
toInt64
andtoNumericDate
to improve code consistency and maintainability.These changes will further enhance the quality and reliability of the JWT claims handling in this package.
README.md (1)
Line range hint
107-132
: Significant changes in dependenciesThe README lists several important changes to the project's dependencies:
- Removal of multiple dependencies, including
go.opentelemetry.io/otel/trace
andgithub.com/dgrijalva/jwt-go
.- Migration to newer versions of key dependencies:
github.com/go-jose/go-jose/v3
=>github.com/go-jose/go-jose/v4
github.com/golang/mock
=>github.com/uber-go/mock
github.com/cristalhq/jwt/v4
=>github.com/golang-jwt/jwt/v5
These changes likely improve the project's maintainability and security posture.
To ensure all dependency changes have been properly implemented, run the following script:
#!/bin/bash # Verify dependency changes # Check go.mod for removed dependencies echo "Checking for removed dependencies:" for dep in "go.opentelemetry.io/otel/trace" "github.com/ecordell/optgen" "github.com/asaskevich/govalidator" "github.com/gorilla/websocket" "github.com/magiconair/properties" "github.com/mattn/goveralls" "github.com/oleiade/reflections" "github.com/ory/go-acc" "github.com/ory/go-convenience" "github.com/ory/x" "github.com/gorilla/sessions" "github.com/gobuffalo/packr" "github.com/form3tech-oss/jwt-go" "github.com/dgrijalva/jwt-go" "github.com/golang-jwt/jwt" do if grep -q "$dep" go.mod; then echo "Warning: $dep is still present in go.mod" fi done # Check for updated dependencies echo "Checking for updated dependencies:" grep -E "github.com/go-jose/go-jose/v4|github.com/uber-go/mock|github.com/golang-jwt/jwt/v5" go.mod # Check for any remaining references to old dependencies in .go files echo "Checking for references to old dependencies in .go files:" rg --type go -l -e "go.opentelemetry.io/otel/trace" -e "github.com/ecordell/optgen" -e "github.com/asaskevich/govalidator" -e "github.com/gorilla/websocket" -e "github.com/magiconair/properties" -e "github.com/mattn/goveralls" -e "github.com/oleiade/reflections" -e "github.com/ory/go-acc" -e "github.com/ory/go-convenience" -e "github.com/ory/x" -e "github.com/gorilla/sessions" -e "github.com/gobuffalo/packr" -e "github.com/form3tech-oss/jwt-go" -e "github.com/dgrijalva/jwt-go" -e "github.com/golang-jwt/jwt"Please review the script output to ensure all dependency changes have been correctly implemented across the codebase.
token/jwt/claims_map.go (3)
19-65
: LGTM: Well-implemented issuer and subject claim handlingThe new methods for retrieving and verifying issuer and subject claims are well-implemented. They follow good practices such as:
- Consistent error handling
- Proper handling of cases where the claim is not required
- Clear separation of concerns between retrieval and verification
These additions enhance the JWT claim handling capabilities of the library.
285-297
: LGTM: Improved JSON unmarshaling with a request for clarificationThe modification to the UnmarshalJSON method, using a custom JSON decoder with specific number type handling, appears to be a good improvement:
- It likely enhances the handling of numeric values in JWT claims.
- The use of jjson.UnmarshalIntOrFloat suggests more flexible number parsing.
Could you provide more context on the rationale behind this change? Specifically:
- What issues did the previous implementation have?
- How does this new approach resolve those issues?
This information would be valuable for understanding the full impact of this change and could be useful for documentation purposes.
Line range hint
1-360
: Overall: Significant enhancements to JWT claim handlingThis pull request introduces substantial improvements to the JWT claim handling capabilities:
- New methods for retrieving and validating various claim types (issuer, subject, audience, expiration time, issued at, not before).
- A comprehensive
Valid
method for validating multiple claims based on provided options.- Enhanced JSON unmarshaling for better handling of numeric values.
- New helper methods for type-safe conversions and claim retrieval.
These changes significantly enhance the functionality, flexibility, and robustness of the JWT claim handling in this library. The code is well-structured, follows consistent patterns, and generally has good error handling.
While the changes are generally excellent, consider the suggestions provided in the individual comments for further improvements, particularly around error handling and code readability.
token/jwt/claims_id_token.go (6)
26-29
: LGTM: Improved type handling for time-related fieldsThe change from
time.Time
to*NumericDate
forExpirationTime
,IssuedAt
,RequestedAt
, andAuthTime
fields is a good improvement. This allows for better JSON serialization/deserialization and explicit representation of null values.
62-139
: LGTM: Comprehensive and flexible claim validationThe updated
Valid
method is a significant improvement:
- It uses
ClaimValidationOption
for flexible validation configuration.- Comprehensive checks are performed on various claims (expiration, issuance, audience, etc.).
- The method returns a
ValidationError
with detailed error information using efficient bitwise operations.The implementation is thorough, well-structured, and easy to follow.
141-171
: LGTM: Safe access methods for time-related fieldsThe addition of safe getter methods (
GetExpirationTimeSafe
,GetIssuedAtSafe
,GetAuthTimeSafe
, andGetRequestedAtSafe
) is a good improvement:
- They provide a safe way to access time-related fields without nil pointer dereferences.
- Returning
time.Unix(0, 0).UTC()
for nil values is a reasonable default.These methods enhance the usability and safety of the
IDTokenClaims
struct.
295-302
: LGTM: ImprovedToMap
methodThe updates to the
ToMap
method are well-implemented:
- The method correctly handles the new
*NumericDate
types, converting them to Unix timestamps.- The use of constants from the
consts
package for claim keys improves consistency and maintainability.These changes ensure that the
ToMap
method accurately represents theIDTokenClaims
struct as a map.Also applies to: 331-331
352-355
: LGTM: NewToMapClaims
methodThe addition of the
ToMapClaims
method is a good improvement:
- It provides a convenient way to get a
MapClaims
representation of theIDTokenClaims
.- The implementation as a wrapper around
ToMap
is appropriate and efficient.This method enhances compatibility with jwt-go library expectations.
371-381
: LGTM: NewtoNumericDate
method with a noteThe addition of the
toNumericDate
method to theIDTokenClaims
struct is a good improvement:
- It provides a way to handle custom date claims stored in the
Extra
map.- The method correctly handles the case when the key is not present in the
Extra
map.However, there's a potential issue to verify:
The method uses a
toNumericDate
function that is not defined in this file. Please ensure that this function is defined elsewhere in the package. You can verify this by running the following command:If the function is not found, consider implementing it or updating the method to use a different approach for converting values to
*NumericDate
.✅ Verification successful
Verification Successful:
toNumericDate
function is definedThe
toNumericDate
function is correctly defined in./token/jwt/claims_jwt.go
, ensuring that thetoNumericDate
method inclaims_id_token.go
references a valid function.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for the toNumericDate function definition rg --type go "func toNumericDate\(" .Length of output: 124
token/jwt/token.go (13)
20-25
: LGTM: New constructor function addedThe
New()
function is a simple constructor for theToken
struct. It initializes theHeader
andHeaderJWE
maps, which is a good practice to avoid nil map errors.
28-36
: LGTM: NewWithClaims function addedThe
NewWithClaims()
function is a more comprehensive constructor that allows setting the signature algorithm and claims. It's a useful addition for creating tokens with predefined claims.
38-52
: LGTM: Parsing functions addedThe new parsing functions (
Parse
,ParseCustom
,ParseWithClaims
,ParseCustomWithClaims
) provide a good range of options for parsing tokens. They handle different scenarios and allow for custom claims and algorithms.
130-147
: LGTM: Token struct updatedThe
Token
struct has been updated to include fields for both JWS (JSON Web Signature) and JWE (JSON Web Encryption) headers. This allows for more comprehensive token handling, supporting both signed and encrypted tokens.
192-210
: LGTM: SetJWS and SetJWE methods addedThe
SetJWS
andSetJWE
methods provide a clean way to set the respective headers and properties for signed and encrypted tokens. This separation of concerns is good for maintainability.
212-244
: LGTM: AssignJWE method addedThe
AssignJWE
method correctly populates the token's JWE-related fields from ajose.JSONWebEncryption
object. The nil check at the beginning is a good defensive programming practice.
247-295
: LGTM: CompactEncrypted method addedThe
CompactEncrypted
method provides a comprehensive way to create an encrypted and signed token. It handles both the signing and encryption processes, which is good for creating secure tokens.
297-342
: LGTM: CompactSigned and CompactSignedString methods addedThese methods provide ways to create signed tokens. The
CompactSigned
method returns both the token string and the signature, which can be useful for certain use cases. TheCompactSignedString
method is compatible with thejwt-go
library, which is good for backwards compatibility.
438-467
: LGTM: IsJWTProfileAccessToken method addedThe
IsJWTProfileAccessToken
method correctly checks if the token is a JWT Profile Access Token by examining both the JWE and JWS headers. The type assertions and checks are thorough.
468-513
: LGTM: Header validation options addedThe
HeaderValidationOption
type and associated functions provide a flexible way to configure token validation. This is a good use of the functional options pattern.
515-535
: LGTM: unsignedToken function addedThe
unsignedToken
function correctly creates an unsigned token by setting the appropriate headers and encoding the token parts. The error handling is thorough.
537-566
: LGTM: newToken function addedThe
newToken
function creates a newToken
struct from a parsed JWT and claims. It correctly handles the headers and performs necessary checks.
579-609
: LGTM: Token type validation functions addedThe
validateTokenType
andvalidateTokenTypeValue
functions provide a way to validate the token type against a list of allowed values. The implementation is straightforward and correct.token/jwt/validate.go (2)
8-19
: Well-structured claim validation optionsThe
ClaimValidationOptions
struct and the associated option functions are well-designed, providing a flexible and extensible way to configure JWT claim validations.
69-81
: Proper use of constant-time comparison inverifyAud
The
verifyAud
function correctly utilizessubtle.ConstantTimeCompare
to prevent timing attacks when comparing audience strings.handler/openid/strategy_jwt.go (6)
46-46
: UpdateRequestedAt
initialization to usejwt.Now()
The change from
time.Now().UTC()
tojwt.Now()
ensures consistent time precision across JWT claims, which is beneficial for token validity and time comparison operations.
113-113
: Embedjwt.Strategy
instead ofjwt.Signer
inDefaultStrategy
By embedding
jwt.Strategy
, the code now accesses both encoding and decoding capabilities, consistent with the updates in token handling methods.
144-145
: InitializejwtClient
for token operationsThe creation of
jwtClient
usingjwt.NewIDTokenClient(requester.GetClient())
appropriately encapsulates client information needed for ID Token encoding and decoding.
160-164
: Validate required claims whenmax_age
is specifiedEnsuring
AuthTime
andRequestedAt
are set whenmax_age
is provided is critical for compliance with OpenID Connect specifications and for accurate token validation.
225-226
: InitializeAuthTime
usingjwt.Now()
when unsetSetting
claims.AuthTime
tojwt.Now()
ensures that theauth_time
claim accurately reflects the current time, which is important for token validation.
243-245
: Encode ID Token using updated strategyUsing
h.Strategy.Encode()
with the provided parameters aligns with the embedding ofjwt.Strategy
and ensures correct token encoding with necessary headers and client information.
ClaimIssuer: jwtClaims.Issuer, | ||
ClaimAudience: jwtClaims.Audience, | ||
ClaimJWTID: jwtClaims.JTI, | ||
ClaimIssuedAt: jwtClaims.IssuedAt.Unix(), | ||
ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(), | ||
"foo": jwtClaims.Extra["foo"], | ||
"baz": jwtClaims.Extra["baz"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but there's a minor inconsistency.
The changes look good overall. The removal of the consts
package dependency and the consistent use of ExpirationTime
instead of ExpiresAt
improve code clarity.
There's an inconsistency in the variable names. The jarmClaimsMap
is using jwtClaims
, which doesn't match the jarmClaims
variable name used earlier in the file. This could lead to confusion or potential bugs.
Please update the variable name for consistency:
var jarmClaimsMap = map[string]any{
- ClaimIssuer: jwtClaims.Issuer,
- ClaimAudience: jwtClaims.Audience,
- ClaimJWTID: jwtClaims.JTI,
- ClaimIssuedAt: jwtClaims.IssuedAt.Unix(),
- ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(),
- "foo": jwtClaims.Extra["foo"],
- "baz": jwtClaims.Extra["baz"],
+ ClaimIssuer: jarmClaims.Issuer,
+ ClaimAudience: jarmClaims.Audience,
+ ClaimJWTID: jarmClaims.JTI,
+ ClaimIssuedAt: jarmClaims.IssuedAt.Unix(),
+ ClaimExpirationTime: jarmClaims.ExpirationTime.Unix(),
+ "foo": jarmClaims.Extra["foo"],
+ "baz": jarmClaims.Extra["baz"],
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
ClaimIssuer: jwtClaims.Issuer, | |
ClaimAudience: jwtClaims.Audience, | |
ClaimJWTID: jwtClaims.JTI, | |
ClaimIssuedAt: jwtClaims.IssuedAt.Unix(), | |
ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(), | |
"foo": jwtClaims.Extra["foo"], | |
"baz": jwtClaims.Extra["baz"], | |
ClaimIssuer: jarmClaims.Issuer, | |
ClaimAudience: jarmClaims.Audience, | |
ClaimJWTID: jarmClaims.JTI, | |
ClaimIssuedAt: jarmClaims.IssuedAt.Unix(), | |
ClaimExpirationTime: jarmClaims.ExpirationTime.Unix(), | |
"foo": jarmClaims.Extra["foo"], | |
"baz": jarmClaims.Extra["baz"], |
var strategy jwt.Strategy | ||
|
||
if signer = config.GetJWTSecuredAuthorizeResponseModeSigner(ctx); signer == nil { | ||
return "", "", errors.New("The JARM response modes require the JWTSecuredAuthorizeResponseModeSignerProvider to return a jwt.Signer but it didn't.") | ||
if strategy = config.GetJWTSecuredAuthorizeResponseModeStrategy(ctx); strategy == nil { | ||
return "", "", errors.New("The JARM response modes require the JWTSecuredAuthorizeResponseModeSignerProvider to return a jwt.Strategy but it didn't.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Error Message Lacks Additional Context
The error message in token/jarm/generate.go
does not include the original error returned by GetJWTSecuredAuthorizeResponseModeStrategy
. Consider updating the error handling to include this context.
🔗 Analysis chain
Updated JWT handling with Strategy pattern
The transition from jwt.Signer
to jwt.Strategy
represents a shift towards a more flexible and extensible approach for JWT handling. This change allows for easier swapping of JWT encoding strategies and improves the overall design.
However, there are a couple of points to consider:
-
Error handling: The error message has been updated to reflect the new
jwt.Strategy
usage, which is good. However, consider adding more context to the error message, such as including the actual error returned byGetJWTSecuredAuthorizeResponseModeStrategy
. -
Encode method: The
Encode
method now includes additional parameters for headers and client. Ensure that this change is consistent with thejwt.Strategy
interface definition and that all implementations of this interface have been updated accordingly.
To ensure consistency across the codebase, let's verify the jwt.Strategy
interface and its implementations:
Also applies to: 72-72
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify jwt.Strategy interface and its implementations
# Test 1: Check the jwt.Strategy interface definition
echo "Checking jwt.Strategy interface definition:"
ast-grep --lang go --pattern 'type Strategy interface {
$$$
Encode($_, $_, $_) ($_, $_, $_)
$$$
}'
# Test 2: Check for implementations of jwt.Strategy
echo "Checking for jwt.Strategy implementations:"
rg --type go "type .+ struct" -A 10 | rg "jwt\.Strategy"
# Test 3: Verify usage of strategy.Encode method
echo "Verifying usage of strategy.Encode method:"
rg --type go "strategy\.Encode\(ctx, .+, jwt\.With"
Length of output: 2848
Script:
#!/bin/bash
# Description: Verify enhanced error handling in token/jarm/generate.go
# Test 1: Extract the error message from lines 66-69
echo "Extracting error message from token/jarm/generate.go lines 66-69:"
sed -n '66,69p' token/jarm/generate.go
# Test 2: Check if the error message includes the original error
echo "Checking if the error message includes the original error:"
grep -E 'errors\.New\(.+GetJWTSecuredAuthorizeResponseModeStrategy\.Error\)' token/jarm/generate.go
Length of output: 678
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { | ||
var value interface{} | ||
|
||
if err = json.Unmarshal(data, &value); err != nil { | ||
return err | ||
} | ||
|
||
var aud []string | ||
|
||
switch v := value.(type) { | ||
case string: | ||
aud = append(aud, v) | ||
case []string: | ||
aud = ClaimStrings(v) | ||
case []interface{}: | ||
for _, vv := range v { | ||
vs, ok := vv.(string) | ||
if !ok { | ||
return ErrInvalidType | ||
} | ||
aud = append(aud, vs) | ||
} | ||
case nil: | ||
return nil | ||
default: | ||
return ErrInvalidType | ||
} | ||
|
||
*s = aud | ||
|
||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider removing unreachable case in UnmarshalJSON
.
The case []string:
block might be unreachable because JSON unmarshaling into interface{}
typically produces []interface{}
for arrays, not []string
. Consider removing this case to simplify the function.
Apply this diff to remove the unreachable case:
switch v := value.(type) {
case string:
aud = append(aud, v)
-case []string:
- aud = ClaimStrings(v)
case []interface{}:
for _, vv := range v {
vs, ok := vv.(string)
if !ok {
return ErrInvalidType
}
aud = append(aud, vs)
}
case nil:
return nil
default:
return ErrInvalidType
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { | |
var value interface{} | |
if err = json.Unmarshal(data, &value); err != nil { | |
return err | |
} | |
var aud []string | |
switch v := value.(type) { | |
case string: | |
aud = append(aud, v) | |
case []string: | |
aud = ClaimStrings(v) | |
case []interface{}: | |
for _, vv := range v { | |
vs, ok := vv.(string) | |
if !ok { | |
return ErrInvalidType | |
} | |
aud = append(aud, vs) | |
} | |
case nil: | |
return nil | |
default: | |
return ErrInvalidType | |
} | |
*s = aud | |
return | |
} | |
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { | |
var value interface{} | |
if err = json.Unmarshal(data, &value); err != nil { | |
return err | |
} | |
var aud []string | |
switch v := value.(type) { | |
case string: | |
aud = append(aud, v) | |
case []interface{}: | |
for _, vv := range v { | |
vs, ok := vv.(string) | |
if !ok { | |
return ErrInvalidType | |
} | |
aud = append(aud, vs) | |
} | |
case nil: | |
return nil | |
default: | |
return ErrInvalidType | |
} | |
*s = aud | |
return | |
} |
} | ||
|
||
func (s ClaimStrings) MarshalJSON() (b []byte, err error) { | ||
if len(s) == 1 && !MarshalSingleStringAsArray { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
MarshalSingleStringAsArray
flag is not properly declared within the package.
- The
MarshalSingleStringAsArray
flag is assigned intoken/jwt/variables.go
but lacks an explicitvar
orconst
declaration.
🔗 Analysis chain
Verify the MarshalSingleStringAsArray
flag.
The MarshalSingleStringAsArray
flag is used but not defined within this file. Ensure that this flag is declared and accessible within the package.
Run the following script to verify the flag's declaration:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the declaration of MarshalSingleStringAsArray flag.
# Test: Search for the flag declaration. Expect: One or more occurrences of the flag declaration.
rg --type go -g '!date.go' $'var MarshalSingleStringAsArray'
Length of output: 61
Script:
#!/bin/bash
# Description: Broadly verify the presence of MarshalSingleStringAsArray flag.
# Test: Search for any declaration or usage of MarshalSingleStringAsArray. Expect: One or more occurrences.
rg --type go -g '!token/jwt/date.go' 'MarshalSingleStringAsArray'
Length of output: 123
Script:
#!/bin/bash
# Description: Verify the declaration type of MarshalSingleStringAsArray flag.
# Test: Search for variable or constant declarations of MarshalSingleStringAsArray. Expect: One or more declarations.
rg --type go -g '!token/jwt/date.go' '^(var|const)\s+MarshalSingleStringAsArray\b'
Length of output: 84
func (c *JARMClaims) GetExpirationTime() (exp *NumericDate, err error) { | ||
return c.ExpirationTime, nil | ||
} | ||
|
||
func (c *JARMClaims) GetIssuedAt() (iat *NumericDate, err error) { | ||
return c.IssuedAt, nil | ||
} | ||
|
||
func (c *JARMClaims) GetNotBefore() (nbf *NumericDate, err error) { | ||
return c.toNumericDate(ClaimNotBefore) | ||
} | ||
|
||
func (c *JARMClaims) GetIssuer() (iss string, err error) { | ||
return c.Issuer, nil | ||
} | ||
|
||
func (c *JARMClaims) GetSubject() (sub string, err error) { | ||
return c.toString(ClaimIssuer) | ||
} | ||
|
||
func (c *JARMClaims) GetAudience() (aud ClaimStrings, err error) { | ||
return c.Audience, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for most methods, but there's an issue in GetSubject
.
The new getter methods provide a consistent interface for accessing claims, which is great for usability and maintainability. However, there's an issue in the GetSubject
method:
func (c *JARMClaims) GetSubject() (sub string, err error) {
return c.toString(ClaimIssuer)
}
It's using ClaimIssuer
instead of ClaimSubject
. This should be corrected to:
func (c *JARMClaims) GetSubject() (sub string, err error) {
return c.toString(ClaimSubject)
}
Please fix the GetSubject
method to use the correct claim key.
The implementation of other getter methods looks good.
if subHint, err = tokenHint.Claims.GetSubject(); subHint == "" || err != nil { | ||
return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject.")) | ||
} else if hintSub != claims.Subject { | ||
} else if subHint != claims.Subject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling when retrieving subject from id_token_hint
The condition subHint == "" || err != nil
conflates an empty subHint
with errors during retrieval. Consider handling these cases separately to provide more precise error messages and improve troubleshooting.
jwtStrategy := &jwt.DefaultStrategy{ | ||
Config: config, | ||
Issuer: jwt.NewDefaultIssuerRS256Unverified(key), | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undefined variable key
in jwtStrategy
initialization
In line 432, the variable key
is used in jwt.NewDefaultIssuerRS256Unverified(key)
, but key
is not defined within the provided code. This will result in a compilation error.
Consider defining the key
variable before using it. For example, you might initialize it as follows:
key := []byte("your-test-key") // Initialize or load your key appropriate for testing
Ensure that key
is properly defined and matches the expected type for the Issuer
function.
claims := &jwt.IDTokenClaims{} | ||
|
||
claims := &IDTokenClaims{} | ||
|
||
_, _, err := parser.ParseUnverified(idToken, claims) | ||
_, err := jwt.UnsafeParseSignedAny(idToken, claims) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of jwt.UnsafeParseSignedAny
may bypass signature verification
The function jwt.UnsafeParseSignedAny
used in line 194 does not verify the signature of the ID token. This could lead to acceptance of invalid or tampered tokens during testing, potentially masking issues in token generation and validation logic.
Consider using a method that performs signature verification to ensure the integrity of the ID token. Replace jwt.UnsafeParseSignedAny
with jwt.ParseSigned
or another function that validates the token's signature:
token, err := jwt.ParseSigned(idToken)
require.NoError(t, err)
err = token.Claims(expectedSigningKey, claims)
require.NoError(t, err)
Ensure you provide the appropriate signing key for verification.
claims := &jwt.IDTokenClaims{} | ||
|
||
_, err := jwt.UnsafeParseSignedAny(idToken, claims) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of jwt.UnsafeParseSignedAny
may bypass signature verification
Similarly, in line 339, using jwt.UnsafeParseSignedAny
skips signature verification for the ID token. This can result in tests passing with invalid or manipulated tokens.
Use a secure parsing method that includes signature verification. Replace jwt.UnsafeParseSignedAny
with a function like jwt.ParseSigned
:
token, err := jwt.ParseSigned(idToken)
require.NoError(t, err)
err = token.Claims(expectedSigningKey, claims)
require.NoError(t, err)
This ensures that only properly signed tokens are accepted in tests.
jwtStrategy := &jwt.DefaultStrategy{ | ||
Config: config, | ||
Issuer: jwt.NewDefaultIssuerRS256Unverified(key), | ||
} | ||
|
||
var idStrategy = &DefaultStrategy{ | ||
Signer: &jwt.DefaultSigner{ | ||
GetPrivateKey: func(_ context.Context) (any, error) { | ||
return gen.MustRSAKey(), nil | ||
}, | ||
}, | ||
Config: &oauth2.Config{ | ||
MinParameterEntropy: minParameterEntropy, | ||
Strategy: &jwt.DefaultStrategy{ | ||
Config: config, | ||
Issuer: jwt.MustGenDefaultIssuer(), | ||
}, | ||
Config: config, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inconsistent issuer initialization between jwtStrategy
and idStrategy
There is a discrepancy in how the issuers are initialized for jwtStrategy
and idStrategy
:
- In
jwtStrategy
(lines 430-433), the issuer is set usingjwt.NewDefaultIssuerRS256Unverified(key)
with an undefinedkey
. - In
idStrategy
(lines 436-438), the issuer is set usingjwt.MustGenDefaultIssuer()
.
This inconsistency could lead to unexpected behavior or test failures due to different signing keys or issuer configurations.
Consider standardizing the issuer initialization for both strategies to ensure consistent behavior. For example, you could use the same issuer function and key for both:
// Define the key once
key := []byte("your-test-key") // Ensure the key is properly defined
// Initialize jwtStrategy and idStrategy with the same issuer
issuer := jwt.NewDefaultIssuerRS256Unverified(key)
jwtStrategy := &jwt.DefaultStrategy{
Config: config,
Issuer: issuer,
}
idStrategy := &DefaultStrategy{
Strategy: &jwt.DefaultStrategy{
Config: config,
Issuer: issuer,
},
Config: config,
}
Ensure that the key
variable is defined and that both strategies use the same issuer for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (7)
token/jwt/util.go (7)
19-22
: LGTM! Consider adding a comment about validation limitations.The regular expressions for validating JWT structures look correct. However, it's worth noting that these are basic structural checks and don't validate the content or cryptographic integrity of the tokens.
Consider adding a comment to clarify the limitations of this validation:
var ( + // Note: These regular expressions only validate the basic structure of JWTs. + // They do not check the content or cryptographic integrity of the tokens. reSignedJWT = regexp.MustCompile(`^[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.([-_A-Za-z0-9]+)?$`) reEncryptedJWT = regexp.MustCompile(`^[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+$`) )
34-71
: LGTM! Consider clarifying the relationship between client secret and password-based algorithms.These functions provide a clear way to categorize JWT algorithms, which is crucial for proper JWT handling. The implementation looks correct and covers the standard algorithms.
In the
IsEncryptedJWTClientSecretAlg
function, consider clarifying the relationship between client secret algorithms and password-based algorithms. You might want to add a comment explaining that password-based algorithms are a subset of client secret algorithms:func IsEncryptedJWTClientSecretAlg(alg string) (csa bool) { switch a := jose.KeyAlgorithm(alg); a { case jose.A128KW, jose.A192KW, jose.A256KW, jose.DIRECT, jose.A128GCMKW, jose.A192GCMKW, jose.A256GCMKW: return true default: + // Password-based algorithms are a subset of client secret algorithms return IsEncryptedJWTPasswordBasedAlg(a) } }
73-143
: LGTM! Consider improving error messages for easier debugging.The
headerValidateJWS
andheaderValidateJWE
functions perform thorough validation of the headers, checking for required fields and potential security issues. The inclusion of PBES2 count validation in the JWE header is a good security practice.Consider making the error messages more specific to aid in debugging. For example:
func headerValidateJWS(headers []jose.Header) (kid, alg string, err error) { switch len(headers) { case 1: break case 0: - return "", "", fmt.Errorf("jws header is missing") + return "", "", fmt.Errorf("JWS header is missing") default: - return "", "", fmt.Errorf("jws header is malformed") + return "", "", fmt.Errorf("JWS header is malformed: expected 1 header, got %d", len(headers)) } if headers[0].Algorithm == "" && headers[0].KeyID == "" { - return "", "", fmt.Errorf("jws header 'alg' and 'kid' values are missing or empty") + return "", "", fmt.Errorf("JWS header 'alg' and 'kid' values are both missing or empty") } if headers[0].JSONWebKey != nil { - return "", "", fmt.Errorf("jws header 'jwk' value is present but not supported") + return "", "", fmt.Errorf("JWS header 'jwk' value is present but not supported in this implementation") } return headers[0].KeyID, headers[0].Algorithm, nil }Similar improvements can be made to the
headerValidateJWE
function.
145-166
: LGTM! Consider simplifying error handling.The
PrivateKey
interface correctly defines the methods required for a private key. TheJWKLookupError
struct provides a way to handle errors specific to JWK lookup, which is good for error handling and debugging.The use of a bitfield for errors might be overly complex for the current implementation. Consider simplifying it if you're not using multiple error flags simultaneously:
type JWKLookupError struct { Description string - Errors uint32 // bitfield. see JWKLookupError... constants + ErrorCode string // Use a string identifier for the error type } func (e *JWKLookupError) Error() string { - return fmt.Sprintf("Error occurred retrieving the JSON Web Key. %s", e.Description) + return fmt.Sprintf("Error occurred retrieving the JSON Web Key (%s): %s", e.ErrorCode, e.Description) }This approach would be simpler and more readable, while still allowing for different error types.
363-393
: LGTM! Consider adding explicit error handling.The
EncodeCompactSigned
andEncodeNestedCompactEncrypted
functions implement the correct logic for encoding JWTs. The handling of nil headers by creating empty headers is a good practice.Consider adding explicit error handling for the
SetJWS
andSetJWE
methods. While these methods might not return errors in the current implementation, adding error checks can make the code more robust and easier to maintain if these methods are updated in the future to return errors.For example:
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) { token := New() if headers == nil { headers = &Headers{} } - token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)) + if err := token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)); err != nil { + return "", "", fmt.Errorf("failed to set JWS: %w", err) + } return token.CompactSigned(key) } func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) { token := New() if headers == nil { headers = &Headers{} } if headersJWE == nil { headersJWE = &Headers{} } - token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)) - token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE) + if err := token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)); err != nil { + return "", "", fmt.Errorf("failed to set JWS: %w", err) + } + if err := token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE); err != nil { + return "", "", fmt.Errorf("failed to set JWE: %w", err) + } return token.CompactEncrypted(keySig, keyEnc) }This change would make the error handling more explicit and consistent throughout the functions.
395-435
: LGTM! Consider optimizing the assign function.The utility functions
getJWTSignature
,assign
, andgetPublicJWK
implement their intended functionality correctly. ThegetJWTSignature
function properly handles different JWT formats, andgetPublicJWK
correctly handles the case of client secret-based algorithms.Consider optimizing the
assign
function. In Go 1.15 and later, you can usemaps.Copy
from thegolang.org/x/exp/maps
package for a more efficient and idiomatic way to merge maps. If you're using an earlier version of Go or prefer not to use experimental packages, you could still optimize the current implementation:-func assign(a, b map[string]any) map[string]any { +func assign(a, b map[string]any) { for k, w := range b { if _, ok := a[k]; ok { continue } a[k] = w } - return a }This change removes the unnecessary return value, as the function modifies the map in-place. Usage would change from
a = assign(a, b)
toassign(a, b)
.If you decide to use
maps.Copy
, the function could be replaced entirely:import "golang.org/x/exp/maps" func assign(a, b map[string]any) { maps.Copy(a, b) }This would be more efficient for large maps.
437-469
: LGTM! Consider refining the error wrapping in newError.The
UnsafeParseSignedAny
function is correctly marked as unsafe and intended for testing only. ThenewError
function provides a flexible way to create formatted errors with multiple error wrapping.The current implementation of
newError
usesfmt.Errorf
with%w
for wrapping multiple errors. However, this might lead to unexpected behavior asfmt.Errorf
only wraps the first error when multiple%w
verbs are used. Consider usingerrors.Join
(introduced in Go 1.20) for a more predictable way of combining multiple errors:import "errors" func newError(message string, err error, more ...error) error { if message != "" { err = fmt.Errorf("%s: %w", message, err) } if len(more) > 0 { return errors.Join(err, errors.Join(more...)) } return err }This implementation ensures that all provided errors are properly combined, and the resulting error will implement the
Unwrap() []error
method, allowing for better error inspection.If you're using an earlier version of Go, you might want to implement a custom error type that can hold multiple errors, or use a third-party errors package that supports this functionality.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- token/jwt/claims_id_token.go (5 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- token/jwt/claims_id_token_test.go
🔇 Additional comments (6)
token/jwt/claims_id_token.go (3)
26-29
: LGTM: Improved claim type definitionsThe changes to
*NumericDate
for time-related fields (ExpirationTime, IssuedAt, RequestedAt, AuthTime) align better with JWT standards. TheExtra
field type change tomap[string]any
allows for more flexible additional claims.Also applies to: 35-35
Line range hint
246-335
: LGTM: Improved ToMap implementationThe updates to the
ToMap
method are well-implemented:
- It correctly handles the new field types, particularly the
*NumericDate
fields.- The use of constants from the
consts
package for claim keys improves maintainability.- Nil checks and appropriate handling of empty values ensure the method behaves correctly in all cases.
These changes make the method more robust and consistent with the updated
IDTokenClaims
struct.
337-340
: LGTM: Useful addition of ToMapClaimsThe
ToMapClaims
method is a valuable addition:
- It provides a convenient way to convert
IDTokenClaims
to aMapClaims
type.- This likely improves interoperability with other JWT libraries or systems expecting this format.
- The implementation is simple and leverages the existing
ToMap
method, promoting code reuse.This addition enhances the usability of the
IDTokenClaims
struct in various JWT-related scenarios.token/jwt/util.go (3)
24-32
: LGTM! Functions provide basic structural validation.The
IsSignedJWT
andIsEncryptedJWT
functions correctly use the defined regular expressions to check if a token string matches the basic structure of a signed or encrypted JWT. This can be useful for initial validation or routing logic.
1-469
: Overall, the implementation is solid with room for minor improvements.This file provides a comprehensive set of functions for JWT and JWK handling. The implementation follows good practices and includes thorough error checking. Here's a summary of the main points from the review:
- The regular expressions for JWT validation are correct but limited to structural checks.
- The algorithm categorization functions are well-implemented, with a suggestion to clarify the relationship between client secret and password-based algorithms.
- Header validation functions are thorough, with a suggestion to improve error messages for easier debugging.
- The JWK lookup and search functions are well-implemented, with a note about potential non-deterministic key selection.
- The key derivation process in
NewClientSecretJWK
is correct, but there's a suggestion to clarify the use of thebits
variable.- JWT encoding functions could benefit from more explicit error handling.
- Some utility functions, like
assign
, could be optimized for better performance.- The error wrapping in
newError
could be improved usingerrors.Join
for better error combining.Implementing these suggestions would further enhance the code's clarity, efficiency, and robustness. Great job overall on this comprehensive JWT and JWK handling implementation!
168-242
: LGTM! Be aware of potential non-deterministic key selection.The
FindClientPublicJWK
andSearchJWKS
functions implement the correct logic for finding and matching JWKs. The handling of cases where multiple keys match is good.There's a potential issue with non-deterministic key selection when multiple keys match and 'strict' is false. This could lead to inconsistent behavior. Let's verify if this is a concern in the current implementation:
If these searches return results, we should consider adding a comment or adjusting the implementation to ensure deterministic key selection.
token/jwt/claims_id_token.go
Outdated
func (c *IDTokenClaims) GetExpirationTime() (exp *NumericDate, err error) { | ||
return c.ExpirationTime, nil | ||
} | ||
|
||
if c.Subject != "" { | ||
ret[consts.ClaimSubject] = c.Subject | ||
} else { | ||
delete(ret, consts.ClaimSubject) | ||
func (c *IDTokenClaims) GetIssuedAt() (iat *NumericDate, err error) { | ||
return c.IssuedAt, nil | ||
} | ||
|
||
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { | ||
return toNumericDate(ClaimNotBefore) | ||
} | ||
|
||
func (c *IDTokenClaims) GetIssuer() (iss string, err error) { | ||
return c.Issuer, nil | ||
} | ||
|
||
func (c *IDTokenClaims) GetSubject() (sub string, err error) { | ||
return c.Subject, nil | ||
} | ||
|
||
func (c *IDTokenClaims) GetAudience() (aud ClaimStrings, err error) { | ||
return c.Audience, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM: New getter methods improve claim access
The addition of getter methods for various claims is a good improvement. They provide a consistent interface for retrieving claim values and allow for proper error handling.
However, there's a potential issue in the GetNotBefore
method:
The GetNotBefore
method uses an undefined toNumericDate
function. This might lead to a compilation error. Please ensure that this function is defined elsewhere in the package or consider implementing it if it's missing.
token/jwt/claims_id_token.go
Outdated
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { | ||
claims := MapClaims{} | ||
|
||
decoder := jjson.NewDecoder(bytes.NewReader(data)) | ||
decoder.SetNumberType(jjson.UnmarshalIntOrFloat) | ||
|
||
if err := decoder.Decode(&claims); err != nil { | ||
return errorsx.WithStack(err) | ||
} | ||
|
||
var ( | ||
ok bool | ||
err error | ||
) | ||
|
||
for claim, value := range claims { | ||
ok = false | ||
|
||
switch claim { | ||
case ClaimJWTID: | ||
c.JTI, ok = value.(string) | ||
case ClaimIssuer: | ||
c.Issuer, ok = value.(string) | ||
case ClaimSubject: | ||
c.Subject, ok = value.(string) | ||
case ClaimAudience: | ||
c.Audience, ok = toStringSlice(value) | ||
case ClaimNonce: | ||
c.Nonce, ok = value.(string) | ||
case ClaimExpirationTime: | ||
if c.ExpirationTime, err = toNumericDate(value); err == nil { | ||
ok = true | ||
} | ||
case ClaimIssuedAt: | ||
if c.IssuedAt, err = toNumericDate(value); err == nil { | ||
ok = true | ||
} | ||
case ClaimRequestedAt: | ||
if c.RequestedAt, err = toNumericDate(value); err == nil { | ||
ok = true | ||
} | ||
case ClaimAuthenticationTime: | ||
if c.AuthTime, err = toNumericDate(value); err == nil { | ||
ok = true | ||
} | ||
case ClaimAuthenticationContextClassReference: | ||
c.AuthenticationContextClassReference, ok = value.(string) | ||
case ClaimAuthenticationMethodsReference: | ||
c.AuthenticationMethodsReferences, ok = toStringSlice(value) | ||
case ClaimAccessTokenHash: | ||
c.AccessTokenHash, ok = value.(string) | ||
case ClaimCodeHash: | ||
c.CodeHash, ok = value.(string) | ||
case ClaimStateHash: | ||
c.StateHash, ok = value.(string) | ||
default: | ||
if c.Extra == nil { | ||
c.Extra = make(map[string]any) | ||
} | ||
|
||
c.Extra[claim] = value | ||
|
||
continue | ||
} | ||
|
||
if !ok { | ||
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) | ||
} | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a security concern: Comprehensive JSON unmarshaling
The UnmarshalJSON
method provides a thorough implementation for decoding JSON data into the IDTokenClaims
struct. It handles various claim types well and correctly populates the Extra
field for additional claims.
However, there's a potential security issue:
On line 239, the error message includes the claim value, which could potentially expose sensitive information. Consider modifying the error message to exclude the value:
- return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+ return fmt.Errorf("claim %s could not be decoded", claim)
This change will prevent potential leakage of sensitive claim values in error messages.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { | |
claims := MapClaims{} | |
decoder := jjson.NewDecoder(bytes.NewReader(data)) | |
decoder.SetNumberType(jjson.UnmarshalIntOrFloat) | |
if err := decoder.Decode(&claims); err != nil { | |
return errorsx.WithStack(err) | |
} | |
var ( | |
ok bool | |
err error | |
) | |
for claim, value := range claims { | |
ok = false | |
switch claim { | |
case ClaimJWTID: | |
c.JTI, ok = value.(string) | |
case ClaimIssuer: | |
c.Issuer, ok = value.(string) | |
case ClaimSubject: | |
c.Subject, ok = value.(string) | |
case ClaimAudience: | |
c.Audience, ok = toStringSlice(value) | |
case ClaimNonce: | |
c.Nonce, ok = value.(string) | |
case ClaimExpirationTime: | |
if c.ExpirationTime, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimIssuedAt: | |
if c.IssuedAt, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimRequestedAt: | |
if c.RequestedAt, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimAuthenticationTime: | |
if c.AuthTime, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimAuthenticationContextClassReference: | |
c.AuthenticationContextClassReference, ok = value.(string) | |
case ClaimAuthenticationMethodsReference: | |
c.AuthenticationMethodsReferences, ok = toStringSlice(value) | |
case ClaimAccessTokenHash: | |
c.AccessTokenHash, ok = value.(string) | |
case ClaimCodeHash: | |
c.CodeHash, ok = value.(string) | |
case ClaimStateHash: | |
c.StateHash, ok = value.(string) | |
default: | |
if c.Extra == nil { | |
c.Extra = make(map[string]any) | |
} | |
c.Extra[claim] = value | |
continue | |
} | |
if !ok { | |
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) | |
} | |
} | |
return nil | |
} | |
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { | |
claims := MapClaims{} | |
decoder := jjson.NewDecoder(bytes.NewReader(data)) | |
decoder.SetNumberType(jjson.UnmarshalIntOrFloat) | |
if err := decoder.Decode(&claims); err != nil { | |
return errorsx.WithStack(err) | |
} | |
var ( | |
ok bool | |
err error | |
) | |
for claim, value := range claims { | |
ok = false | |
switch claim { | |
case ClaimJWTID: | |
c.JTI, ok = value.(string) | |
case ClaimIssuer: | |
c.Issuer, ok = value.(string) | |
case ClaimSubject: | |
c.Subject, ok = value.(string) | |
case ClaimAudience: | |
c.Audience, ok = toStringSlice(value) | |
case ClaimNonce: | |
c.Nonce, ok = value.(string) | |
case ClaimExpirationTime: | |
if c.ExpirationTime, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimIssuedAt: | |
if c.IssuedAt, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimRequestedAt: | |
if c.RequestedAt, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimAuthenticationTime: | |
if c.AuthTime, err = toNumericDate(value); err == nil { | |
ok = true | |
} | |
case ClaimAuthenticationContextClassReference: | |
c.AuthenticationContextClassReference, ok = value.(string) | |
case ClaimAuthenticationMethodsReference: | |
c.AuthenticationMethodsReferences, ok = toStringSlice(value) | |
case ClaimAccessTokenHash: | |
c.AccessTokenHash, ok = value.(string) | |
case ClaimCodeHash: | |
c.CodeHash, ok = value.(string) | |
case ClaimStateHash: | |
c.StateHash, ok = value.(string) | |
default: | |
if c.Extra == nil { | |
c.Extra = make(map[string]any) | |
} | |
c.Extra[claim] = value | |
continue | |
} | |
if !ok { | |
return fmt.Errorf("claim %s could not be decoded", claim) | |
} | |
} | |
return nil | |
} |
6540167
to
3d978f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (1)
token/jwt/util.go (1)
19-71
: LGTM! Consider adding documentation for exported functions.The regular expressions and utility functions are well-implemented and provide necessary checks for JWT handling. However, it would be beneficial to add godoc comments for exported functions to improve code documentation.
Consider adding godoc comments for exported functions like
IsSignedJWT
,IsEncryptedJWT
,IsSignedJWTClientSecretAlgStr
,IsSignedJWTClientSecretAlg
,IsEncryptedJWTClientSecretAlg
, andIsEncryptedJWTPasswordBasedAlg
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- token/jwt/claims_id_token.go (4 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- token/jwt/claims_id_token_test.go
🔇 Additional comments (7)
token/jwt/claims_id_token.go (6)
24-35
: LGTM: ImprovedIDTokenClaims
structThe changes to the
IDTokenClaims
struct are well-implemented:
- Using
*NumericDate
for time-related fields allows for better handling of optional fields and aligns with JWT standards.- New fields have been added to support additional claims, improving the struct's flexibility.
These changes enhance the overall functionality and standards compliance of the
IDTokenClaims
struct.
38-60
: LGTM: Well-implemented getter methods with a minor concernThe new getter methods are a great addition:
- They provide a consistent interface for accessing claim values.
- The use of
*NumericDate
aligns with the struct field types.- Safe getter methods handle nil values gracefully.
However, there's a potential issue in the
GetNotBefore
method:func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { return toNumericDate(ClaimNotBefore) }This method seems to be calling
toNumericDate
with a constantClaimNotBefore
instead of a field from the struct. This might not work as intended.Could you please verify the implementation of the
GetNotBefore
method? It should probably return a field from the struct instead of callingtoNumericDate
.Also applies to: 141-171
Line range hint
246-334
: LGTM: Well-implemented updates to theToMap
methodThe updates to the
ToMap
method are well-implemented:
- The method correctly handles the new field types, including
*NumericDate
.- The use of the
consts
package for claim keys improves consistency and maintainability.- Nil values are handled appropriately by deleting the corresponding keys.
These changes ensure that the
ToMap
method accurately represents theIDTokenClaims
struct as a map, which is crucial for serialization and interoperability.
337-340
: LGTM: Useful addition ofToMapClaims
methodThe new
ToMapClaims
method is a valuable addition:
- It provides a convenient way to get a jwt-go
MapClaims
representation of theIDTokenClaims
.- This method enhances compatibility with the jwt-go library.
- The implementation is simple and efficient, leveraging the existing
ToMap
method.This addition improves the overall usability and interoperability of the
IDTokenClaims
struct.
356-398
: LGTM: Useful helper functionsThe new helper functions are well-implemented and improve the code's functionality:
toNumericDate
method:
- Provides a convenient way to convert claim values to
NumericDate
.- Handles the case where the claim might not exist in the
Extra
map.
toStringSlice
function:
- Handles various input types (nil, string, []string, []interface{}), making it versatile for different JSON representations.
- Provides clear type checking and conversion.
These functions enhance the overall robustness and flexibility of the claims handling in the package.
173-244
:⚠️ Potential issueFix potential exposure of sensitive data in error messages
The
UnmarshalJSON
method provides a thorough implementation for decoding JSON data into theIDTokenClaims
struct. It handles various claim types well and correctly populates theExtra
field for additional claims.However, there's a potential security issue:
On line 239, the error message includes the claim value, which could potentially expose sensitive information. Consider modifying the error message to exclude the value:
- return fmt.Errorf("claim %s with value %v could not be decoded", claim, value) + return fmt.Errorf("claim %s could not be decoded", claim)This change will prevent potential leakage of sensitive claim values in error messages.
Likely invalid or redundant comment.
token/jwt/util.go (1)
1-17
: LGTM! Package declaration and imports are appropriate.The package name "jwt" is suitable for the file's purpose, and the imports cover both standard library and necessary third-party packages for JWT handling.
func headerValidateJWS(headers []jose.Header) (kid, alg string, err error) { | ||
switch len(headers) { | ||
case 1: | ||
break | ||
case 0: | ||
return "", "", fmt.Errorf("jws header is missing") | ||
default: | ||
return "", "", fmt.Errorf("jws header is malformed") | ||
} | ||
|
||
if headers[0].Algorithm == "" && headers[0].KeyID == "" { | ||
return "", "", fmt.Errorf("jws header 'alg' and 'kid' values are missing or empty") | ||
} | ||
|
||
if headers[0].JSONWebKey != nil { | ||
return "", "", fmt.Errorf("jws header 'jwk' value is present but not supported") | ||
} | ||
|
||
return headers[0].KeyID, headers[0].Algorithm, nil | ||
} | ||
|
||
func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) { | ||
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) { | ||
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty") | ||
} | ||
|
||
if header.Algorithm == "" { | ||
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty") | ||
} | ||
|
||
var ( | ||
value any | ||
ok bool | ||
) | ||
|
||
if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) { | ||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]; ok { | ||
switch p2c := value.(type) { | ||
case float64: | ||
if p2c > 5000000 { | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': more than 5,000,000", int(p2c)) | ||
} else if p2c < 200000 { | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': less than 200,000", int(p2c)) | ||
} | ||
default: | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' value has invalid type %T", p2c) | ||
} | ||
} | ||
} | ||
|
||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]; ok { | ||
switch encv := value.(type) { | ||
case string: | ||
if encv != "" { | ||
enc = encv | ||
|
||
break | ||
} | ||
|
||
return "", "", "", "", fmt.Errorf("jwe header 'enc' value is empty") | ||
default: | ||
return "", "", "", "", fmt.Errorf("jwe header 'enc' value has invalid type %T", encv) | ||
} | ||
} | ||
|
||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderContentType]; ok { | ||
cty, _ = value.(string) | ||
} | ||
|
||
return header.KeyID, header.Algorithm, enc, cty, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving error handling and consistency in header validation functions.
The headerValidateJWS
and headerValidateJWE
functions perform important checks, but there are a few areas for improvement:
- Error messages could be more consistent and informative.
- The
headerValidateJWE
function has nested if statements that could be simplified. - Consider using custom error types for better error handling.
Here's a suggestion for improving the headerValidateJWE
function:
func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
if header.Algorithm == "" {
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
}
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
}
if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
if err := validatePBES2Count(header); err != nil {
return "", "", "", "", err
}
}
enc, err = validateEncryptionAlgorithm(header)
if err != nil {
return "", "", "", "", err
}
cty, _ = header.ExtraHeaders[JSONWebTokenHeaderContentType].(string)
return header.KeyID, header.Algorithm, enc, cty, nil
}
func validatePBES2Count(header jose.Header) error {
value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]
if !ok {
return nil
}
p2c, ok := value.(float64)
if !ok {
return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value)
}
if p2c < 200000 || p2c > 5000000 {
return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c))
}
return nil
}
func validateEncryptionAlgorithm(header jose.Header) (string, error) {
value, ok := header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]
if !ok {
return "", nil
}
enc, ok := value.(string)
if !ok {
return "", fmt.Errorf("jwe header 'enc' value has invalid type %T", value)
}
if enc == "" {
return "", fmt.Errorf("jwe header 'enc' value is empty")
}
return enc, nil
}
This refactoring improves readability, error handling, and consistency in the headerValidateJWE
function.
// PrivateKey properly describes crypto.PrivateKey. | ||
type PrivateKey interface { | ||
Public() crypto.PublicKey | ||
Equal(x crypto.PrivateKey) bool | ||
} | ||
|
||
const ( | ||
JWKLookupErrorClientNoJWKS uint32 = 1 << iota | ||
) | ||
|
||
type JWKLookupError struct { | ||
Description string | ||
Errors uint32 // bitfield. see JWKLookupError... constants | ||
} | ||
|
||
func (e *JWKLookupError) GetDescription() string { | ||
return e.Description | ||
} | ||
|
||
func (e *JWKLookupError) Error() string { | ||
return fmt.Sprintf("Error occurred retrieving the JSON Web Key. %s", e.Description) | ||
} | ||
|
||
// FindClientPublicJWK given a BaseClient, JWKSFetcherStrategy, and search parameters will return a *jose.JSONWebKey on | ||
// a valid match. The *jose.JSONWebKey is guaranteed to match the alg and use values, and if strict is true it must | ||
// match the kid value as well. | ||
func FindClientPublicJWK(ctx context.Context, client BaseClient, fetcher JWKSFetcherStrategy, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) { | ||
var ( | ||
keys *jose.JSONWebKeySet | ||
) | ||
|
||
if keys = client.GetJSONWebKeys(); keys != nil { | ||
return SearchJWKS(keys, kid, alg, use, strict) | ||
} | ||
|
||
if location := client.GetJSONWebKeysURI(); len(location) > 0 { | ||
if keys, err = fetcher.Resolve(ctx, location, false); err != nil { | ||
return nil, err | ||
} | ||
|
||
if key, err = SearchJWKS(keys, kid, alg, use, strict); err == nil { | ||
return key, nil | ||
} | ||
|
||
if keys, err = fetcher.Resolve(ctx, location, true); err != nil { | ||
return nil, err | ||
} | ||
|
||
return SearchJWKS(keys, kid, alg, use, strict) | ||
} | ||
|
||
return nil, &JWKLookupError{Description: "No JWKs have been registered for the client"} | ||
} | ||
|
||
func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) { | ||
if len(jwks.Keys) == 0 { | ||
return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."} | ||
} | ||
|
||
var keys []jose.JSONWebKey | ||
|
||
if kid == "" { | ||
keys = jwks.Keys | ||
} else { | ||
keys = jwks.Key(kid) | ||
} | ||
|
||
if len(keys) == 0 { | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)} | ||
} | ||
|
||
var matched []jose.JSONWebKey | ||
|
||
for _, k := range keys { | ||
if k.Use != use { | ||
continue | ||
} | ||
|
||
if k.Algorithm != alg { | ||
continue | ||
} | ||
|
||
matched = append(matched, k) | ||
} | ||
|
||
switch len(matched) { | ||
case 1: | ||
return &matched[0], nil | ||
case 0: | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} | ||
default: | ||
if strict { | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} | ||
} | ||
|
||
return &matched[0], nil | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving error handling and code organization in JWK lookup functions.
The FindClientPublicJWK
and SearchJWKS
functions are well-implemented, but there are a few areas for improvement:
- Error handling could be more consistent by using the custom
JWKLookupError
type throughout. - The
SearchJWKS
function could be simplified by using early returns. - Consider adding more detailed comments explaining the logic, especially for edge cases.
Here's a suggestion for improving the SearchJWKS
function:
func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (*jose.JSONWebKey, error) {
if len(jwks.Keys) == 0 {
return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."}
}
keys := jwks.Keys
if kid != "" {
keys = jwks.Key(kid)
}
if len(keys) == 0 {
return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)}
}
var matched []jose.JSONWebKey
for _, k := range keys {
if k.Use == use && k.Algorithm == alg {
matched = append(matched, k)
}
}
switch len(matched) {
case 0:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
case 1:
return &matched[0], nil
default:
if strict {
return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
}
return &matched[0], nil
}
}
This refactoring simplifies the logic, improves readability, and uses consistent error handling with the JWKLookupError
type.
token/jwt/util.go
Outdated
// NewClientSecretJWK returns a client secret based JWK from a client secret value. | ||
// | ||
// The symmetric encryption key is derived from the client_secret value by using the left-most bits of a truncated | ||
// SHA-2 hash of the octets of the UTF-8 representation of the client_secret. For keys of 256 or fewer bits, SHA-256 | ||
// is used; for keys of 257-384 bits, SHA-384 is used; for keys of 385-512 bits, SHA-512 is used. The hash value MUST | ||
// be truncated retaining the left-most bits to the appropriate bit length for the AES key wrapping or direct | ||
// encryption algorithm used, for instance, truncating the SHA-256 hash to 128 bits for A128KW. If a symmetric key with | ||
// greater than 512 bits is needed, a different method of deriving the key from the client_secret would have to be | ||
// defined by an extension. Symmetric encryption MUST NOT be used by public (non-confidential) Clients because of | ||
// their inability to keep secrets. | ||
func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (jwk *jose.JSONWebKey, err error) { | ||
if len(secret) == 0 { | ||
return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"} | ||
} | ||
|
||
switch use { | ||
case JSONWebTokenUseSignature: | ||
var ( | ||
hasher hash.Hash | ||
) | ||
|
||
switch jose.SignatureAlgorithm(alg) { | ||
case jose.HS256: | ||
hasher = sha256.New() | ||
case jose.HS384: | ||
hasher = sha512.New384() | ||
case jose.HS512: | ||
hasher = sha512.New() | ||
default: | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported algorithm '%s'", alg)} | ||
} | ||
|
||
if _, err = hasher.Write(secret); err != nil { | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())} | ||
} | ||
|
||
return &jose.JSONWebKey{ | ||
Key: hasher.Sum(nil), | ||
KeyID: kid, | ||
Algorithm: alg, | ||
Use: use, | ||
}, nil | ||
case JSONWebTokenUseEncryption: | ||
var ( | ||
hasher hash.Hash | ||
bits int | ||
) | ||
|
||
keyAlg := jose.KeyAlgorithm(alg) | ||
|
||
switch keyAlg { | ||
case jose.A128KW, jose.A128GCMKW, jose.A192KW, jose.A192GCMKW, jose.A256KW, jose.A256GCMKW, jose.PBES2_HS256_A128KW: | ||
hasher = sha256.New() | ||
case jose.PBES2_HS384_A192KW: | ||
hasher = sha512.New384() | ||
case jose.PBES2_HS512_A256KW, jose.DIRECT: | ||
hasher = sha512.New() | ||
default: | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported algorithm '%s'", alg)} | ||
} | ||
|
||
switch keyAlg { | ||
case jose.A128KW, jose.A128GCMKW, jose.PBES2_HS256_A128KW: | ||
bits = aes.BlockSize | ||
case jose.A192KW, jose.A192GCMKW, jose.PBES2_HS384_A192KW: | ||
bits = aes.BlockSize * 1.5 | ||
case jose.A256KW, jose.A256GCMKW, jose.PBES2_HS512_A256KW: | ||
bits = aes.BlockSize * 2 | ||
case jose.DIRECT: | ||
switch jose.ContentEncryption(enc) { | ||
case jose.A128CBC_HS256, "": | ||
bits = aes.BlockSize * 2 | ||
case jose.A192CBC_HS384: | ||
bits = aes.BlockSize * 3 | ||
case jose.A256CBC_HS512: | ||
bits = aes.BlockSize * 4 | ||
default: | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported content encryption for the direct key algorthm '%s'", enc)} | ||
} | ||
} | ||
|
||
if _, err = hasher.Write(secret); err != nil { | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())} | ||
} | ||
|
||
return &jose.JSONWebKey{ | ||
Key: hasher.Sum(nil)[:bits], | ||
KeyID: kid, | ||
Algorithm: alg, | ||
Use: use, | ||
}, nil | ||
default: | ||
return &jose.JSONWebKey{ | ||
Key: secret, | ||
KeyID: kid, | ||
Algorithm: alg, | ||
Use: use, | ||
}, nil | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider refactoring key derivation logic for better organization and error handling.
The NewClientSecretJWKFromClient
and NewClientSecretJWK
functions implement correct key derivation logic, but there are opportunities for improvement:
- The
NewClientSecretJWK
function is quite long and could be split into smaller, more focused functions. - Error handling could be more consistent by using custom error types throughout.
- Consider using constants for magic numbers and repeated strings.
Here's a suggestion for refactoring the NewClientSecretJWK
function:
func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (*jose.JSONWebKey, error) {
if len(secret) == 0 {
return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"}
}
switch use {
case JSONWebTokenUseSignature:
return newSignatureJWK(secret, kid, alg)
case JSONWebTokenUseEncryption:
return newEncryptionJWK(secret, kid, alg, enc)
default:
return &jose.JSONWebKey{
Key: secret,
KeyID: kid,
Algorithm: alg,
Use: use,
}, nil
}
}
func newSignatureJWK(secret []byte, kid, alg string) (*jose.JSONWebKey, error) {
hasher, err := getHasher(jose.SignatureAlgorithm(alg))
if err != nil {
return nil, err
}
if _, err := hasher.Write(secret); err != nil {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
}
return &jose.JSONWebKey{
Key: hasher.Sum(nil),
KeyID: kid,
Algorithm: alg,
Use: JSONWebTokenUseSignature,
}, nil
}
func newEncryptionJWK(secret []byte, kid, alg, enc string) (*jose.JSONWebKey, error) {
keyAlg := jose.KeyAlgorithm(alg)
hasher, err := getHasher(keyAlg)
if err != nil {
return nil, err
}
bits, err := getKeyBits(keyAlg, enc)
if err != nil {
return nil, err
}
if _, err := hasher.Write(secret); err != nil {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
}
return &jose.JSONWebKey{
Key: hasher.Sum(nil)[:bits],
KeyID: kid,
Algorithm: alg,
Use: JSONWebTokenUseEncryption,
}, nil
}
// Implement getHasher and getKeyBits functions...
This refactoring improves code organization, readability, and maintainability by splitting the large function into smaller, more focused functions.
func getJWTSignature(tokenString string) (signature string, err error) { | ||
switch segments := strings.SplitN(tokenString, ".", 5); len(segments) { | ||
case 5: | ||
return "", errors.WithStack(errors.New("invalid token: the token is probably encrypted")) | ||
case 3: | ||
return segments[2], nil | ||
default: | ||
return "", errors.WithStack(fmt.Errorf("invalid token: the format is unknown")) | ||
} | ||
} | ||
|
||
func assign(a, b map[string]any) map[string]any { | ||
for k, w := range b { | ||
if _, ok := a[k]; ok { | ||
continue | ||
} | ||
a[k] = w | ||
} | ||
return a | ||
} | ||
|
||
func getPublicJWK(jwk *jose.JSONWebKey) jose.JSONWebKey { | ||
if jwk == nil { | ||
return jose.JSONWebKey{} | ||
} | ||
|
||
if _, ok := jwk.Key.([]byte); ok && IsSignedJWTClientSecretAlgStr(jwk.Algorithm) { | ||
return jose.JSONWebKey{ | ||
KeyID: jwk.KeyID, | ||
Key: jwk.Key, | ||
Algorithm: jwk.Algorithm, | ||
Use: jwk.Use, | ||
Certificates: jwk.Certificates, | ||
CertificatesURL: jwk.CertificatesURL, | ||
CertificateThumbprintSHA1: jwk.CertificateThumbprintSHA1, | ||
CertificateThumbprintSHA256: jwk.CertificateThumbprintSHA256, | ||
} | ||
} | ||
|
||
return jwk.Public() | ||
} | ||
|
||
// UnsafeParseSignedAny is a function that will attempt to parse any signed token without any verification process. | ||
// It's unsafe for production and should only be used for tests. | ||
func UnsafeParseSignedAny(tokenString string, dest any) (token *jwt.JSONWebToken, err error) { | ||
if token, err = jwt.ParseSigned(tokenString, SignatureAlgorithmsNone); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err = token.UnsafeClaimsWithoutVerification(dest); err != nil { | ||
return nil, err | ||
} | ||
|
||
return token, nil | ||
} | ||
|
||
func newError(message string, err error, more ...error) error { | ||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving error handling and adding documentation for utility functions.
The utility functions are well-implemented, but there are a few areas for improvement:
- Adding godoc comments for exported functions would improve the package documentation.
- The
UnsafeParseSignedAny
function should have a more prominent warning about its unsafe nature. - Consider using custom error types for better error handling in some functions.
Here are some suggestions for improvement:
- Add godoc comments to exported functions, especially
UnsafeParseSignedAny
:
// UnsafeParseSignedAny attempts to parse any signed token without any verification process.
// WARNING: This function is unsafe for production use and should only be used for testing purposes.
// It may lead to security vulnerabilities if used with untrusted input.
func UnsafeParseSignedAny(tokenString string, dest any) (*jwt.JSONWebToken, error) {
// ... (existing implementation)
}
- Consider using a custom error type for
getJWTSignature
:
type JWTError struct {
Message string
}
func (e *JWTError) Error() string {
return e.Message
}
func getJWTSignature(tokenString string) (signature string, err error) {
segments := strings.SplitN(tokenString, ".", 5)
switch len(segments) {
case 5:
return "", &JWTError{"invalid token: the token is probably encrypted"}
case 3:
return segments[2], nil
default:
return "", &JWTError{"invalid token: the format is unknown"}
}
}
- The
newError
function could be simplified usingerrors.Join
from the standard library (Go 1.20+):
func newError(message string, err error, more ...error) error {
errors := make([]error, 0, len(more)+2)
errors = append(errors, err)
if message != "" {
errors = append(errors, fmt.Errorf(message))
}
errors = append(errors, more...)
return errors.Join(errors...)
}
These changes would improve the overall quality and maintainability of the utility functions.
3d978f4
to
2a7c7fa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (8)
token/jwt/claims_id_token.go (3)
39-61
: LGTM: New getter methods improve claim accessThe addition of getter methods for various claims is a good improvement. They provide a consistent interface for retrieving claim values and allow for proper error handling.
However, there's a potential issue in the
GetNotBefore
method:func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { return toNumericDate(ClaimNotBefore) }The
toNumericDate
function is called withClaimNotBefore
, which seems to be a constant string. This might not be the intended behavior. Consider updating this method to returnc.NotBefore
instead, similar to other getter methods.
63-140
: LGTM: Comprehensive claim validation implementationThe
Valid
method provides thorough validation of claims, covering expiration, issuance, audience, and other important checks. The use of aValidationError
struct for accumulating errors is a good practice.Consider refactoring this method to improve readability:
- Extract the validation logic for each claim type into separate methods.
- Use a slice of validation functions to iterate over, reducing the method's complexity.
Example refactoring:
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error { vopts := &ClaimValidationOptions{} for _, opt := range opts { opt(vopts) } now := getValidationTime(vopts) vErr := new(ValidationError) validations := []func(*ValidationError, int64, *ClaimValidationOptions){ c.validateExpiration, c.validateIssuedAt, c.validateNotBefore, c.validateIssuer, c.validateSubject, c.validateAudience, } for _, validate := range validations { validate(vErr, now, vopts) } if vErr.valid() { return nil } return vErr } func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) { if date, err := c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) { vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } } // Implement other validation methods similarly...This refactoring would make the
Valid
method more modular and easier to maintain.
174-251
: LGTM with a suggestion: Comprehensive JSON marshaling and unmarshalingThe
MarshalJSON
andUnmarshalJSON
methods provide thorough implementations for encoding and decoding JSON data. TheUnmarshalJSON
method handles various claim types well and correctly populates theExtra
field for additional claims.Consider refactoring the
UnmarshalJSON
method to improve readability:
- Extract the switch statement into a separate method.
- Use a map of claim handlers to reduce the size of the switch statement.
Example:
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error { // ... (existing code) handlers := map[string]func(any) error{ ClaimJWTID: func(v any) error { var ok bool c.JTI, ok = v.(string) return boolToError(ok) }, // ... (other handlers) } for claim, value := range claims { if handler, ok := handlers[claim]; ok { if err := handler(value); err != nil { return fmt.Errorf("claim %s could not be decoded", claim) } } else { if c.Extra == nil { c.Extra = make(map[string]any) } c.Extra[claim] = value } } return nil } func boolToError(ok bool) error { if !ok { return errors.New("invalid type") } return nil }This refactoring would make the
UnmarshalJSON
method more modular and easier to maintain.token/jwt/util.go (5)
73-143
: LGTM: Header validation functions are thorough and well-implemented.The
headerValidateJWS
andheaderValidateJWE
functions perform comprehensive checks on JWT headers. The error messages are descriptive, which will aid in debugging.Consider extracting the PBES2 count validation in
headerValidateJWE
into a separate function for better readability and maintainability. For example:func validatePBES2Count(header jose.Header) error { value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count] if !ok { return nil } p2c, ok := value.(float64) if !ok { return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value) } if p2c < 200000 || p2c > 5000000 { return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c)) } return nil }This would simplify the
headerValidateJWE
function and make the code more modular.
145-166
: LGTM: PrivateKey interface and JWKLookupError type are well-defined.The
PrivateKey
interface andJWKLookupError
type provide good abstractions for handling private keys and JWK lookup errors respectively.Consider adding comments to explain the purpose of the
JWKLookupErrorClientNoJWKS
constant and how theErrors
bitfield inJWKLookupError
should be used. This would improve the clarity of the code for future maintainers. For example:// JWKLookupErrorClientNoJWKS indicates that the client has no JWKS configured const ( JWKLookupErrorClientNoJWKS uint32 = 1 << iota // Add more error constants here as needed ) // JWKLookupError represents an error that occurred during JWK lookup type JWKLookupError struct { Description string Errors uint32 // Bitfield of error conditions. Use JWKLookupError... constants }
168-242
: LGTM: FindClientPublicJWK and SearchJWKS functions are well-implemented.The
FindClientPublicJWK
andSearchJWKS
functions provide robust functionality for retrieving and searching JWKs. The error handling is consistent, and the logic covers various scenarios.Consider refactoring the
SearchJWKS
function to improve readability and reduce nesting. Here's a suggestion:func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (*jose.JSONWebKey, error) { if len(jwks.Keys) == 0 { return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."} } keys := jwks.Keys if kid != "" { keys = jwks.Key(kid) } if len(keys) == 0 { return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)} } var matched []jose.JSONWebKey for _, k := range keys { if k.Use == use && k.Algorithm == alg { matched = append(matched, k) } } switch len(matched) { case 0: return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} case 1: return &matched[0], nil default: if strict { return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} } return &matched[0], nil } }This refactored version reduces nesting and improves the overall structure of the function.
244-361
: LGTM: NewClientSecretJWKFromClient and NewClientSecretJWK functions are well-implemented.The functions for creating JWKs from client secrets are thorough and follow the specified rules for key derivation. The error handling is consistent, and the code is well-documented.
Consider refactoring the
NewClientSecretJWK
function to reduce its complexity. You could extract the logic for creating signature and encryption JWKs into separate functions. For example:func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (*jose.JSONWebKey, error) { if len(secret) == 0 { return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"} } switch use { case JSONWebTokenUseSignature: return newSignatureJWK(secret, kid, alg) case JSONWebTokenUseEncryption: return newEncryptionJWK(secret, kid, alg, enc) default: return &jose.JSONWebKey{ Key: secret, KeyID: kid, Algorithm: alg, Use: use, }, nil } } func newSignatureJWK(secret []byte, kid, alg string) (*jose.JSONWebKey, error) { // Implementation for signature JWK } func newEncryptionJWK(secret []byte, kid, alg, enc string) (*jose.JSONWebKey, error) { // Implementation for encryption JWK }This refactoring would improve readability and make the function easier to maintain.
363-469
: LGTM: Utility functions are well-implemented with room for minor improvements.The utility functions provide a good set of tools for JWT operations. The
UnsafeParseSignedAny
function is correctly marked as unsafe for production use.
In the
EncodeCompactSigned
andEncodeNestedCompactEncrypted
functions, consider adding error checking for theSetJWS
andSetJWE
method calls.The
getJWTSignature
function could be simplified using a switch statement:func getJWTSignature(tokenString string) (signature string, err error) { switch segments := strings.SplitN(tokenString, ".", 5); len(segments) { case 5: return "", errors.New("invalid token: the token is probably encrypted") case 3: return segments[2], nil default: return "", fmt.Errorf("invalid token: the format is unknown") } }
- The
newError
function could be simplified usingerrors.Join
from the standard library (Go 1.20+):func newError(message string, err error, more ...error) error { errors := make([]error, 0, len(more)+2) errors = append(errors, err) if message != "" { errors = append(errors, fmt.Errorf(message)) } errors = append(errors, more...) return errors.Join(errors...) }These changes would improve the readability and maintainability of the code.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- token/jwt/claims_id_token.go (4 hunks)
- token/jwt/claims_id_token_test.go (3 hunks)
- token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- token/jwt/claims_id_token_test.go
🔇 Additional comments (7)
token/jwt/claims_id_token.go (4)
20-36
: LGTM: Improved claim type definitionsThe changes to the
IDTokenClaims
struct, particularly the use of*NumericDate
for time-based claims, enhance compatibility with JWT standards and improve JSON handling. The addition of new fields likeExpirationTime
provides more comprehensive claim support.
142-172
: LGTM: Safe retrieval methods for time-based claimsThe addition of safe retrieval methods for time-based claims (
GetExpirationTimeSafe
,GetIssuedAtSafe
,GetAuthTimeSafe
,GetRequestedAtSafe
) is a good practice. These methods prevent nil pointer dereferences by returning a zero time when the field is nil, enhancing the overall robustness of the code.
Line range hint
254-341
: LGTM: ImprovedToMap
methodThe updates to the
ToMap
method correctly reflect the changes made to theIDTokenClaims
struct. The use of constants from theconsts
package for claim keys is a good practice that improves consistency and reduces the likelihood of errors due to typos. The method now handles the new field types appropriately, ensuring correct mapping of claims.
363-405
: LGTM: Useful helper functions addedThe addition of helper functions
toNumericDate
andtoStringSlice
is a good improvement. These functions provide robust type conversion for numeric dates and string slices, enhancing the JSON unmarshaling process. ThetoStringSlice
function, in particular, handles various input types gracefully, improving the flexibility of the claims parsing.token/jwt/util.go (3)
1-22
: LGTM: Package declaration, imports, and regular expressions are well-defined.The package name, imports, and regular expressions for JWT validation are appropriate and correctly implemented.
24-71
: LGTM: Utility functions for JWT and algorithm checks are well-implemented.The functions for checking JWT types and algorithms are correctly implemented and cover a wide range of cases. The code is clear and easy to understand.
1-469
: Overall, the JWT utility implementation is robust and well-structured.This file provides a comprehensive set of functions for JWT and JWK handling. The code is generally well-organized, documented, and implements proper error handling. The suggested improvements throughout the review are minor and mostly relate to code organization, readability, and small optimizations.
Key strengths:
- Thorough implementation of JWT and JWK operations
- Consistent error handling using custom error types
- Well-documented functions with clear purposes
Areas for potential improvement:
- Further modularization of some larger functions
- Minor readability enhancements in certain areas
- Possible performance optimizations in some utility functions
These improvements can be addressed in future iterations to further enhance the quality of the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- token/jwt/claims_id_token.go (4 hunks)
🔇 Additional comments (1)
token/jwt/claims_id_token.go (1)
358-368
: 🛠️ Refactor suggestionUse pointer receiver for
toNumericDate
methodThe
toNumericDate
method uses a value receiver but accesses pointer fields. To prevent unnecessary copying and to access the pointer fields correctly, change the method to use a pointer receiver.Apply this diff:
-func (c IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) { +func (c *IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) {Likely invalid or redundant comment.
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { | ||
return toNumericDate(ClaimNotBefore) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix missing receiver in GetNotBefore
method
The method GetNotBefore
calls toNumericDate
without the receiver c.
, which will result in a compilation error since toNumericDate
is a method of IDTokenClaims
. Please add the receiver c.
to the method call.
Apply this diff to fix the issue:
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
- return toNumericDate(ClaimNotBefore)
+ return c.toNumericDate(ClaimNotBefore)
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { | |
return toNumericDate(ClaimNotBefore) | |
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) { | |
return c.toNumericDate(ClaimNotBefore) |
token/jwt/claims_id_token.go
Outdated
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) { | ||
vopts := &ClaimValidationOptions{} | ||
|
||
for _, opt := range opts { | ||
opt(vopts) | ||
} | ||
|
||
if c.Issuer != "" { | ||
ret[consts.ClaimIssuer] = c.Issuer | ||
var now int64 | ||
|
||
if vopts.timef != nil { | ||
now = vopts.timef().UTC().Unix() | ||
} else { | ||
delete(ret, consts.ClaimIssuer) | ||
now = TimeFunc().UTC().Unix() | ||
} | ||
|
||
vErr := new(ValidationError) | ||
|
||
var date *NumericDate | ||
|
||
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token is expired") | ||
vErr.Errors |= ValidationErrorExpired | ||
} | ||
|
||
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token used before issued") | ||
vErr.Errors |= ValidationErrorIssuedAt | ||
} | ||
|
||
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | ||
vErr.Inner = errors.New("Token is not valid yet") | ||
vErr.Errors |= ValidationErrorNotValidYet | ||
} | ||
|
||
var str string | ||
|
||
if len(vopts.iss) != 0 { | ||
if str, err = c.GetIssuer(); err != nil { | ||
vErr.Inner = errors.New("Token has invalid issuer") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} else if !validString(str, vopts.iss, true) { | ||
vErr.Inner = errors.New("Token has invalid issuer") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} | ||
} | ||
|
||
if len(vopts.sub) != 0 { | ||
if str, err = c.GetSubject(); err != nil { | ||
vErr.Inner = errors.New("Token has invalid subject") | ||
vErr.Errors |= ValidationErrorIssuer | ||
} else if !validString(str, vopts.sub, true) { | ||
vErr.Inner = errors.New("Token has invalid subject") | ||
vErr.Errors |= ValidationErrorSubject | ||
} | ||
} | ||
|
||
var aud ClaimStrings | ||
|
||
if len(vopts.aud) != 0 { | ||
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) { | ||
vErr.Inner = errors.New("Token has invalid audience") | ||
vErr.Errors |= ValidationErrorAudience | ||
} | ||
} | ||
|
||
if len(vopts.audAll) != 0 { | ||
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) { | ||
vErr.Inner = errors.New("Token has invalid audience") | ||
vErr.Errors |= ValidationErrorAudience | ||
} | ||
} | ||
|
||
if vErr.valid() { | ||
return nil | ||
} | ||
|
||
return vErr | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use pointer receiver for Valid
method
The Valid
method uses a value receiver, but since it accesses pointer fields and for consistency with other methods, consider using a pointer receiver to avoid unnecessary copying and potential issues.
Apply this diff:
-func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
+func (c *IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) { | |
vopts := &ClaimValidationOptions{} | |
for _, opt := range opts { | |
opt(vopts) | |
} | |
if c.Issuer != "" { | |
ret[consts.ClaimIssuer] = c.Issuer | |
var now int64 | |
if vopts.timef != nil { | |
now = vopts.timef().UTC().Unix() | |
} else { | |
delete(ret, consts.ClaimIssuer) | |
now = TimeFunc().UTC().Unix() | |
} | |
vErr := new(ValidationError) | |
var date *NumericDate | |
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token is expired") | |
vErr.Errors |= ValidationErrorExpired | |
} | |
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token used before issued") | |
vErr.Errors |= ValidationErrorIssuedAt | |
} | |
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token is not valid yet") | |
vErr.Errors |= ValidationErrorNotValidYet | |
} | |
var str string | |
if len(vopts.iss) != 0 { | |
if str, err = c.GetIssuer(); err != nil { | |
vErr.Inner = errors.New("Token has invalid issuer") | |
vErr.Errors |= ValidationErrorIssuer | |
} else if !validString(str, vopts.iss, true) { | |
vErr.Inner = errors.New("Token has invalid issuer") | |
vErr.Errors |= ValidationErrorIssuer | |
} | |
} | |
if len(vopts.sub) != 0 { | |
if str, err = c.GetSubject(); err != nil { | |
vErr.Inner = errors.New("Token has invalid subject") | |
vErr.Errors |= ValidationErrorIssuer | |
} else if !validString(str, vopts.sub, true) { | |
vErr.Inner = errors.New("Token has invalid subject") | |
vErr.Errors |= ValidationErrorSubject | |
} | |
} | |
var aud ClaimStrings | |
if len(vopts.aud) != 0 { | |
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) { | |
vErr.Inner = errors.New("Token has invalid audience") | |
vErr.Errors |= ValidationErrorAudience | |
} | |
} | |
if len(vopts.audAll) != 0 { | |
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) { | |
vErr.Inner = errors.New("Token has invalid audience") | |
vErr.Errors |= ValidationErrorAudience | |
} | |
} | |
if vErr.valid() { | |
return nil | |
} | |
return vErr | |
} | |
func (c *IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) { | |
vopts := &ClaimValidationOptions{} | |
for _, opt := range opts { | |
opt(vopts) | |
} | |
var now int64 | |
if vopts.timef != nil { | |
now = vopts.timef().UTC().Unix() | |
} else { | |
now = TimeFunc().UTC().Unix() | |
} | |
vErr := new(ValidationError) | |
var date *NumericDate | |
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token is expired") | |
vErr.Errors |= ValidationErrorExpired | |
} | |
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token used before issued") | |
vErr.Errors |= ValidationErrorIssuedAt | |
} | |
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) { | |
vErr.Inner = errors.New("Token is not valid yet") | |
vErr.Errors |= ValidationErrorNotValidYet | |
} | |
var str string | |
if len(vopts.iss) != 0 { | |
if str, err = c.GetIssuer(); err != nil { | |
vErr.Inner = errors.New("Token has invalid issuer") | |
vErr.Errors |= ValidationErrorIssuer | |
} else if !validString(str, vopts.iss, true) { | |
vErr.Inner = errors.New("Token has invalid issuer") | |
vErr.Errors |= ValidationErrorIssuer | |
} | |
} | |
if len(vopts.sub) != 0 { | |
if str, err = c.GetSubject(); err != nil { | |
vErr.Inner = errors.New("Token has invalid subject") | |
vErr.Errors |= ValidationErrorIssuer | |
} else if !validString(str, vopts.sub, true) { | |
vErr.Inner = errors.New("Token has invalid subject") | |
vErr.Errors |= ValidationErrorSubject | |
} | |
} | |
var aud ClaimStrings | |
if len(vopts.aud) != 0 { | |
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) { | |
vErr.Inner = errors.New("Token has invalid audience") | |
vErr.Errors |= ValidationErrorAudience | |
} | |
} | |
if len(vopts.audAll) != 0 { | |
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) { | |
vErr.Inner = errors.New("Token has invalid audience") | |
vErr.Errors |= ValidationErrorAudience | |
} | |
} | |
if vErr.valid() { | |
return nil | |
} | |
return vErr | |
} |
94f63ef
to
7637fe0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Outside diff range and nitpick comments (1)
token/jwt/util.go (1)
438-450
: Enhance warning forUnsafeParseSignedAny
and add documentation.The
UnsafeParseSignedAny
function is unsafe for production use.Add a prominent warning and documentation:
// UnsafeParseSignedAny attempts to parse any signed token without verification. // WARNING: This function is unsafe for production use and should only be used for testing purposes. func UnsafeParseSignedAny(tokenString string, dest any) (token *jwt.JSONWebToken, err error) { // Function implementation... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- token/jwt/claims_id_token.go (3 hunks)
- token/jwt/claims_map.go (1 hunks)
- token/jwt/util.go (1 hunks)
🧰 Additional context used
🔇 Additional comments (7)
token/jwt/claims_id_token.go (4)
39-41
: LGTM:GetExpirationTime
method correctly implementedThe
GetExpirationTime
method properly retrieves theExpirationTime
claim and is correctly implemented.
47-49
: Fix missing receiver inGetNotBefore
methodThe
GetNotBefore
method callstoNumericDate
without the receiverc.
, which will result in a compilation error sincetoNumericDate
is a method ofIDTokenClaims
. This issue was previously flagged in a past review comment.
63-140
: Consider using a pointer receiver for theValid
methodThe
Valid
method uses a value receiver, which may lead to unnecessary copying of the struct and inconsistent receiver types across methods. This concern was previously raised in a past review comment.
174-246
: Avoid including claim values in error messagesIn the
UnmarshalJSON
method, the error message includes the claim value, which could potentially expose sensitive information. This issue was previously raised in a past review comment.token/jwt/util.go (3)
74-144
: Consider improving error handling and consistency in header validation functions.The
headerValidateJWS
andheaderValidateJWE
functions perform essential checks but can be enhanced:
- Consistency in Error Messages: Ensure error messages are uniform and provide clear information.
- Simplify Nested Conditions: Refactor nested
if
statements for better readability.- Use Helper Functions: Extract repeated logic into helper functions to reduce duplication.
200-243
: Address potential non-deterministic key selection inSearchJWKS
.When multiple keys match the criteria and
strict
isfalse
, the function returns the first matched key. Since the order of keys injwks.Keys
is not guaranteed, this could lead to unpredictable behavior.
308-331
:⚠️ Potential issueClarify key length units to prevent confusion between bits and bytes.
In the
NewClientSecretJWK
function, the variablebits
represents the key length in bytes, but the name suggests bits. This can cause confusion.Rename
bits
tokeyLengthBytes
for clarity:var ( - hasher hash.Hash - bits int + hasher hash.Hash + keyLengthBytes int )Update the assignments accordingly:
switch keyAlg { case jose.A128KW, jose.A128GCMKW, jose.PBES2_HS256_A128KW: - bits = aes.BlockSize + keyLengthBytes = aes.BlockSizeMake similar changes for other cases where
bits
is assigned.
// NewMapClaims returns a set of MapClaims from an object that has the appropriate JSON tags. | ||
func NewMapClaims(obj any) (claims MapClaims) { | ||
return toMap(obj) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undefined Function: toMap
is not defined
The function toMap
used in NewMapClaims
is not defined within this file or imported from any package. This will result in a compile-time error.
Please provide the definition of toMap
or ensure it is properly imported.
return m.toString(ClaimSubject) | ||
} | ||
|
||
// VerifySubject compares the iss claim against cmp. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in Comment: Incorrect Claim Reference
The comment for VerifySubject
incorrectly references the 'iss' claim instead of the 'sub' claim. This might cause confusion for future maintainers.
Apply this diff to fix the comment:
-// VerifySubject compares the iss claim against cmp.
+// VerifySubject compares the sub claim against cmp.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// VerifySubject compares the iss claim against cmp. | |
// VerifySubject compares the sub claim against cmp. |
ExpirationTime *NumericDate `json:"exp"` | ||
IssuedAt *NumericDate `json:"iat"` | ||
AuthTime *NumericDate `json:"auth_time,omitempty"` | ||
RequestedAt *NumericDate `json:"rat,omitempty"` | ||
Nonce string `json:"nonce,omitempty"` | ||
AuthenticationContextClassReference string `json:"acr,omitempty"` | ||
AuthenticationMethodsReferences []string `json:"amr,omitempty"` | ||
AuthorizedParty string `json:"azp,omitempty"` | ||
AccessTokenHash string `json:"at_hash,omitempty"` | ||
CodeHash string `json:"c_hash,omitempty"` | ||
StateHash string `json:"s_hash,omitempty"` | ||
Extra map[string]any `json:"ext,omitempty"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing 'Not Before' (nbf
) claim in IDTokenClaims
struct
The IDTokenClaims
struct lacks the NotBefore
(nbf
) claim, which is a standard JWT claim representing the time before which the token is valid. Since methods like GetNotBefore()
expect this field, its absence may lead to runtime errors or incorrect validations.
Consider adding the NotBefore
field to the struct:
+ NotBefore *NumericDate `json:"nbf,omitempty"`
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
ExpirationTime *NumericDate `json:"exp"` | |
IssuedAt *NumericDate `json:"iat"` | |
AuthTime *NumericDate `json:"auth_time,omitempty"` | |
RequestedAt *NumericDate `json:"rat,omitempty"` | |
Nonce string `json:"nonce,omitempty"` | |
AuthenticationContextClassReference string `json:"acr,omitempty"` | |
AuthenticationMethodsReferences []string `json:"amr,omitempty"` | |
AuthorizedParty string `json:"azp,omitempty"` | |
AccessTokenHash string `json:"at_hash,omitempty"` | |
CodeHash string `json:"c_hash,omitempty"` | |
StateHash string `json:"s_hash,omitempty"` | |
Extra map[string]any `json:"ext,omitempty"` | |
} | |
ExpirationTime *NumericDate `json:"exp"` | |
IssuedAt *NumericDate `json:"iat"` | |
AuthTime *NumericDate `json:"auth_time,omitempty"` | |
RequestedAt *NumericDate `json:"rat,omitempty"` | |
NotBefore *NumericDate `json:"nbf,omitempty"` | |
Nonce string `json:"nonce,omitempty"` | |
AuthenticationContextClassReference string `json:"acr,omitempty"` | |
AuthenticationMethodsReferences []string `json:"amr,omitempty"` | |
AuthorizedParty string `json:"azp,omitempty"` | |
AccessTokenHash string `json:"at_hash,omitempty"` | |
CodeHash string `json:"c_hash,omitempty"` | |
StateHash string `json:"s_hash,omitempty"` | |
Extra map[string]any `json:"ext,omitempty"` | |
} |
func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) { | ||
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) { | ||
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty") | ||
} | ||
|
||
if header.Algorithm == "" { | ||
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty") | ||
} | ||
|
||
var ( | ||
value any | ||
ok bool | ||
) | ||
|
||
if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) { | ||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]; ok { | ||
switch p2c := value.(type) { | ||
case float64: | ||
if p2c > 5000000 { | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': more than 5,000,000", int(p2c)) | ||
} else if p2c < 200000 { | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': less than 200,000", int(p2c)) | ||
} | ||
default: | ||
return "", "", "", "", fmt.Errorf("jwe header 'p2c' value has invalid type %T", p2c) | ||
} | ||
} | ||
} | ||
|
||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]; ok { | ||
switch encv := value.(type) { | ||
case string: | ||
if encv != "" { | ||
enc = encv | ||
|
||
break | ||
} | ||
|
||
return "", "", "", "", fmt.Errorf("jwe header 'enc' value is empty") | ||
default: | ||
return "", "", "", "", fmt.Errorf("jwe header 'enc' value has invalid type %T", encv) | ||
} | ||
} | ||
|
||
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderContentType]; ok { | ||
cty, _ = value.(string) | ||
} | ||
|
||
return header.KeyID, header.Algorithm, enc, cty, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor headerValidateJWE
for improved readability and error handling.
To enhance the headerValidateJWE
function, consider the following refactoring:
- Extract Validation Logic into Helper Functions: Separate the validation of
p2c
andenc
into dedicated functions. - Improve Error Messages: Provide detailed and consistent error messages for easier debugging.
Apply this refactored version:
func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
if header.Algorithm == "" {
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
}
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
}
if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
if err := validatePBES2Count(header); err != nil {
return "", "", "", "", err
}
}
enc, err = validateEncryptionAlgorithm(header)
if err != nil {
return "", "", "", "", err
}
cty, _ = header.ExtraHeaders[JSONWebTokenHeaderContentType].(string)
return header.KeyID, header.Algorithm, enc, cty, nil
}
func validatePBES2Count(header jose.Header) error {
value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]
if !ok {
return nil
}
p2c, ok := value.(float64)
if !ok {
return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value)
}
if p2c < 200000 || p2c > 5000000 {
return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c))
}
return nil
}
func validateEncryptionAlgorithm(header jose.Header) (string, error) {
value, ok := header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]
if !ok {
return "", nil
}
enc, ok := value.(string)
if !ok {
return "", fmt.Errorf("jwe header 'enc' value has invalid type %T", value)
}
if enc == "" {
return "", fmt.Errorf("jwe header 'enc' value is empty")
}
return enc, nil
}
func toMap(obj any) (result map[string]any) { | ||
result = map[string]any{} | ||
|
||
if obj == nil { | ||
return result | ||
} | ||
|
||
v := reflect.TypeOf(obj) | ||
|
||
reflectValue := reflect.ValueOf(obj) | ||
reflectValue = reflect.Indirect(reflectValue) | ||
|
||
if v.Kind() == reflect.Ptr { | ||
v = v.Elem() | ||
} | ||
|
||
for i := 0; i < v.NumField(); i++ { | ||
tag, opts := parseTag(v.Field(i).Tag.Get("json")) | ||
field := reflectValue.Field(i).Interface() | ||
if tag != "" && tag != "-" { | ||
if opts.Contains("omitempty") && isEmptyValue(reflect.ValueOf(field)) { | ||
continue | ||
} | ||
|
||
if v.Field(i).Type.Kind() == reflect.Struct { | ||
result[tag] = toMap(field) | ||
} else { | ||
result[tag] = field | ||
} | ||
} | ||
} | ||
|
||
return result | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize toMap
function for better performance and simplicity.
The toMap
function can be simplified by using JSON marshaling.
Consider using json.Marshal
and json.Unmarshal
:
import "encoding/json"
func toMap(obj any) (result map[string]any) {
var inrec, outrec []byte
var err error
if inrec, err = json.Marshal(obj); err != nil {
return nil
}
if err = json.Unmarshal(inrec, &result); err != nil {
return nil
}
return result
}
This approach leverages standard library functions for serialization, reducing complexity.
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding. | ||
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) { | ||
token := New() | ||
|
||
if headers == nil { | ||
headers = &Headers{} | ||
} | ||
|
||
token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)) | ||
|
||
return token.CompactSigned(key) | ||
} | ||
|
||
// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within | ||
// an encrypted compact encoded JWT. | ||
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) { | ||
token := New() | ||
|
||
if headers == nil { | ||
headers = &Headers{} | ||
} | ||
|
||
if headersJWE == nil { | ||
headersJWE = &Headers{} | ||
} | ||
|
||
token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)) | ||
token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE) | ||
|
||
return token.CompactEncrypted(keySig, keyEnc) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling in JWT encoding functions.
The EncodeCompactSigned
and EncodeNestedCompactEncrypted
functions lack error checks after critical operations.
For EncodeCompactSigned
, add error handling after setting the JWS:
token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))
+if err != nil {
+ return "", "", fmt.Errorf("failed to set JWS: %w", err)
+}
Similarly, add error handling in EncodeNestedCompactEncrypted
:
token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
+if err != nil {
+ return "", "", fmt.Errorf("failed to set JWS: %w", err)
+}
token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)
+if err != nil {
+ return "", "", fmt.Errorf("failed to set JWE: %w", err)
+}
This ensures that any failures during token setup are caught and properly handled.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding. | |
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) { | |
token := New() | |
if headers == nil { | |
headers = &Headers{} | |
} | |
token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)) | |
return token.CompactSigned(key) | |
} | |
// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within | |
// an encrypted compact encoded JWT. | |
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) { | |
token := New() | |
if headers == nil { | |
headers = &Headers{} | |
} | |
if headersJWE == nil { | |
headersJWE = &Headers{} | |
} | |
token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)) | |
token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE) | |
return token.CompactEncrypted(keySig, keyEnc) | |
} | |
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding. | |
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) { | |
token := New() | |
if headers == nil { | |
headers = &Headers{} | |
} | |
err = token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)) | |
if err != nil { | |
return "", "", fmt.Errorf("failed to set JWS: %w", err) | |
} | |
return token.CompactSigned(key) | |
} | |
// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within | |
// an encrypted compact encoded JWT. | |
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) { | |
token := New() | |
if headers == nil { | |
headers = &Headers{} | |
} | |
if headersJWE == nil { | |
headersJWE = &Headers{} | |
} | |
err = token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)) | |
if err != nil { | |
return "", "", fmt.Errorf("failed to set JWS: %w", err) | |
} | |
err = token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE) | |
if err != nil { | |
return "", "", fmt.Errorf("failed to set JWE: %w", err) | |
} | |
return token.CompactEncrypted(keySig, keyEnc) | |
} |
switch len(matched) { | ||
case 1: | ||
return &matched[0], nil | ||
case 0: | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} | ||
default: | ||
if strict { | ||
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)} | ||
} | ||
|
||
return &matched[0], nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure deterministic key selection when multiple keys match.
To prevent non-deterministic behavior in SearchJWKS
, consider implementing a deterministic key selection mechanism:
- Sort the Keys: Sort the
matched
keys based on a consistent attribute (e.g.,KeyID
). - Require Specific Parameters: Enforce stricter criteria or require additional parameters to uniquely identify the key.
Apply this diff to enhance the function:
+import "sort"
func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) {
// ... existing code ...
switch len(matched) {
case 1:
return &matched[0], nil
case 0:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
default:
+ // Sort the matched keys to ensure deterministic selection
+ sort.SliceStable(matched, func(i, j int) bool {
+ return matched[i].KeyID < matched[j].KeyID
+ })
+
if strict {
return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
}
return &matched[0], nil
}
}
Committable suggestion was skipped due to low confidence.
f157069
to
3deacdc
Compare
53abe26
to
b11ab45
Compare
b11ab45
to
c497ea6
Compare
No description provided.