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

account

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package account defines the account lifecycle domain (signup, signin, verification, password reset).

Index

Constants

View Source
const BreachCheckTimeout = 5 * time.Second

BreachCheckTimeout bounds one HIBP range lookup.

Variables

View Source
var (
	ErrInvalidCredentials = errors.New("account: invalid credentials")
	ErrEmailTaken         = errors.New("account: email already taken")
	ErrUsernameTaken      = errors.New("account: username already taken")
	ErrUserBanned         = errors.New("account: user is banned")
	ErrAccountLocked      = errors.New("account: account temporarily locked due to too many failed attempts")
	ErrSessionExpired     = errors.New("account: session expired")
	ErrWeakPassword       = errors.New("account: password does not meet requirements")
	ErrPasswordExpired    = errors.New("account: password has expired and must be changed")
	ErrPasswordReused     = errors.New("account: password was recently used and cannot be reused")
	ErrEmailNotVerified   = errors.New("account: email address must be verified before signing in")
	ErrTooManyAttempts    = errors.New("account: too many verification attempts; request a new code")
	ErrRateLimited        = errors.New("account: too many attempts for this identifier; try again later")
	ErrMFARequired        = errors.New("account: MFA challenge required to complete sign-in")
)

Sentinel errors for account operations.

View Source
var ErrBreachCheckUnavailable = errors.New("account: breach check could not run")

ErrBreachCheckUnavailable is what IsBreachedContext returns when the lookup could not run: the request failed, HIBP answered something other than 200, or the body could not be read. The password is not reported as breached in that case. The transport's own error is not wrapped, because it quotes the request URL and the URL carries the hash prefix.

View Source
var ErrPasswordBreached = fmt.Errorf("%w: password found in data breach", ErrWeakPassword)

ErrPasswordBreached is returned when a password has been found in a data breach.

Functions

func CheckPassword

func CheckPassword(hash, password string) error

CheckPassword verifies a password against a hash, auto-detecting the algorithm from the hash prefix. Supports bcrypt ($2a$/$2b$) and Argon2id ($argon2id$).

func CheckPasswordArgon2

func CheckPasswordArgon2(encoded, password string) error

CheckPasswordArgon2 verifies a password against an Argon2id PHC-format hash.

func GenerateOTPCode

func GenerateOTPCode() (string, error)

GenerateOTPCode returns a cryptographically random 6-digit numeric code, zero-padded (e.g. "004271"). Used for email/phone OTP verification where the user types a short code instead of clicking a long token link.

func GenerateVerificationToken

func GenerateVerificationToken() (string, error)

GenerateVerificationToken generates a new verification token string.

func HashPassword

func HashPassword(password string, cost int) (string, error)

HashPassword hashes a password using bcrypt (legacy convenience function).

func HashPasswordArgon2

func HashPasswordArgon2(password string, params Argon2Params) (string, error)

HashPasswordArgon2 hashes a password using Argon2id and returns a PHC-format string.

func HashPasswordWithPolicy

func HashPasswordWithPolicy(password string, policy PasswordPolicy) (string, error)

HashPasswordWithPolicy hashes a password using the algorithm specified in the policy.

func IsArgon2Hash

func IsArgon2Hash(hash string) bool

IsArgon2Hash returns true if the hash string is an Argon2id hash.

func NeedsRehash

func NeedsRehash(hash string, policy PasswordPolicy) bool

NeedsRehash returns true if the hash should be regenerated using the current policy. It triggers in three cases:

  1. Algorithm changed (e.g. bcrypt → argon2id or vice versa).
  2. Argon2id hash uses parameters weaker than the policy currently demands (memory, iterations, or parallelism). Operators bumping these values to keep up with hardware progress get transparent rehash on next login without forcing a password reset.
  3. Bcrypt hash uses a cost lower than the policy's BcryptCost.

Returns false for unparseable hashes — better to leave a working hash alone than to fail-closed and force a reset on a corrupted row.

func NewSession

func NewSession(appID id.AppID, userID id.UserID, cfg SessionConfig) (*session.Session, error)

NewSession creates a new session for a user.

func NewUser

func NewUser(req *SignUpRequest, passwordHash string) *user.User

NewUser creates a new user from a signup request.

func RefreshSession

func RefreshSession(sess *session.Session, cfg SessionConfig) error

RefreshSession generates new tokens for an existing session. When cfg.RotateRefreshToken is true, both the access token and refresh token are regenerated (the old refresh token becomes invalid). When false, only the access token is rotated and the refresh token is reused.

Types

type Argon2Params

type Argon2Params struct {
	Memory      uint32 // Memory in KiB (default: 64*1024 = 64 MiB)
	Iterations  uint32 // Time cost (default: 3)
	Parallelism uint8  // Parallelism factor (default: 2)
	SaltLength  uint32 // Salt length in bytes (default: 16)
	KeyLength   uint32 // Derived key length in bytes (default: 32)
}

