auth

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	WebAuthnPurposeRegister = "register"
	WebAuthnPurposeLogin    = "login"
	WebAuthnPurposeStepUp   = "step_up"
)

WebAuthn ceremony purposes. A challenge is minted for exactly one of these and may only be redeemed by the matching finish handler.

Variables

This section is empty.

Functions

func DummyVerify added in v0.2.0

func DummyVerify(secret string)

DummyVerify performs a verification against a throwaway hash so that code paths handling an unknown user or token spend roughly the same CPU time as a successful lookup. This blunts timing side channels that would otherwise leak account/token existence. The boolean result is meaningless and ignored.

func FormatToken added in v0.2.0

func FormatToken(id, secret string) string

FormatToken renders the credential a caller presents for a personal access token: the public record id, a separator, and the secret. Embedding the id lets the server look the record up in O(1) and run a single verification, instead of trying every stored token.

func GenerateRecoveryCodes added in v0.2.0

func GenerateRecoveryCodes(n int) ([]string, error)

GenerateRecoveryCodes returns n single-use recovery codes shown to the user exactly once; only their HashSecret hashes are persisted.

func GenerateTOTPSecret added in v0.2.0

func GenerateTOTPSecret() (string, error)

GenerateTOTPSecret returns a fresh base32 (unpadded) shared secret.

func HashBindingToken added in v0.2.0

func HashBindingToken(token string) string

HashBindingToken returns the hex SHA-256 of a browser-binding token. The callback hashes the presented cookie and compares it to the stored hash.

func HashRecoveryCode added in v0.2.0

func HashRecoveryCode(code string) string

HashRecoveryCode hashes a recovery code for storage. Recovery codes carry ~80 bits of entropy, so a fast SHA-256 (not a slow password KDF) is sufficient and keeps the constant-time, lock-held comparison in the store cheap.

func HashSecret

func HashSecret(secret string) (string, error)

func NewRandomToken

func NewRandomToken(bytes int) (string, error)

func OTPAuthURI added in v0.2.0

func OTPAuthURI(issuer, account, secret string) string

OTPAuthURI builds the otpauth:// URI an authenticator app consumes. The issuer and account are shown to the user; the secret is the base32 shared key.

func SplitToken added in v0.2.0

func SplitToken(presented string) (id, secret string, ok bool)

SplitToken parses a presented credential of the form "<id>.<secret>". Record ids never contain a dot and secrets are URL-safe base64 (also dot-free), so splitting on the first dot is unambiguous.

func TOTPCodeAt added in v0.2.0

func TOTPCodeAt(secret string, t time.Time) (string, error)

TOTPCodeAt returns the 6-digit code for a specific instant. Exposed mainly so tests can assert against the RFC 6238 vectors and so enrollment can preview.

func ValidateTOTP added in v0.2.0

func ValidateTOTP(secret, code string, t time.Time) bool

ValidateTOTP reports whether code matches secret at time t, tolerating one step of clock skew on either side. The comparison is constant-time.

func ValidateTOTPStep added in v0.2.0

func ValidateTOTPStep(secret, code string, t time.Time) (uint64, bool)

ValidateTOTPStep reports whether code matches secret at time t (tolerating one step of clock skew on either side) and, on a match, returns the RFC-6238 counter (step) that matched. The returned step lets a caller enforce single-use of each code by persisting the highest accepted step and rejecting any submission whose step is not strictly greater. The comparison is constant-time; every candidate step is compared without an early return so the timing does not reveal which window matched.

func VerifySecret

func VerifySecret(encoded, secret string) bool

Types

type OIDCAuthState added in v0.2.0

type OIDCAuthState struct {
	State         string `json:"state"`
	Nonce         string `json:"nonce"`
	CodeVerifier  string `json:"code_verifier"`
	ProviderID    string `json:"provider_id"`
	ClientIP      string `json:"client_ip"`
	RedirectAfter string `json:"redirect_after"`
	// BindingHash is the SHA-256 (hex) of a per-flow token set as an HttpOnly
	// cookie at /start; the callback must present the matching cookie. This
	// binds the round trip to the initiating browser (login-CSRF defense), the
	// spec-intended role of `state`. Only the hash is persisted.
	BindingHash string    `json:"binding_hash"`
	CreatedAt   time.Time `json:"created_at"`
	ExpiresAt   time.Time `json:"expires_at"`
}

OIDCAuthState is the short-lived, single-use state for one in-flight OIDC auth-code login. It is created at /start, persisted, and consumed at /callback. It binds the round trip together:

  • State: the OAuth2 `state` parameter (CSRF defense; also the store key).
  • Nonce: checked against the ID token `nonce` claim (replay defense).
  • CodeVerifier: the PKCE verifier (binds the code to this client).
  • ClientIP: the requester's IP; the callback must come from the same IP.
  • RedirectAfter: a sanitized same-origin path to land on after login.

func NewOIDCAuthState added in v0.2.0

func NewOIDCAuthState(providerID, clientIP, redirectAfter, codeVerifier, bindingToken string, ttl time.Duration) (OIDCAuthState, error)

NewOIDCAuthState mints state with fresh random `state` and `nonce` values, the caller-supplied PKCE verifier (generated by the oidc layer via oauth2.GenerateVerifier so this package stays dependency-free), and the SHA-256 of the caller-supplied browser-binding token.

func (OIDCAuthState) Expired added in v0.2.0

func (s OIDCAuthState) Expired() bool

Expired reports whether the state has passed its TTL.

type Session

