feat: Create API Token Expiration Parameter. BED-7448#2420
feat: Create API Token Expiration Parameter. BED-7448#2420RaymondLaubert wants to merge 4 commits intomainfrom
Conversation
… Column, made Methods for the APITokenExpiration Parameter in the parameter file, and created unit test for the APITokenExpiration Parameter. BED-7448
📝 WalkthroughWalkthroughThis pull request introduces a new API token expiration configuration parameter across the database schema, parameter model, and test suite. A database migration adds the parameter entry, a new parameter type is defined with getter logic, and an integration test verifies retrieval of the configuration. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
cmd/api/src/database/parameters_test.go (1)
221-223: Use a non-defaultexpiration_periodto harden this test.Using
expirationPeriod = 90matches the function default, so the period assertion is weaker against fallback behavior.💡 Suggested tweak
- expirationPeriod = 90 + expirationPeriod = 30Also applies to: 235-236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/api/src/database/parameters_test.go` around lines 221 - 223, The test sets expirationPeriod = 90 which equals the production/default value and weakens assertions; change expirationPeriod to a non-default value (e.g., 30 or another distinct integer) in the test so the assertion validates the test input rather than the function default, and update both occurrences referenced (the declaration with apiKeyExpiration/expirationPeriod and the later use at lines 235-236) to use the new non-default value; ensure any assertions that compare against the expected period use this new constant (expirationPeriod) so the test remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/api/src/database/parameters_test.go`:
- Around line 233-236: The test assigns to valObtained without declaring it;
change the assignment to declare the variable (e.g., use valObtained :=
appcfg.GetAPITokenExpirationParameter(testCtx, db)) or explicitly declare var
valObtained <ReturnType> then assign, so valObtained is in scope for the
subsequent require.Equal checks that reference it and aligns with the return
type of GetAPITokenExpirationParameter.
In `@cmd/api/src/model/appcfg/parameter.go`:
- Around line 584-601: The GetAPITokenExpirationParameter function must validate
the mapped ExpirationPeriod to prevent zero/negative (and unreasonably large)
values from being used; after cfg.Map(&result) succeeds, check
result.ExpirationPeriod and if it's < 1 or > 3650 (or whatever agreed max)
replace it with the default (90) and emit a slog.WarnContext including the
invalid value and APITokenExpiration key; keep APITokenExpirationParameter,
GetAPITokenExpirationParameter, APITokenExpiration, and ParameterService as the
touch points and perform the validation immediately after cfg.Map so runtime
config cannot set invalid expirations.
---
Nitpick comments:
In `@cmd/api/src/database/parameters_test.go`:
- Around line 221-223: The test sets expirationPeriod = 90 which equals the
production/default value and weakens assertions; change expirationPeriod to a
non-default value (e.g., 30 or another distinct integer) in the test so the
assertion validates the test input rather than the function default, and update
both occurrences referenced (the declaration with
apiKeyExpiration/expirationPeriod and the later use at lines 235-236) to use the
new non-default value; ensure any assertions that compare against the expected
period use this new constant (expirationPeriod) so the test remains consistent.
ℹ️ Review info
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cmd/api/src/database/migration/migrations/v8.7.0.sqlcmd/api/src/database/parameters_test.gocmd/api/src/model/appcfg/parameter.go
| valObtained = appcfg.GetAPITokenExpirationParameter(testCtx, db) | ||
|
|
||
| require.Equal(t, apiKeyExpiration, valObtained.Enabled) | ||
| require.Equal(t, expirationPeriod, valObtained.ExpirationPeriod) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for valObtained usage and declarations
rg -n -C2 '\bvalObtained\b' cmd/api/src/database/parameters_test.go
echo "---"
rg -nP '\bvalObtained\s*:=' cmd/api/src/database/parameters_test.goRepository: SpecterOps/BloodHound
Length of output: 294
🏁 Script executed:
#!/bin/bash
# Check for any prior declaration of valObtained in broader context
rg -n '\bvalObtained\b' cmd/api/src/database/parameters_test.go
echo "---"
# Check the full function to see if there's a var block declaration
sed -n '200,237p' cmd/api/src/database/parameters_test.goRepository: SpecterOps/BloodHound
Length of output: 1407
Build breaker: valObtained is never declared.
At line 233, valObtained is assigned with =, but no prior declaration exists in scope. This fails compilation.
✅ Minimal fix
- valObtained = appcfg.GetAPITokenExpirationParameter(testCtx, db)
+ valObtained := appcfg.GetAPITokenExpirationParameter(testCtx, db)📝 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.
| valObtained = appcfg.GetAPITokenExpirationParameter(testCtx, db) | |
| require.Equal(t, apiKeyExpiration, valObtained.Enabled) | |
| require.Equal(t, expirationPeriod, valObtained.ExpirationPeriod) | |
| valObtained := appcfg.GetAPITokenExpirationParameter(testCtx, db) | |
| require.Equal(t, apiKeyExpiration, valObtained.Enabled) | |
| require.Equal(t, expirationPeriod, valObtained.ExpirationPeriod) |
🧰 Tools
🪛 golangci-lint (2.5.0)
[major] 233-233: : # github.com/specterops/bloodhound/cmd/api/src/database_test [github.com/specterops/bloodhound/cmd/api/src/database.test]
cmd/api/src/database/parameters_test.go:233:2: undefined: valObtained
cmd/api/src/database/parameters_test.go:235:37: undefined: valObtained
cmd/api/src/database/parameters_test.go:236:37: undefined: valObtained
(typecheck)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmd/api/src/database/parameters_test.go` around lines 233 - 236, The test
assigns to valObtained without declaring it; change the assignment to declare
the variable (e.g., use valObtained :=
appcfg.GetAPITokenExpirationParameter(testCtx, db)) or explicitly declare var
valObtained <ReturnType> then assign, so valObtained is in scope for the
subsequent require.Equal checks that reference it and aligns with the return
type of GetAPITokenExpirationParameter.
| type APITokenExpirationParameter struct { | ||
| Enabled bool `json:"enabled"` | ||
| ExpirationPeriod int `json:"expiration_period"` | ||
| } | ||
|
|
||
| func GetAPITokenExpirationParameter(ctx context.Context, service ParameterService) APITokenExpirationParameter { | ||
| result := APITokenExpirationParameter{Enabled: false, ExpirationPeriod: 90} | ||
|
|
||
| if cfg, err := service.GetConfigurationParameter(ctx, APITokenExpiration); err != nil { | ||
| slog.WarnContext(ctx, "Failed to fetch API tokens expiration configuration; returning default values.") | ||
| } else if err := cfg.Map(&result); err != nil { | ||
| slog.WarnContext(ctx, "Invalid API tokens expiration configuration supplied, returning default values.", | ||
| slog.String("invalid_configuration", err.Error()), | ||
| slog.String("parameter_key", string(APITokenExpiration))) | ||
| } | ||
|
|
||
| return result | ||
| } |
There was a problem hiding this comment.
Add bounds validation for expiration_period to prevent invalid runtime config.
APITokenExpiration is user-updatable, but ExpirationPeriod currently accepts any integer (including 0/negative). This can produce broken token-expiry behavior.
💡 Suggested fix
type APITokenExpirationParameter struct {
- Enabled bool `json:"enabled"`
- ExpirationPeriod int `json:"expiration_period"`
+ Enabled bool `json:"enabled"`
+ ExpirationPeriod int `json:"expiration_period" validate:"min=1,max=3650"`
}
func GetAPITokenExpirationParameter(ctx context.Context, service ParameterService) APITokenExpirationParameter {
result := APITokenExpirationParameter{Enabled: false, ExpirationPeriod: 90}
if cfg, err := service.GetConfigurationParameter(ctx, APITokenExpiration); err != nil {
slog.WarnContext(ctx, "Failed to fetch API tokens expiration configuration; returning default values.")
} else if err := cfg.Map(&result); err != nil {
slog.WarnContext(ctx, "Invalid API tokens expiration configuration supplied, returning default values.",
slog.String("invalid_configuration", err.Error()),
slog.String("parameter_key", string(APITokenExpiration)))
+ } else if result.ExpirationPeriod <= 0 {
+ slog.WarnContext(ctx, "Invalid API token expiration period supplied; returning default values.",
+ slog.Int("invalid_expiration_period", result.ExpirationPeriod),
+ slog.String("parameter_key", string(APITokenExpiration)))
+ result.ExpirationPeriod = 90
}
return result
}📝 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.
| type APITokenExpirationParameter struct { | |
| Enabled bool `json:"enabled"` | |
| ExpirationPeriod int `json:"expiration_period"` | |
| } | |
| func GetAPITokenExpirationParameter(ctx context.Context, service ParameterService) APITokenExpirationParameter { | |
| result := APITokenExpirationParameter{Enabled: false, ExpirationPeriod: 90} | |
| if cfg, err := service.GetConfigurationParameter(ctx, APITokenExpiration); err != nil { | |
| slog.WarnContext(ctx, "Failed to fetch API tokens expiration configuration; returning default values.") | |
| } else if err := cfg.Map(&result); err != nil { | |
| slog.WarnContext(ctx, "Invalid API tokens expiration configuration supplied, returning default values.", | |
| slog.String("invalid_configuration", err.Error()), | |
| slog.String("parameter_key", string(APITokenExpiration))) | |
| } | |
| return result | |
| } | |
| type APITokenExpirationParameter struct { | |
| Enabled bool `json:"enabled"` | |
| ExpirationPeriod int `json:"expiration_period" validate:"min=1,max=3650"` | |
| } | |
| func GetAPITokenExpirationParameter(ctx context.Context, service ParameterService) APITokenExpirationParameter { | |
| result := APITokenExpirationParameter{Enabled: false, ExpirationPeriod: 90} | |
| if cfg, err := service.GetConfigurationParameter(ctx, APITokenExpiration); err != nil { | |
| slog.WarnContext(ctx, "Failed to fetch API tokens expiration configuration; returning default values.") | |
| } else if err := cfg.Map(&result); err != nil { | |
| slog.WarnContext(ctx, "Invalid API tokens expiration configuration supplied, returning default values.", | |
| slog.String("invalid_configuration", err.Error()), | |
| slog.String("parameter_key", string(APITokenExpiration))) | |
| } else if result.ExpirationPeriod <= 0 { | |
| slog.WarnContext(ctx, "Invalid API token expiration period supplied; returning default values.", | |
| slog.Int("invalid_expiration_period", result.ExpirationPeriod), | |
| slog.String("parameter_key", string(APITokenExpiration))) | |
| result.ExpirationPeriod = 90 | |
| } | |
| return result | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmd/api/src/model/appcfg/parameter.go` around lines 584 - 601, The
GetAPITokenExpirationParameter function must validate the mapped
ExpirationPeriod to prevent zero/negative (and unreasonably large) values from
being used; after cfg.Map(&result) succeeds, check result.ExpirationPeriod and
if it's < 1 or > 3650 (or whatever agreed max) replace it with the default (90)
and emit a slog.WarnContext including the invalid value and APITokenExpiration
key; keep APITokenExpirationParameter, GetAPITokenExpirationParameter,
APITokenExpiration, and ParameterService as the touch points and perform the
validation immediately after cfg.Map so runtime config cannot set invalid
expirations.
superlinkx
left a comment
There was a problem hiding this comment.
Looks like the tests won't even compile currently due to syntax errors. Aside from that, CodeRabbit is correct in its comments and we should address them.
Additionally, I'm seeing the parameter configuration here, but I'm not seeing how we ensure each API token created after the parameter is enabled is getting the expires_at added.
Finally, we also need invalidation logic to make sure expired tokens are no longer accepted by the auth system.
Description
Creates the API token expiration parameter which will allow for expiring API tokens to be enabled/disabled.
Motivation and Context
Resolves BED-7448
This change resolves the request for API tokens that are able to be expired automatically after a set time frame.
How Has This Been Tested?
Created new unit tests that tests setting and obtaining the parameter.
Screenshots (optional):
Types of changes
Checklist:
Summary by CodeRabbit
New Features
Tests