package handlers import ( "context" "fmt" "strconv" "strings" "time" "armature/store" ) // SessionConfig holds parsed, clamped session TTL settings. type SessionConfig struct { AccessTokenTTL time.Duration RefreshTokenTTL time.Duration IdleTimeout time.Duration // 0 = disabled } // Defaults & clamp bounds const ( defaultAccessTTL = 15 * time.Minute defaultRefreshTTL = 7 * 24 * time.Hour // 7d defaultIdleTimeout = 0 // disabled minAccessTTL = 5 * time.Minute maxAccessTTL = 60 * time.Minute minRefreshTTL = 1 * time.Hour maxRefreshTTL = 90 * 24 * time.Hour // 90d minIdleTTL = 5 * time.Minute maxIdleTTL = 24 * time.Hour ) // LoadSessionConfig reads session settings from the global_settings table. // Returns defaults if the key is missing or values are unparseable. func LoadSessionConfig(ctx context.Context, gc store.GlobalConfigStore) SessionConfig { sc := SessionConfig{ AccessTokenTTL: defaultAccessTTL, RefreshTokenTTL: defaultRefreshTTL, IdleTimeout: defaultIdleTimeout, } raw, err := gc.Get(ctx, "session") if err != nil || raw == nil { return sc } if v, ok := raw["access_token_ttl"].(string); ok && v != "" { if d, err := parseDurationString(v); err == nil { sc.AccessTokenTTL = clamp(d, minAccessTTL, maxAccessTTL) } } if v, ok := raw["refresh_token_ttl"].(string); ok && v != "" { if d, err := parseDurationString(v); err == nil { sc.RefreshTokenTTL = clamp(d, minRefreshTTL, maxRefreshTTL) } } if v, ok := raw["idle_timeout"].(string); ok && v != "" { if d, err := parseDurationString(v); err == nil { sc.IdleTimeout = clamp(d, minIdleTTL, maxIdleTTL) } } return sc } // parseDurationString parses strings like "15m", "2h", "7d", "30d". // Supports suffixes: s (seconds), m (minutes), h (hours), d (days). func parseDurationString(s string) (time.Duration, error) { s = strings.TrimSpace(s) if s == "" { return 0, fmt.Errorf("empty duration") } // Try Go's standard parser first (handles "15m", "2h30m", etc.) if d, err := time.ParseDuration(s); err == nil { return d, nil } // Handle "Xd" suffix (days) if strings.HasSuffix(s, "d") { numStr := strings.TrimSuffix(s, "d") n, err := strconv.ParseFloat(numStr, 64) if err != nil { return 0, fmt.Errorf("invalid duration: %s", s) } return time.Duration(n * float64(24*time.Hour)), nil } return 0, fmt.Errorf("invalid duration: %s", s) } func clamp(d, min, max time.Duration) time.Duration { if d < min { return min } if d > max { return max } return d }