Documentation
¶
Overview ¶
Package user implements user registration, authentication, session management, password reset, email verification, and TOTP-based two-factor auth. It depends only on the store interfaces it needs (no concrete database) and on a jwt.Signer for token issuance.
Index ¶
- Variables
- type AccountState
- type AuthResult
- type Config
- type EmailSender
- type EmailVerificationPolicy
- type Hooks
- type NoopEmailSender
- type Service
- func (s *Service) AccountState(ctx context.Context, userID string) (AccountState, error)
- func (s *Service) Authenticate(ctx context.Context, email, password, userAgent, ipAddress string) (AuthResult, error)
- func (s *Service) BeginTwoFactorSetup(ctx context.Context, userID, accountEmail string) (TwoFactorSetup, error)
- func (s *Service) ChangePassword(ctx context.Context, userID, currentPassword, newPassword string) error
- func (s *Service) ConfirmTwoFactorSetup(ctx context.Context, userID, code string) (TwoFactorEnrollment, error)
- func (s *Service) DisableTwoFactor(ctx context.Context, userID, code string) error
- func (s *Service) ListSessions(ctx context.Context, userID, currentRefreshToken string) ([]Session, error)
- func (s *Service) Logout(ctx context.Context, refreshToken string) error
- func (s *Service) MarkEmailVerified(ctx context.Context, userID string) error
- func (s *Service) Refresh(ctx context.Context, refreshToken, userAgent, ipAddress string) (TokenPair, error)
- func (s *Service) RegenerateBackupCodes(ctx context.Context, userID, totpCode string) ([]string, error)
- func (s *Service) Register(ctx context.Context, email, password string) (store.User, error)
- func (s *Service) RequestEmailVerification(ctx context.Context, userID string) error
- func (s *Service) RequestEmailVerificationByEmail(ctx context.Context, email string) error
- func (s *Service) RequestPasswordReset(ctx context.Context, email string) error
- func (s *Service) ResetPassword(ctx context.Context, rawToken, newPassword string) error
- func (s *Service) ResetTwoFactor(ctx context.Context, operatorID, userID string) error
- func (s *Service) RevokeAllSessions(ctx context.Context, operatorID, userID string) (int, error)
- func (s *Service) RevokeOtherSessions(ctx context.Context, userID, currentRefreshToken string) error
- func (s *Service) RevokeSession(ctx context.Context, userID, sessionID string) error
- func (s *Service) SendPasswordReset(ctx context.Context, userID string) error
- func (s *Service) SessionMode() SessionMode
- func (s *Service) TwoFactorStatus(ctx context.Context, userID string) (TwoFactorStatus, error)
- func (s *Service) Unlock(ctx context.Context, operatorID, userID string) error
- func (s *Service) ValidatePasswordResetToken(ctx context.Context, rawToken string) error
- func (s *Service) ValidateSession(ctx context.Context, token string) (authitjwt.Claims, error)
- func (s *Service) VerifyEmail(ctx context.Context, rawToken string) error
- func (s *Service) VerifyTwoFactorLogin(ctx context.Context, pendingToken, code, userAgent, ipAddress string) (AuthResult, error)
- type Session
- type SessionMode
- type Stores
- type TokenPair
- type TwoFactorEnrollment
- type TwoFactorSetup
- type TwoFactorStatus
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidCredentials = errors.New("authit/user: invalid credentials") ErrEmailTaken = errors.New("authit/user: email already registered") ErrEmailNotVerified = errors.New("authit/user: email not verified") ErrAccountLocked = errors.New("authit/user: account locked") ErrInvalidToken = errors.New("authit/user: invalid or expired token") ErrTwoFactorRequired = errors.New("authit/user: two-factor code required") ErrTwoFactorEnabled = errors.New("authit/user: two-factor already enabled") ErrTwoFactorNotEnabled = errors.New("authit/user: two-factor not enabled") ErrInvalidTwoFactor = errors.New("authit/user: invalid two-factor code") ErrSessionNotFound = errors.New("authit/user: session not found") // ErrCurrentSessionRequired means RevokeOtherSessions was not told // which session to keep. It is the caller's mistake, not a fault: with // nothing to exclude, the only honest options are revoking every // session including the caller's own, or refusing. It refuses. ErrCurrentSessionRequired = errors.New("authit/user: the current session must be identified") // ErrWrongSessionMode means the operation does not exist in the // configured Config.SessionMode. It covers both directions: // // Refresh in SessionModeOpaque -- there is no refresh token // in that mode; the session token is the credential, // extended by use rather than exchanged. // ValidateSession in SessionModeJWT -- there is no session row to // look a token up in; verify its signature instead. // // One sentinel rather than two because a caller can do nothing // different about them: both mean the code was written against the // other mode. It is deliberately not ErrInvalidToken -- looking the // token up first and reporting "not found" would tell a caller their // session expired when the truth is that this server never had one. ErrWrongSessionMode = errors.New("authit/user: operation is not available in the configured session mode") )
var ErrRateLimited = ratelimit.ErrRateLimited
ErrRateLimited is returned when Config.RateLimiter refuses an operation. Alias for ratelimit.ErrRateLimited, so errors.Is matches either; use ratelimit.RetryAfter to recover a wait hint for a 429 response.
A limiter's own failure (a Redis timeout, say) is NOT this error and is propagated unchanged, so a host can tell "too many attempts" from "the limiter is down" — which want different status codes.
var ErrWeakPassword = authitcrypto.ErrWeakPassword
ErrWeakPassword is returned by Register, ChangePassword and ResetPassword when Config.PasswordValidator rejects the new password. It is an alias for crypto.ErrWeakPassword, so errors.Is matches either. A custom validator may return any error it likes and the service passes it through, so do not assume every rejection wraps this one.
Functions ¶
This section is empty.
Types ¶
type AccountState ¶ added in v0.4.0
type AccountState struct {
Email string
EmailVerified bool
// Locked is an explicit lock (store.LockoutStore.LockAccount).
Locked bool
// Throttled is the temporary lockout derived from recent failures.
Throttled bool
TwoFactor bool
}
AccountState is what an operator sees of one account.
type AuthResult ¶
type AuthResult struct {
User store.User
Tokens *TokenPair
RequiresTwoFactor bool
PendingTwoFactorToken string
}
AuthResult is the outcome of Authenticate. Exactly one of Tokens or PendingTwoFactorToken is set: if the account has 2FA enabled, the caller must call VerifyTwoFactorLogin with PendingTwoFactorToken to obtain Tokens.
type Config ¶
type Config struct {
// SessionMode selects the session model. Defaults to SessionModeJWT.
SessionMode SessionMode
// SessionSlidingWindow is how much of a session's life must elapse
// before using it extends it, in SessionModeOpaque. Defaults to a
// quarter of RefreshTokenTTL; a negative value disables extension, so
// sessions expire a fixed time after being issued.
//
// A threshold rather than extending on every request, because the
// latter is a write per request. Nothing here is consulted in
// SessionModeJWT.
SessionSlidingWindow time.Duration
// AccessTokenTTL is how long an issued access JWT is valid for.
// Ignored in SessionModeOpaque, which issues no JWT.
AccessTokenTTL time.Duration
// RefreshTokenTTL is how long a refresh token (session) stays valid if
// never revoked.
RefreshTokenTTL time.Duration
// PasswordResetTTL is how long a password reset link stays valid.
PasswordResetTTL time.Duration
// EmailVerificationTTL is how long an email verification link stays
// valid.
EmailVerificationTTL time.Duration
// PendingTwoFactorTTL is how long a caller has to complete the 2FA step
// after a correct password before having to log in again.
PendingTwoFactorTTL time.Duration
// MaxFailedLoginAttempts is how many recent failed logins put an
// address into temporary lockout. Both the password step and the
// second-factor step count against it, and it is only reset once a
// login fully succeeds -- so an attacker holding a valid password gets
// this many guesses at the second factor per FailedLoginWindow, not
// unlimited guesses.
MaxFailedLoginAttempts int
// FailedLoginWindow is the lookback window used when counting recent
// failed attempts, and therefore also how long a temporary lockout
// lasts: it lifts once the recorded attempts age out. Nothing has to
// unlock the account, and no operator action is required.
FailedLoginWindow time.Duration
// TOTPIssuer is the issuer name embedded in generated TOTP QR codes.
TOTPIssuer string
// TOTPEncryptionKey encrypts TOTP secrets at rest (AES-256-GCM). Must be
// exactly 32 bytes. Required if 2FA methods are used.
TOTPEncryptionKey []byte
// BackupCodeCount is how many backup codes are generated on 2FA setup.
BackupCodeCount int
// EmailVerification decides whether Authenticate gates login on
// User.EmailVerified. Defaults to EmailVerificationRequired.
EmailVerification EmailVerificationPolicy
// AuditLogger receives security-relevant events (login, lockout,
// password/2FA changes, session revocation). Nil means events are not
// recorded — see package audit.
AuditLogger audit.Logger
// PasswordHasher hashes and verifies passwords. Nil means
// crypto.DefaultHasher() — Argon2id at OWASP's recommended minimum.
//
// Changing this does not invalidate existing passwords: every Hasher
// verifies any format authit has written, and Authenticate re-hashes a
// user's password on their next successful login once the configured
// hasher reports NeedsRehash. An application upgrading from a version
// that hardcoded bcrypt migrates its corpus by doing nothing.
PasswordHasher authitcrypto.Hasher
// PasswordValidator rejects unacceptable passwords on registration,
// change and reset. Nil means crypto.DefaultPasswordPolicy() (length
// only). It is never consulted on login, so tightening it does not lock
// out existing users; set it to a no-op func to disable it entirely.
PasswordValidator authitcrypto.PasswordValidator
// RateLimiter throttles the paths where refusing early matters. Nil
// means ratelimit.Noop — the control is off, not broken.
//
// This does not replace rate limiting in your HTTP middleware, which
// sees routes, headers and every request; it covers what middleware
// cannot reach. Its most important job is refusing an attacker
// *before* Authenticate runs the password KDF: Argon2id costs 19 MiB
// and real CPU per attempt by default, so an unauthenticated flood is
// a resource-exhaustion vector regardless of whether any password is
// ever guessed.
//
// Keys passed to Allow, so an implementation can apply its own policy
// per operation:
//
// login:ip:<ip> Authenticate, per source address
// login:email:<email> Authenticate, per account
// two-factor:ip:<ip> VerifyTwoFactorLogin
// two-factor:user:<user id> VerifyTwoFactorLogin
// password-reset:email:<email> RequestPasswordReset
// email-verification:email:<email> RequestEmailVerificationByEmail
// email-verification:user:<user id> RequestEmailVerification
//
// The email in a key is always the normalised form (see
// store.NormalizeEmail), so case cannot be used to get a fresh budget.
// An empty IP address is skipped rather than collapsing every caller
// into one bucket, so a host that does not supply one loses the
// per-address limit but keeps the rest.
RateLimiter ratelimit.Limiter
// Hooks are optional callbacks into the host's own code at the points
// where a flow stops being authit's business. The zero value does
// nothing. See Hooks for what Before and After each guarantee, which
// differs depending on whether Stores.Tx is configured.
Hooks Hooks
}
Config tunes the user package's flows. Zero-value fields are replaced with sane defaults by NewService.
type EmailSender ¶
type EmailSender interface {
SendPasswordReset(ctx context.Context, email, token string) error
SendEmailVerification(ctx context.Context, email, token string) error
}
EmailSender delivers the links/codes authit's flows generate. authit ships no concrete implementation; host applications wire in whatever sends mail (SMTP, a queue, a transactional-email API, ...).
type EmailVerificationPolicy ¶
type EmailVerificationPolicy int
EmailVerificationPolicy decides whether Authenticate refuses a login from an account whose email address has not been verified yet.
The zero value is EmailVerificationRequired, so a Config that says nothing about this gets the strict behaviour.
const ( // EmailVerificationRequired refuses to authenticate a user whose // address is not verified, returning ErrEmailNotVerified. This is the // default, and the right choice for a self-serve signup where the // address is otherwise unproven. EmailVerificationRequired EmailVerificationPolicy = iota // EmailVerificationOptional lets an unverified address log in. It is // for hosts whose signup path already proves the address by other // means — an emailed, tokenised B2B invite; SSO/IdP provisioning that // arrives pre-verified — and for seeded demo/test accounts. Verified // state is still tracked on the user, so the host can still gate its // own features on User.EmailVerified; only login stops depending on // it. EmailVerificationOptional )
type Hooks ¶ added in v0.4.0
type Hooks struct {
// BeforeRegister runs before a new account is created, with the
// normalised address. Returning an error refuses the registration.
//
// It fires before authit checks whether the address is already taken,
// so it is reached even for a duplicate registration: it is a cheap
// gate — an invite-only check, a domain allow-list — and making it pay
// for a database round trip first would defeat the point.
BeforeRegister func(ctx context.Context, email string) error
// AfterRegister runs once the account exists.
AfterRegister func(ctx context.Context, u store.User) error
// BeforeAuthenticate runs before the password is verified, with the
// normalised address. Returning an error refuses the login.
//
// It runs after rate limiting and the lockout check, so it cannot be
// used to bypass either, and before the password hash comparison, so
// refusing here costs no KDF work.
BeforeAuthenticate func(ctx context.Context, email string) error
// AfterAuthenticate runs when a login has *fully* succeeded — which
// for an account with two-factor enabled means after the second
// factor, not after the password. A hook stamping a last-seen
// timestamp must not fire for a login that stopped half way.
AfterAuthenticate func(ctx context.Context, u store.User) error
// AfterPasswordChange runs after ChangePassword or ResetPassword, with
// the user as they now are.
AfterPasswordChange func(ctx context.Context, u store.User) error
}
Hooks are optional callbacks into a host's own code at the points where "what happens when a user registers" stops being authit's business and starts being the application's: provisioning a workspace, refusing an address outside your domain, stamping a last-seen timestamp.
Every field is nil-safe; leaving one unset means nothing happens there. A hook's error is returned to the caller unchanged, so a host can define its own sentinels and match them with errors.Is.
Before and After mean different things ¶
A Before hook is a gate. It runs before anything is written, and returning an error refuses the operation cleanly — nothing happened.
An After hook runs once the operation has succeeded, and whether its error can still undo anything depends on Stores.Tx:
- With a TxRunner configured, After hooks run inside the same transaction as the operation's own writes. Returning an error rolls the whole thing back, so "create the account only if provisioning succeeds" actually holds.
- Without one, the writes have already landed independently. The error still reaches the caller, but the user exists, the session is issued, the password is changed. Treat the hook as a notification.
That difference is worth deciding about deliberately. If an After hook guards something that must not half-happen, configure a TxRunner.
What a hook must not do ¶
Hooks run in the request's goroutine, and with a TxRunner they run inside an open database transaction. Slow work — sending mail, calling a third-party API — belongs in a queue the hook writes to, not in the hook.
Values are passed by copy, so mutating one changes nothing. A hook that wants to alter stored state should write it through its own store.
type NoopEmailSender ¶
type NoopEmailSender struct{}
NoopEmailSender discards every message. Useful for tests, or apps that deliver verification/reset links out of band.
func (NoopEmailSender) SendEmailVerification ¶
func (NoopEmailSender) SendEmailVerification(ctx context.Context, email, token string) error
func (NoopEmailSender) SendPasswordReset ¶
func (NoopEmailSender) SendPasswordReset(ctx context.Context, email, token string) error
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements user auth flows.
func NewService ¶
func NewService(stores Stores, signer authitjwt.Signer, emailer EmailSender, cfg Config) (*Service, error)
NewService constructs a Service. emailer may be nil, in which case NoopEmailSender is used (useful for tests or apps that deliver links out of band). Config.AuditLogger may be nil, in which case audit.NoopLogger is used.
func (*Service) AccountState ¶ added in v0.4.0
AccountState reports userID's state. An id with no account is store.ErrNotFound.
func (*Service) Authenticate ¶
func (s *Service) Authenticate(ctx context.Context, email, password, userAgent, ipAddress string) (AuthResult, error)
Authenticate verifies email/password and, if the account has no 2FA enabled, issues a token pair. If 2FA is enabled, it returns a pending two-factor token instead — call VerifyTwoFactorLogin to complete login.
Under the default Config.EmailVerification (EmailVerificationRequired) an account whose address is unverified is refused with ErrEmailNotVerified; see EmailVerificationPolicy for when to relax that.
func (*Service) BeginTwoFactorSetup ¶
func (s *Service) BeginTwoFactorSetup(ctx context.Context, userID, accountEmail string) (TwoFactorSetup, error)
BeginTwoFactorSetup generates a new TOTP secret for userID and stores it (encrypted) with Enabled=false. The caller must call ConfirmTwoFactorSetup with a valid code before 2FA takes effect.
func (*Service) ChangePassword ¶
func (s *Service) ChangePassword(ctx context.Context, userID, currentPassword, newPassword string) error
ChangePassword updates an authenticated user's password after verifying their current one.
func (*Service) ConfirmTwoFactorSetup ¶
func (s *Service) ConfirmTwoFactorSetup(ctx context.Context, userID, code string) (TwoFactorEnrollment, error)
ConfirmTwoFactorSetup verifies a code against the pending secret from BeginTwoFactorSetup, enables 2FA, and returns freshly generated backup codes (plaintext, shown once).
func (*Service) DisableTwoFactor ¶
DisableTwoFactor turns off 2FA, accepting either a TOTP code or a backup code (so a user who lost their authenticator can still disable it).
func (*Service) ListSessions ¶
func (s *Service) ListSessions(ctx context.Context, userID, currentRefreshToken string) ([]Session, error)
ListSessions returns every active (unrevoked, unexpired) session for a user. If currentRefreshToken is non-empty, the matching session (if any) is flagged IsCurrent.
func (*Service) Logout ¶
Logout revokes a single refresh token. It is idempotent: revoking an already-revoked or unknown token is not an error.
func (*Service) MarkEmailVerified ¶
MarkEmailVerified marks userID's address verified directly, without minting and redeeming a token. It is the trusted-caller counterpart to VerifyEmail, for the paths where the address is already proven and a round-trip through an email would be ceremony: a seeder provisioning demo or test accounts, a tokenised B2B invite the recipient just followed, or SSO/IdP provisioning that arrives pre-verified.
Never call this from an unauthenticated, user-supplied path — it is exactly the check VerifyEmail exists to perform.
It is idempotent: on an already-verified user it is a no-op and leaves EmailVerifiedAt at the original time. Any outstanding verification tokens for the user are deleted, so a link already in an inbox can't be redeemed afterwards.
func (*Service) Refresh ¶
func (s *Service) Refresh(ctx context.Context, refreshToken, userAgent, ipAddress string) (TokenPair, error)
Refresh exchanges a valid, unrevoked refresh token for a new token pair, rotating the refresh token (the old one is revoked).
func (*Service) RegenerateBackupCodes ¶
func (s *Service) RegenerateBackupCodes(ctx context.Context, userID, totpCode string) ([]string, error)
RegenerateBackupCodes invalidates existing backup codes and issues a new set. Requires a valid TOTP code (not a backup code, to stop a stolen backup code from being used to mint fresh ones).
func (*Service) Register ¶
Register creates a new user with the given email/password. The password is hashed before storage; the plaintext never leaves this call.
func (*Service) RequestEmailVerification ¶
RequestEmailVerification generates a verification token for userID and emails it.
func (*Service) RequestEmailVerificationByEmail ¶
RequestEmailVerificationByEmail is the public, unauthenticated variant used for a "resend verification email" form. Like RequestPasswordReset, it always succeeds regardless of whether the address is registered or already verified, to avoid leaking account existence.
func (*Service) RequestPasswordReset ¶
RequestPasswordReset generates a reset token and emails it to the given address. It always succeeds regardless of whether the address is registered, so callers can return the same response either way and avoid leaking account existence.
func (*Service) ResetPassword ¶
ResetPassword consumes a password reset token and sets a new password. Every other session for the user is revoked, forcing re-login everywhere else.
func (*Service) ResetTwoFactor ¶ added in v0.4.0
ResetTwoFactor removes the account's second factor without a code, for someone who lost both the app and the backup codes. The next sign-in is by password alone, so pair it with RevokeAllSessions when the account may be compromised.
func (*Service) RevokeAllSessions ¶ added in v0.4.0
RevokeAllSessions ends every session the account has and reports how many. Access tokens already issued live out their TTL.
func (*Service) RevokeOtherSessions ¶
func (s *Service) RevokeOtherSessions(ctx context.Context, userID, currentRefreshToken string) error
RevokeOtherSessions revokes every session for userID except the one matching currentRefreshToken.
func (*Service) RevokeSession ¶
RevokeSession revokes one session by ID, scoped to userID so a caller can't revoke another user's session.
func (*Service) SendPasswordReset ¶ added in v0.4.0
SendPasswordReset mails the account holder a reset link through the host's EmailSender, as if they had asked. The operator never sees the link, so a reset never passes through anyone but the owner of the inbox.
func (*Service) SessionMode ¶ added in v0.4.0
func (s *Service) SessionMode() SessionMode
SessionMode reports the configured session model, so a caller that must behave differently in each -- an HTTP route group deciding whether it serves /refresh at all -- can ask rather than be told twice.
func (*Service) TwoFactorStatus ¶
TwoFactorStatus reports whether 2FA is enabled and how many backup codes remain.
func (*Service) Unlock ¶ added in v0.4.0
Unlock lifts both kinds of lockout: the explicit lock and the failure count the temporary one is derived from.
func (*Service) ValidatePasswordResetToken ¶
ValidatePasswordResetToken reports whether a reset token is still valid, without consuming it — used to give the UI an early error before the user types a new password.
func (*Service) ValidateSession ¶ added in v0.4.0
ValidateSession resolves an opaque session token to its principal.
This is the lookup that SessionModeOpaque buys: a session revoked a moment ago is refused here, by the next request, rather than staying usable until an access token expires. It satisfies authithttp.SessionValidator, and authithttp.SessionAuth is how a route group is wired to it.
Revoked, expired and unknown are one answer, ErrInvalidToken, for the usual reason: telling them apart tells a caller holding a stolen token which kind of stolen it is.
The error for a store failure is the store's own, unwrapped. That distinction is the whole reason this returns an error rather than a bool -- authithttp.StatusFor answers 500 to it and 401 to ErrInvalidToken, and a caller who collapses the two reports an outage as a wall of failed logins.
func (*Service) VerifyEmail ¶
VerifyEmail consumes a verification token and marks the user's email verified.
func (*Service) VerifyTwoFactorLogin ¶
func (s *Service) VerifyTwoFactorLogin(ctx context.Context, pendingToken, code, userAgent, ipAddress string) (AuthResult, error)
VerifyTwoFactorLogin completes a login that Authenticate flagged as RequiresTwoFactor: it exchanges the pending token plus a valid TOTP or backup code for a real token pair.
type Session ¶
type Session struct {
ID string
IsCurrent bool
UserAgent string
IPAddress string
CreatedAt time.Time
ExpiresAt time.Time
}
Session is a user-facing view of a RefreshToken, for session-management UIs (list active sessions, revoke one).
type SessionMode ¶ added in v0.4.0
type SessionMode int
SessionMode decides what an authenticated caller carries and how a protected route checks it.
The zero value is SessionModeJWT, so a Config that says nothing keeps the behaviour authit has always had.
const ( // SessionModeJWT issues a short-lived signed access token plus a // long-lived opaque refresh token. Checking the access token is a // signature check: no database, no context, no way for it to fail // because a store is unreachable. That is what makes it the right // default for APIs, CLIs and multi-service fan-out, where a lookup per // service per request is the cost nobody wants to pay. // // The price is stated plainly because it is real: revoking a session // stops it being refreshable at once, but the access token it already // minted stays valid until it expires. AccessTokenTTL is that window. SessionModeJWT SessionMode = iota // SessionModeOpaque issues one random token, validated by looking it up // on every request. Revocation takes effect immediately, which is the // entire reason to choose it. // // It is a different shape, not a different encoding, and the // differences bite: // // - There is no refresh token. The pair exists so that the common // path avoids a lookup; once every request performs one, a second // credential for avoiding lookups is ceremony. TokenPair.RefreshToken // is empty and Refresh returns ErrWrongSessionMode. // - Every protected request costs a round trip to the refresh-token // store, which is the session store in this mode. // - Authentication can now fail because a database is down, which is // a 500 and not a 401. Use authithttp.SessionAuth, which classifies // it, rather than treating every failure as unauthenticated. // // Sessions still expire after RefreshTokenTTL, extended on use once // SessionSlidingWindow has elapsed. SessionModeOpaque )
type Stores ¶
type Stores struct {
Users store.UserStore
RefreshTokens store.RefreshTokenStore
PasswordResets store.PasswordResetStore
EmailVerifications store.EmailVerificationStore
TOTP store.TOTPStore
PendingTwoFactor store.PendingTwoFactorStore
Lockouts store.LockoutStore
// Tx is optional. Supplying it makes the flows that write more than
// once atomic -- Refresh, VerifyTwoFactorLogin and ResetPassword. Nil
// leaves them as a sequence of independent writes, which is what they
// have always been. See store.TxRunner for the obligation it puts on
// an implementation; it is not a formality.
Tx store.TxRunner
}
Stores groups the persistence ports the user package needs. A host application supplies concrete implementations (or reuses memstore).
type TwoFactorEnrollment ¶
type TwoFactorEnrollment struct {
BackupCodes []string
}
TwoFactorEnrollment is returned by ConfirmTwoFactorSetup: the plaintext backup codes, shown to the user exactly once.
type TwoFactorSetup ¶
TwoFactorSetup is returned by BeginTwoFactorSetup; the caller renders Secret/OTPAuthURL as a QR code for the user to scan.