auth

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package auth is Gombit's runtime Bearer JWT surface (C3 / M5-2).

Behavior lives here, not in generated apps. framework.New mounts the Huma routes when config.Auth.JWTSecret is set and a database is attached.

Access tokens are short-lived JWTs. Refresh tokens are opaque, stored hashed, and rotated on each successful refresh. The generated SPA keeps both in memory — never localStorage or sessionStorage. Cookie/session + CSRF (--auth cookie) is a first-class alternative (M5-3).

Users can receive stable permission keys directly or through groups. IsSuperuser bypasses every permission check.

Index

Constants

View Source
const (
	// AccessCookieName holds the short-lived access JWT in cookie mode.
	AccessCookieName = "gombit_access"
	// RefreshCookieName holds the rotating refresh token in cookie mode.
	RefreshCookieName = "gombit_refresh"
	// CSRFCookieName holds the signed double-submit CSRF token.
	CSRFCookieName = "gombit_csrf"
	// CSRFHeaderName is the header clients must echo the CSRF cookie value
	// into for state-changing (POST/PUT/PATCH/DELETE) requests.
	CSRFHeaderName = "X-CSRF-Token"
)

Cookie and CSRF header names for cookie-mode session auth (M5-3). See docs/auth-cookie.md for the threat model these pair with.

Variables

View Source
var ErrEmailTaken = errEmailTaken

ErrEmailTaken is the exported form of errEmailTaken. Register and CreateSuperuser return it (wrapped) so callers such as gombit createsuperuser can detect duplicates with errors.Is without a second uniqueness check.

Functions

func AddUserToGroup

func AddUserToGroup(ctx context.Context, db *gorm.DB, user *User, group *Group) error

AddUserToGroup adds a persisted user to a persisted group.

func CSRFMiddleware

func CSRFMiddleware(cfg config.Config, exemptPaths ...string) gin.HandlerFunc

CSRFMiddleware enforces the double-submit CSRF defense documented in docs/auth-cookie.md for cookie-mode session auth (M5-3). It is a Gin middleware (not scoped to Huma auth routes) because CSRF must cover every state-changing request in the app, including future feature routes, not just the auth surface.

Safe methods (GET/HEAD/OPTIONS) bootstrap a signed CSRF cookie when one is missing. Other methods require a X-CSRF-Token header that matches the gombit_csrf cookie and verifies against cfg.Auth.JWTSecret; otherwise the request is rejected with a D10 403 authorization error.

framework.New wires this into the runtime middleware stack automatically when cfg.Auth.Enabled() and cfg.Auth.EffectiveMode() == AuthModeCookie.

exemptPaths are exact request paths that opt out of CSRF enforcement on unsafe methods — for non-browser endpoints (webhooks, server-to-server callbacks) that cannot participate in the double-submit defense and must authenticate themselves by other means (e.g. HMAC signature verification). Safe methods still bootstrap the cookie on those paths. See framework.WithCSRFExemptPaths and docs/auth-cookie.md.

func GrantPermissionToGroup

func GrantPermissionToGroup(ctx context.Context, db *gorm.DB, group *Group, permission *Permission) error

GrantPermissionToGroup grants a persisted permission to a persisted group.

func GrantPermissionToUser

func GrantPermissionToUser(ctx context.Context, db *gorm.DB, user *User, permission *Permission) error

GrantPermissionToUser grants a persisted permission directly to a user.

func HasPermission

func HasPermission(ctx context.Context, db *gorm.DB, user User, key string) (bool, error)

HasPermission reports whether user has key directly, through a group, or through the Django-style superuser bypass.

func HasPermissions

func HasPermissions(ctx context.Context, db *gorm.DB, user User, keys ...string) (map[string]bool, error)

HasPermissions checks several keys while loading the user's associations once. Missing users and unassigned keys are reported as false.

func Migrate

func Migrate(db *gorm.DB) error

Migrate creates the auth identity, token, group, and permission tables.

func Models

func Models() []any

Models returns GORM models for Atlas / AutoMigrate.

func Mount

func Mount(api huma.API, db *gorm.DB, cfg config.Config) error

Mount registers framework-owned auth Huma routes on api. The route shapes differ by cfg.Auth.EffectiveMode(): AuthModeJWT (default) mounts the Bearer token surface (register/login/refresh/logout/me exchanging JSON tokens); AuthModeCookie mounts the same operation IDs and paths but exchanges HttpOnly session cookies instead, plus GET /auth/csrf. The two modes are mutually exclusive per app — see docs/auth-cookie.md for the cookie/CSRF threat model.

Types

type Clock

type Clock interface {
	Now() time.Time
}

Clock is the time source used for token expiry. Tests inject a fake.

type Group

