Documentation
¶
Overview ¶
Package backoffice is the back office the apps share: who may use an app (roles, access states), the session check that enforces it on every request, the admin routes over the accounts, View as, the announcement shown to everyone, the features an admin keeps to the admins, the audit log and the account stats. schema.sql lists the columns and the tables it needs beside lib's auth tables and audit_events.
An app keeps its sign-in flows: they call Admit and AuditSignIn in the transaction that creates the session, and every handler that needs a signed-in user calls Check (or RequireAdmin). Mount serves the admin routes under /api/v1/admin/ and the features under /api/v1/features; the UI kit (lib's web/admin, web/features.svelte.ts) draws them.
Index ¶
- Constants
- Variables
- func IsPending(accessState, pendingSince string) bool
- type AdminUser
- type Announcement
- type Backoffice
- func (b *Backoffice) AccountStats(ctx context.Context, now time.Time) (Stats, error)
- func (b *Backoffice) ActiveAnnouncement(ctx context.Context) (*Announcement, error)
- func (b *Backoffice) Admit(ctx context.Context, tx *sql.Tx, email string, now time.Time) (User, string, error)
- func (b *Backoffice) AuditSignIn(ctx context.Context, tx *sql.Tx, u User, joined, via, ip string) error
- func (b *Backoffice) Check(w http.ResponseWriter, r *http.Request) (Session, bool)
- func (b *Backoffice) CheckAllowPending(w http.ResponseWriter, r *http.Request) (Session, bool)
- func (b *Backoffice) EndSession(w http.ResponseWriter, r *http.Request) (found bool, err error)
- func (b *Backoffice) Features(ctx context.Context) ([]FeatureState, error)
- func (b *Backoffice) IsSuperAdmin(role, email string) bool
- func (b *Backoffice) ListUsers(ctx context.Context, connectedOnly bool) ([]AdminUser, error)
- func (b *Backoffice) MeOf(ctx context.Context, sess Session) (Me, error)
- func (b *Backoffice) Mount(mux *http.ServeMux)
- func (b *Backoffice) RequireAdmin(w http.ResponseWriter, r *http.Request) (Session, bool)
- type Config
- type Feature
- type FeatureState
- type LoggedEvent
- type Me
- type Session
- type Stats
- type User
Constants ¶
const ( AccessActive = "active" AccessRestrained = "restrained" AccessPending = "pending" )
Access states. The database keeps a pending account restrained with pending_since set: AccessPending only exists in the API.
const ( AudienceEveryone = "everyone" AudienceAdmins = "admins" )
Audiences of a feature: everyone, or the admins only, as for a beta.
const ( JoinedAdminEmail = "admin_email" JoinedFirstUser = "first_user" JoinedOpen = "open" JoinedUnknown = "unknown_email" )
Why a sign-in created an account, as the signed_up audit row and log line say it.
const RoleAdmin = "admin"
RoleAdmin is the role of the accounts that use the back office; every app has it.
Variables ¶
var ( // ErrRestrained refuses the sign-in of an account an admin restrained. ErrRestrained = errors.New("user_restrained") )
Functions ¶
Types ¶
type AdminUser ¶
type AdminUser struct {
User
LastSeenAt string `json:"last_seen_at"`
ActiveSessionCount int64 `json:"active_session_count"`
Connected bool `json:"connected"`
}
AdminUser is an account in the admin list. Connected means it has a live session of its own (not a View as).
type Announcement ¶
type Announcement struct {
Text string `json:"text"`
Tone string `json:"tone"`
UpdatedAt string `json:"updated_at"`
}
Announcement is the banner an admin shows to every active account.
type Backoffice ¶
type Backoffice struct {
// contains filtered or unexported fields
}
Backoffice serves the back office of one app.
func (*Backoffice) AccountStats ¶
AccountStats counts the accounts at now; last seen leaves out the sessions of a View as.
func (*Backoffice) ActiveAnnouncement ¶
func (b *Backoffice) ActiveAnnouncement(ctx context.Context) (*Announcement, error)
ActiveAnnouncement returns the announcement shown now, or nil when there is none.
func (*Backoffice) Admit ¶
func (b *Backoffice) Admit(ctx context.Context, tx *sql.Tx, email string, now time.Time) (User, string, error)
Admit returns the account of a sign-in that proved email, in the transaction that creates its session:
- a new account is created: an admin when its email is a super admin's or the database has no account yet (or nobody could ever approve anyone), else the member role, active unless Config.Approval keeps it pending;
- a super admin is made an admin again at every sign-in;
- an account an admin restrained gets ErrRestrained.
joined says why the account was created, "" for an existing one. email must be normalized.
func (*Backoffice) AuditSignIn ¶
func (b *Backoffice) AuditSignIn(ctx context.Context, tx *sql.Tx, u User, joined, via, ip string) error
AuditSignIn writes signed_up first when the sign-in created the account, then signed_in, in the sign-in transaction. via names the sign-in method.
func (*Backoffice) Check ¶
func (b *Backoffice) Check(w http.ResponseWriter, r *http.Request) (Session, bool)
Check returns the session of an active account, or answers 401 (no valid session), 403 access_pending (an account waiting for an admin, who keeps the cookie) or 403 read_only_impersonation (a write while viewing the app as someone else) and returns false.
func (*Backoffice) CheckAllowPending ¶
func (b *Backoffice) CheckAllowPending(w http.ResponseWriter, r *http.Request) (Session, bool)
CheckAllowPending is Check that also lets an account waiting for an admin through: for /me and sign out.
func (*Backoffice) EndSession ¶
func (b *Backoffice) EndSession(w http.ResponseWriter, r *http.Request) (found bool, err error)
EndSession signs the request out and clears its cookie: a View as goes back to the admin's own session, any other session is revoked with a signed_out audit row. found is false when the request had no valid session.
func (*Backoffice) Features ¶ added in v0.5.0
func (b *Backoffice) Features(ctx context.Context) ([]FeatureState, error)
Features returns the features of Config.Features in their order, with their audience. An app that declares none reads nothing, so it needs no features table.
func (*Backoffice) IsSuperAdmin ¶
func (b *Backoffice) IsSuperAdmin(role, email string) bool
IsSuperAdmin reports whether an account is an admin whose email is in Config.SuperAdmins.
func (*Backoffice) ListUsers ¶
ListUsers returns every account, the pending ones first, then the newest; connectedOnly keeps the connected ones.
func (*Backoffice) Mount ¶
func (b *Backoffice) Mount(mux *http.ServeMux)
Mount serves the admin routes, each needing an admin session, and GET /api/v1/features, which needs none.
func (*Backoffice) RequireAdmin ¶
func (b *Backoffice) RequireAdmin(w http.ResponseWriter, r *http.Request) (Session, bool)
RequireAdmin is Check for the admins only: anyone else gets 403 forbidden.
type Config ¶
type Config struct {
DB *sql.DB
// Roles an admin can give, RoleAdmin included; MemberRole is the role of a new account that is not an admin.
Roles []string
MemberRole string
// SuperAdmins are the normalized emails of the app's admin flag: always admins, protected from the other
// admins, the only ones who can view the app as someone else.
SuperAdmins map[string]struct{}
// Approval makes a new account wait for an admin unless Admit lets it in; without it every account is active.
Approval bool
// Admit, with Approval, lets a new account in at once and says why ("" keeps it pending); it runs in the
// sign-in transaction.
Admit func(ctx context.Context, tx *sql.Tx, email string) (string, error)
// Actions are the app's own audit codes, listed after audit.AccountActions in the audit log filter.
Actions []string
// Sessions is the session lifetime policy the check refreshes sessions with.
Sessions sessions.SessionPolicy
// Features are the parts of the app an admin can keep to the admins, in the order the back office lists them.
Features []Feature
}
Config is what an app decides about its accounts.
type Feature ¶ added in v0.5.0
Feature is a part of the app an admin can keep to the admins; the app's UI hides it from everyone else. Name is the key the UI tests, Label what the back office shows.
type FeatureState ¶ added in v0.5.0
FeatureState is a feature with its audience: AudienceEveryone until an admin chooses.
type LoggedEvent ¶
type LoggedEvent struct {
audit.Event
Actor string `json:"actor"`
TargetLabel string `json:"target_label"`
}
LoggedEvent is an audit event with its actor and target named for a person: emails for accounts.
type Me ¶
type Me struct {
Role string `json:"role"`
AccessState string `json:"access_state"`
ImpersonatedBy string `json:"impersonated_by,omitempty"`
CanImpersonate bool `json:"can_impersonate"`
Announcement *Announcement `json:"announcement,omitempty"`
}
Me is what /me adds about access: the API access state, who views the app as this user, whether the user may View as, and the announcement to show (only to an active account).
type Session ¶
type Session struct {
ID string
UserID string
Email string
Role string
AccessState string // as stored: active or restrained
PendingSince string
// ImpersonatorID is the super admin viewing the app as this user, "" for a normal session.
ImpersonatorID string
CreatedAt string
ExpiresAt string
LastSeenAt string
}
Session is the session of a request, with its account.
type Stats ¶
type Stats struct {
Total int `json:"total"`
ByRole map[string]int `json:"by_role"`
Active24h int `json:"active_24h"`
Active7d int `json:"active_7d"`
NeverBack int `json:"never_seen_since_sign_up"`
New7d int `json:"new_7d"`
Pending int `json:"pending"`
Restrained int `json:"restrained"`
}
Stats counts the accounts: by role and access, new and active over the last day and week.
type User ¶
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Role string `json:"role"`
AccessState string `json:"access_state"` // the API value: active, restrained or pending
PendingSince string `json:"-"`
CreatedAt string `json:"created_at"`
}
User is an account as the back office sees it.