auth

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

Package auth signs users in and out, remembers them, finds the current user of a request, issues API tokens and password-reset and email-verification tokens, and checks typed policies. Passwords are hashed by package auth/password.

The app describes its users with Users, then adds the middleware after the session middleware:

users := auth.Users[*models.User]{
	ByID:    func(ctx context.Context, id string) (*models.User, error) { … },
	ByLogin: func(ctx context.Context, email string) (*models.User, error) { … },
}
a, err := auth.ForApp(app, users)
pages := r.Group("", sessions.Middleware, web.CSRF(), a.Middleware)
pages.Group("", a.Require).Get("/dashboard", …)

Handlers sign users in with Auth.Attempt (checks the password, with login throttling) and out with Auth.Logout, and read the current user with User:

u, ok := auth.User[*models.User](c)

See docs/site/guides/authentication.md.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoUser is what Users functions return when there is no such
	// user (db.ErrNotFound works too).
	ErrNoUser = errors.New("auth: no such user")
	// ErrInvalidCredentials is returned by Attempt for an unknown login
	// or a wrong password (which of the two isn't said). 401.
	ErrInvalidCredentials error = &statusError{http.StatusUnauthorized, "auth: invalid credentials"}
	// ErrUnauthenticated means the request has no signed-in user. 401.
	ErrUnauthenticated error = &statusError{http.StatusUnauthorized, "auth: not signed in"}
	// ErrForbidden means a policy refused the action. 403.
	ErrForbidden error = &statusError{http.StatusForbidden, "auth: not allowed"}
	// ErrInvalidToken means a password-reset or verification token is
	// malformed, expired or used. 400.
	ErrInvalidToken error = &statusError{http.StatusBadRequest, "auth: invalid or expired token"}
	// ErrDisabled is returned by Attempt (once the password checks out)
	// and Login for a user whose account is disabled (Users.Disabled).
	// 403.
	ErrDisabled error = &statusError{http.StatusForbidden, "auth: this account is disabled"}
	// ErrNotImpersonating is returned by StopImpersonating when the
	// session isn't acting as anyone. 409.
	ErrNotImpersonating error = &statusError{http.StatusConflict, "auth: not acting as another user"}
)

Errors. Each has an HTTP status, so a handler can return it.

View Source
var (
	// ErrTwoFactorRequired is returned by Attempt and SignIn when the
	// password (or other sign-in) checked out and the user has two-factor
	// sign-in on: the sign-in waits for a code ([Auth.AttemptTwoFactor]),
	// at AUTH_CHALLENGE_URL. 401.
	ErrTwoFactorRequired error = &statusError{http.StatusUnauthorized, "auth: a two-factor code is required"}
	// ErrNoPendingSignIn is returned by AttemptTwoFactor when no sign-in
	// waits for a code: there was none, it expired (after 10 minutes),
	// or the user's password changed meanwhile. 401.
	ErrNoPendingSignIn error = &statusError{http.StatusUnauthorized, "auth: no sign-in waits for a code"}
	// ErrInvalidCode is returned for a wrong, expired or used two-factor
	// or recovery code. 422.
	ErrInvalidCode error = &statusError{http.StatusUnprocessableEntity, "auth: invalid code"}
	// ErrTwoFactorOn is returned by StartTwoFactor for a user who has
	// two-factor sign-in on already: turn it off first. 409.
	ErrTwoFactorOn error = &statusError{http.StatusConflict, "auth: two-factor sign-in is on already"}
	// ErrTwoFactorOff is returned by ConfirmTwoFactor without a started
	// setup, and by NewRecoveryCodes for a user without two-factor
	// sign-in. 409.
	ErrTwoFactorOff error = &statusError{http.StatusConflict, "auth: two-factor sign-in isn't on"}
)

Errors of two-factor sign-in.

View Source
var ErrPasswordNotConfirmed error = &statusError{http.StatusLocked, "auth: confirm your password first"}

ErrPasswordNotConfirmed is returned by RequireConfirmed to API clients and htmx requests whose user hasn't confirmed their password lately. 423.

Functions

func ActAs added in v0.3.0

func ActAs(ctx context.Context, userID string, opts ...ActOption) (context.Context, error)

ActAs is Auth.ActAs with the app's Auth (auth.ForApp), from ctx: for packages that don't know the app's user type, such as package ai's queued replies.

func Allows

func Allows[U Authenticatable, T any](ctx context.Context, policy func(context.Context, U, T) bool, subject T) bool

Allows reports whether a policy allows the request's user the action on subject: false for a guest. Use it to show or hide links and buttons.

func AllowsUser

func AllowsUser[U Authenticatable](ctx context.Context, policy func(context.Context, U) bool) bool

AllowsUser reports whether a policy about the user alone allows the request's user: false for a guest.

func Authorize

func Authorize[U Authenticatable, T any](ctx context.Context, policy func(context.Context, U, T) bool, subject T) error

Authorize checks a policy for the request's user and subject: nil if allowed, ErrUnauthenticated (401) for a guest, ErrForbidden (403) if the policy says no. Policies are plain functions, usually methods of a policy type, so the compiler checks the user and subject types:

