Help us shape the future of Go! Take the 2026 Go Developer Survey

session

package
v0.0.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: Apache-2.0, MIT Imports: 29 Imported by: 0

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

View Source
const (
	// DefaultAccessTokenTTL bounds the bearer token's usefulness
	// after exfiltration when no lifetime is configured; refresh
	// stays cookie-only.
	DefaultAccessTokenTTL = 10 * time.Minute
	// AccessTokenIssuer and AccessTokenAudience pin the internal
	// token space; OIDC tokens carry the public issuer URL and a
	// client id instead.
	AccessTokenIssuer   = "tango:internal"
	AccessTokenAudience = "tango:rpc"
	// AccessTokenCookieName mirrors the access token in an HttpOnly
	// cookie scoped to the auth bridge paths, so the worker can
	// bootstrap without a refresh rotation right after sign-in.
	AccessTokenCookieName = "tango_access"
	// AccessTokenPath scopes the access cookie to the bridge.
	AccessTokenPath = "/api/auth"
)

The internal access token is a short-lived RS256 JWT the auth worker holds in memory and sends as `Authorization: Bearer` on RPC calls. Issuer and audience are pinned away from the OIDC values so an internal token can never be exchanged for a relying-party token or vice versa.

Variables

View Source
var ErrInvalidCredentials = errors.New("session: invalid credentials")

ErrInvalidCredentials mirrors the password feature error so the handler maps both to 401 without importing it.

View Source
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 RequestIP

func RequestIP(r *http.Request) string

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.

Types

type AccessClaims added in v0.0.12

type AccessClaims struct {
	// SessionID is the owning session TypeID — the link that keeps
	// RPC revocation exact even inside the token TTL.
	SessionID string `json:"sid"`
	Admin     bool   `json:"admin"`
}

AccessClaims are the private claims of an access token; the standard subject carries the user TypeID.

type AccessTokenSigner added in v0.0.12

type AccessTokenSigner struct {
	// contains filtered or unexported fields
}

AccessTokenSigner mints and verifies internal access tokens with the shared signing key provider.

func NewAccessTokenSigner added in v0.0.12

func NewAccessTokenSigner(provider jwtutils.KeyProvider, opts ...AccessTokenSignerOption) *AccessTokenSigner

NewAccessTokenSigner builds the access-token service on top of a cached key provider.

func (*AccessTokenSigner) Issue added in v0.0.12

Issue signs a fresh access token for the principal and returns it with its expiry.

func (*AccessTokenSigner) Verify added in v0.0.12

Verify checks signature, issuer, audience, and expiry.

type AccessTokenSignerOption added in v0.0.12

type AccessTokenSignerOption func(*AccessTokenSigner)

AccessTokenSignerOption configures the signer.

func WithAccessTokenTTL added in v0.0.12

func WithAccessTokenTTL(ttl time.Duration) AccessTokenSignerOption

WithAccessTokenTTL sets the bearer token lifetime; a non-positive value keeps the default.

type AuthTokenID

type AuthTokenID = typeid.TypeID[authTokenPrefix]

Typed IDs for the session tables: UUIDv7 suffix, snake_case prefix matching the singular table name.

type Meta

type Meta struct {
	UserAgent  string
	DeviceName string
	IPAddress  string
	// Remember selects the long session lifetime; the zero value asks
	// for the short one.
	Remember bool
}

Meta carries request context captured at sign-in.

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) Rotate added in v0.0.12

func (s *PostgresStore) Rotate(ctx context.Context, id string, tokenHash string, expiresAt time.Time) error

Rotate replaces the session's refresh token hash and restarts its sliding expiry; the previous token stops resolving.

func (*PostgresStore) Touch

func (s *PostgresStore) Touch(ctx context.Context, id string, expiresAt time.Time) error

Touch extends a live session's expiry (sliding window) and marks the refresh time.

func (*PostgresStore) ValidByID added in v0.0.12

func (s *PostgresStore) ValidByID(ctx context.Context, id string) (Session, user.User, error)

ValidByID resolves one live session with its user by ID; expired and revoked rows are invisible (indistinguishable from missing).

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

