fix(api): stop promoting the ephemeral Garmin client after setup completes
handleSetupComplete reused the onboarding client object as-is in the
permanent per-user cache, but its garmin.Config.TokenStorePath was fixed
at construction to the ephemeral setup/{hash} directory and never
corrected after that directory was renamed to the permanent {userID}
path. The next respawn of that same client (e.g. any Profile-page save,
which unconditionally calls UpdateCredentials) wrote a fresh token file
back under setup/{hash}, forcing a real re-login/MFA on the next Garmin
connect even though a valid session already existed under {userID}.
Close the ephemeral client instead and let the next garminFor call build
a fresh one against the correct, already-renamed directory.
This commit is contained in:
@@ -74,10 +74,15 @@ func NewServer(db *store.DB, garminFactory func(garmin.Config) garmin.Client, ga
|
||||
// setupSession is a temporary, not-yet-persisted Garmin authentication
|
||||
// attempt made during onboarding, before any users/profile row exists --
|
||||
// keyed by OIDC subject (the only stable identifier available pre-account)
|
||||
// rather than a user id. Promoted into Server.userGarmin once
|
||||
// /api/setup/complete actually creates the account; evicted lazily (the
|
||||
// next setup-endpoint touch for that subject checks staleness first) once
|
||||
// idle past setupSessionIdleTimeout.
|
||||
// rather than a user id. Closed (never promoted into Server.userGarmin) once
|
||||
// /api/setup/complete actually creates the account -- its Client's
|
||||
// garmin.Config.TokenStorePath is permanently pinned to the ephemeral
|
||||
// setup/{hash} directory, so reusing the object after that directory is
|
||||
// renamed to the permanent {userID} one would respawn against a stale path
|
||||
// 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.
|
||||
type setupSession struct {
|
||||
Client garmin.Client
|
||||
Email, Password string
|
||||
@@ -162,9 +167,9 @@ func (s *Server) recordSetupAuthResult(sub string, res garmin.AuthResult) {
|
||||
|
||||
// removeSetupSession closes and drops sub's ephemeral Garmin session, if
|
||||
// any, and best-effort removes its token-store directory. Used when
|
||||
// abandoning onboarding (logout) -- NOT used during promotion in
|
||||
// handleSetupComplete, which transfers ownership of the client (and
|
||||
// renames the directory) instead of discarding them.
|
||||
// abandoning onboarding (logout). handleSetupComplete closes the client the
|
||||
// same way but keeps (renames) the directory instead of removing it, since
|
||||
// that's the real, now-permanent session.
|
||||
func (s *Server) removeSetupSession(sub string) {
|
||||
s.mu.Lock()
|
||||
sess, ok := s.setupGarmin[sub]
|
||||
|
||||
Reference in New Issue
Block a user