Argon2Params holds the parameters for Argon2id hashing.

func DefaultArgon2Params

func DefaultArgon2Params() Argon2Params

DefaultArgon2Params returns OWASP-recommended Argon2id parameters.

type BreachChecker

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

BreachChecker checks passwords against the HaveIBeenPwned Passwords API using the k-Anonymity model (only a 5-character SHA-1 prefix is sent).

func NewBreachChecker

func NewBreachChecker() *BreachChecker

NewBreachChecker creates a new breach checker with sensible defaults.

func NewBreachCheckerWithClient

func NewBreachCheckerWithClient(client *http.Client) *BreachChecker

NewBreachCheckerWithClient is NewBreachChecker sending its lookups through client. A nil client is a plain one with BreachCheckTimeout.

func (*BreachChecker) IsBreached

func (bc *BreachChecker) IsBreached(password string) (bool, error)

IsBreached checks if a password appears in known data breaches. Returns true if the password hash appears in the HIBP database. On network errors, returns false (fail-open to avoid blocking auth).

func (*BreachChecker) IsBreachedContext

func (bc *BreachChecker) IsBreachedContext(ctx context.Context, password string) (bool, error)

IsBreachedContext is IsBreached with the lookup made under ctx, so it carries ctx's values (its trace span among them) and stops when ctx does. It fails open too, reporting false, but says so: when the lookup could not run the error is ErrBreachCheckUnavailable, so a caller can count a control that did not run.

type LockedError

type LockedError struct {
	Until time.Time
}

LockedError is ErrAccountLocked carrying when the lock lifts, so a handler can answer 423 with a Retry-After. errors.Is(err, ErrAccountLocked) holds for it.

func (*LockedError) Error

func (e *LockedError) Error() string

func (*LockedError) Is

func (e *LockedError) Is(target error) bool

Is lets errors.Is match the sentinel.

func (*LockedError) RetryAfter

func (e *LockedError) RetryAfter(now time.Time) int

RetryAfter is the whole seconds until the lock lifts, at least one.

type MemoryPasswordHistoryStore

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

MemoryPasswordHistoryStore is an in-memory password history store for development and testing.

func NewMemoryPasswordHistoryStore

func NewMemoryPasswordHistoryStore() *MemoryPasswordHistoryStore

NewMemoryPasswordHistoryStore creates an in-memory password history store.

func (*MemoryPasswordHistoryStore) GetPasswordHistory

func (s *MemoryPasswordHistoryStore) GetPasswordHistory(_ context.Context, userID id.UserID, limit int) ([]PasswordHistoryEntry, error)

func (*MemoryPasswordHistoryStore) SavePasswordHash

func (s *MemoryPasswordHistoryStore) SavePasswordHash(_ context.Context, userID id.UserID, hash string) error

type PasswordHistoryEntry

type PasswordHistoryEntry struct {
	ID        string    `json:"id"`
	UserID    id.UserID `json:"user_id"`
	Hash      string    `json:"-"`
	CreatedAt time.Time `json:"created_at"`
}

PasswordHistoryEntry records a previously used password hash.

type PasswordHistoryStore

type PasswordHistoryStore interface {
	// SavePasswordHash records a new password hash for the user.
	SavePasswordHash(ctx context.Context, userID id.UserID, hash string) error

	// GetPasswordHistory returns the most recent N password hashes.
	GetPasswordHistory(ctx context.Context, userID id.UserID, limit int) ([]PasswordHistoryEntry, error)
}

PasswordHistoryStore persists previous password hashes so they cannot be reused. Implementations should store only the hash, never plaintext.

type PasswordPolicy

type PasswordPolicy struct {
	MinLength        int
	RequireUppercase bool
	RequireLowercase bool
	RequireDigit     bool
	RequireSpecial   bool
	BcryptCost       int
	Algorithm        string       // "bcrypt" (default) or "argon2id"
	Argon2Params     Argon2Params // Used when Algorithm = "argon2id"
	CheckBreached    bool         // Enable HIBP breach checking
}

PasswordPolicy configures password validation rules.

func (PasswordPolicy) ValidatePassword

func (p PasswordPolicy) ValidatePassword(password string) error

ValidatePassword checks a password against the policy.

type PasswordReset

type PasswordReset struct {
	ID        id.PasswordResetID `json:"id"`
	AppID     id.AppID           `json:"app_id"`
	EnvID     id.EnvironmentID   `json:"env_id"`
	UserID    id.UserID          `json:"user_id"`
	Token     string             `json:"-"`
	TokenHash string             `json:"-"`
	ExpiresAt time.Time          `json:"expires_at"`
	Consumed  bool               `json:"consumed"`
	CreatedAt time.Time          `json:"created_at"`
}