type RefreshTokenID = typeid.TypeID[refreshTokenPrefix]

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 retained auth endpoints inside the shared /api group: the stateless token refresh. Sign-in, sign-out, and session reads serve ConnectRPC exclusively — see handler_rpc.go.

func (*Service) IssueAccess added in v0.0.12

func (s *Service) IssueAccess(ctx context.Context, p kernel.Principal) (string, time.Time, error)

IssueAccess mints the RPC bearer JWT for a live session.

func (*Service) IssueForUser

func (s *Service) IssueForUser(ctx context.Context, userID user.UserID, provider string, meta Meta) (string, Session, 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

func (s *Service) ListForUser(ctx context.Context, userID user.UserID) ([]Session, error)

ListForUser returns the user's live sessions, newest first.

func (*Service) Name

func (s *Service) Name() string

Name names the feature for logs.

func (*Service) RPCService added in v0.0.12

func (s *Service) RPCService() (string, http.Handler)

RPCService returns the Connect registration for the auth surface: the procedure prefix and the handler. SignIn is public; SignOut and GetSession resolve the RPC bearer token and are guarded by the interceptor, because a service-wide middleware would lock out the anonymous entry point.

func (*Service) Resolve

func (s *Service) Resolve(ctx context.Context, token string) (user.User, Session, error)

Resolve maps a cookie token back to its principal and live session, sliding the expiry forward when past half-life.

func (*Service) ResolveAccess added in v0.0.12

func (s *Service) ResolveAccess(ctx context.Context, token string) (kernel.Principal, error)

ResolveAccess verifies an RPC bearer access token: signature, issuer, audience, and expiry first, then the owning session — so a revoked or expired session cannot ride out the token TTL.

func (*Service) ResolveSession

func (s *Service) ResolveSession(ctx context.Context, token string) (kernel.Principal, error)

ResolveSession implements kernel.Authenticator: cookie token in, transport principal out.

func (*Service) RevokeAllForUser

func (s *Service) RevokeAllForUser(ctx context.Context, userID user.UserID, keepID string) error

RevokeAllForUser revokes every session, optionally sparing one (the current session during a password change).

func (*Service) RevokeCurrent

func (s *Service) RevokeCurrent(ctx context.Context, token string) error

RevokeCurrent signs out the caller's session by token.

func (*Service) RevokeForUser

func (s *Service) 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 (*Service) Rotate added in v0.0.12

func (s *Service) Rotate(ctx context.Context, se Session) (string, Session, error)

Rotate issues the next refresh token for a live session: the new token replaces the hash and restarts the sliding expiry, so the previous token stops resolving immediately.

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 WithAccessTokens added in v0.0.12

func WithAccessTokens(signer *AccessTokenSigner) ServiceOption

WithAccessTokens wires the internal access-token signer; required for the RPC bearer bridge and the Connect handlers.

func WithClock

func WithClock(now func() time.Time) ServiceOption

WithClock overrides the service clock (tests).

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).

func WithShortLifetime added in v0.0.12

func WithShortLifetime(d time.Duration) ServiceOption

WithShortLifetime sets the lifetime of a session issued without "remember me". It must not exceed the lifetime set by WithLifetime.

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

	// Remember records the duration requested at sign-in: a session
	// issued with remember=false keeps the short lifetime across
	// sliding refreshes and rotations.
	Remember bool
}

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

type SessionID = typeid.TypeID[sessionPrefix]

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)
	// ValidByID resolves one live session with its user by session
	// ID — the access-token check anchors on it.
	ValidByID(ctx context.Context, id string) (Session, user.User, error)
	Touch(ctx context.Context, id string, expiresAt time.Time) error
	// Rotate replaces the session's refresh token hash and restarts
	// its sliding expiry; the previous token stops resolving.
	Rotate(ctx context.Context, id string, tokenHash 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.

type Verifier

type Verifier interface {
	VerifyIdentity(ctx context.Context, identityText, secret string) (user.User, error)
}

Verifier checks an identity + secret pair. Implemented by the password feature; passkeys plug in here later.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL