Documentation
¶
Overview ¶
Package session is the server-side session store for admin logins. Sessions are stored in hatamoto_sessions and looked up by SHA-256 hash of the opaque cookie value — the raw value never lives in the DB. The row is the source of truth for expiry, validity and revocation.
Index ¶
- Constants
- Variables
- type Kind
- type Session
- type Store
- func (s *Store) IssueAPI(ctx context.Context, userID uuid.UUID, ttl time.Duration, userAgent, ip string) (token string, sess *Session, err error)
- func (s *Store) IssueAdmin(ctx context.Context, userID uuid.UUID, ttl time.Duration, userAgent, ip string) (token string, sess *Session, err error)
- func (s *Store) PurgeExpired(ctx context.Context) (int64, error)
- func (s *Store) Revoke(ctx context.Context, sessionID uuid.UUID) error
- func (s *Store) RevokeAllForUser(ctx context.Context, userID uuid.UUID) error
- func (s *Store) Rotate(ctx context.Context, oldToken string, ttl time.Duration) (string, *Session, error)
- func (s *Store) Validate(ctx context.Context, token string) (*Session, error)
Constants ¶
const IdleTimeout = 7 * 24 * time.Hour
IdleTimeout is how long a session may go unused before Validate refuses (and deletes) it. The absolute lifetime is the row's expires_at.
Variables ¶
var ( ErrInvalid = errors.New("invalid session") ErrExpired = errors.New("session expired") )
Errors returned by the store. Deliberately opaque — handlers must not leak which check failed to clients (timing attacks, enumeration).
Functions ¶
This section is empty.
Types ¶
type Session ¶
type Session struct {
ID uuid.UUID
Kind Kind
UserID *uuid.UUID
ExpiresAt time.Time
CreatedAt time.Time
LastUsedAt *time.Time
UserAgent string
IP string
}
Session is a row from hatamoto_sessions.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes sessions in hatamoto_sessions. It runs under superadmin context for writes (session rows have no tenant_id; lookups by token hash are global by design).
func (*Store) IssueAPI ¶
func (s *Store) IssueAPI(ctx context.Context, userID uuid.UUID, ttl time.Duration, userAgent, ip string) (token string, sess *Session, err error)
IssueAPI creates a new JSON API session and returns the opaque token. It is the same shape as an admin session but a different kind, so an API cookie can never act as the admin form session.
func (*Store) IssueAdmin ¶
func (s *Store) IssueAdmin(ctx context.Context, userID uuid.UUID, ttl time.Duration, userAgent, ip string) (token string, sess *Session, err error)
IssueAdmin creates a new admin session and returns the opaque cookie value (the caller must set this on the response cookie). The cookie value is 32 bytes of cryptographic randomness, base64-encoded.
func (*Store) PurgeExpired ¶
PurgeExpired removes all sessions whose expires_at is in the past. Run periodically (e.g. via the scheduler) to keep the table bounded.
func (*Store) RevokeAllForUser ¶
RevokeAllForUser deletes every admin and API session for a given user. Used for "log out everywhere" and on password change.
func (*Store) Rotate ¶
func (s *Store) Rotate(ctx context.Context, oldToken string, ttl time.Duration) (string, *Session, error)
Rotate atomically consumes an admin session token and issues a fresh one. The old row is DELETEd first (the DELETE ... RETURNING is the concurrency gate): exactly one caller can delete it, so two concurrent refreshes with the same token can't both succeed, and a replay of an already-rotated token deletes zero rows and returns ErrInvalid. Only after a successful delete is a new session issued. A stolen cookie is therefore valid only until the next legitimate refresh. (Non-admin/expired/unknown tokens also return ErrInvalid.)
NOTE: hard-delete means a replayed token is indistinguishable from an unknown one, so there is no "kill the whole family" escalation on replay \u2014 that would need a soft-revoke column (documented follow-up).
func (*Store) Validate ¶
Validate looks up a session by the opaque cookie value. Returns ErrInvalid for missing/unknown tokens, ErrExpired for aged-out or idle (IdleTimeout) sessions; an idle session is revoked. On success, updates last_used_at as a side effect (fire-and-forget; a failed update is logged but doesn't fail the request).