Documentation
¶
Overview ¶
Package auth implements the hub's local accounts: first-run setup of the admin, password hashing, login sessions and the HTTP middleware that protects the API.
Index ¶
- Constants
- Variables
- func HashPassword(password string) (string, error)
- func LoadOrCreateSecret(dir string) ([]byte, error)
- func RequireAnyPermission(permissions []string, next http.Handler) http.Handler
- func RequireJSON(next http.Handler) http.Handler
- func RequirePermission(permission string, next http.Handler) http.Handler
- func VerifyPassword(encoded, password string) (bool, error)
- func WithClient(ctx context.Context, c Client) context.Context
- type AuditEntry
- type Client
- type LoginResult
- type Option
- type Permission
- type Policy
- type Recovery
- type Role
- type RoleChange
- type RoleInfo
- type Route
- type Service
- func (s *Service) Audit(ctx context.Context, limit int) ([]AuditEntry, error)
- func (s *Service) Authenticate(ctx context.Context, token string) (User, error)
- func (s *Service) BeginTwoFactor(ctx context.Context, u User, password string) (TwoFactorSetup, error)
- func (s *Service) ChangePassword(ctx context.Context, user User, keepToken, current, next string) error
- func (s *Service) CompleteLogin(ctx context.Context, remote, pending, code string) (LoginResult, error)
- func (s *Service) CreateReset(ctx context.Context, by User, username string) (string, time.Time, error)
- func (s *Service) CreateRole(ctx context.Context, by User, name Role, description string, perms []string) (RoleInfo, error)
- func (s *Service) CreateUser(ctx context.Context, by User, username, password string, role Role) (User, error)
- func (s *Service) DeleteRole(ctx context.Context, by User, name Role) error
- func (s *Service) DeleteUser(ctx context.Context, by User, username string) error
- func (s *Service) DisableTwoFactor(ctx context.Context, u User, password, code string) error
- func (s *Service) EnableTwoFactor(ctx context.Context, u User, code string) ([]string, error)
- func (s *Service) Login(ctx context.Context, remote, username, password string) (User, string, time.Time, error)
- func (s *Service) Logout(ctx context.Context, token string) error
- func (s *Service) Mandatory(ctx context.Context, u User) bool
- func (s *Service) MustEnroll(ctx context.Context, u User) bool
- func (s *Service) Policy() Policy
- func (s *Service) Record(ctx context.Context, actor, event, target, detail string)
- func (s *Service) Register(mux *http.ServeMux)
- func (s *Service) RenewRecoveryCodes(ctx context.Context, u User, password, code string) ([]string, error)
- func (s *Service) Require(next http.Handler) http.Handler
- func (s *Service) ResetPassword(ctx context.Context, remote, token, password string) error
- func (s *Service) ResetTwoFactor(ctx context.Context, by User, username string) error
- func (s *Service) RevokeSession(ctx context.Context, user User, id string) error
- func (s *Service) Roles(ctx context.Context) ([]RoleInfo, error)
- func (s *Service) Sessions(ctx context.Context, user User, currentToken string) ([]SessionInfo, error)
- func (s *Service) SetPolicy(ctx context.Context, by User, p Policy) error
- func (s *Service) Setup(ctx context.Context, setupToken, username, password string) (User, string, time.Time, error)
- func (s *Service) SetupRequired() bool
- func (s *Service) SetupToken() string
- func (s *Service) TwoFactorStatus(ctx context.Context, u User) (TwoFactorStatus, error)
- func (s *Service) UpdateRole(ctx context.Context, by User, name Role, ch RoleChange) (RoleInfo, error)
- func (s *Service) UpdateUser(ctx context.Context, by User, username string, ch UserChange) (User, error)
- func (s *Service) Users(ctx context.Context) ([]User, error)
- type SessionInfo
- type TwoFactorRequiredError
- type TwoFactorSetup
- type TwoFactorStatus
- type User
- type UserChange
Constants ¶
const ( PermServersRead = "servers.read" PermServersManage = "servers.manage" PermLayoutEdit = "layout.edit" PermUsersManage = "users.manage" PermRolesManage = "roles.manage" PermAuditRead = "audit.read" PermCoreUpdate = "core.update" )
The permissions known to the hub. Features add theirs here (restarting services, package updates, logs, plugins) together with the endpoints that check them.
const ( EventResetCreated = "reset_created" EventPasswordReset = "password_reset" )
Audit events of the credential reset.
const ( EventRoleCreated = "role_created" EventRoleUpdated = "role_updated" EventRoleDeleted = "role_deleted" )
Audit events about roles. Giving a user another role is EventRoleChanged.
const ( EventTwoFactorEnabled = "two_factor_enabled" EventTwoFactorDisabled = "two_factor_disabled" EventTwoFactorReset = "two_factor_reset" EventRecoveryCodeUsed = "recovery_code_used" EventRecoveryCodesRenewed = "recovery_codes_renewed" EventTwoFactorPolicyChange = "two_factor_policy_changed" )
Audit events of two-factor authentication.
const ( EventSetup = "setup" EventLogin = "login" EventLoginFailed = "login_failed" EventUserCreated = "user_created" EventRoleChanged = "role_changed" EventUserDisabled = "user_disabled" EventUserEnabled = "user_enabled" EventUserDeleted = "user_deleted" EventPasswordChange = "password_changed" EventPasswordFailed = "password_change_failed" EventSessionRevoked = "session_revoked" )
Audit events recorded by the service.
const ConsoleActor = "console"
ConsoleActor is the name under which commands run on the server appear in the audit log.
const CookieName = "regulus_session"
CookieName is the name of the session cookie.
const ( // PendingTTL is how long a login waits for the second factor. PendingTTL = 5 * time.Minute )
Two-factor limits.
const ResetTTL = time.Hour
ResetTTL is how long a reset token can be used.
const ( // SessionTTL is how long a login stays valid. SessionTTL = 7 * 24 * time.Hour )
Variables ¶
var ( ErrRoleNotFound = errors.New("role not found") ErrRoleExists = errors.New("a role with this name already exists") ErrBuiltinRole = errors.New("the built-in roles cannot be changed or deleted") ErrRoleInUse = errors.New("users still have this role") )
Errors about roles.
var ( ErrSetupDone = errors.New("setup was already completed") ErrBadSetupToken = errors.New("invalid setup token") ErrInvalidCredentials = errors.New("invalid username or password") ErrRateLimited = errors.New("too many failed attempts, try again later") ErrUnauthenticated = errors.New("authentication required") ErrForbidden = errors.New("you are not allowed to do this") ErrUserNotFound = errors.New("user not found") ErrUserExists = errors.New("a user with this name already exists") ErrLastAdmin = errors.New("this would leave the hub without an enabled admin") ErrWrongPassword = errors.New("the current password is incorrect") ErrSessionNotFound = errors.New("session not found") )
Errors returned by the store and the service.
var ( ErrTwoFactorOn = errors.New("two-factor authentication is already on") ErrTwoFactorOff = errors.New("two-factor authentication is not on") ErrTwoFactorNotStarted = errors.New("start the setup first") ErrTwoFactorMandatory = errors.New("the hub requires two-factor authentication for your account, so it cannot be turned off") ErrInvalidCode = errors.New("the code is not valid") ErrLoginExpired = errors.New("this sign-in expired: sign in again") // ErrTwoFactorRequired is wrapped by a TwoFactorRequiredError. ErrTwoFactorRequired = errors.New("a second factor is required") )
Errors of two-factor authentication.
var ErrInvalidReset = errors.New("this reset link is invalid or has expired")
ErrInvalidReset is the one answer for a token that is unknown, expired or already used, so it says nothing about which accounts exist.
var Routes = []string{
"GET /api/auth/me",
"POST /api/auth/setup",
"POST /api/auth/login",
"POST /api/auth/login/two-factor",
"POST /api/auth/logout",
"POST /api/auth/reset",
}
Routes are the patterns Register adds to the mux.
Functions ¶
func HashPassword ¶
HashPassword returns the argon2id hash of password in the PHC string format, for example $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>.
func LoadOrCreateSecret ¶
LoadOrCreateSecret returns the hub secret kept in dir, creating it (readable by the owner only) at the first start.
func RequireAnyPermission ¶
RequireAnyPermission lets a request through if the user has at least one of the permissions.
func RequireJSON ¶
RequireJSON rejects requests that change state and that a browser could send from another site: the body must be JSON (a content type forms cannot send without a CORS preflight) and an Origin header, when present, must be the hub's own. Wrap every POST, PUT and DELETE handler with it.
func RequirePermission ¶
RequirePermission lets a request through only if the logged-in user has the permission, otherwise it answers 403. Use it inside Service.Require.
func VerifyPassword ¶
VerifyPassword reports whether password matches the encoded hash. It returns an error only when the stored hash is malformed.
Types ¶
type AuditEntry ¶
type AuditEntry struct {
ID int64 `json:"id"`
At time.Time `json:"at"`
Actor string `json:"actor"`
Event string `json:"event"`
Target string `json:"target"`
Remote string `json:"remote"`
Detail string `json:"detail"`
}
AuditEntry is one recorded account event.
type Client ¶
Client describes where a request came from. It is recorded with sessions and in the audit log.
type LoginResult ¶
type LoginResult struct {
User User
Token string
Expires time.Time
// RecoveryUsed is true when a recovery code, not an app code, was used.
RecoveryUsed bool
// RecoveryLeft is how many recovery codes remain; set with RecoveryUsed.
RecoveryLeft int
}
LoginResult is the outcome of a completed login.
type Option ¶
type Option func(*Service)
Option customizes a Service.
func WithSecret ¶
WithSecret sets the hub secret that protects the TOTP secrets (see LoadOrCreateSecret). Without it two-factor authentication is unavailable.
type Permission ¶
Permission is something a role can allow. The API checks permissions, never role names.
func Permissions ¶
func Permissions() []Permission
Permissions returns every permission the hub knows, in a stable order.
type Recovery ¶
type Recovery struct {
Username string
Token string
Expires time.Time
// Reenabled is true if the account was disabled and is enabled now.
Reenabled bool
// TwoFactorCleared is true if the account had two-factor authentication,
// which was removed so that the admin can sign in with the new password.
TwoFactorCleared bool
}
Recovery is what RecoverAdmin did.
func RecoverAdmin ¶
func RecoverAdmin(ctx context.Context, db *sql.DB, username string, log *slog.Logger) (Recovery, error)
RecoverAdmin gives back access to a hub whose admin cannot log in. It runs on the server, against the database, for someone who can reach the data directory, which is the proof of ownership: it makes a reset token for the named admin (or the only admin, when username is empty), enables the account and ends its sessions. The password is chosen later, in the dashboard, so it never shows up in a terminal or in the shell history.
type RoleChange ¶
RoleChange lists the changes to a custom role; nil fields stay as they are.
type RoleInfo ¶
type RoleInfo struct {
Name Role `json:"name"`
Description string `json:"description"`
Builtin bool `json:"builtin"`
Permissions []string `json:"permissions"`
// Users is how many accounts have the role.
Users int `json:"users"`
}
RoleInfo is a role as shown to the users who may see roles.
type Route ¶
Route is an API endpoint that needs a login.
func APIRoutes ¶
APIRoutes are the account endpoints. They all need a login (wrap them with Require) and check a permission: users.manage for the accounts, roles.manage for the roles, audit.read for the audit log. The ones under /api/account are for the logged-in user's own account.
GET /api/users list the accounts
POST /api/users create an account
PATCH /api/users/{username} change role or enable/disable
DELETE /api/users/{username} delete an account
POST /api/users/{username}/reset make a one-time password reset token
GET /api/audit recent account events
GET /api/roles the roles and the permissions (users.manage or roles.manage)
POST /api/roles create a role
PATCH /api/roles/{name} change a custom role
DELETE /api/roles/{name} delete a custom role
POST /api/account/password change the own password
GET /api/account/sessions list the own sessions
DELETE /api/account/sessions/{id} end one of the own sessions
The two-factor endpoints are listed at twoFactorRoutes.
The Service may be nil to list the routes only.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages accounts and sessions.
func New ¶
New returns the service for the hub database. If there is no user yet it generates a one-time setup token and logs it: whoever can read the hub log can create the first admin, and nobody else.
func (*Service) Authenticate ¶
Authenticate returns the user of a session token.
func (*Service) BeginTwoFactor ¶
func (s *Service) BeginTwoFactor(ctx context.Context, u User, password string) (TwoFactorSetup, error)
BeginTwoFactor starts the enrollment: it makes a secret for the user, who must confirm it with EnableTwoFactor before it counts. The password is needed so that a stolen session cannot add a second factor of its own.
func (*Service) ChangePassword ¶
func (s *Service) ChangePassword(ctx context.Context, user User, keepToken, current, next string) error
ChangePassword sets a new password for the user after checking the current one, and ends all their other sessions (keepToken is the one to keep). Wrong current passwords count toward the rate limit, so a stolen session cannot be used to guess the password.
func (*Service) CompleteLogin ¶
func (s *Service) CompleteLogin(ctx context.Context, remote, pending, code string) (LoginResult, error)
CompleteLogin finishes a login that waits for the second factor: with a valid code the session starts. remote identifies the client for the limit on failed attempts, which wrong codes count toward.
func (*Service) CreateReset ¶
func (s *Service) CreateReset(ctx context.Context, by User, username string) (string, time.Time, error)
CreateReset makes a one-time token that lets the owner of an account choose a new password, and ends all the sessions of the account, since its credentials can no longer be trusted. The admin never learns the password. The caller must be an admin; that is checked by the HTTP layer.
func (*Service) CreateRole ¶
func (s *Service) CreateRole(ctx context.Context, by User, name Role, description string, perms []string) (RoleInfo, error)
CreateRole adds a custom role. The caller must hold every permission it gives.
func (*Service) CreateUser ¶
func (s *Service) CreateUser(ctx context.Context, by User, username, password string, role Role) (User, error)
CreateUser adds an account with an initial password. The caller must be an admin; that is checked by the HTTP layer.
func (*Service) DeleteRole ¶
DeleteRole removes a custom role that no user has. The caller must hold every permission the role has.
func (*Service) DeleteUser ¶
DeleteUser removes an account and its sessions. The last enabled admin cannot be deleted.
func (*Service) DisableTwoFactor ¶
DisableTwoFactor turns two-factor off after the password and a valid code (or recovery code). It is refused when the policy requires it.
func (*Service) EnableTwoFactor ¶
EnableTwoFactor confirms the enrollment with a code from the app and turns two-factor on. It returns the recovery codes, which are never shown again.
func (*Service) Login ¶
func (s *Service) Login(ctx context.Context, remote, username, password string) (User, string, time.Time, error)
Login checks the credentials and starts a session. remote identifies the client (its IP address) for the limit on failed attempts.
func (*Service) Mandatory ¶
Mandatory reports whether the policy requires the user to have two-factor. "admins" means whoever can manage users or roles.
func (*Service) MustEnroll ¶
MustEnroll reports whether the user has to set up two-factor before doing anything else.
func (*Service) Record ¶
Record adds an event to the audit log on behalf of the other parts of the hub. The remote address is taken from the context (see WithClient).
func (*Service) Register ¶
Register adds the authentication endpoints to mux:
GET /api/auth/me who is logged in, and whether the first-run setup is pending POST /api/auth/setup create the first admin (needs the one-time setup token) POST /api/auth/login start a session, or answer that a second factor is needed POST /api/auth/login/two-factor finish such a login with a code POST /api/auth/logout end the session POST /api/auth/reset choose a new password with a one-time reset token
The cookie is HttpOnly and SameSite=Lax, and POST requests must be JSON from the same origin, which together rule out cross-site request forgery.
func (*Service) RenewRecoveryCodes ¶
func (s *Service) RenewRecoveryCodes(ctx context.Context, u User, password, code string) ([]string, error)
RenewRecoveryCodes replaces the recovery codes after the password and a valid code, and returns the new ones.
func (*Service) Require ¶
Require lets a request through only if it carries a valid session cookie; otherwise it answers 401. The user is available with UserFrom.
func (*Service) ResetPassword ¶
ResetPassword sets a new password with a reset token, ends every session of the account and uses the token up. remote identifies the client for the limit on failed attempts. It does not log the user in.
func (*Service) ResetTwoFactor ¶
ResetTwoFactor removes the second factor of another user, for someone who lost the app and the recovery codes. The caller must be an admin (checked by the HTTP layer). The sessions of the user end.
func (*Service) RevokeSession ¶
RevokeSession ends one session of the user, by the id shown in Sessions.
func (*Service) Sessions ¶
func (s *Service) Sessions(ctx context.Context, user User, currentToken string) ([]SessionInfo, error)
Sessions lists the active sessions of the user; currentToken marks the one that made the request.
func (*Service) SetPolicy ¶
SetPolicy changes who must use two-factor authentication. Users it now applies to are asked to set it up at their next request.
func (*Service) Setup ¶
func (s *Service) Setup(ctx context.Context, setupToken, username, password string) (User, string, time.Time, error)
Setup creates the first admin account, which must present the setup token, and logs it in. It returns the session token for the cookie.
func (*Service) SetupRequired ¶
SetupRequired reports whether the hub has no accounts yet.
func (*Service) SetupToken ¶
SetupToken returns the pending one-time setup token, or "" when the setup was already completed.
func (*Service) TwoFactorStatus ¶
TwoFactorStatus returns the two-factor state of the user.
func (*Service) UpdateRole ¶
func (s *Service) UpdateRole(ctx context.Context, by User, name Role, ch RoleChange) (RoleInfo, error)
UpdateRole changes a custom role. The users that have it follow a rename and get the new permissions at their next request. The caller must hold every permission the role had and will have.
func (*Service) UpdateUser ¶
func (s *Service) UpdateUser(ctx context.Context, by User, username string, ch UserChange) (User, error)
UpdateUser changes the role or the enabled state of an account. The last enabled admin cannot be demoted or disabled. Disabling an account ends its sessions.
type SessionInfo ¶
type SessionInfo struct {
ID string `json:"id"`
RemoteAddr string `json:"remote_addr"`
UserAgent string `json:"user_agent"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt time.Time `json:"last_used_at"`
ExpiresAt time.Time `json:"expires_at"`
// Current is true for the session that made the request.
Current bool `json:"current"`
}
SessionInfo is a login session as shown to its owner.
type TwoFactorRequiredError ¶
type TwoFactorRequiredError struct {
// Pending is the token to present, with a code, to CompleteLogin.
Pending string
Expires time.Time
}
TwoFactorRequiredError is what Login returns when the password was right but the account has two-factor on: the login goes on with CompleteLogin.
func (*TwoFactorRequiredError) Error ¶
func (e *TwoFactorRequiredError) Error() string
func (*TwoFactorRequiredError) Is ¶
func (e *TwoFactorRequiredError) Is(target error) bool
Is lets errors.Is(err, ErrTwoFactorRequired) match.
type TwoFactorSetup ¶
type TwoFactorSetup struct {
// Secret is the key to type into the app, in groups for reading.
Secret string `json:"secret"`
// URI is the otpauth:// address the QR code holds.
URI string `json:"uri"`
// QR is the QR code as a PNG data URL.
QR string `json:"qr"`
}
TwoFactorSetup is what the user needs to add the account to an app.
type TwoFactorStatus ¶
type TwoFactorStatus struct {
Enabled bool `json:"enabled"`
Mandatory bool `json:"mandatory"`
Policy Policy `json:"policy"`
RecoveryCodesLeft int `json:"recovery_codes_left"`
}
TwoFactorStatus is what the user sees about their own two-factor state.
type User ¶
type User struct {
ID int64 `json:"-"`
Username string `json:"username"`
Role Role `json:"role"`
CreatedAt time.Time `json:"created_at"`
// Disabled accounts cannot log in and have no sessions.
Disabled bool `json:"disabled"`
// TwoFactor is true when the user confirmed a second factor (TOTP).
TwoFactor bool `json:"two_factor"`
// Permissions are what the role allows. Only users that come from a
// session carry them.
Permissions []string `json:"permissions,omitempty"`
}
User is a local account.
type UserChange ¶
UserChange lists the changes to an account; nil fields stay as they are.