func (PostPolicy) Update(ctx context.Context, u *models.User, p *models.Post) bool {
	return p.AuthorID == u.ID
}

if err := auth.Authorize(c, policies.Post.Update, &post); err != nil {
	return nil, err
}

func AuthorizeUser

func AuthorizeUser[U Authenticatable](ctx context.Context, policy func(context.Context, U) bool) error

AuthorizeUser checks a policy about the user alone (an admin area, creating something): nil if allowed, ErrUnauthenticated or ErrForbidden otherwise.

if err := auth.AuthorizeUser(c, policies.Post.Create); err != nil {

func Check

func Check(ctx context.Context) bool

Check reports whether the request has a signed-in user.

func Current

func Current[U Authenticatable](ctx context.Context) (U, error)

Current returns the request's signed-in user, ErrUnauthenticated if there is none, or the error that kept it from being loaded.

func CurrentID added in v0.3.0

func CurrentID(ctx context.Context) (string, error)

CurrentID returns the Authenticatable.AuthID of the request's signed-in user, ErrUnauthenticated if there is none, or the error that kept it from being loaded. It is for code that works with any user type, such as package auth/rbac; handlers use Current or User.

func Impersonator added in v0.3.0

func Impersonator(ctx context.Context) (string, bool)

Impersonator returns the ID of the user acting as the signed-in one (Auth.Impersonate), and false if no one is.

func Intended

func Intended(ctx context.Context, fallback string) string

Intended returns the page a guest asked for before Auth.Require sent them to the login page (removing it from the session), or fallback: where to redirect after a login. A path of the app is in the request's locale (web.LocalePath).

func IsDenied

func IsDenied(err error) bool

IsDenied reports whether err is ErrUnauthenticated or ErrForbidden.

func Migrations

func Migrations() *migrate.Set

Migrations returns the migration creating the api_tokens table, for migrate.ForApp.

func RequireAbilities added in v0.4.0

func RequireAbilities(abilities ...string) web.Middleware

RequireAbilities is middleware letting through requests that may do every one of abilities (TokenCan): a token needs them (or "*"); a session's request may do anything its user may. A token without them gets 403, a guest 401 with "WWW-Authenticate: Bearer" (never the login page: put it after Auth.Require, which sends a page's guests there):

me := api.Group("", a.Require)
me.With(auth.RequireAbilities("bookmarks:write")).Post("/bookmarks", web.H(h.Create))

API descriptions (package web/openapi) list the abilities as the bearer token's scopes, and the 403. It panics without abilities.

func TokenCan

func TokenCan(ctx context.Context, ability string) bool

TokenCan reports whether the request may do ability: a request signed in with an API token needs the ability on the token; one signed in with a session (the app's own pages and front end) may do anything its user may. A guest may do nothing.

func TwoFactorCode added in v0.3.0

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

TwoFactorCode returns the code an authenticator app shows at t for secret (TwoFactorSetup.Secret): for tests.

func User

func User[U Authenticatable](ctx context.Context) (U, bool)

User returns the request's signed-in user, and whether there is one. A failure to load the user (the database is down) is logged and counts as no user; use Current to tell the two apart.

u, ok := auth.User[*models.User](c)

Types

type ActOption added in v0.3.0

type ActOption func(*state)

ActOption configures Auth.ActAs.

func WithAbilities added in v0.3.0

func WithAbilities(abilities []string) ActOption

WithAbilities limits the context to abilities, as an API token with them would (TokenCan, and packages that read CurrentToken, such as auth/rbac): for work done for a request that was signed in with a token, so that it can't do more than the token could. Its Token has the abilities and user ID, and no ID.

type Auth

type Auth[U Authenticatable] struct {
	// contains filtered or unexported fields
}

Auth signs users of type U in and out. Create it with ForApp or New; it is safe for concurrent use.

func ForApp

func ForApp[U Authenticatable](app *anetos.App, users Users[U], opts ...Option) (*Auth[U], error)

ForApp returns an Auth configured from the AUTH_* settings and APP_KEY, and provides it to the app. Login throttling needs the app's cache (cache.ForApp, called first). An app has one Auth: its session keys and cookie are fixed, so a second one would read the first one's users. The options come after ForApp's own (the app's logger, APP_NAME as the issuer, insecure cookies outside production); DefaultHomeURL sets where users go after signing in, unless AUTH_HOME_URL is set.

func New

func New[U Authenticatable](cfg Config, users Users[U], enc *encryption.Encrypter, opts ...Option) (*Auth[U], error)

New returns an Auth for users, with keys from enc (APP_KEY).

func (*Auth[U]) ActAs added in v0.3.0

func (a *Auth[U]) ActAs(ctx context.Context, userID string, opts ...ActOption) context.Context

ActAs returns ctx in which the user with userID is the signed-in user, for work done for a user outside their requests: a queue job, a command. User, Current, CurrentID, policies and what builds on them (package auth/rbac, AI tools) see that user, loaded from Users.ByID when first asked for; a user that doesn't exist is a guest. There is no session: Attempt, Login and Logout fail. There is no API token either, unless WithAbilities gives the limits of one.

