feat(auth): extend refresh TTL and keep session on transient errors
Bumps refresh token lifetime to 720h with env overrides, and stops clearing the local session for non-401 refresh failures so users aren't logged out by transient network blips. Adds a 60s retry timer when a refresh fails for non-auth reasons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,8 @@ object_storage:
|
||||
|
||||
jwt:
|
||||
secret: "your-local-secret"
|
||||
access_ttl: 15m
|
||||
refresh_ttl: 720h
|
||||
|
||||
llm:
|
||||
provider: ark
|
||||
|
||||
@@ -137,7 +137,7 @@ cache:
|
||||
jwt:
|
||||
secret: "dev-secret-change-in-production"
|
||||
access_ttl: 15m
|
||||
refresh_ttl: 168h
|
||||
refresh_ttl: 720h
|
||||
|
||||
log:
|
||||
level: debug,info,warn,error
|
||||
|
||||
@@ -376,6 +376,15 @@ func applyEnvOverrides(cfg *Config) {
|
||||
return
|
||||
}
|
||||
|
||||
if secret, ok := lookupNonEmptyEnv("JWT_SECRET"); ok {
|
||||
cfg.JWT.Secret = secret
|
||||
}
|
||||
if ttl, ok := lookupDurationEnv("JWT_ACCESS_TTL"); ok {
|
||||
cfg.JWT.AccessTTL = ttl
|
||||
}
|
||||
if ttl, ok := lookupDurationEnv("JWT_REFRESH_TTL"); ok {
|
||||
cfg.JWT.RefreshTTL = ttl
|
||||
}
|
||||
if apiKey, ok := lookupNonEmptyEnv("LLM_API_KEY"); ok {
|
||||
cfg.LLM.APIKey = apiKey
|
||||
}
|
||||
@@ -784,6 +793,19 @@ func lookupBoolEnv(key string) (bool, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func lookupDurationEnv(key string) (time.Duration, bool) {
|
||||
value, ok := lookupNonEmptyEnv(key)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return duration, true
|
||||
}
|
||||
|
||||
func maxInt(value, fallback int) int {
|
||||
if value < fallback {
|
||||
return fallback
|
||||
|
||||
@@ -166,6 +166,33 @@ scheduler:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPrefersEnvJWTSettings(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", "env-jwt-secret")
|
||||
t.Setenv("JWT_ACCESS_TTL", "20m")
|
||||
t.Setenv("JWT_REFRESH_TTL", "720h")
|
||||
|
||||
configPath := writeTestConfig(t, `
|
||||
jwt:
|
||||
secret: config-jwt-secret
|
||||
access_ttl: 15m
|
||||
refresh_ttl: 168h
|
||||
`)
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if cfg.JWT.Secret != "env-jwt-secret" {
|
||||
t.Fatalf("expected env jwt secret, got %q", cfg.JWT.Secret)
|
||||
}
|
||||
if cfg.JWT.AccessTTL != 20*time.Minute {
|
||||
t.Fatalf("expected env jwt access ttl 20m, got %s", cfg.JWT.AccessTTL)
|
||||
}
|
||||
if cfg.JWT.RefreshTTL != 720*time.Hour {
|
||||
t.Fatalf("expected env jwt refresh ttl 720h, got %s", cfg.JWT.RefreshTTL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesMembershipDefaults(t *testing.T) {
|
||||
configPath := writeTestConfig(t, `
|
||||
membership: {}
|
||||
|
||||
Reference in New Issue
Block a user