Documentation
¶
Overview ¶
Package session manages sign-in sessions: issue (cookie-backed opaque tokens), validate (sliding expiry), list, and revoke. The sign-in endpoint lives here; credential checking is delegated to a Verifier (the password feature) via consumer-side wiring.
Index ¶
- Constants
- Variables
- func ClearCookie(w http.ResponseWriter, secure bool)
- func RequestIP(r *http.Request) string
- func WriteCookie(w http.ResponseWriter, token string, expires time.Time, secure bool)
- func WritePendingCookie(w http.ResponseWriter, token string, secure bool)
- type AuthTokenID
- type Meta
- type PostgresStore
- func (s *PostgresStore) Create(ctx context.Context, se *Session) error
- func (s *PostgresStore) ListActiveForUser(ctx context.Context, userID user.UserID) ([]Session, error)
- func (s *PostgresStore) RevokeAllForUser(ctx context.Context, userID user.UserID, exceptID string) error
- func (s *PostgresStore) RevokeByTokenHash(ctx context.Context, tokenHash string) error
- func (s *PostgresStore) RevokeForUser(ctx context.Context, userID user.UserID, sessionID string) error
- func (s *PostgresStore) Touch(ctx context.Context, id string, expiresAt time.Time) error
- func (s *PostgresStore) ValidByTokenHash(ctx context.Context, tokenHash string) (Session, user.User, error)
- type RefreshTokenID
- type Service
- func (s *Service) APIRoutes(r chi.Router, _ identity.RouteGroups)
- func (s *Service) IssueForUser(ctx context.Context, userID user.UserID, provider string, meta Meta) (string, error)
- func (s *Service) ListForUser(ctx context.Context, userID user.UserID) ([]Session, error)
- func (s *Service) Name() string
- func (s *Service) Resolve(ctx context.Context, token string) (user.User, Session, error)
- func (s *Service) ResolveSession(ctx context.Context, token string) (kernel.Principal, error)
- func (s *Service) RevokeAllForUser(ctx context.Context, userID user.UserID, keepID string) error
- func (s *Service) RevokeCurrent(ctx context.Context, token string) error
- func (s *Service) RevokeForUser(ctx context.Context, userID user.UserID, sessionID string) error
- func (s *Service) SignIn(ctx context.Context, identityText, secret string, meta Meta) (string, user.User, Session, error)
- func (s *Service) SignInWithPending(ctx context.Context, identityText, secret string, meta Meta) (SignInWithPendingResult, error)
- type ServiceOption
- type Session
- type SessionID
- type SignInWithPendingResult
- type Store
- type Verifier
Constants ¶
const CookieName = "tango_session"
CookieName is the browser session cookie.
Variables ¶
var ErrInvalidCredentials = errors.New("session: invalid credentials")
ErrInvalidCredentials mirrors the password feature error so the handler maps both to 401 without importing it.
var ( // ErrNotFound is returned when no session matches, including // expired and revoked ones (they must be indistinguishable). ErrNotFound = errors.New("session: not found") )
Errors surfaced by stores and the service.
Functions ¶
func ClearCookie ¶
func ClearCookie(w http.ResponseWriter, secure bool)
ClearCookie expires the session cookie.
func RequestIP ¶
RequestIP extracts the client IP for session metadata; proxy headers are trusted only via the transport chain, so the socket peer is the source of truth here.
func WriteCookie ¶
WriteCookie stores the session token: HttpOnly (no script access), SameSite=Lax (CSRF-safe for top-level navigation), Secure except in plain development mode, path-scoped to the whole site.
func WritePendingCookie ¶
func WritePendingCookie(w http.ResponseWriter, token string, secure bool)
WritePendingCookie sets the short-lived pending-auth cookie: the bridge token for MFA verification, never a session token.
Types ¶
type AuthTokenID ¶
Typed IDs for the session tables: UUIDv7 suffix, snake_case prefix matching the singular table name.
type PostgresStore ¶
type PostgresStore struct {
// contains filtered or unexported fields
}
PostgresStore persists sessions in public.sessions; the token hash is the lookup key, the TypeID string is the public identifier.
func NewPostgresStore ¶
func NewPostgresStore(exec datastore.Executor) *PostgresStore
NewPostgresStore builds the production session store.
func (*PostgresStore) Create ¶
func (s *PostgresStore) Create(ctx context.Context, se *Session) error
func (*PostgresStore) ListActiveForUser ¶
func (s *PostgresStore) ListActiveForUser(ctx context.Context, userID user.UserID) ([]Session, error)
ListActiveForUser returns live sessions, newest first.
func (*PostgresStore) RevokeAllForUser ¶
func (s *PostgresStore) RevokeAllForUser(ctx context.Context, userID user.UserID, exceptID string) error
RevokeAllForUser revokes every live session, optionally sparing one (the caller's current session).
func (*PostgresStore) RevokeByTokenHash ¶
func (s *PostgresStore) RevokeByTokenHash(ctx context.Context, tokenHash string) error
RevokeByTokenHash revokes the session matching a token hash.
func (*PostgresStore) RevokeForUser ¶
func (s *PostgresStore) RevokeForUser(ctx context.Context, userID user.UserID, sessionID string) error
RevokeForUser revokes one session owned by the user; unknown or foreign session IDs surface ErrNotFound.
func (*PostgresStore) Touch ¶
Touch extends a live session's expiry (sliding window) and marks the refresh time.
func (*PostgresStore) ValidByTokenHash ¶
func (s *PostgresStore) ValidByTokenHash(ctx context.Context, tokenHash string) (Session, user.User, error)
ValidByTokenHash resolves one live session with its user; expired and revoked rows are invisible (indistinguishable from missing).
type RefreshTokenID ¶
Typed IDs for the session tables: UUIDv7 suffix, snake_case prefix matching the singular table name.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service owns the sign-in flow: verify credentials, issue opaque tokens, resolve them back to principals, and manage revocation.
func NewService ¶
func NewService(store Store, verifier Verifier, users user.Store, recorder identity.Recorder, opts ...ServiceOption) *Service
NewService builds the session feature.
func (*Service) APIRoutes ¶
func (s *Service) APIRoutes(r chi.Router, _ identity.RouteGroups)
APIRoutes mounts the auth endpoints inside the shared /api group: public sign-in, then cookie-guarded session management.
func (*Service) IssueForUser ¶
func (s *Service) IssueForUser(ctx context.Context, userID user.UserID, provider string, meta Meta) (string, error)
IssueForUser mints a session for an already-authenticated identity — the alternative sign-in providers (passkeys, device login, one-time access) call this after verifying their own ceremony. A confirmed second factor fails these flows closed: the pending bridge belongs to the password sign-in path only, so no provider can bypass MFA.
func (*Service) ListForUser ¶
ListForUser returns the user's live sessions, newest first.
func (*Service) Resolve ¶
Resolve maps a cookie token back to its principal and live session, sliding the expiry forward when past half-life.
func (*Service) ResolveSession ¶
ResolveSession implements kernel.Authenticator: cookie token in, transport principal out.
func (*Service) RevokeAllForUser ¶
RevokeAllForUser revokes every session, optionally sparing one (the current session during a password change).
func (*Service) RevokeCurrent ¶
RevokeCurrent signs out the caller's session by token.
func (*Service) RevokeForUser ¶
RevokeForUser revokes one session owned by the user; unknown or foreign session IDs surface ErrNotFound.
func (*Service) SignIn ¶
func (s *Service) SignIn(ctx context.Context, identityText, secret string, meta Meta) (string, user.User, Session, error)
SignIn verifies the credentials and issues a full session; callers that compose with the second factor use SignInWithPending.
func (*Service) SignInWithPending ¶
func (s *Service) SignInWithPending(ctx context.Context, identityText, secret string, meta Meta) (SignInWithPendingResult, error)
SignInWithPending verifies the credentials and lands in a full session — or in the pending-auth bridge when a confirmed second factor requires it. The pending token goes to the pending cookie, never to the session cookie.
type ServiceOption ¶
type ServiceOption func(*Service)
ServiceOption configures the session service.
func WithClock ¶
func WithClock(now func() time.Time) ServiceOption
WithClock overrides the service clock (tests).
func WithCookieSecure ¶
func WithCookieSecure(secure bool) ServiceOption
WithCookieSecure toggles the Secure cookie flag.
func WithLifetime ¶
func WithLifetime(d time.Duration) ServiceOption
WithLifetime sets the session lifetime and the sliding-refresh threshold (half the lifetime).
func WithMFAPort ¶
func WithMFAPort(port identity.MFAPendingIssuer) ServiceOption
WithMFAPort wires the second-factor port; a confirmed enrollment makes password sign-in (and every other non-MFA provider) land in a pending authentication instead of a full session.
func WithRecorder ¶
func WithRecorder(recorder identity.Recorder) ServiceOption
WithRecorder overrides the audit recorder (tests).
type Session ¶
type Session struct {
ID string
UserID user.UserID
Provider string
TokenHash string
UserAgent *string
DeviceName *string
IPAddress *string
CreatedAt time.Time
ExpiresAt time.Time
RefreshedAt *time.Time
RevokedAt *time.Time
}
Session is one active sign-in. The ID is public (listed, revoked by users); the token never leaves the service — only its SHA-256 hash is stored, and only the cookie carries it.
type SessionID ¶
Typed IDs for the session tables: UUIDv7 suffix, snake_case prefix matching the singular table name.
type SignInWithPendingResult ¶
type SignInWithPendingResult struct {
Pending bool
Token string // pending token when Pending, else the session token
User user.User
Session Session
}
SignIn verifies the identity + secret pair and issues a session token. The token goes to the client once (cookie); only its hash is stored. SignInWithPendingResult carries either a full session or the pending-auth bridge that only MFA verification can upgrade.
type Store ¶
type Store interface {
Create(ctx context.Context, s *Session) error
ValidByTokenHash(ctx context.Context, tokenHash string) (Session, user.User, error)
Touch(ctx context.Context, id string, expiresAt time.Time) error
RevokeByTokenHash(ctx context.Context, tokenHash string) error
RevokeForUser(ctx context.Context, userID user.UserID, sessionID string) error
RevokeAllForUser(ctx context.Context, userID user.UserID, exceptID string) error
ListActiveForUser(ctx context.Context, userID user.UserID) ([]Session, error)
}
Store persists sessions. Validity (revoked/expired) is enforced by every read.