func (*Auth[U]) Attempt

func (a *Auth[U]) Attempt(ctx context.Context, login, pw string, remember bool) (U, error)

Attempt signs in the user whose login and password match, and returns them. It fails with ErrInvalidCredentials for an unknown login or a wrong password (taking about as long either way), and with a *ThrottledError after AUTH_THROTTLE attempts in a minute for this login, or this account, from this IP address, or AUTH_THROTTLE_IP failures in a minute from this IP address. A success clears the login's and account's counts, upgrades a weaker password hash (with Users.SetPassword) and signs the user in, as Auth.SignIn does: for a user with two-factor sign-in on, it returns the user and ErrTwoFactorRequired, and the sign-in waits for a code.

u, err := a.Attempt(c, in.Email, in.Password, in.Remember)
if errors.Is(err, auth.ErrInvalidCredentials) {
	return nil, validate.Fail("email", "These credentials don't match our records.")
}

func (*Auth[U]) AttemptCredentials added in v0.4.0

func (a *Auth[U]) AttemptCredentials(ctx context.Context, login, pw string) (U, error)

AttemptCredentials checks a login and password as Auth.Attempt does (the same throttling, timing and rehashing) and returns the user, without signing anyone in to a session: for an API's login, which answers with an API token (Auth.CreateToken). It fails as Attempt does, and with ErrDisabled for a disabled account. For a user with two-factor sign-in on, it fails with a *TwoFactorChallenge (which is ErrTwoFactorRequired): give the client its Token, to send back with a code to Auth.AttemptTwoFactorChallenge. The route needs Auth.TokenMiddleware (or Auth.Middleware), which knows the client's address.

u, err := a.AttemptCredentials(c, in.Email, in.Password)
var challenge *auth.TwoFactorChallenge
if errors.As(err, &challenge) {
	return SignInResponse{TwoFactor: true, Challenge: challenge.Token}, nil
}

func (*Auth[U]) AttemptTwoFactor added in v0.3.0

func (a *Auth[U]) AttemptTwoFactor(ctx context.Context, code string) (U, error)

AttemptTwoFactor finishes the sign-in waiting for a code (Auth.SignIn, Auth.Attempt): code is the current one of the user's authenticator app (each used once), or one of their recovery codes (then used up). It fails with ErrInvalidCode for a wrong code, ErrNoPendingSignIn if no sign-in waits (send them to sign in again), and a *ThrottledError after AUTH_THROTTLE attempts in a minute for the user, from any address.

u, err := a.AttemptTwoFactor(c, in.Code)
if errors.Is(err, auth.ErrInvalidCode) {
	return nil, validate.Fail("code", "That code isn't right.")
}

func (*Auth[U]) AttemptTwoFactorChallenge added in v0.4.0

func (a *Auth[U]) AttemptTwoFactorChallenge(ctx context.Context, challenge, code string) (U, error)

AttemptTwoFactorChallenge finishes an API's sign-in that Auth.AttemptCredentials answered with a *TwoFactorChallenge: challenge is its Token, code the current one of the user's authenticator app (each used once) or one of their recovery codes (then used up). It returns the user, to give an API token; it signs nothing in to a session. It fails as Auth.AttemptTwoFactor does: ErrInvalidCode, a *ThrottledError (AUTH_THROTTLE tries a minute and 50 wrong codes a day for the user), and ErrNoPendingSignIn for a malformed or expired challenge, or one made before the user's password or session key changed; and ErrDisabled for a disabled account. A challenge works more than once in its 10 minutes, each time with a new code.

func (*Auth[U]) CanRemember

func (a *Auth[U]) CanRemember() bool

CanRemember reports whether "remember me" is available: Users has RememberToken and SetRememberToken.

func (*Auth[U]) CanSignOutEverywhere added in v0.3.0

func (a *Auth[U]) CanSignOutEverywhere() bool

CanSignOutEverywhere reports whether Auth.SignOutEverywhere is available: Users has SessionKey and SetSessionKey.

func (*Auth[U]) CanTwoFactor added in v0.3.0

func (a *Auth[U]) CanTwoFactor() bool

CanTwoFactor reports whether two-factor sign-in is available: Users has TwoFactor and SetTwoFactor.

func (*Auth[U]) ChangePassword added in v0.3.0

func (a *Auth[U]) ChangePassword(ctx context.Context, u U, current, pw string) error

ChangePassword changes the signed-in user u's password to pw (hashed with password.Hash), when current is their password; a user without a password (who signs in with Google, say) sets one if they signed in in the last AUTH_CONFIRM_TTL. It signs u out of their other sessions (with Users.SessionKey) and remember-me cookies, keeping this one (Auth.SignOutOthers). It fails with ErrInvalidCredentials for a wrong current password, ErrPasswordNotConfirmed for a user without one who didn't sign in lately, password.ErrTooLong, and a *ThrottledError after AUTH_THROTTLE tries in a minute, or 50 wrong passwords in a day (UTC, with Auth.ConfirmPassword's). Not while acting as another user. Check pw's length and the like before (validate rules).

func (*Auth[U]) CheckEmailRevertToken added in v0.3.0

func (a *Auth[U]) CheckEmailRevertToken(ctx context.Context, token string) (u U, oldEmail, newEmail string, err error)

CheckEmailRevertToken returns the user, the old address and the new one of a token from Auth.EmailRevertToken, or ErrInvalidToken. Undo the change only if the user's address (or the one they asked for) is still the new one.

func (*Auth[U]) CheckPassword added in v0.4.0

func (a *Auth[U]) CheckPassword(ctx context.Context, u U, pw string) error

CheckPassword checks that pw is u's password, with the budget of Auth.ConfirmPassword (and Auth.ChangePassword): AUTH_THROTTLE tries a minute and 50 wrong passwords a day (UTC) for the user, from any address, so a stolen session or API token can't be used to guess it. It needs no session: an API asks for the password in the request of what the pages put behind Auth.RequireConfirmed (creating a token, changing two-factor sign-in). It fails with ErrInvalidCredentials for a wrong password (or a user without one) and a *ThrottledError.

func (*Auth[U]) CheckPasswordResetToken

func (a *Auth[U]) CheckPasswordResetToken(ctx context.Context, token string) (U, error)

CheckPasswordResetToken returns the user a token from Auth.PasswordResetToken was made for, or ErrInvalidToken if it is malformed, expired or already used. Store the new password, then sign the user in (or send them to the login page).

func (*Auth[U]) CheckVerificationToken

func (a *Auth[U]) CheckVerificationToken(ctx context.Context, token string) (U, string, error)

CheckVerificationToken returns the user and the email address a token from Auth.VerificationToken was made for, or ErrInvalidToken. Mark the address verified if it is still the user's.

func (a *Auth[U]) ClientLink(path string, query url.Values) (string, error)

ClientLink returns the address of path (with query, if any) in the client app, AUTH_CLIENT_URL: the link an API's emails give, whose page sends the token in it back to the API.

link, err := a.ClientLink("/reset-password", url.Values{"token": {a.PasswordResetToken(u)}})

It fails, naming the setting, when AUTH_CLIENT_URL isn't set.

func (*Auth[U]) Config

func (a *Auth[U]) Config() Config

Config returns the configuration.

func (*Auth[U]) ConfirmPassword added in v0.3.0

func (a *Auth[U]) ConfirmPassword(ctx context.Context, pw string) error

ConfirmPassword checks the signed-in user's password and, if it is right, marks it confirmed in the session for AUTH_CONFIRM_TTL (Auth.PasswordConfirmed). It fails with ErrInvalidCredentials for a wrong one (or a user without a password), ErrUnauthenticated for a guest, and a *ThrottledError after AUTH_THROTTLE attempts in a minute, or 50 wrong passwords for the user in a day (UTC; wrong passwords given to Auth.ChangePassword count too), so a stolen session can't be used to guess the password. Not while acting as another user (Auth.Impersonate).

func (*Auth[U]) ConfirmTwoFactor added in v0.3.0

func (a *Auth[U]) ConfirmTwoFactor(ctx context.Context, u U, code string) ([]string, error)

ConfirmTwoFactor turns on u's two-factor sign-in, started with Auth.StartTwoFactor, when code is the current one of their authenticator app, and returns their recovery codes: shown once, each signs in once without the app. It fails with ErrInvalidCode for a wrong code, ErrTwoFactorOff without a started setup, and ErrTwoFactorOn if it is on already. Attempts are throttled (AUTH_THROTTLE a minute).

func (*Auth[U]) CreateToken

func (a *Auth[U]) CreateToken(ctx context.Context, u U, name string, abilities []string, ttl time.Duration) (string, *Token, error)

CreateToken issues an API token for u with abilities ("*" for all), expiring after ttl (0: never). It returns the token to give the client, shown once (only its hash is stored), and the stored Token. Not while acting as another user (Auth.Impersonate): a token would outlive the impersonation and its checks (403).

plain, tok, err := a.CreateToken(c, u, "deploy script", []string{"deploy"}, 90*24*time.Hour)

func (*Auth[U]) DisableTwoFactor added in v0.3.0

func (a *Auth[U]) DisableTwoFactor(ctx context.Context, u U) error

DisableTwoFactor turns off u's two-factor sign-in (or drops a started setup); their recovery codes stop working.

func (*Auth[U]) Disabled added in v0.3.0

func (a *Auth[U]) Disabled(u U) bool

Disabled reports whether u's account is disabled (Users.Disabled).

func (*Auth[U]) EmailRevertToken added in v0.3.0

func (a *Auth[U]) EmailRevertToken(u U, oldEmail, newEmail string) string

EmailRevertToken returns a token for a link, sent to u's address oldEmail when they ask to change it to newEmail, that undoes the change: for the owner of the old address, if the change wasn't theirs. It works for AUTH_REVERT_TTL (7 days), after the change too.

func (*Auth[U]) Guest

func (a *Auth[U]) Guest(next http.Handler) http.Handler

Guest lets only guests through: signed-in users are redirected to AUTH_HOME_URL. Use it on the login and registration pages.

func (*Auth[U]) Impersonate added in v0.3.0

func (a *Auth[U]) Impersonate(ctx context.Context, u U) error

Impersonate signs the current user in as u, to see the app as u does (support, an admin's "act as"): the session keeps who they are, and Auth.StopImpersonating signs them back in. It is for sessions only (not API tokens), doesn't nest, and refuses a disabled u (ErrDisabled) or the user themselves. While it lasts, Impersonator returns the impersonator's ID, and each request checks that they may still sign in; Logout ends both. Check that the current user may act as u first (rbac.AuthorizeOver).

func (*Auth[U]) Login

func (a *Auth[U]) Login(ctx context.Context, u U, remember bool) error

Login signs u in: the session gets a new ID (so a session identifier seen before the login is useless) and remembers the user; with remember, a remember-me cookie keeps them signed in for AUTH_REMEMBER_LIFETIME after the session ends. Use it after registration. It doesn't ask for a two-factor code: for sign-in methods other than passwords, use Auth.SignIn.

func (*Auth[U]) LoginSession added in v0.3.0

func (a *Auth[U]) LoginSession(s *session.Session, u U) error

LoginSession writes a signed-in session for u into s, as Auth.Login without remember-me would, but without a request: for tests (see anetostest.ActingAs) and tools that prepare sessions. It returns ErrDisabled for a disabled account.

func (*Auth[U]) Logout

func (a *Auth[U]) Logout(ctx context.Context) error

Logout signs the request's user out: the session is emptied and gets a new ID, the remember-me cookie is removed, and the user's remember-me token is replaced, which signs them out of every remembered browser. With cookie sessions (SESSION_DRIVER=cookie), a copy of the session cookie taken before the logout keeps working until it expires; a server-side session driver revokes it.

func (*Auth[U]) Middleware

func (a *Auth[U]) Middleware(next http.Handler) http.Handler

Middleware makes the request's user available to User, Check and the Auth methods. Put it after the session middleware: it finds the user from the session, or from a remember-me cookie (signing them in to a new session). The user is loaded when first asked for, so pages that don't need one don't query for it.

func (*Auth[U]) NewRecoveryCodes added in v0.3.0

func (a *Auth[U]) NewRecoveryCodes(ctx context.Context, u U) ([]string, error)

NewRecoveryCodes replaces u's recovery codes with new ones, and returns them. It fails with ErrTwoFactorOff if two-factor sign-in is off.

func (*Auth[U]) PasswordConfirmed added in v0.3.0

func (a *Auth[U]) PasswordConfirmed(ctx context.Context) bool

PasswordConfirmed reports whether the signed-in user confirmed their password (Auth.ConfirmPassword) in the last AUTH_CONFIRM_TTL; for a user without a password, whether they signed in in that time. Signing in, out, or acting as another user forgets it; requests signed in with an API token never have it.

func (*Auth[U]) PasswordResetToken

func (a *Auth[U]) PasswordResetToken(u U) string

PasswordResetToken returns a token for a link that lets u choose a new password. It works for AUTH_RESET_TTL, and only until the password changes, so it can be used once, or the user is signed out everywhere (a new session key: SignOutEverywhere, SignOutOthers).

func (*Auth[U]) RememberCookie added in v0.3.0

func (a *Auth[U]) RememberCookie() string

RememberCookie returns the remember-me cookie's name.

func (*Auth[U]) Require

func (a *Auth[U]) Require(next http.Handler) http.Handler

Require lets only signed-in users through. Guests asking for a page are redirected to AUTH_LOGIN_URL, and the page they wanted is remembered for Intended; other requests (JSON, htmx) get 401, with "WWW-Authenticate: Bearer" on routes without sessions or behind Auth.TokenMiddleware. Responses to signed-in users get "Cache-Control: no-store" (a handler may set another), so browsers don't keep them after logout. API descriptions (package web/openapi) show its routes as needing a bearer token.

func (*Auth[U]) RequireConfirmed added in v0.3.0

func (a *Auth[U]) RequireConfirmed(next http.Handler) http.Handler

RequireConfirmed lets through users who confirmed their password in the last AUTH_CONFIRM_TTL. Others are sent to AUTH_CONFIRM_URL, to come back after (to the page for GET requests, else to the page the form was on); API clients and htmx requests get ErrPasswordNotConfirmed. Put it after Auth.Require.

func (*Auth[U]) RevokeAllTokens

func (a *Auth[U]) RevokeAllTokens(ctx context.Context, u U) error

RevokeAllTokens deletes every API token of u: after a password reset, say, so that whoever had the account loses its API access too.

func (*Auth[U]) RevokeOtherTokens added in v0.4.0

func (a *Auth[U]) RevokeOtherTokens(ctx context.Context, u U, keep int64) error

RevokeOtherTokens deletes every API token of u but keep (the request's, after a password change through the API, say: the API's "sign out other devices"). A keep of 0 keeps none.

func (*Auth[U]) RevokeToken

func (a *Auth[U]) RevokeToken(ctx context.Context, u U, id int64) error

RevokeToken deletes u's API token id. Revoking a token that isn't there, or isn't u's, does nothing.

func (*Auth[U]) SignIn added in v0.3.0

func (a *Auth[U]) SignIn(ctx context.Context, u U, remember bool) error

SignIn signs u in, as Auth.Login does, unless they have two-factor sign-in on: then the sign-in waits for a code, for 10 minutes, and it returns ErrTwoFactorRequired; send them to AUTH_CHALLENGE_URL, whose handler calls Auth.AttemptTwoFactor. Use it for sign-in methods other than passwords (package auth/social does).

func (*Auth[U]) SignOutEverywhere added in v0.3.0

func (a *Auth[U]) SignOutEverywhere(ctx context.Context, u U) error

SignOutEverywhere ends every session of u, and every browser they are remembered in: it gives them a new session key (Users.SetSessionKey), which their sessions and remember-me cookies no longer match, and a new remember-me token. Their API tokens are untouched (RevokeAllTokens). The current request, if it is u's, stays signed in until it ends.

func (*Auth[U]) SignOutOthers added in v0.3.0

func (a *Auth[U]) SignOutOthers(ctx context.Context, u U) error

SignOutOthers ends u's other sessions (with Users.SessionKey), their other remember-me cookies and their password-reset links, keeping the request's session if it is u's: it gets a new ID (copies of its cookie stop working, with a server-side session driver), and a remember-me cookie it had is issued again. ChangePassword and ConfirmTwoFactor call it; call it when something else about the account changes, such as its email address.

func (*Auth[U]) StartTwoFactor added in v0.3.0

func (a *Auth[U]) StartTwoFactor(ctx context.Context, u U, account string) (TwoFactorSetup, error)

StartTwoFactor starts turning on u's two-factor sign-in: it stores a new secret (replacing a setup started before) and returns it. The user adds it to their authenticator app (account names them there, with APP_NAME as the issuer), and Auth.ConfirmTwoFactor turns it on with a code. It fails with ErrTwoFactorOn if it is on.

func (*Auth[U]) StartedTwoFactor added in v0.3.0

func (a *Auth[U]) StartedTwoFactor(u U, account string) (TwoFactorSetup, error)

StartedTwoFactor returns u's setup started with Auth.StartTwoFactor and not yet confirmed, to show it again. It fails with ErrTwoFactorOff if none was started, and ErrTwoFactorOn once it is on (the secret is never shown again).

func (*Auth[U]) StopImpersonating added in v0.3.0

func (a *Auth[U]) StopImpersonating(ctx context.Context) (U, error)

StopImpersonating ends Auth.Impersonate: it signs the impersonator back in and returns them. If they can no longer sign in (deleted, disabled, password changed, signed out everywhere), the session is signed out and the error says why. Without impersonation it returns ErrNotImpersonating.

func (*Auth[U]) TokenMiddleware

func (a *Auth[U]) TokenMiddleware(next http.Handler) http.Handler

TokenMiddleware signs in the user of the request's API token, sent as "Authorization: Bearer <token>". A request without one (or with another Authorization scheme) goes through as a guest: put Auth.Require after it to refuse those. One with an invalid, expired or revoked token gets 401. Use it on API routes, in a group of their own: without the session middleware and web.CSRF, which would refuse API clients' posts. API descriptions (package web/openapi) list that 401.

func (*Auth[U]) Tokens

func (a *Auth[U]) Tokens(ctx context.Context, u U) ([]Token, error)

Tokens returns u's API tokens, newest first.

func (*Auth[U]) TwoFactor added in v0.3.0

func (a *Auth[U]) TwoFactor(u U) (TwoFactorStatus, error)

TwoFactor reports u's two-factor sign-in. It fails if the state can't be read (APP_KEY changed without APP_PREVIOUS_KEYS).

func (*Auth[U]) TwoFactorPending added in v0.3.0

func (a *Auth[U]) TwoFactorPending(ctx context.Context) bool

TwoFactorPending reports whether the session has a sign-in waiting for a two-factor code: the challenge page shows only then.

func (*Auth[U]) VerificationToken

func (a *Auth[U]) VerificationToken(u U, email string) string

VerificationToken returns a token for a link that confirms u owns email. It works for AUTH_VERIFY_TTL.

type Authenticatable

type Authenticatable interface {
	// AuthID returns the user's identifier, as stored in the session and
	// in API tokens: usually the primary key as text.
	AuthID() string
	// AuthPassword returns the user's password hash (from
	// password.Hash), or "" for users who can't sign in with a password.
	AuthPassword() string
}

Authenticatable is what package auth needs from the app's user type.

type Config

type Config struct {
	// LoginURL is where [Auth.Require] sends guests. AUTH_LOGIN_URL,
	// default /login.
	LoginURL string `env:"AUTH_LOGIN_URL" default:"/login"`
	// HomeURL is the app's page for signed-in users: where [Auth.Guest]
	// sends them, and where signing in leads when there's no page they
	// wanted ([Intended]'s usual fallback). AUTH_HOME_URL, default /
	// ([DefaultHomeURL] sets another default).
	HomeURL string `env:"AUTH_HOME_URL" default:"/"`
	// RememberLifetime is how long "remember me" lasts.
	// AUTH_REMEMBER_LIFETIME, default 720h (30 days).
	RememberLifetime time.Duration `env:"AUTH_REMEMBER_LIFETIME" default:"720h"`
	// Throttle is the number of failed logins allowed per minute for one
	// login from one IP address. AUTH_THROTTLE, default 5.
	Throttle int `env:"AUTH_THROTTLE" default:"5"`
	// ThrottleIP is the number of failed logins allowed per minute from
	// one IP address (an IPv6 /64), whatever the login. AUTH_THROTTLE_IP,
	// default 50.
	ThrottleIP int `env:"AUTH_THROTTLE_IP" default:"50"`
	// ResetTTL is how long a password-reset token works. AUTH_RESET_TTL,
	// default 60m.
	ResetTTL time.Duration `env:"AUTH_RESET_TTL" default:"60m"`
	// VerifyTTL is how long an email-verification token works.
	// AUTH_VERIFY_TTL, default 24h.
	VerifyTTL time.Duration `env:"AUTH_VERIFY_TTL" default:"24h"`
	// RevertTTL is how long a link that undoes a change of email address
	// works ([Auth.EmailRevertToken]). AUTH_REVERT_TTL, default 168h (7
	// days).
	RevertTTL time.Duration `env:"AUTH_REVERT_TTL" default:"168h"`
	// ChallengeURL is where a sign-in waiting for a two-factor code
	// asks for it ([ErrTwoFactorRequired]). AUTH_CHALLENGE_URL, default
	// /two-factor-challenge.
	ChallengeURL string `env:"AUTH_CHALLENGE_URL" default:"/two-factor-challenge"`
	// TwoFactorURL is where users turn two-factor sign-in on and off.
	// AUTH_TWO_FACTOR_URL, default /two-factor.
	TwoFactorURL string `env:"AUTH_TWO_FACTOR_URL" default:"/two-factor"`
	// SettingsURL is where signed-in users change their account settings
	// (make:auth's page; the admin links there). AUTH_SETTINGS_URL,
	// default /settings.
	SettingsURL string `env:"AUTH_SETTINGS_URL" default:"/settings"`
	// ConfirmURL is where [Auth.RequireConfirmed] sends users to confirm
	// their password. AUTH_CONFIRM_URL, default /confirm-password.
	ConfirmURL string `env:"AUTH_CONFIRM_URL" default:"/confirm-password"`
	// ConfirmTTL is how long a confirmed password holds.
	// AUTH_CONFIRM_TTL, default 15m.
	ConfirmTTL time.Duration `env:"AUTH_CONFIRM_TTL" default:"15m"`
	// ClientURL is the address of the app people use when the app is an
	// API (a single-page app, a mobile app's web pages): where emailed
	// links lead ([Auth.ClientLink]), such as https://app.example.com.
	// AUTH_CLIENT_URL, default none.
	ClientURL string `env:"AUTH_CLIENT_URL"`
}

Config holds the AUTH_* settings.

func LoadConfig

func LoadConfig(src config.Source) (Config, error)

LoadConfig reads the AUTH_* settings.

func (Config) Validate

func (c Config) Validate() error

Validate implements config.Validator.

type Option

type Option func(*options)

Option configures New.

func DefaultHomeURL added in v0.3.0

func DefaultHomeURL(path string) Option

DefaultHomeURL sets HomeURL's default, for ForApp: the page users go to after signing in when AUTH_HOME_URL isn't set, instead of /. make:auth's setupAuth gives /dashboard. AUTH_HOME_URL still wins, so each deployment can choose. New takes its Config as it is and ignores it.

func WithInsecureCookies

func WithInsecureCookies() Option

WithInsecureCookies lets the remember-me cookie travel over plain HTTP, for development and tests. ForApp uses it outside production-like environments, like the session cookie.

func WithIssuer added in v0.3.0

func WithIssuer(name string) Option

WithIssuer names the app in users' authenticator apps (two-factor sign-in). ForApp uses APP_NAME.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger. Default slog.Default().

type ThrottledError

type ThrottledError struct {
	// RetryAfter is how long until attempts are allowed again.
	RetryAfter time.Duration
}

ThrottledError is returned by Attempt when a login has failed too often from one address. 429.

func (*ThrottledError) Error

func (e *ThrottledError) Error() string

Error implements error.

func (*ThrottledError) HTTPStatus

func (e *ThrottledError) HTTPStatus() int

HTTPStatus implements web.StatusCoder.

type Token

type Token struct {
	db.Model
	// UserID is the owner's AuthID.
	UserID string `db:"user_id" json:"user_id"`
	// Name says what the token is for ("CLI on my laptop").
	Name string `db:"name" json:"name"`
	// Hash is the SHA-256 of the secret, in hex.
	Hash string `db:"token_hash" json:"-"`
	// Abilities are what the token may do; "*" is everything.
	Abilities []string `db:"abilities,json" json:"abilities"`
	// LastUsedAt is when the token was last used (to the minute).
	LastUsedAt *time.Time `db:"last_used_at" json:"last_used_at"`
	// ExpiresAt is when the token stops working; nil for never.
	ExpiresAt *time.Time `db:"expires_at" json:"expires_at"`
}

Token is an API token of a user, in the api_tokens table. Clients send it as "Authorization: Bearer <id>|<secret>"; only a hash of the secret is stored.

func CurrentToken

func CurrentToken(ctx context.Context) (*Token, bool)

CurrentToken returns the API token the request authenticated with, and whether it did.

func (*Token) Can

func (t *Token) Can(ability string) bool

Can reports whether the token has the ability ("*" has them all).

func (Token) TableName

func (Token) TableName() string

TableName implements db.Tabler.

type TwoFactorChallenge added in v0.4.0

type TwoFactorChallenge struct {
	// Token is the challenge to give the client, which sends it back
	// with a code to [Auth.AttemptTwoFactorChallenge]. It works for 10
	// minutes, until the user's password or session key changes; it is
	// encrypted with APP_KEY and names the user.
	Token string
}

TwoFactorChallenge is the error of Auth.AttemptCredentials for a user with two-factor sign-in on: the password was right, and the sign-in waits for a code. errors.Is(err, ErrTwoFactorRequired) is true. 401.

func (*TwoFactorChallenge) Error added in v0.4.0

func (*TwoFactorChallenge) Error() string

Error implements error.

func (*TwoFactorChallenge) HTTPStatus added in v0.4.0

func (*TwoFactorChallenge) HTTPStatus() int

HTTPStatus implements web.StatusCoder: 401.

func (*TwoFactorChallenge) Is added in v0.4.0

func (*TwoFactorChallenge) Is(target error) bool

Is makes the challenge ErrTwoFactorRequired.

type TwoFactorSetup added in v0.3.0

type TwoFactorSetup struct {
	// Secret is the key, in base32, for typing it in.
	Secret string
	// URI is the otpauth:// URI, for a QR code (package qr).
	URI string
}

TwoFactorSetup is a started two-factor setup: what the user's authenticator app needs.

type TwoFactorStatus added in v0.3.0

type TwoFactorStatus struct {
	// On says sign-in asks for a code.
	On bool
	// Started says a setup was started and not confirmed.
	Started bool
	// RecoveryCodes is how many unused recovery codes are left.
	RecoveryCodes int
}

TwoFactorStatus is a user's two-factor sign-in, as Auth.TwoFactor reports it.

type Users

type Users[U Authenticatable] struct {
	// ByID returns the user with the identifier from U.AuthID.
	ByID func(ctx context.Context, id string) (U, error)
	// ByLogin returns the user who signs in with login: usually an email
	// address, compared without regard to case.
	ByLogin func(ctx context.Context, login string) (U, error)
	// RememberToken returns the user's remember-me token, stored with
	// the user (a remember_token column); "" if none yet. Optional: with
	// SetRememberToken, it enables "remember me".
	RememberToken func(u U) string
	// SetRememberToken stores a new remember-me token for the user. Auth
	// sets one at the first remembered login and replaces it at logout,
	// which signs the user out of every remembered browser.
	SetRememberToken func(ctx context.Context, u U, token string) error
	// SetPassword stores a new password hash for the user. Optional:
	// Attempt uses it to upgrade old hashes (password.NeedsRehash) after
	// a successful login.
	SetPassword func(ctx context.Context, u U, hash string) error
	// Disabled reports whether the user's account is disabled (a
	// disabled_at column). Optional. A disabled user is signed out on
	// their next request and can't sign in ([ErrDisabled]); their API
	// tokens and remember-me cookies stop working.
	Disabled func(u U) bool
	// SessionKey returns the user's session key (a session_key column),
	// "" if none yet. Optional: with SetSessionKey, it enables
	// [Auth.SignOutEverywhere]. Sessions and remember-me cookies are
	// bound to it, as to the password hash.
	SessionKey func(u U) string
	// SetSessionKey stores a new session key for the user.
	SetSessionKey func(ctx context.Context, u U, key string) error
	// TwoFactor returns the user's two-factor sign-in state, as stored
	// (a two_factor column, text), "" if none. Optional: with
	// SetTwoFactor, it enables two-factor sign-in ([Auth.StartTwoFactor]).
	// Package auth writes it: the secret encrypted with APP_KEY, the
	// recovery codes hashed.
	TwoFactor func(u U) string
	// SetTwoFactor stores the user's two-factor state ("" for none).
	SetTwoFactor func(ctx context.Context, u U, state string) error
}

Users tells package auth how to find and update the app's users. ByID and ByLogin are required; they return ErrNoUser or db.ErrNotFound when there is no such user.

Directories

Path Synopsis
Package password hashes and checks passwords: argon2id for new hashes, with bcrypt hashes (from another framework, say Laravel) still accepted.
Package password hashes and checks passwords: argon2id for new hashes, with bcrypt hashes (from another framework, say Laravel) still accepted.
Package rbac gives users roles and permissions, globally or in a scope such as a team, and checks them for the request's signed-in user.
Package rbac gives users roles and permissions, globally or in a scope such as a team, and checks them for the request's signed-in user.
Package social signs users in with an account at another service: Google, GitHub, or any OpenID Connect provider.
Package social signs users in with an account at another service: Google, GitHub, or any OpenID Connect provider.

Jump to

Keyboard shortcuts

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