PasswordReset represents a password reset token.

func NewPasswordReset

func NewPasswordReset(_ context.Context, appID id.AppID, userID id.UserID, ttl time.Duration) (*PasswordReset, error)

NewPasswordReset creates a new password reset record.

type SessionConfig

type SessionConfig struct {
	TokenTTL           time.Duration
	RefreshTokenTTL    time.Duration
	MaxActiveSessions  int
	RotateRefreshToken bool

	// TokenExchangeTTL caps tokens minted by the RFC 8693 token exchange
	// grant. Short by design: an exchanged token is meant to be re-minted,
	// not held. Zero means the caller's own default applies.
	TokenExchangeTTL time.Duration
}

SessionConfig holds session token configuration.

type SignInRequest

type SignInRequest struct {
	AppID     id.AppID         `json:"app_id"`
	EnvID     id.EnvironmentID `json:"env_id"`
	Email     string           `json:"email,omitempty"`
	Username  string           `json:"username,omitempty"`
	Password  string           `json:"password"`
	IPAddress string           `json:"ip_address,omitempty"`
	UserAgent string           `json:"user_agent,omitempty"`

	// DPoPJKT binds the issued session to a client-held key (RFC 9449).
	// Empty issues an ordinary unbound session.
	DPoPJKT string `json:"-"`
}

SignInRequest is the input for account authentication.

type SignUpRequest

type SignUpRequest struct {
	AppID     id.AppID          `json:"app_id"`
	EnvID     id.EnvironmentID  `json:"env_id"`
	Email     string            `json:"email"`
	Password  string            `json:"password"`
	FirstName string            `json:"first_name,omitempty"`
	LastName  string            `json:"last_name,omitempty"`
	Username  string            `json:"username,omitempty"`
	Metadata  map[string]string `json:"metadata,omitempty"` // custom form fields
	IPAddress string            `json:"ip_address,omitempty"`
	UserAgent string            `json:"user_agent,omitempty"`

	// DPoPJKT binds the newly created session to a client-held key
	// (RFC 9449). Empty issues an ordinary unbound session.
	DPoPJKT string `json:"-"`
}

SignUpRequest is the input for account registration.

type Store

type Store interface {
	CreateVerification(ctx context.Context, v *Verification) error
	GetVerification(ctx context.Context, token string) (*Verification, error)
	ConsumeVerification(ctx context.Context, token string) error

	// GetActiveEmailVerification returns the most recent unconsumed, unexpired
	// email verification for the user, or ErrNotFound. The OTP flow looks up by
	// user (codes are short and not globally unique) rather than by token.
	GetActiveEmailVerification(ctx context.Context, userID id.UserID) (*Verification, error)

	// UpdateVerification persists mutable fields of an existing verification
	// (Attempts, Consumed).
	UpdateVerification(ctx context.Context, v *Verification) error

	CreatePasswordReset(ctx context.Context, pr *PasswordReset) error
	GetPasswordReset(ctx context.Context, token string) (*PasswordReset, error)
	ConsumePasswordReset(ctx context.Context, token string) error
}

Store defines the persistence interface for account lifecycle operations.

type Verification

type Verification struct {
	ID     id.VerificationID `json:"id"`
	AppID  id.AppID          `json:"app_id"`
	EnvID  id.EnvironmentID  `json:"env_id"`
	UserID id.UserID         `json:"user_id"`
	// Token is the plaintext, present on the way into the store and on a
	// verification looked up by that plaintext. TokenHash is what the store
	// keeps; a verification read back by user carries only the hash.
	Token     string           `json:"-"`
	TokenHash string           `json:"-"`
	Type      VerificationType `json:"type"`
	Attempts  int              `json:"attempts"`
	ExpiresAt time.Time        `json:"expires_at"`
	Consumed  bool             `json:"consumed"`
	CreatedAt time.Time        `json:"created_at"`
}

Verification represents an email or phone verification token.

Token holds either a long random token (link-based flows like magic link) or a short 6-digit OTP code (email verification). For OTP flows, Attempts tracks failed entry attempts so callers can enforce a maximum before requiring a resend.

func NewEmailVerificationCode

func NewEmailVerificationCode(appID id.AppID, userID id.UserID, ttl time.Duration) (*Verification, error)

NewEmailVerificationCode creates an email-verification record carrying a 6-digit OTP code (stored in Token) that the user types to verify. Attempts starts at 0.

func NewVerification

func NewVerification(_ context.Context, appID id.AppID, userID id.UserID, vType VerificationType, ttl time.Duration) (*Verification, error)

NewVerification creates a new email/phone verification record.

type VerificationType

type VerificationType string

VerificationType identifies the kind of verification.

const (
	VerificationEmail VerificationType = "email"
	VerificationPhone VerificationType = "phone"
)

Jump to

Keyboard shortcuts

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