Skip to content

feat: Create API Token Expiration Parameter. BED-7448#2420

Open
RaymondLaubert wants to merge 4 commits intomainfrom
BED-7448/ApiKey-TokenExpirationParam
Open

feat: Create API Token Expiration Parameter. BED-7448#2420
RaymondLaubert wants to merge 4 commits intomainfrom
BED-7448/ApiKey-TokenExpirationParam

Conversation

@RaymondLaubert
Copy link
Contributor

@RaymondLaubert RaymondLaubert commented Feb 25, 2026

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

  • New feature (non-breaking change which adds functionality)

Checklist:

Summary by CodeRabbit

  • New Features

    • Added API token expiration configuration support, defaulting to disabled with a 90-day expiration period.
  • Tests

    • Added test coverage for API token expiration parameter retrieval.

@RaymondLaubert RaymondLaubert self-assigned this Feb 25, 2026
@RaymondLaubert RaymondLaubert added enhancement New feature or request api A pull request containing changes affecting the API code. labels Feb 25, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Database Migration
cmd/api/src/database/migration/migrations/v8.7.0.sql
Inserts new 'auth.api_token_expiration' configuration parameter with default JSON value enabling 90-day expiration settings.
Parameter Model
cmd/api/src/model/appcfg/parameter.go
Introduces APITokenExpirationParameter type with Enabled and ExpirationPeriod fields; adds parameter key constant, validation support, and public getter with sensible defaults.
Integration Tests
cmd/api/src/database/parameters_test.go
Adds TestParameters_GetAPITokenExpirationParameter to verify retrieval and parsing of API token expiration configuration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • superlinkx
  • elikmiller
  • stephanieslamb
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: creating an API token expiration parameter. It is concise, specific, and directly related to the primary objective of the pull request.
Description check ✅ Passed The description covers all critical sections: clear description of changes, motivation/context with ticket reference, testing approach, and properly completed checklist indicating documentation and tests are addressed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-7448/ApiKey-TokenExpirationParam

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
cmd/api/src/database/parameters_test.go (1)

221-223: Use a non-default expiration_period to harden this test.

Using expirationPeriod = 90 matches the function default, so the period assertion is weaker against fallback behavior.

💡 Suggested tweak
-		expirationPeriod = 90
+		expirationPeriod = 30

Also 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

📥 Commits

Reviewing files that changed from the base of the PR and between a13c695 and 824bea2.

📒 Files selected for processing (3)
  • cmd/api/src/database/migration/migrations/v8.7.0.sql
  • cmd/api/src/database/parameters_test.go
  • cmd/api/src/model/appcfg/parameter.go

Comment on lines +233 to +236
valObtained = appcfg.GetAPITokenExpirationParameter(testCtx, db)

require.Equal(t, apiKeyExpiration, valObtained.Enabled)
require.Equal(t, expirationPeriod, valObtained.ExpirationPeriod)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.go

Repository: 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.go

Repository: 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.

Suggested change
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.

Comment on lines +584 to +601
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
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link
Contributor

@superlinkx superlinkx left a comment

Choose a reason for hiding this comment

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

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.

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

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants