Documentation
¶
Overview ¶
Package auth keeps the accounts of the people who edit the site, their sessions and their groups of rights. Passwords are stored as Argon2id hashes, and sessions only as a SHA-256 digest of their token, so a copy of the database lets nobody sign in. Visitors of the public site have no accounts.
An account can turn on two-step sign-in: after the password, signing in asks for a code of an authenticator app (TOTP) or a recovery code (see SignIn). The secrets the apps share are stored encrypted with a key of the site (see UseSecrets), recovery codes only as digests.
Index ¶
- Constants
- Variables
- func ClearCookie(w http.ResponseWriter, secure bool)
- func ClearPendingCookie(w http.ResponseWriter, secure bool)
- func KeyURI(issuer, account string, secret []byte) string
- func PendingToken(r *http.Request) string
- func Schema() fs.FS
- func SecretText(secret []byte) string
- func SetCookie(w http.ResponseWriter, token string, secure bool)
- func SetPendingCookie(w http.ResponseWriter, token string, secure bool)
- func TOTP(secret []byte, at time.Time) string
- func Token(r *http.Request) string
- func WithUser(ctx context.Context, user User) context.Context
- type Attempt
- type Group
- type Permissions
- type Role
- type Secrets
- type Service
- func (s *Service) Authenticate(ctx context.Context, token string) (User, error)
- func (s *Service) BeginTwoStep(ctx context.Context, userID string) error
- func (s *Service) Bootstrap(ctx context.Context, email, password string) error
- func (s *Service) CancelTwoStep(ctx context.Context, userID string) error
- func (s *Service) ChangePassword(ctx context.Context, userID, current, password, keep string) error
- func (s *Service) CheckPending(ctx context.Context, pending string) error
- func (s *Service) CheckResetToken(ctx context.Context, token string) error
- func (s *Service) Cleanup(ctx context.Context) error
- func (s *Service) ConfirmTwoStep(ctx context.Context, userID, code string) ([]string, error)
- func (s *Service) CreateUser(ctx context.Context, email, name, password string, role Role) (User, error)
- func (s *Service) DeleteGroup(ctx context.Context, id string) error
- func (s *Service) DeleteUser(ctx context.Context, userID string) error
- func (s *Service) DisableTwoStep(ctx context.Context, userID, password, code string) error
- func (s *Service) FinishSignIn(ctx context.Context, pending, code, previous string) (string, error)
- func (s *Service) Group(ctx context.Context, id string) (Group, error)
- func (s *Service) Groups(ctx context.Context) ([]Group, error)
- func (s *Service) HasUsers(ctx context.Context) (bool, error)
- func (s *Service) Login(ctx context.Context, email, password, previous string) (string, error)
- func (s *Service) Logout(ctx context.Context, token string) error
- func (s *Service) Names(ctx context.Context) (map[string]string, error)
- func (s *Service) NewRecoveryCodes(ctx context.Context, userID, code string) ([]string, error)
- func (s *Service) Peek(ctx context.Context, token string) (User, error)
- func (s *Service) Permissions(ctx context.Context, user User) (Permissions, error)
- func (s *Service) RemoveTwoStep(ctx context.Context, userID string) error
- func (s *Service) RequestPasswordReset(ctx context.Context, email string) (string, User, error)
- func (s *Service) ResetPassword(ctx context.Context, token, password string) error
- func (s *Service) RevokeSession(ctx context.Context, userID, sessionID string) error
- func (s *Service) SaveGroup(ctx context.Context, group Group) (Group, error)
- func (s *Service) Sessions(ctx context.Context, userID string) ([]Session, error)
- func (s *Service) SetRole(ctx context.Context, userID string, role Role) error
- func (s *Service) SetStatus(ctx context.Context, userID string, status Status) error
- func (s *Service) SetUserGroups(ctx context.Context, userID string, groupIDs []string) error
- func (s *Service) SetupAdmin(ctx context.Context, email, name, password string) (User, error)
- func (s *Service) SetupSecret(ctx context.Context, userID string) ([]byte, error)
- func (s *Service) SignIn(ctx context.Context, email, password, previous string) (Attempt, error)
- func (s *Service) TwoStep(ctx context.Context, userID string) (TwoStep, error)
- func (s *Service) TwoStepAvailable() bool
- func (s *Service) UpdateUser(ctx context.Context, userID, email, name string) error
- func (s *Service) UseSecrets(secrets Secrets)
- func (s *Service) User(ctx context.Context, userID string) (User, error)
- func (s *Service) UserGroups(ctx context.Context, userID string) ([]string, error)
- func (s *Service) Users(ctx context.Context) ([]User, error)
- type Session
- type Status
- type TwoStep
- type User
Constants ¶
const ( // Read opens a section and its items. Read = "read" // Create adds new items. Create = "create" // Edit changes items. Edit = "edit" // Publish makes changes visible on the public site, or hides them. Publish = "publish" // Delete moves items to the trash, and brings them back or deletes // them for good there. Delete = "delete" // Review approves or rejects revisions submitted for review, in sections // whose review workflow is on (see content.Review). Review = "review" // Export downloads the personal data a section holds, such as a // newsletter's subscribers or the registrations of an event, as CSV. Export = "export" )
Actions in the permission matrix, the columns a group's rights are set in for every section.
const CookieName = "wt_admin"
CookieName is the admin session cookie.
const PendingCookieName = "wt_admin_pending"
PendingCookieName is the cookie of a pending sign-in, which waits for the code of its second step (see SignIn).
const PendingLifetime = 5 * time.Minute
PendingLifetime is how long a sign-in whose password was right waits for the code of its second step (see SignIn).
const ( // ResetLifetime is how long a password reset link works. ResetLifetime = time.Hour )
Variables ¶
var ( // ErrCredentials is a sign-in with an unknown e-mail address or a wrong // password. The two are not told apart, so the answer reveals no // accounts. ErrCredentials = errors.New("invalid_credentials") // ErrSession is a session token that is unknown, expired, revoked or // belongs to a disabled account. ErrSession = errors.New("invalid_session") // ErrBootstrap refuses the first-run setup once any account exists. ErrBootstrap = errors.New("bootstrap_requires_empty_accounts") // ErrPassword is a password shorter than 12 characters (or absurdly // long). ErrPassword = errors.New("password_too_short") // ErrEmail is an e-mail address that is not one. ErrEmail = errors.New("invalid_email") // ErrDuplicate is an e-mail address another account has, or a group // name another group has. ErrDuplicate = errors.New("duplicate_email_or_group") // ErrRateLimit refuses a sign-in after too many failed attempts for the // same address in a short time, to slow down guessing. ErrRateLimit = errors.New("login_rate_limited") // ErrNotFound is an account, session or group that does not exist. ErrNotFound = errors.New("account_not_found") // ErrLastAdmin refuses a change that would leave no active // administrator, as nobody could manage the accounts any more. ErrLastAdmin = errors.New("last_active_admin") )
var ( // ErrSecondStep is the correct password of an account with two-step // sign-in on, given to Login. Such accounts sign in with SignIn and // FinishSignIn. ErrSecondStep = errors.New("second_step_required") // ErrCode is a code that is wrong or was used already: a code of the // authenticator app or a recovery code. ErrCode = errors.New("invalid_code") // ErrPending is a pending sign-in that is unknown, finished or older // than PendingLifetime. ErrPending = errors.New("invalid_pending_sign_in") // ErrTwoStepOn refuses to set up two-step sign-in while it is on. ErrTwoStepOn = errors.New("two_step_on") // ErrTwoStepOff is an account without two-step sign-in, or without a // setup that waits for its first code. ErrTwoStepOff = errors.New("two_step_off") // ErrNoSecrets refuses two-step sign-in on a service without the site's // keys (see UseSecrets): it could not keep the secrets of // authenticator apps. ErrNoSecrets = errors.New("two_step_without_keys") )
Actions lists the matrix columns in order.
var ErrGroupName = errors.New("invalid_group_name")
ErrGroupName is a group name that is empty or longer than 100 characters.
var ErrResetToken = errors.New("invalid_reset_token")
ErrResetToken is returned for a password reset link that is unknown, used or expired.
Functions ¶
func ClearCookie ¶
func ClearCookie(w http.ResponseWriter, secure bool)
ClearCookie removes the session cookie.
func ClearPendingCookie ¶
func ClearPendingCookie(w http.ResponseWriter, secure bool)
ClearPendingCookie removes the cookie of a pending sign-in.
func KeyURI ¶
KeyURI is the otpauth address a QR code gives an authenticator app (the Key URI Format of Google Authenticator, which other apps read too): the issuer and account name the app shows, the secret, and the settings of TOTP. issuer is the site, such as its title; account the e-mail address.
func PendingToken ¶
PendingToken reads the token of a pending sign-in from the request cookie.
func SecretText ¶
SecretText is secret as people type it into an authenticator app: base32 (RFC 4648) without padding, such as GEZDGNBVGY3TQOJQ.
func SetCookie ¶
func SetCookie(w http.ResponseWriter, token string, secure bool)
SetCookie stores the session token. secure must be false only for plain HTTP. The cookie reaches the whole site, so public pages can show editors an edit link, wherever the admin is; it stays HttpOnly and SameSite=Lax, and the admin refuses cross-origin form posts (see http.CrossOriginProtection).
func SetPendingCookie ¶
func SetPendingCookie(w http.ResponseWriter, token string, secure bool)
SetPendingCookie stores the token of a pending sign-in for PendingLifetime. secure must be false only for plain HTTP. The cookie is HttpOnly and SameSite=Strict: only the admin's own page for the code sends it back.
func TOTP ¶
TOTP returns the code an authenticator app shows for secret at time at (RFC 6238), with the settings every app uses: the HOTP value (RFC 4226, HMAC-SHA1) of the number of 30-second steps since the Unix epoch, as 6 digits.
Types ¶
type Attempt ¶
type Attempt struct {
// Session is the token of the new session.
Session string
// Pending is the token of the pending sign-in, for the browser to keep
// (see SetPendingCookie) until FinishSignIn turns it into a session,
// within PendingLifetime.
Pending string
}
Attempt is what SignIn gives for a correct password: a session, or for an account with two-step sign-in on, a pending sign-in that waits for a code. One of the two is set.
type Group ¶
type Group struct {
ID string `db:"id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
CreatedBy db.Ref `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy db.Ref `db:"updated_by"`
Members []string `db:"-"`
Permissions Permissions `db:"-"`
}
Group gives its members rights: allowed actions per section (Pages, Media, a module instance, ...). Administrators do not need groups.
type Permissions ¶
Permissions maps a section ID to its allowed actions.
func (Permissions) Allows ¶
func (p Permissions) Allows(section, action string) bool
Allows reports whether action is allowed in section.
type Role ¶
type Role string
Role is what an account may do in general. Administrators may do everything; editors may do what their groups allow.
type Secrets ¶
Secrets hands out the site's keys: random keys of 32 bytes by name, the same in every process of the site, made on first use. site.Service is one (site.Service.Secret).
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages accounts, sessions and groups in the database. It is safe for concurrent use.
func New ¶
New returns the Service on database, whose schema (Schema) must be applied. It fails only when the system has no randomness.
func (*Service) Authenticate ¶
Authenticate returns the active user of a valid session token and records the session's activity, to the minute.
func (*Service) BeginTwoStep ¶
BeginTwoStep starts turning on two-step sign-in for the account: it makes a random secret of 20 bytes, which SetupSecret returns for the authenticator app, in place of the secret of an earlier setup. Two-step sign-in is on only once ConfirmTwoStep receives a code of the app. It returns ErrTwoStepOn while it is on already.
func (*Service) Bootstrap ¶
Bootstrap creates the first admin. It does nothing once any account exists and returns ErrBootstrap in that case.
func (*Service) CancelTwoStep ¶
CancelTwoStep drops the account's setup that waits for its first code, if any. It does not turn off two-step sign-in that is on.
func (*Service) ChangePassword ¶
ChangePassword sets a new password after checking the current one. Wrong guesses count towards the same limit as failed sign-ins. Every other session of the account is revoked, and its pending sign-ins end; the session with token keep, the one the change was made in, stays signed in.
func (*Service) CheckPending ¶
CheckPending reports whether a pending sign-in still waits for its code: ErrPending when it is unknown, finished or expired.
func (*Service) CheckResetToken ¶
CheckResetToken reports whether a reset token can still be used, so the form to choose a new password is only shown for working links.
func (*Service) Cleanup ¶
Cleanup removes expired sessions and pending sign-ins, ended login windows and expired password reset links. The app runs it in the background.
func (*Service) ConfirmTwoStep ¶
ConfirmTwoStep turns two-step sign-in on with a code of the app the setup was scanned into, and returns the account's 10 recovery codes, such as 4f7k2-9xq3m. Only their digests are stored, so they cannot be shown again. A wrong code returns ErrCode and leaves the setup waiting; without a setup it returns ErrTwoStepOff.
func (*Service) CreateUser ¶
func (s *Service) CreateUser(ctx context.Context, email, name, password string, role Role) (User, error)
CreateUser adds an active account. Passwords need at least 12 characters.
func (*Service) DeleteGroup ¶
DeleteGroup removes a group; its members keep their other groups.
func (*Service) DeleteUser ¶
DeleteUser removes an account with its sessions, group memberships, reset links and two-step sign-in. The last active administrator cannot be removed.
func (*Service) DisableTwoStep ¶
DisableTwoStep turns two-step sign-in off with the account's password and a code: of the app or a recovery code. Wrong passwords and codes count towards the sign-in limit (ErrRateLimit). It returns ErrTwoStepOff when it is off already.
func (*Service) FinishSignIn ¶
FinishSignIn completes a pending sign-in (see SignIn) with a code of the authenticator app or a recovery code, and returns a new session token. A previous token from the same browser is revoked. A code of the app works for its 30-second step and one step either side, and only once: a step that was used, or one before it, is refused. Each recovery code works once. Wrong codes return ErrCode and count towards the sign-in limit (ErrRateLimit), together with wrong passwords; the pending sign-in stays until it expires.
func (*Service) HasUsers ¶
HasUsers reports whether any account exists. Without one, the admin offers its first-run setup.
func (*Service) Login ¶
Login checks credentials and returns a new session token. A previous token from the same browser is revoked. Errors never reveal whether an account exists. An account with two-step sign-in on gets no session from its password alone: Login returns ErrSecondStep, and such an account signs in with SignIn.
func (*Service) Logout ¶
Logout ends the session of token, so it no longer signs anyone in. An empty or unknown token is no error: there is nothing to end.
func (*Service) NewRecoveryCodes ¶
NewRecoveryCodes replaces the account's recovery codes with 10 new ones, with a code: of the app or one of the old recovery codes. Wrong codes count towards the sign-in limit (ErrRateLimit).
func (*Service) Peek ¶
Peek is Authenticate without recording activity: one read, for checks on the public site where editors only browse.
func (*Service) Permissions ¶
Permissions returns what a user may do: the combined rights of their groups. Administrators may do everything and get nil; check IsAdmin first.
func (*Service) RemoveTwoStep ¶
RemoveTwoStep turns two-step sign-in off for an account without its password or a code, as an administrator does for someone who lost both their phone and their recovery codes. The account's pending sign-ins end. It returns ErrTwoStepOff when it is off already.
func (*Service) RequestPasswordReset ¶
RequestPasswordReset creates a one-time token for the active account with email, to send in a reset link; only its digest is stored. It returns ErrNotFound when there is no such active account and ErrRateLimit after three links within an hour. Callers must answer the same in every case, so the answer does not reveal which addresses have an account.
func (*Service) ResetPassword ¶
ResetPassword sets a new password with a reset token and uses the token up, with every other reset link of the account. All sessions of the account are revoked, its pending sign-ins end and its failed sign-ins are forgotten, so it signs in anew with the new password: with two-step sign-in on, also with a code (see SignIn).
func (*Service) RevokeSession ¶
RevokeSession ends one session of an account, such as one on a lost phone. It returns ErrNotFound when the account has no such active session.
func (*Service) SaveGroup ¶
SaveGroup creates a group (empty ID) or renames it and replaces its permissions.
func (*Service) Sessions ¶
Sessions lists the account's sessions that have not expired yet, newest first; revoked ones are included until they would have expired.
func (*Service) SetRole ¶
SetRole makes a user an administrator or an editor. The last active administrator cannot become an editor.
func (*Service) SetStatus ¶
SetStatus enables or disables an account. Disabling revokes its sessions; the last active admin cannot be disabled.
func (*Service) SetUserGroups ¶
SetUserGroups replaces the groups a user belongs to.
func (*Service) SetupAdmin ¶
SetupAdmin creates the first account, an administrator, as Bootstrap does, with a name. It returns ErrBootstrap once any account exists, also when another request created one first.
func (*Service) SetupSecret ¶
SetupSecret returns the secret of the account's setup that waits for its first code (see BeginTwoStep), to show as a QR code (KeyURI) and as text (SecretText). Once two-step sign-in is on, the secret is never shown again: SetupSecret then returns ErrTwoStepOff, as it does without a setup.
func (*Service) SignIn ¶
SignIn checks credentials as Login does. For an account with two-step sign-in on, a correct password starts a pending sign-in rather than a session: FinishSignIn completes it with a code within PendingLifetime. Wrong passwords count towards the sign-in limit, and so do wrong codes; failures are forgotten only once a sign-in completes.
func (*Service) TwoStepAvailable ¶
TwoStepAvailable reports whether two-step sign-in can be turned on: the service has the site's keys (see UseSecrets).
func (*Service) UpdateUser ¶
UpdateUser changes an account's e-mail address and name. The address must be valid and not in use by another account.
func (*Service) UseSecrets ¶
UseSecrets gives the service the site's keys. Two-step sign-in encrypts the secrets of authenticator apps with one of them (AES-GCM), named "two_step_sign_in"; without keys it cannot be turned on (ErrNoSecrets). Call it before the service is used.
func (*Service) UserGroups ¶
UserGroups lists the IDs of the groups a user belongs to.
type Session ¶
type Session struct {
ID string `db:"id"`
UserID string `db:"user_id"`
ExpiresAt time.Time `db:"expires_at"`
RevokedAt *time.Time `db:"revoked_at"`
CreatedAt time.Time `db:"created_at"`
LastSeenAt *time.Time `db:"last_seen_at"`
}
Session is one sign-in of an account, on one browser, as the account's session list shows it. The token itself is never stored.
type TwoStep ¶
type TwoStep struct {
// On means that signing in asks for a code after the password.
On bool
// Setup means that turning it on waits for the first code of the app
// (see BeginTwoStep).
Setup bool
// RecoveryCodes is the number of recovery codes not used yet.
RecoveryCodes int
}
TwoStep is the state of an account's two-step sign-in.
type User ¶
type User struct {
ID string `db:"id"`
Email string `db:"email"`
Name string `db:"name"`
Role Role `db:"role"`
Status Status `db:"status"`
PasswordHash string `db:"password_hash" json:"-"`
CreatedAt time.Time `db:"created_at"`
CreatedBy db.Ref `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy db.Ref `db:"updated_by"`
}
User is an account. Lists and the signed-in user (CurrentUser) carry no password hash; only sign-in reads it.
func CurrentUser ¶
CurrentUser returns the user that ctx is signed in as (see WithUser), and false outside a signed-in request, such as on the public site.
func (User) DisplayName ¶
DisplayName is the name, or the email when no name is set.