fix: IDEAS.md quickfixes — idle-timeout app config, id_token out of Claims, FormEvent import

session.idle_timeout (minutes, default 15) joins the app-config registry
and drives the onboarding Garmin session eviction, distinct from
session.duration (the login cookie lifetime in hours). The raw Keycloak
ID token no longer rides in auth.Claims through every request context:
it's minted into the session cookie separately and read back only by the
logout handler via IDTokenFromSessionCookie. OnboardingWizard uses the
type-imported FormEvent<HTMLFormElement> instead of the React.FormEvent
namespace alias.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 17:44:37 +02:00
parent dcbb1d8bb0
commit 8c9285f33c
15 changed files with 160 additions and 61 deletions

View File

@@ -17,7 +17,16 @@ type AppKey struct {
}
// KeySessionDuration is the session cookie lifetime, in integer hours.
const KeySessionDuration = "session.duration"
const (
KeySessionDuration = "session.duration"
// KeySessionIdleTimeout bounds an *onboarding Garmin session* (the
// ephemeral, pre-account login attempt), not the login cookie --
// session.duration is how long a signed-in user stays signed in
// (hours); session.idle_timeout is how long an unfinished setup
// attempt survives untouched before its subprocess is torn down
// (minutes).
KeySessionIdleTimeout = "session.idle_timeout"
)
var appRegistry = []AppKey{
{
@@ -26,6 +35,12 @@ var appRegistry = []AppKey{
Description: "Session cookie lifetime in hours",
Validate: validatePositiveInt,
},
{
Key: KeySessionIdleTimeout,
Default: "15",
Description: "Idle timeout in minutes for an unfinished onboarding Garmin session",
Validate: validatePositiveInt,
},
}
// AppRegistry returns every known application-configuration key, in
@@ -57,7 +72,8 @@ func ValidateAppValue(key, value string) error {
// AppConfig is the typed result of merging DB overrides over registry
// defaults.
type AppConfig struct {
SessionDuration time.Duration
SessionDuration time.Duration
SetupSessionIdleTimeout time.Duration
}
// LoadApp merges overrides (store.ConfigValues' map) over defaults. Pure
@@ -76,5 +92,9 @@ func LoadApp(overrides map[string]string) (AppConfig, error) {
merged[key] = value
}
hours, _ := strconv.Atoi(merged[KeySessionDuration])
return AppConfig{SessionDuration: time.Duration(hours) * time.Hour}, nil
idleMinutes, _ := strconv.Atoi(merged[KeySessionIdleTimeout])
return AppConfig{
SessionDuration: time.Duration(hours) * time.Hour,
SetupSessionIdleTimeout: time.Duration(idleMinutes) * time.Minute,
}, nil
}

View File

@@ -11,11 +11,14 @@ func TestLoadApp(t *testing.T) {
name string
overrides map[string]string
want time.Duration
wantIdle time.Duration
wantErr string
}{
{name: "defaults when no overrides", overrides: nil, want: 720 * time.Hour},
{name: "override applied", overrides: map[string]string{"session.duration": "168"}, want: 168 * time.Hour},
{name: "defaults when no overrides", overrides: nil, want: 720 * time.Hour, wantIdle: 15 * time.Minute},
{name: "override applied", overrides: map[string]string{"session.duration": "168"}, want: 168 * time.Hour, wantIdle: 15 * time.Minute},
{name: "idle timeout override applied", overrides: map[string]string{"session.idle_timeout": "30"}, want: 720 * time.Hour, wantIdle: 30 * time.Minute},
{name: "invalid stored value", overrides: map[string]string{"session.duration": "zero"}, wantErr: "session.duration"},
{name: "invalid idle timeout", overrides: map[string]string{"session.idle_timeout": "0"}, wantErr: "session.idle_timeout"},
{name: "unknown stored key", overrides: map[string]string{"bogus.key": "1"}, wantErr: "unknown configuration key"},
}
for _, tt := range tests {
@@ -33,6 +36,9 @@ func TestLoadApp(t *testing.T) {
if got.SessionDuration != tt.want {
t.Fatalf("SessionDuration = %v, want %v", got.SessionDuration, tt.want)
}
if got.SetupSessionIdleTimeout != tt.wantIdle {
t.Fatalf("SetupSessionIdleTimeout = %v, want %v", got.SetupSessionIdleTimeout, tt.wantIdle)
}
})
}
}