✨ feat(user): magic-link expired-token cleanup loop (ADR-0028 Phase A consequence)
Periodically delete magic-link tokens past their expires_at — the rows accumulate without cleanup since the consume endpoint only marks them consumed but never removes them, and DeleteExpiredMagicLinkTokens (added in Phase A.3, PR #61) was never wired up. Mirrors the JWT secret cleanup pattern (ADR-0021). - pkg/user/magic_link_cleanup.go: MagicLinkCleanupRunner with StartCleanupLoop(ctx, interval) and an inner runOnce(ctx) for testability - pkg/user/magic_link_cleanup_test.go: unit tests cover happy path, error propagation, ctx-cancel termination, zero-interval no-op - pkg/server/server.go: start the loop right after the JWT cleanup loop - pkg/config/config.go: auth.magic_link.cleanup_interval (default 1h), env DLC_AUTH_MAGIC_LINK_CLEANUP_INTERVAL, getter GetMagicLinkCleanupInterval Mostly authored by Mistral Vibe in batch1-task-b worktree (parallel with batch1-task-a OIDC config). Mistral hit --max-price 1.50 right before the final commit step, Claude finished the ship via trainer takeover (Q-045 pattern, 2nd recurrence in two batches — to be addressed in Phase 1bis).
This commit is contained in:
@@ -114,8 +114,9 @@ type AuthConfig struct {
|
||||
|
||||
// MagicLinkConfig holds passwordless-auth magic-link parameters (ADR-0028 Phase A).
|
||||
type MagicLinkConfig struct {
|
||||
TTL time.Duration `mapstructure:"ttl"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
TTL time.Duration `mapstructure:"ttl"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
CleanupInterval time.Duration `mapstructure:"cleanup_interval"`
|
||||
}
|
||||
|
||||
// OIDCConfig holds OpenID Connect provider configuration (ADR-0028 Phase B).
|
||||
@@ -300,6 +301,7 @@ func LoadConfig() (*Config, error) {
|
||||
// Magic-link defaults (ADR-0028 Phase A).
|
||||
v.SetDefault("auth.magic_link.ttl", 15*time.Minute)
|
||||
v.SetDefault("auth.magic_link.base_url", "http://localhost:8080")
|
||||
v.SetDefault("auth.magic_link.cleanup_interval", 1*time.Hour)
|
||||
|
||||
// OIDC defaults (ADR-0028 Phase B). Providers map is empty by default;
|
||||
// configured per environment via config file or env vars.
|
||||
@@ -361,6 +363,7 @@ func LoadConfig() (*Config, error) {
|
||||
// Magic-link environment variables (ADR-0028 Phase A).
|
||||
v.BindEnv("auth.magic_link.ttl", "DLC_AUTH_MAGIC_LINK_TTL")
|
||||
v.BindEnv("auth.magic_link.base_url", "DLC_AUTH_MAGIC_LINK_BASE_URL")
|
||||
v.BindEnv("auth.magic_link.cleanup_interval", "DLC_AUTH_MAGIC_LINK_CLEANUP_INTERVAL")
|
||||
|
||||
// OIDC environment variables (ADR-0028 Phase B). One canonical "default"
|
||||
// provider is bindable via env; additional providers must be defined in config.yaml.
|
||||
@@ -528,6 +531,14 @@ func (c *Config) GetOIDCProviders() map[string]OIDCProvider {
|
||||
return c.Auth.OIDC.Providers
|
||||
}
|
||||
|
||||
// GetMagicLinkCleanupInterval returns the magic-link cleanup interval (ADR-0028 Phase A consequence).
|
||||
func (c *Config) GetMagicLinkCleanupInterval() time.Duration {
|
||||
if c.Auth.MagicLink.CleanupInterval <= 0 {
|
||||
return 1 * time.Hour
|
||||
}
|
||||
return c.Auth.MagicLink.CleanupInterval
|
||||
}
|
||||
|
||||
// GetJWTTTL returns the JWT TTL
|
||||
func (c *Config) GetJWTTTL() time.Duration {
|
||||
if c.Auth.JWT.TTL == 0 {
|
||||
|
||||
Reference in New Issue
Block a user