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
- Variables
- func AddUserToGroup(ctx context.Context, db *gorm.DB, user *User, group *Group) error
- func CSRFMiddleware(cfg config.Config, exemptPaths ...string) gin.HandlerFunc
- func GrantPermissionToGroup(ctx context.Context, db *gorm.DB, group *Group, permission *Permission) error
- func GrantPermissionToUser(ctx context.Context, db *gorm.DB, user *User, permission *Permission) error
- func HasPermission(ctx context.Context, db *gorm.DB, user User, key string) (bool, error)
- func HasPermissions(ctx context.Context, db *gorm.DB, user User, keys ...string) (map[string]bool, error)
- func Migrate(db *gorm.DB) error
- func Models() []any
- func Mount(api huma.API, db *gorm.DB, cfg config.Config) error
- type Clock
- type Group
- type Hasher
- type Permission
- type PublicUser
- type RefreshToken
- type Service
- func (s *Service) Authenticate(ctx context.Context, email, password string) (User, error)
- func (s *Service) CreateSuperuser(ctx context.Context, email, password string) (User, error)
- func (s *Service) IssueTokens(ctx context.Context, user User) (TokenPair, error)
- func (s *Service) ParseAccess(ctx context.Context, token string) (User, error)
- func (s *Service) Register(ctx context.Context, email, password string) (User, error)
- func (s *Service) RequireCookieSession() func(ctx huma.Context, next func(huma.Context))
- func (s *Service) RevokeRefresh(ctx context.Context, raw string) error
- func (s *Service) RotateRefresh(ctx context.Context, raw string) (TokenPair, error)
- type TokenPair
- type User
Constants ¶
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 ¶
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 ¶
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 ¶
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 Mount ¶
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 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.
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 ¶
NewService constructs a Service. cfg.Auth.JWTSecret must be set.
func (*Service) Authenticate ¶
Authenticate verifies email and password.
func (*Service) CreateSuperuser ¶
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 ¶
IssueTokens returns a new access JWT and a rotating refresh token.
func (*Service) ParseAccess ¶
ParseAccess validates a Bearer access JWT and loads the user.
func (*Service) RequireCookieSession ¶
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 ¶
RevokeRefresh invalidates one refresh token. Missing/already-revoked tokens succeed so logout is idempotent.
func (*Service) RotateRefresh ¶
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.