type Session struct {
	ID        string
	ActorID   string
	CSRFToken string
	// Epoch records the user's SecurityEpoch at the instant this session was
	// minted. principalFromRequest rejects a session whose Epoch is older than
	// the user's current SecurityEpoch, so a privilege-reducing event (2FA
	// disable, password change, admin revoke) invalidates all prior sessions.
	Epoch     uint64
	CreatedAt time.Time
	ExpiresAt time.Time
	Revoked   bool
}

func NewSession

func NewSession(actorID string, ttl time.Duration) (Session, error)

func (Session) Active

func (s Session) Active(now time.Time) bool

type TOTPChallenge added in v0.2.0

type TOTPChallenge struct {
	ID        string
	UserID    string
	ClientIP  string
	CreatedAt time.Time
	ExpiresAt time.Time
	Used      bool
	// Attempts counts failed second-factor submissions; the store burns the
	// challenge once it reaches the per-challenge cap.
	Attempts int
}

TOTPChallenge is a short-lived, single-use gate issued after a first factor (password or OIDC) succeeds but before a session is granted to a 2FA user. It is store-backed and IP-bound, mirroring Session.

func NewTOTPChallenge added in v0.2.0

func NewTOTPChallenge(userID, clientIP string, ttl time.Duration) (TOTPChallenge, error)

NewTOTPChallenge mints a challenge bound to a user and the requesting client.

func (TOTPChallenge) Active added in v0.2.0

func (c TOTPChallenge) Active(now time.Time) bool

Active reports whether the challenge can still be redeemed at now.

type WebAuthnChallenge added in v0.2.0

type WebAuthnChallenge struct {
	ID string `json:"id"`
	// UserID is the owning user for a registration ceremony. It is empty for a
	// usernameless (discoverable) login, where the user is only resolved at finish
	// from the authenticator's user handle.
	UserID string `json:"user_id,omitempty"`
	// ClientIP binds the challenge to the requesting client, exactly like
	// TOTPChallenge, so a challenge captured on one network cannot be completed
	// from another.
	ClientIP string `json:"client_ip"`
	// Purpose is one of WebAuthnPurpose*. The finish handler refuses a challenge
	// minted for the other ceremony.
	Purpose string `json:"purpose"`
	// SessionData is the JSON-encoded webauthn.SessionData for this ceremony.
	SessionData []byte    `json:"session_data"`
	CreatedAt   time.Time `json:"created_at"`
	ExpiresAt   time.Time `json:"expires_at"`
	Used        bool      `json:"used"`
}

WebAuthnChallenge is the short-lived, single-use, IP-bound gate that ties the begin and finish steps of a WebAuthn ceremony together, mirroring TOTPChallenge. It stores the library's opaque SessionData (the server-side half of the ceremony: the random challenge, the allowed-credential list, and the required user-verification level) so the client cannot tamper with it between the two calls.

func NewWebAuthnChallenge added in v0.2.0

func NewWebAuthnChallenge(userID, clientIP, purpose string, sessionData []byte, ttl time.Duration) (WebAuthnChallenge, error)

NewWebAuthnChallenge mints a challenge for a ceremony bound to the requesting client (and, for registration, to a user). The id is a fresh unguessable token returned to the client and echoed back at finish.

func (WebAuthnChallenge) Active added in v0.2.0

func (c WebAuthnChallenge) Active(now time.Time) bool

Active reports whether the challenge can still be redeemed at now.

type WebAuthnCredential added in v0.2.0

type WebAuthnCredential struct {
	// ID is the opaque store record id (id.New("wacred")). It is the handle the
	// management UI uses to rename/delete; it is NOT the WebAuthn credential id.
	ID     string `json:"id"`
	UserID string `json:"user_id"`
	// Name is an operator-editable label, defaulted at creation from the
	// authenticator/User-Agent so a user with several passkeys can tell them apart.
	Name string `json:"name"`
	// CredentialID is the raw WebAuthn credential id (the authenticator's handle
	// for this key). Unique per credential; used to look a credential up on login.
	CredentialID []byte `json:"credential_id"`
	// PublicKey is the COSE-encoded public key used to verify assertions.
	PublicKey []byte `json:"public_key"`
	// AAGUID identifies the authenticator model (all-zero for many platform
	// authenticators, including Apple's). Advisory only.
	AAGUID []byte `json:"aaguid,omitempty"`
	// SignCount is the last observed authenticator signature counter. Synced
	// passkeys (e.g. Apple's, in iCloud Keychain) always report 0; a regression
	// from a previously non-zero value is a clone signal, logged but not fatal.
	SignCount uint32 `json:"sign_count"`
	// Transports are the hints the authenticator advertised ("internal", "hybrid",
	// "usb", …); passed back to the browser to speed up future prompts.
	Transports []string `json:"transports,omitempty"`
	// BackupEligible (BE) reports whether the credential can be backed up/synced.
	// It is fixed for the life of the credential.
	BackupEligible bool `json:"backup_eligible"`
	// BackupState (BS) reports whether the credential is currently backed
	// up/synced. It can change over time and is refreshed on every login.
	BackupState bool      `json:"backup_state"`
	CreatedAt   time.Time `json:"created_at"`
	LastUsedAt  time.Time `json:"last_used_at,omitempty"`
}

WebAuthnCredential is a single registered passkey belonging to a user. The public key and credential id are, by design, not secret (the whole point of asymmetric WebAuthn is that the private key never leaves the authenticator), so unlike TOTPSecret this record needs no at-rest envelope encryption.

Jump to

Keyboard shortcuts

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