auth

package
v0.1.0-alpha.1 Latest Latest
Warning

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

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

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

View Source
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.

View Source
const (
	EventResetCreated  = "reset_created"
	EventPasswordReset = "password_reset"
)

Audit events of the credential reset.

View Source
const (
	EventRoleCreated = "role_created"
	EventRoleUpdated = "role_updated"
	EventRoleDeleted = "role_deleted"
)

Audit events about roles. Giving a user another role is EventRoleChanged.

View Source
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.

View Source
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.

View Source
const ConsoleActor = "console"

ConsoleActor is the name under which commands run on the server appear in the audit log.

View Source
const CookieName = "regulus_session"

CookieName is the name of the session cookie.

View Source
const (
	// PendingTTL is how long a login waits for the second factor.
	PendingTTL = 5 * time.Minute
)

Two-factor limits.

View Source
const ResetTTL = time.Hour

ResetTTL is how long a reset token can be used.

View Source
const (
	// SessionTTL is how long a login stays valid.
	SessionTTL = 7 * 24 * time.Hour
)

Variables

View Source
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.

View Source
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.

View Source
var (
	ErrTwoFactorUnavailable = errors.New("two-factor authentication is not available: the hub has no secret key")
	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.

View Source
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.

View Source
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

func HashPassword(password string) (string, error)

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

func LoadOrCreateSecret(dir string) ([]byte, error)

LoadOrCreateSecret returns the hub secret kept in dir, creating it (readable by the owner only) at the first start.

func RequireAnyPermission

func RequireAnyPermission(permissions []string, next http.Handler) http.Handler

RequireAnyPermission lets a request through if the user has at least one of the permissions.

func RequireJSON

func RequireJSON(next http.Handler) http.Handler

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

func RequirePermission(permission string, next http.Handler) http.Handler

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

func VerifyPassword(encoded, password string) (bool, error)

VerifyPassword reports whether password matches the encoded hash. It returns an error only when the stored hash is malformed.

func WithClient

func WithClient(ctx context.Context, c Client) context.Context

WithClient returns a context that carries the client of a request, for the service methods that record it. The HTTP handlers do this themselves.

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

type Client struct {
	Remote    string
	UserAgent string
}

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

func WithSecret(secret []byte) Option

WithSecret sets the hub secret that protects the TOTP secrets (see LoadOrCreateSecret). Without it two-factor authentication is unavailable.

type Permission

type Permission struct {
	ID          string `json:"id"`
	Description string `json:"description"`
}

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 Policy

type Policy string

Policy says who must use two-factor authentication.

const (
	PolicyOff    Policy = "off"
	PolicyAdmins Policy = "admins"
	PolicyAll    Policy = "all"
)

The policies an admin can choose.

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 Role

type Role string

Role is the name of a built-in or custom role (see permissions.go).

const (
	RoleAdmin    Role = "admin"
	RoleOperator Role = "operator"
	RoleViewer   Role = "viewer"
)

The built-in roles. They live in the code, not in the database, so a new version can give them the permissions of its new features, and nobody can edit them by mistake; custom roles cover the variations.

type RoleChange

type RoleChange struct {
	Name        *Role
	Description *string
	Permissions *[]string
}

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

type Route struct {
	Pattern string
	Handler http.Handler
}

Route is an API endpoint that needs a login.

func APIRoutes

func APIRoutes(s *Service) []Route

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

func New(ctx context.Context, db *sql.DB, log *slog.Logger, opts ...Option) (*Service, error)

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) Audit

func (s *Service) Audit(ctx context.Context, limit int) ([]AuditEntry, error)

Audit returns the most recent account events, newest first.

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, token string) (User, error)

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

func (s *Service) DeleteRole(ctx context.Context, by User, name Role) error

DeleteRole removes a custom role that no user has. The caller must hold every permission the role has.

func (*Service) DeleteUser

func (s *Service) DeleteUser(ctx context.Context, by User, username string) error

DeleteUser removes an account and its sessions. The last enabled admin cannot be deleted.

func (*Service) DisableTwoFactor

func (s *Service) DisableTwoFactor(ctx context.Context, u User, password, code string) error

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

func (s *Service) EnableTwoFactor(ctx context.Context, u User, code string) ([]string, error)

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) Logout

func (s *Service) Logout(ctx context.Context, token string) error

Logout ends the session of the token. An unknown token is not an error.

func (*Service) Mandatory

func (s *Service) Mandatory(ctx context.Context, u User) bool

Mandatory reports whether the policy requires the user to have two-factor. "admins" means whoever can manage users or roles.

func (*Service) MustEnroll

func (s *Service) MustEnroll(ctx context.Context, u User) bool

MustEnroll reports whether the user has to set up two-factor before doing anything else.

func (*Service) Policy

func (s *Service) Policy() Policy

Policy returns who must use two-factor authentication.

func (*Service) Record

func (s *Service) Record(ctx context.Context, actor, event, target, detail string)

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

func (s *Service) Register(mux *http.ServeMux)

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

func (s *Service) Require(next http.Handler) http.Handler

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

func (s *Service) ResetPassword(ctx context.Context, remote, token, password string) error

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

func (s *Service) ResetTwoFactor(ctx context.Context, by User, username string) error

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

func (s *Service) RevokeSession(ctx context.Context, user User, id string) error

RevokeSession ends one session of the user, by the id shown in Sessions.

func (*Service) Roles

func (s *Service) Roles(ctx context.Context) ([]RoleInfo, error)

Roles lists the built-in roles, then the custom ones.

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

func (s *Service) SetPolicy(ctx context.Context, by User, p Policy) error

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

func (s *Service) SetupRequired() bool

SetupRequired reports whether the hub has no accounts yet.

func (*Service) SetupToken

func (s *Service) SetupToken() string

SetupToken returns the pending one-time setup token, or "" when the setup was already completed.

func (*Service) TwoFactorStatus

func (s *Service) TwoFactorStatus(ctx context.Context, u User) (TwoFactorStatus, error)

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.

func (*Service) Users

func (s *Service) Users(ctx context.Context) ([]User, error)

Users lists all accounts, ordered by name.

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.

func UserFrom

func UserFrom(ctx context.Context) (User, bool)

UserFrom returns the authenticated user of a request that passed Require.

func (User) Can

func (u User) Can(permission string) bool

Can reports whether the user has the permission. Users from Authenticate carry their permissions.

type UserChange

type UserChange struct {
	Role     *Role
	Disabled *bool
}

UserChange lists the changes to an account; nil fields stay as they are.

Jump to

Keyboard shortcuts

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