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

@@ -13,11 +13,23 @@ import (
applog "geniusrun/backend/internal/log"
)
// setupSessionIdleTimeout bounds how long an onboarding Garmin session
// survives without being touched (login, MFA, or complete) before it's
// evicted -- long enough to check email for an MFA code, short enough that
// an abandoned attempt doesn't leave a subprocess running indefinitely.
const setupSessionIdleTimeout = 15 * time.Minute
// defaultSetupSessionIdleTimeout bounds how long an onboarding Garmin
// session survives without being touched (login, MFA, or complete) before
// it's evicted -- long enough to check email for an MFA code, short enough
// that an abandoned attempt doesn't leave a subprocess running
// indefinitely. Tunable via the session.idle_timeout application-config
// key (minutes -- distinct from session.duration, the login cookie
// lifetime); this constant is the fallback when the Server field was never
// wired (tests building a bare NewServer).
const defaultSetupSessionIdleTimeout = 15 * time.Minute
// setupIdleTimeout returns the configured onboarding-session idle timeout.
func (s *Server) setupIdleTimeout() time.Duration {
if s.SetupSessionIdleTimeout > 0 {
return s.SetupSessionIdleTimeout
}
return defaultSetupSessionIdleTimeout
}
// setupSession is a temporary, not-yet-persisted Garmin authentication
// attempt made during onboarding, before any users/profile row exists --
@@ -30,7 +42,7 @@ const setupSessionIdleTimeout = 15 * time.Minute
// the next time anything closes and restarts its subprocess; a later
// garminFor(ctx, userID) call builds a fresh client with the correct path
// instead. Otherwise evicted lazily (the next setup-endpoint touch for that
// subject checks staleness first) once idle past setupSessionIdleTimeout.
// subject checks staleness first) once idle past setupIdleTimeout().
type setupSession struct {
Client garmin.Client
Email, Password string
@@ -213,7 +225,7 @@ func (s *Server) setupSessionFor(sub string) (*setupSession, bool) {
if !ok {
return nil, false
}
if time.Since(sess.LastUsed) > setupSessionIdleTimeout {
if time.Since(sess.LastUsed) > s.setupIdleTimeout() {
sess.Client.Close()
delete(s.setupSession, sub)
if s.ClientConfig.TokenStorePath != "" {