Cap refresh chain lifetime via RefreshChainAbsoluteTTL

Sessions had an absolute cap (created_at + SessionAbsoluteTTL) but the
JWT path only had per-token TTL on the refresh row, letting a
well-behaved client refresh indefinitely. Add chain_started_at to
authkit_tokens, copy it forward on every rotation, and reject in
RefreshJWT when now > chainStartedAt + RefreshChainAbsoluteTTL.
Default 30d, mirroring SessionAbsoluteTTL.

Schema, verifier, queries, model, and integration test updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
juancwu 2026-04-26 23:41:02 +00:00
commit ca5525d4bd
11 changed files with 129 additions and 53 deletions

View file

@ -46,12 +46,15 @@ const (
// Token is one row in authkit_tokens. AttemptsRemaining is non-nil only for
// tokens that allow retry on incorrect input (email OTPs); other kinds are
// strictly one-shot via ConsumeToken.
// strictly one-shot via ConsumeToken. ChainStartedAt is non-nil only for
// refresh-token rows; copied forward on every rotation so the absolute-cap
// check in RefreshJWT is O(1).
type Token struct {
Hash []byte
Kind TokenKind
UserID uuid.UUID
ChainID *string
ChainStartedAt *time.Time
ConsumedAt *time.Time
AttemptsRemaining *int
CreatedAt time.Time