Documentation
¶
Overview ¶
Package superuser implements a system-operator identity plane that is structurally separate from the user package: its own table (via SuperuserStore), its own claims type, and — critically — its own JWT audience, so a perfectly valid user access token can never be accepted by superuser middleware, and vice versa, even though both planes may share the same underlying jwt.Signer/secret.
Index ¶
- Constants
- Variables
- type Claims
- type Config
- type LoginResult
- type Service
- func (s *Service) Authenticate(ctx context.Context, email, password, userAgent, ipAddress string) (LoginResult, error)
- func (s *Service) BeginTwoFactorSetup(ctx context.Context, superuserID, email string) (TwoFactorSetup, error)
- func (s *Service) Bootstrap(ctx context.Context, email, password, displayName string) (store.Superuser, error)
- func (s *Service) ChangePassword(ctx context.Context, superuserID, currentSessionID, current, next string) (int, error)
- func (s *Service) ConfirmTwoFactorSetup(ctx context.Context, superuserID, code string) ([]string, error)
- func (s *Service) CreateFromShell(ctx context.Context, email, password, displayName string) (store.Superuser, error)
- func (s *Service) CreateSuperuser(ctx context.Context, email, password, displayName, createdByID string) (store.Superuser, error)
- func (s *Service) Deactivate(ctx context.Context, callerID, targetID string) error
- func (s *Service) DisableTwoFactor(ctx context.Context, superuserID, code string) error
- func (s *Service) Impersonate(ctx context.Context, superuserID, targetUserID, targetUserEmail string) (string, error)
- func (s *Service) ListSessions(ctx context.Context, superuserID, currentSessionID string) ([]Session, error)
- func (s *Service) ListSuperusers(ctx context.Context) ([]store.Superuser, error)
- func (s *Service) Logout(ctx context.Context, refreshToken string) error
- func (s *Service) Refresh(ctx context.Context, refreshToken, userAgent, ipAddress string) (TokenPair, error)
- func (s *Service) RegenerateBackupCodes(ctx context.Context, superuserID, code string) ([]string, error)
- func (s *Service) RevokeOtherSessions(ctx context.Context, superuserID, currentSessionID string) (int, error)
- func (s *Service) RevokeSession(ctx context.Context, superuserID, sessionID string) error
- func (s *Service) TwoFactorStatus(ctx context.Context, superuserID string) (TwoFactorStatus, error)
- func (s *Service) Verify(token string) (Claims, error)
- func (s *Service) VerifyTwoFactorLogin(ctx context.Context, pendingToken, code, userAgent, ipAddress string) (TokenPair, error)
- type Session
- type Stores
- type TokenPair
- type TwoFactorSetup
- type TwoFactorStatus
Constants ¶
const DefaultAudience = "authit-superuser"
DefaultAudience is the JWT audience claim that marks a token as belonging to the superuser plane.
Variables ¶
var ( ErrInvalidCredentials = errors.New("authit/superuser: invalid credentials") ErrAccountLocked = errors.New("authit/superuser: account locked") ErrInactive = errors.New("authit/superuser: account is not active") ErrInvalidToken = errors.New("authit/superuser: invalid or expired token") ErrCannotDeactivateSelf = errors.New("authit/superuser: cannot deactivate your own account") ErrAlreadyBootstrapped = errors.New("authit/superuser: at least one superuser already exists") ErrTwoFactorEnabled = errors.New("authit/superuser: a second factor is already enabled") ErrTwoFactorNotEnabled = errors.New("authit/superuser: no second factor is enabled") ErrInvalidTwoFactor = errors.New("authit/superuser: invalid second-factor code") ErrSessionNotFound = errors.New("authit/superuser: no such session") )
var ErrRateLimited = ratelimit.ErrRateLimited
ErrRateLimited is returned when Config.RateLimiter refuses a login. Alias for ratelimit.ErrRateLimited. A limiter's own failure is propagated unchanged instead.
var ErrWeakPassword = authitcrypto.ErrWeakPassword
ErrWeakPassword is returned by Bootstrap and CreateSuperuser when Config.PasswordValidator rejects the password. Alias for crypto.ErrWeakPassword so errors.Is matches either.
Functions ¶
This section is empty.
Types ¶
type Claims ¶
type Claims struct {
jwt.RegisteredClaims
Email string `json:"email,omitempty"`
}
Claims is the admin-plane access token claim set. Subject carries the superuser ID; ID (jti) carries the session — the refresh token's row id — so a request can tell which of an operator's sessions it is. Impersonation does NOT use this type — Impersonate mints an ordinary jwt.Claims (user-plane) token instead, so it flows through existing user routes unchanged; see jwt.Claims.ActorID.
func (Claims) HasAudience ¶
HasAudience reports whether aud is present in the claims' audience list.
type Config ¶
type Config struct {
// Audience is the JWT audience claim that marks a token as belonging to
// this plane. Defaults to DefaultAudience.
Audience string
// AccessTokenTTL defaults to 5 minutes.
AccessTokenTTL time.Duration
// RefreshTokenTTL defaults to 7 days.
RefreshTokenTTL time.Duration
// ImpersonationTTL defaults to 15 minutes.
ImpersonationTTL time.Duration
// MaxFailedLoginAttempts defaults to 5. Ignored if Stores.Lockouts is
// nil.
MaxFailedLoginAttempts int
// FailedLoginWindow defaults to 15 minutes.
FailedLoginWindow time.Duration
// AuditLogger receives security-relevant events (login, lockout,
// deactivation, impersonation). Nil means events are not recorded —
// see package audit.
AuditLogger audit.Logger
// PasswordHasher hashes and verifies operator passwords. Nil means
// crypto.DefaultHasher() — Argon2id. Existing hashes in any format
// authit has written keep verifying, and are upgraded on next login.
PasswordHasher authitcrypto.Hasher
// PasswordValidator rejects unacceptable passwords when an operator
// account is created. Nil means crypto.DefaultPasswordPolicy().
// Consider a stricter policy here than on the user plane: these
// accounts can impersonate.
PasswordValidator authitcrypto.PasswordValidator
// ImpersonationSigner signs the user-plane tokens Impersonate mints.
// Nil means this plane's own signer, which is right when one key serves
// both planes. Set it when the user plane has a key of its own: a token
// the user plane cannot verify impersonates nobody.
ImpersonationSigner authitjwt.Signer
// RateLimiter throttles Authenticate before the password KDF runs.
// Nil means ratelimit.Noop. Keys:
//
// superuser-login:ip:<ip> per source address
// superuser-login:email:<email> per account, normalised
RateLimiter ratelimit.Limiter
// TOTPEncryptionKey encrypts TOTP secrets at rest; 32 bytes. Required
// when Stores.TwoFactor is set.
TOTPEncryptionKey []byte
// TOTPIssuer names the account in an authenticator app. Defaults to
// "authit-superuser".
TOTPIssuer string
// BackupCodeCount defaults to 10.
BackupCodeCount int
// PendingTwoFactorTTL is how long an operator has between a correct
// password and a correct code. Defaults to 5 minutes.
PendingTwoFactorTTL time.Duration
}
Config tunes the superuser package's flows. Defaults are intentionally stricter than the user package's: short-lived access tokens, since operator sessions warrant more frequent re-authentication.
type LoginResult ¶ added in v0.4.0
LoginResult is what Authenticate returns: tokens, or — for an operator with a second factor — the pending token VerifyTwoFactorLogin exchanges for them. Exactly one is set.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements the superuser/operator auth plane.
func NewService ¶
NewService constructs a Service. signer may be the same jwt.Signer used by the user package — audience separation, not a separate secret, is what keeps the two planes from accepting each other's tokens. Config.AuditLogger may be nil, in which case audit.NoopLogger is used.
func (*Service) Authenticate ¶
func (s *Service) Authenticate(ctx context.Context, email, password, userAgent, ipAddress string) (LoginResult, error)
Authenticate verifies email/password against the superuser table and issues a token pair scoped to this plane's audience.
An operator with a second factor gets a LoginResult carrying only a TwoFactorToken; VerifyTwoFactorLogin completes the sign-in.
func (*Service) BeginTwoFactorSetup ¶ added in v0.4.0
func (s *Service) BeginTwoFactorSetup(ctx context.Context, superuserID, email string) (TwoFactorSetup, error)
BeginTwoFactorSetup stores a fresh secret, not yet enabled; nothing changes at sign-in until ConfirmTwoFactorSetup proves the app holds it.
func (*Service) Bootstrap ¶
func (s *Service) Bootstrap(ctx context.Context, email, password, displayName string) (store.Superuser, error)
Bootstrap creates the first superuser, and only the first: it fails with ErrAlreadyBootstrapped if any superuser already exists. Intended to be called once at application startup from a trusted source (e.g. an environment variable), never from an HTTP handler.
func (*Service) ChangePassword ¶ added in v0.4.0
func (s *Service) ChangePassword(ctx context.Context, superuserID, currentSessionID, current, next string) (int, error)
ChangePassword replaces the operator's password and ends every other session, reporting how many: whoever else knew the old one is signed out with it.
func (*Service) ConfirmTwoFactorSetup ¶ added in v0.4.0
func (s *Service) ConfirmTwoFactorSetup(ctx context.Context, superuserID, code string) ([]string, error)
ConfirmTwoFactorSetup enables the factor from BeginTwoFactorSetup and returns the backup codes, in plaintext, once.
func (*Service) CreateFromShell ¶ added in v0.4.0
func (s *Service) CreateFromShell(ctx context.Context, email, password, displayName string) (store.Superuser, error)
CreateFromShell creates a superuser attributed to nobody, for a command run by whoever holds a shell on the deployment. That access already outranks this plane, so there is no signed-in superuser to name as creator.
func (*Service) CreateSuperuser ¶
func (s *Service) CreateSuperuser(ctx context.Context, email, password, displayName, createdByID string) (store.Superuser, error)
CreateSuperuser creates an additional superuser, attributed to createdByID (another superuser's ID). There is no public registration endpoint for this plane by design — only an authenticated superuser (or Bootstrap) can create one.
func (*Service) Deactivate ¶
Deactivate soft-deletes a superuser account and revokes all of its sessions. There is deliberately no reactivate: a deactivated superuser is not recoverable through this API.
func (*Service) DisableTwoFactor ¶ added in v0.4.0
DisableTwoFactor removes the factor, given a current code or a backup code, so an operator who lost the app can still turn it off.
func (*Service) Impersonate ¶
func (s *Service) Impersonate(ctx context.Context, superuserID, targetUserID, targetUserEmail string) (string, error)
Impersonate mints a short-lived, ordinary user-plane access token (no superuser audience) for targetUserID, stamped with ActorID=superuserID so it is distinguishable from a normal login (jwt.Claims.IsImpersonation). Because it carries the user-plane's normal shape, it flows through existing user routes/middleware unchanged.
The token is not tracked server-side: it is valid until it naturally expires (Config.ImpersonationTTL, default 15 minutes) and cannot be revoked early. This bounds blast radius via a short TTL rather than a live revocation check on every request. The call is recorded through Config.AuditLogger if one is configured (see package audit) — early revocation is still on the host, but the trail is not.
func (*Service) ListSessions ¶ added in v0.4.0
func (s *Service) ListSessions(ctx context.Context, superuserID, currentSessionID string) ([]Session, error)
ListSessions returns the operator's live sessions, oldest first.
func (*Service) ListSuperusers ¶
ListSuperusers lists every superuser account.
func (*Service) Refresh ¶
func (s *Service) Refresh(ctx context.Context, refreshToken, userAgent, ipAddress string) (TokenPair, error)
Refresh exchanges a valid, unrevoked superuser refresh token for a new token pair, rotating it.
func (*Service) RegenerateBackupCodes ¶ added in v0.4.0
func (s *Service) RegenerateBackupCodes(ctx context.Context, superuserID, code string) ([]string, error)
RegenerateBackupCodes replaces the backup codes. It takes an app code only, so a stolen backup code cannot mint more.
func (*Service) RevokeOtherSessions ¶ added in v0.4.0
func (s *Service) RevokeOtherSessions(ctx context.Context, superuserID, currentSessionID string) (int, error)
RevokeOtherSessions ends every session but currentSessionID and reports how many it ended.
func (*Service) RevokeSession ¶ added in v0.4.0
RevokeSession ends one of the operator's own sessions. Another operator's session id is ErrSessionNotFound, like one that does not exist.
func (*Service) TwoFactorStatus ¶ added in v0.4.0
TwoFactorStatus reports whether superuserID has a second factor.
func (*Service) Verify ¶
Verify validates a superuser access token, additionally requiring the configured audience so a valid user-plane token can never be accepted here.
func (*Service) VerifyTwoFactorLogin ¶ added in v0.4.0
func (s *Service) VerifyTwoFactorLogin(ctx context.Context, pendingToken, code, userAgent, ipAddress string) (TokenPair, error)
VerifyTwoFactorLogin completes a sign-in Authenticate left pending, exchanging the pending token and an app or backup code for tokens.
type Session ¶ added in v0.4.0
type Session struct {
ID string
IsCurrent bool
UserAgent string
IPAddress string
CreatedAt time.Time
ExpiresAt time.Time
}
Session is one of an operator's signed-in devices.
type Stores ¶
type Stores struct {
Superusers store.SuperuserStore
RefreshTokens store.SuperuserRefreshTokenStore
// Lockouts is optional; if nil, failed-login lockout is disabled.
Lockouts store.LockoutStore
// Tx is optional. Supplying it makes Refresh's rotation atomic; nil
// leaves it as two independent writes. See store.TxRunner.
Tx store.TxRunner
// TwoFactor is optional; if nil, operators cannot enrol a second factor
// and Authenticate never asks for one. Requires Config.TOTPEncryptionKey.
TwoFactor store.SuperuserTwoFactorStore
}
Stores groups the persistence ports the superuser package needs.
type TwoFactorSetup ¶ added in v0.4.0
TwoFactorSetup is what BeginTwoFactorSetup returns for the operator's authenticator app.