Documentation
¶
Overview ¶
Package authtoken mints and verifies the short-lived, purpose-scoped tokens behind the self-service account flows: password reset, email verification, and email change. Tokens are signed JWTs (HS256), reusing the app's JWT secret rather than hand-rolling a MAC — the same choice as internal/tracking.
Two properties make the single secret safe to share across flows:
- A `prp` (purpose) claim is validated on parse, so a token minted for one flow (say, signup verification) cannot be replayed against another (reset).
- The HMAC signing key is *derived per token* from (purpose, userID, binding), where `binding` is a value that changes when the action completes — the user's password hash for reset, their current email for a change. Once the action lands, the binding no longer matches and the token stops verifying, giving single-use semantics with no extra database column.
Index ¶
- type BindingError
- type Purpose
- type Signer
- func (s *Signer) Mint(purpose Purpose, userID int64, binding string, ttl time.Duration, ...) (string, error)
- func (s *Signer) Parse(token string, purpose Purpose, bindingFor func(userID int64) (string, error)) (userID int64, extra map[string]string, err error)
- func (s *Signer) WithClock(now func() time.Time) *Signer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BindingError ¶
type BindingError struct{ Err error }
BindingError is the failure of the binding lookup a Parse was given: the lookup failed, which says nothing about the token. A caller tells a user that is gone (the token is stale) from a storage failure (the answer is a server error, not "invalid link") by what Err is.
func (*BindingError) Error ¶
func (e *BindingError) Error() string
func (*BindingError) Unwrap ¶
func (e *BindingError) Unwrap() error
type Purpose ¶
type Purpose string
Purpose scopes a token to one flow. It is both baked into the signing key and carried as a validated claim.
const ( PurposePasswordReset Purpose = "pwreset" PurposeEmailVerify Purpose = "email_verify" PurposeEmailChange Purpose = "email_change" // PurposeIdentityNotMe is the "this was not me" link of the notice mailed when a // Sign-in provider was linked silently (ADR 0048). It carries the Linked identity to // undo and has no binding: once the identity is gone the link has nothing left to do. PurposeIdentityNotMe Purpose = "identity_not_me" // PurposeUserDeletion is the one-time link mailed to a User with no password to confirm // their own User deletion (ADR 0050). Keyed to the User id and session epoch, never to // the address. PurposeUserDeletion Purpose = "user_deletion" // PurposeLoginChallenge is the challenge between the password step and the // Second factor step of a login (ADR 0020). PurposeLoginChallenge Purpose = "login_challenge" // PurposeStaffLoginChallenge is the same step for a Staff member (ADR 0026). Its // own purpose, and a signer on the Staff member secret, keep it from ever verifying // as a User's challenge: Staff member and User ids share no space. PurposeStaffLoginChallenge Purpose = "staff_login_challenge" )
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer mints and parses authtoken JWTs against a single secret.
func (*Signer) Mint ¶
func (s *Signer) Mint(purpose Purpose, userID int64, binding string, ttl time.Duration, extra map[string]string) (string, error)
Mint signs a token for purpose + userID, keyed to binding (the value that changes when the action completes, giving single-use for free) and expiring after ttl. extra carries purpose-specific string claims (e.g. the requested new email for a change); nil is fine.
func (*Signer) Parse ¶
func (s *Signer) Parse(token string, purpose Purpose, bindingFor func(userID int64) (string, error)) (userID int64, extra map[string]string, err error)
Parse validates a token for the expected purpose and returns the user id plus any extra claims. bindingFor loads the current binding value for the decoded user id (e.g. their password hash) so a completed action invalidates the token; it is called with the claimed user id before the signature is verified.