type Group struct {
	ID          uint         `gorm:"primaryKey"`
	Name        string       `gorm:"uniqueIndex;not null;size:100"`
	Permissions []Permission `gorm:"many2many:auth_group_permissions;"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

Group collects permissions that can be assigned to users.

func EnsureGroup

func EnsureGroup(ctx context.Context, db *gorm.DB, name string) (Group, error)

EnsureGroup returns the group with name, creating it when needed.

type Hasher

type Hasher interface {
	Hash(password string) (string, error)
	Compare(hash, password string) error
}

Hasher hashes and compares passwords.

type Permission

type Permission struct {
	ID          uint   `gorm:"primaryKey"`
	Key         string `gorm:"column:permission_key;uniqueIndex;not null;size:120"` // not "key": reserved in MySQL
	Description string `gorm:"size:255"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

Permission is a stable authorization key, such as admin.widgets.view.

func EnsurePermission

func EnsurePermission(ctx context.Context, db *gorm.DB, key, description string) (Permission, error)

EnsurePermission returns the permission for key, creating it when needed.

type PublicUser

type PublicUser struct {
	ID    uint   `json:"id" example:"1" doc:"User identifier"`
	Email string `json:"email" example:"ada@example.com" format:"email" doc:"Normalized email"`
}

PublicUser is the safe user representation returned by register and /me.

type RefreshToken

type RefreshToken struct {
	ID         uint       `gorm:"primaryKey"`
	UserID     uint       `gorm:"index;not null"`
	TokenHash  string     `gorm:"uniqueIndex;size:64;not null"`
	ExpiresAt  time.Time  `gorm:"index;not null"`
	RevokedAt  *time.Time `gorm:"index"`
	ReplacedBy *uint
	CreatedAt  time.Time
}

RefreshToken is a hashed, rotating refresh credential.

type Service

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

Service is the runtime auth implementation: users, password hashes, access JWTs, and rotating refresh tokens.

func NewService

func NewService(db *gorm.DB, cfg config.Config) (*Service, error)

NewService constructs a Service. cfg.Auth.JWTSecret must be set.

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, email, password string) (User, error)

Authenticate verifies email and password.

func (*Service) CreateSuperuser

func (s *Service) CreateSuperuser(ctx context.Context, email, password string) (User, error)

CreateSuperuser creates a user with IsSuperuser set, for gombit createsuperuser (M4-6). It shares Register's hasher and uniqueness path; duplicate emails return errEmailTaken.

func (*Service) IssueTokens

func (s *Service) IssueTokens(ctx context.Context, user User) (TokenPair, error)

IssueTokens returns a new access JWT and a rotating refresh token.

func (*Service) ParseAccess

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

ParseAccess validates a Bearer access JWT and loads the user.

func (*Service) Register

func (s *Service) Register(ctx context.Context, email, password string) (User, error)

Register creates a user. Duplicate emails return errEmailTaken.

func (*Service) RequireCookieSession

func (s *Service) RequireCookieSession() func(ctx huma.Context, next func(huma.Context))

RequireCookieSession is the exported cookie-session gate used by runtime packages such as admin. It authenticates the gombit_access cookie and stores the User on the request context for UserFromContext. It does not itself enforce CSRF; CSRFMiddleware (csrf.go) is wired as global Gin middleware ahead of it for state-changing requests.

func (*Service) RevokeRefresh

func (s *Service) RevokeRefresh(ctx context.Context, raw string) error

RevokeRefresh invalidates one refresh token. Missing/already-revoked tokens succeed so logout is idempotent.

func (*Service) RotateRefresh

func (s *Service) RotateRefresh(ctx context.Context, raw string) (TokenPair, error)

RotateRefresh validates the current refresh token, revokes it, and issues a new pair. Reuse of an already-rotated token revokes the user's remaining tokens. Concurrent callers with the same still-valid token share one rotation so a lost race cannot family-revoke the winner's new session.

type TokenPair

type TokenPair struct {
	AccessToken  string `json:"access_token" example:"eyJhbGciOiJIUzI1NiJ9..." doc:"Short-lived Bearer access JWT. Hold in memory only."`
	RefreshToken string `json:"refresh_token" example:"aa11..." doc:"Opaque rotating refresh token. Hold in memory only; never web storage."`
	TokenType    string `json:"token_type" example:"Bearer" doc:"Token type; always Bearer"`
	ExpiresIn    int    `json:"expires_in" example:"900" doc:"Access token lifetime in seconds"`
}

TokenPair is the login/refresh success payload (inside D10 data).

type User

type User struct {
	ID           uint         `gorm:"primaryKey"`
	Email        string       `gorm:"uniqueIndex;size:255;not null"`
	PasswordHash string       `gorm:"not null"`
	IsSuperuser  bool         `gorm:"not null;default:false"`
	Groups       []Group      `gorm:"many2many:auth_user_groups;"`
	Permissions  []Permission `gorm:"many2many:auth_user_permissions;"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

User is the auth identity. IsSuperuser bypasses every permission check; regular users receive permissions directly or through groups.

func UserFromContext

func UserFromContext(ctx context.Context) (User, bool)

UserFromContext returns the authenticated user stored by requireBearer or RequireCookieSession.

Jump to

Keyboard shortcuts

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