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

session

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

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

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

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

type Kind string

Kind is the session kind. Mirrors the chk_sessions_kind DB constraint.

const (
	KindAdmin Kind = "admin"
	// KindAPI is a session issued by the JSON API login. It is never valid
	// for the admin UI.
	KindAPI Kind = "api"
)

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.

func (*Session) IsExpired

func (s *Session) IsExpired() bool

IsExpired returns true if the session has aged out.

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 NewStore

func NewStore(db *database.DB) *Store

NewStore constructs a session store backed by the given database.

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

func (s *Store) PurgeExpired(ctx context.Context) (int64, error)

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

func (s *Store) Revoke(ctx context.Context, sessionID uuid.UUID) error

Revoke deletes a session by ID. Used for logout.

func (*Store) RevokeAllForUser

func (s *Store) RevokeAllForUser(ctx context.Context, userID uuid.UUID) error

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

func (s *Store) Validate(ctx context.Context, token string) (*Session, error)

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

Jump to

Keyboard shortcuts

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