auth

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package auth holds backd's identity model: users, their sign-in identities and password handling. It is storage-neutral; MongoDB specifics live behind Store in internal/mongodb.

A user carries no credentials. Each way of signing in is an Identity (provider + subject), so adding login providers later only adds identities: a password user has a "password" identity, and a user who signs in only through another provider needs none.

Index

Constants

View Source
const (
	AuditUserCreate      = "user.create"   // by an administrator (or bootstrap)
	AuditUserSignup      = "user.signup"   // self-service sign-up
	AuditUserPassword    = "user.password" // set by an administrator
	AuditPasswordChange  = "user.password_change"
	AuditUserVerifyEmail = "user.verify_email"      // details: verified
	AuditPasswordReset   = "user.password_reset"    // by the link in an email; details: verified_address
	AuditUserPurged      = "user.purged_unverified" // details: count; never addresses
	AuditUserDisable     = "user.disable"
	AuditUserEnable      = "user.enable"
	AuditUserDelete      = "user.delete"
	AuditAccountDelete   = "user.delete_account" // self-service: the account is deactivated
	AuditUserErased      = "user.erased"         // details: job_id and counts, or needs_attention
	AuditUserNetworks    = "user.networks"       // details: admin_networks, login_networks
	AuditSessionRevoke   = "session.revoke"      // by an administrator; details: session
	AuditRoleAdd         = "role.add"            // details: role
	AuditRoleRemove      = "role.remove"         // details: role
	AuditAPIKeyCreate    = "apikey.create"       // details: role, expires_at, networks
	AuditAPIKeyRevoke    = "apikey.revoke"
	AuditEmailChanged    = "user.email_changed"         // details: by (self or admin)
	AuditEmailReverted   = "user.email_change_reverted" // the old address undid a change
	AuditInviteSent      = "invitation.sent"            // details: expires_at
	AuditInviteCreate    = "invitation.create"          // details: bound_to_email, expires_at
	AuditInviteRevoke    = "invitation.revoke"
	AuditAdminLogin      = "admin.login"   // login of a user holding an admin role
	AuditAdminRefused    = "admin.refused" // details: reason
	AuditBootstrap       = "realm.bootstrap"
	// The admin data route (documents written past the collections' rules);
	// the target is database/collection/id, never content.
	AuditDataCreate  = "data.create"
	AuditDataUpdate  = "data.update"
	AuditDataDelete  = "data.delete"
	AuditDataRestore = "data.restore"
	AuditDataPurge   = "data.purge"
	// A schema check started by an administrator; the target is the scope
	// (<database>/<collection>, <database> or the realm), details: collections, limit.
	AuditDataCheck = "data.check"
	// A job cancelled or re-run by an administrator; the target is job:<id>,
	// details: function (and, for a re-run, the new job's id).
	AuditJobCancel = "job.cancel"
	AuditJobRerun  = "job.rerun"
	// A scheduled function paused or resumed by an administrator; the target is
	// schedule:<database>/<function>.
	AuditSchedulePause  = "schedule.pause"
	AuditScheduleResume = "schedule.resume"
	// An administrator ran `backd storage check`; details: ok, provider, bucket.
	AuditStorageCheck = "storage.check"
	// An administrator ran `backd storage reconcile`; details: delete, orphans, deleted.
	AuditStorageReconcile = "storage.reconcile"
	// An administrator ran `backd files regenerate`; details: version, missing_only, job_id.
	AuditVersionsRegenerate = "files.versions.regenerate"
	AuditSecretSet          = "secret.set"        // details: database ("": realm scope); never the value
	AuditSecretDelete       = "secret.delete"     // details: database
	AuditSecretsRotated     = "secret.rotate_key" // by backd secret rotate-key; details: count
)

Audit actions: security-sensitive changes and events, recorded in the realm's append-only audit trail.

View Source
const (
	// ActorConfig applies realm.yaml's role and network seeds.
	ActorConfig = "config:realm.yaml"
	// ActorBootstrap is `backd bootstrap`.
	ActorBootstrap = "cli:bootstrap"
	// ActorSystem is backd itself, in the background (purging unverified accounts).
	ActorSystem = "system"
	// ActorAnonymous is a caller without credentials.
	ActorAnonymous = "anonymous"
)

Actors that aren't a credential.

View Source
const (
	JournalWriting   = "writing"   // the upload started; the object may be partly stored
	JournalStored    = "stored"    // the object is stored; the document doesn't reference it yet
	JournalAttaching = "attaching" // a write is attaching a pending upload to a document
	JournalAttached  = "attached"  // a document references it
	JournalFailed    = "failed"    // it never became part of a document; its object was queued for deletion
)

Journal states of an upload.

View Source
const (
	// FileJournalGrace is how long an upload may stay writing or stored before a
	// worker treats it as abandoned.
	FileJournalGrace = time.Hour

	// FileDeletionLease is how long a worker owns a deletion it claimed.
	FileDeletionLease = 5 * time.Minute
)
View Source
const (
	IdempotencyRunning = "running"
	IdempotencyDone    = "done"
)

Idempotency record statuses.

View Source
const (
	DefaultInvitationTTL = 7 * 24 * time.Hour
	MaxInvitationTTL     = 90 * 24 * time.Hour
)

Invitation lifetimes.

View Source
const (
	JobQueued  = "queued"
	JobRunning = "running"
	JobDone    = "done"
)

Job statuses.

View Source
const (
	StepRunning   = "running"
	StepDone      = "done"
	StepFailed    = "failed"
	StepTimedOut  = "timed_out"
	StepCancelled = "cancelled"
)

Step statuses.

View Source
const (
	// CheckOrigin is the origin of a schema check's job.
	CheckOrigin = "backd:schema.check"
	// CheckLock is the exclusive name of a schema check's job: one at a time.
	CheckLock = "schema-check"
	// CheckLease is how long a worker holds a check before another may claim it; the
	// worker renews it after every batch.
	CheckLease = 10 * time.Minute
	// CheckMaxDuration is the longest a check attempt runs before it stops and
	// says its report is incomplete.
	CheckMaxDuration = 6 * time.Hour
	// DefaultCheckLimit and MaxCheckLimit bound how many invalid documents a
	// report lists per collection.
	DefaultCheckLimit = 100
	MaxCheckLimit     = 1000
	// MaxCheckProblems is how many problems a report lists per document.
	MaxCheckProblems = 5
)
View Source
const APIKeyPrefix = "bdk_"

APIKeyPrefix starts every API key; see SessionTokenPrefix.

View Source
const CallbackTokenPrefix = "bdf_"

CallbackTokenPrefix starts every callback token: the credential a function uses to reach the realm's data (ctx.db, ctx.admin.db) for the duration of one invocation.

View Source
const (
	EraseOrigin = "backd:account.erase"
)

What an erase job is called in the job list and in the audit trail.

View Source
const ImageOrigin = "backd:image.versions"

ImageOrigin is the origin of the job that makes a file's image versions.

View Source
const InvitationPrefix = "bdi_"

InvitationPrefix starts every invitation token.

View Source
const MaxOpenPendingUploads = 20

MaxOpenPendingUploads is how many unused pending uploads one caller may hold.

View Source
const MaxPasswordLength = 128

MaxPasswordLength is the longest accepted password, in characters.

View Source
const MaxScopes = 32

MaxScopes bounds the grants of one key.

View Source
const MaxSteps = 100

MaxSteps is how many steps a job or an invocation record keeps: the first half and the latest half of what a function reported.

View Source
const ProviderPassword = "password"

ProviderPassword is the identity provider for email + password sign-in. Its subject is the user's id, so it doesn't depend on the email.

View Source
const ResultCancelled = "cancelled"

ResultCancelled is the result status of a job an administrator cancelled.

View Source
const ResultSkipped = "skipped"

ResultSkipped is the result status of a scheduled run that didn't happen because the function's previous run hadn't finished (overlap: skip).

View Source
const SessionTokenPrefix = "bds_"

SessionTokenPrefix starts every session token, so tokens are easy to recognize (e.g. by secret scanners) and to tell apart from API keys.

Variables

View Source
var (
	ErrKeyNameTaken = errors.New("an API key with this name already exists")
	ErrKeyNotFound  = errors.New("API key not found")
)
View Source
var (
	ErrNotFound   = errors.New("user not found")
	ErrEmailTaken = errors.New("email is already registered")
	// ErrBusy means no password-hashing slot freed up before the deadline.
	ErrBusy = errors.New("too many password operations in progress; try again later")
)
View Source
var (
	// ErrEmailChangeDisabled means the realm doesn't let users change their
	// own address (account.allow_email_change).
	ErrEmailChangeDisabled = errors.New("this realm doesn't allow changing the email address")
	// ErrSameEmail means the new address is the one the user already has.
	ErrSameEmail = errors.New("that is already the address of this account")
)
View Source
var (
	// ErrAlreadyErased means the user was erased and nothing is left to do.
	ErrAlreadyErased = errors.New("this user was already erased")
	// ErrUserErased means an operation that needs a user was asked of an
	// erased one: all that is left is a tombstone.
	ErrUserErased = errors.New("this user was erased")
)
View Source
var (
	// ErrInvitationRequired means the realm only accepts sign-ups with an invitation.
	ErrInvitationRequired = errors.New("sign-up requires an invitation")
	// ErrInvalidInvitation covers unknown, used, expired and mismatched
	// invitations alike.
	ErrInvalidInvitation = errors.New("invalid or expired invitation")
)
View Source
var (
	// ErrJobFinished means the job is done: there is nothing left to cancel.
	ErrJobFinished = errors.New("the job has already finished")
	// ErrJobNotFinished means a job can't be re-run while it may still run.
	ErrJobNotFinished = errors.New("the job hasn't finished: cancel it, or wait for it to end")
	// ErrJobNotFunction means the job isn't a function call: backd's own
	// emails and erasures are not cancelled or re-run by hand.
	ErrJobNotFunction = errors.New("only a function's job can be cancelled or re-run")
)
View Source
var (
	// ErrInvalidCredentials is the one answer to a failed login, whatever
	// the reason (unknown email, wrong password, no password, disabled).
	ErrInvalidCredentials = errors.New("invalid email or password")
	// ErrUnauthenticated means the token is unknown, expired, revoked or
	// belongs to a disabled user.
	ErrUnauthenticated = errors.New("invalid or expired token")
	// ErrSignupClosed means the realm doesn't allow self sign-up.
	ErrSignupClosed = errors.New("sign-up is not open in this realm")
	// ErrEmailNotVerified answers a login with the right password for an
	// address that isn't verified yet, in a realm that requires it. It is
	// only returned after the password was checked, so it reveals nothing
	// to someone who doesn't have it.
	ErrEmailNotVerified = errors.New("verify your email address before signing in")
)
View Source
var DefaultArgon2Params = Argon2Params{Memory: 64 * 1024, Time: 3, Threads: 1}

DefaultArgon2Params follow RFC 9106's second recommendation (64 MiB, 3 passes), with one thread per hash: concurrency is capped per instance instead.

View Source
var ErrEmailNotConfigured = errors.New("this realm doesn't send email")

ErrEmailNotConfigured means the realm has no email section: it sends no email.

View Source
var ErrInvalidCallback = errors.New("invalid or expired callback token")

ErrInvalidCallback means a callback token is malformed, forged or expired.

View Source
var ErrInvalidToken = errors.New("invalid, expired or used token")

ErrInvalidToken is every way a token can be refused (unknown, expired, used, or for another purpose); callers answer them all the same.

View Source
var ErrInvitationNeedsAddress = errors.New("an invitation sent by email needs an email address")

ErrInvitationNeedsAddress means an invitation to be sent by email has no address to send it to.

View Source
var ErrJobExclusive = errors.New("an exclusive job is not finished yet")

ErrJobExclusive means a job with the same exclusive name is not finished yet.

View Source
var ErrJobExists = errors.New("job already exists")

ErrJobExists means a job with that id was already enqueued.

View Source
var ErrNetworksOutsideRealm = errors.New("admin networks must lie within the realm's admin.allowed_networks")

ErrNetworksOutsideRealm means a user's admin networks aren't all inside the realm's admin.allowed_networks.

View Source
var ErrNotAllowedOnBehalf = errors.New("only API keys can act on behalf of a user")

ErrNotAllowedOnBehalf means a caller other than an API key tried to act on behalf of a user.

View Source
var ErrRegenerateRunning = errors.New("a regeneration of this field's versions is already queued or running")

ErrRegenerateRunning means a regeneration of the same field is queued or running.

View Source
var ErrSecretNotFound = errors.New("secret not found")

ErrSecretNotFound means no secret is stored at that scope and name.

View Source
var ErrSecretsNotConfigured = errors.New("the secrets master key is not configured (BACKD_SECRETS_KEY)")

ErrSecretsNotConfigured means BACKD_SECRETS_KEY isn't set: secrets can be neither stored nor read.

View Source
var ErrUndeclaredRole = errors.New("role is not declared in realm.yaml")

ErrUndeclaredRole means a role isn't declared in the realm's realm.yaml.

View Source
var ErrUserDisabled = errors.New("user is disabled")

ErrUserDisabled means the user an API key acts for is disabled.

Functions

func CheckPassword

func CheckPassword(password string, minLength int) (string, error)

CheckPassword applies the length policy and returns the password in Unicode NFKC form, which is what gets hashed. Lengths count characters.

func CompletionJobID added in v0.8.0

func CompletionJobID(jobID string) string

CompletionJobID is the deterministic id of the notification of a job's end.

func DefaultHashConcurrency

func DefaultHashConcurrency(p Argon2Params) int

DefaultHashConcurrency is how many password hashes may run at once when PASSWORD_HASH_CONCURRENCY isn't set: no more than the Go scheduler's processors (GOMAXPROCS, which follows the container's CPU limit), and no more than fit in half the memory limit (the cgroup's, or GOMEMLIMIT), so a flood of logins can't get the process killed for running out of memory. runtime.NumCPU is not used: in a container it reports the host's CPUs.

func ErasedEmail added in v0.4.0

func ErasedEmail(id string) string

ErasedEmail is the unique, undeliverable address a tombstone gets: the .invalid domain is reserved, so it can't receive mail, and the id makes it unique. The real address is freed for a new registration.

func HashEmailToken added in v0.4.0

func HashEmailToken(token string) string

HashEmailToken is the hash a token is stored under.

func HashToken

func HashToken(token string) string

HashToken is the stored form of a token: hex SHA-256. Tokens are random and long, so a fast hash is enough.

func HashUploadToken added in v0.8.0

func HashUploadToken(token string) string

HashUploadToken is what is stored of an upload token.

func IPKey

func IPKey(ip string) string

IPKey groups client addresses for throttling: IPv4 addresses one by one, IPv6 by /64, since one client usually controls a whole /64.

func IdempotencyID

func IdempotencyID(database, name, callerSubject, key string) string

IdempotencyID builds a record's id: unique per function, per caller (Caller.Subject(), so ownership can never cross callers), per key.

func NewSessionToken

func NewSessionToken() (string, error)

NewSessionToken returns a new random session token (256 bits).

func NewUploadToken added in v0.8.0

func NewUploadToken() (token, hash string)

NewUploadToken makes the secret a pending upload's creator holds, and the hash that is stored instead of it.

func ScheduledJobID

func ScheduledJobID(database, function string, scheduledAt time.Time) string

ScheduledJobID is the deterministic id of a scheduled run.

func SignCallback

func SignCallback(key []byte, c CallbackClaims) string

SignCallback returns a callback token for c, signed with key.

func WithAuditSource

func WithAuditSource(ctx context.Context, source func() AuditSource) context.Context

WithAuditSource returns a context whose audit records take their actor, request id and client address from source, called when a record is written (the actor may be known only after authentication).

Types

type APIKey

type APIKey struct {
	Name       string
	Role       KeyRole           // "" (stored before roles existed) counts as data
	Networks   registry.Networks // where the key may be used from; empty: anywhere
	Scopes     Scopes            // what the key reaches; empty: everything
	Prefix     string            // first characters of the key, for recognizing it
	Hash       string
	CreatedAt  time.Time
	LastUsedAt *time.Time
	ExpiresAt  *time.Time // nil: never expires
}

APIKey is a realm API key for server-side callers. Only a hash of the key is stored.

func (APIKey) Expired

func (k APIKey) Expired(now time.Time) bool

Expired reports whether the key has expired at now.

func (APIKey) IsAdmin

func (k APIKey) IsAdmin() bool

IsAdmin reports whether the key may use the admin API.

type Argon2Params

type Argon2Params struct {
	Memory  uint32 // KiB
	Time    uint32
	Threads uint8
}

Argon2Params are argon2id cost parameters.

type Attempts

type Attempts struct {
	Failures      int
	LastFailureAt time.Time
}

Attempts is the failure counter of one key ("account:<email>" or "ip:<addr>").

type AuditFilter

type AuditFilter struct {
	Action, Actor, Target string
	Since, Until          time.Time // At >= Since, At < Until
	Limit, Skip           int
}

AuditFilter selects audit records. Zero fields match everything.

type AuditRecord

type AuditRecord struct {
	ID        string
	At        time.Time
	ExpiresAt time.Time // removed after this (the realm's retention)
	Action    string
	Actor     string // "user:<id>", "key:<name>", "anonymous", or an Actor* constant
	Target    string // "user:<id>", "key:<name>", "invitation:<id>", or ""
	Details   map[string]any
	RequestID string
	ClientIP  string
}

AuditRecord is one entry of the audit trail. It names users by id, keys by name and invitations by id, and never holds secrets: no passwords or hashes, tokens, keys, emails or request bodies.

type AuditSource

type AuditSource struct {
	Actor, RequestID, ClientIP string
}

AuditSource says who is acting and from where. The HTTP layer puts it in the request context; commands set their own.

type CallbackClaims

type CallbackClaims struct {
	Realm    string    `json:"r"`
	Function string    `json:"f"` // <database>/<name>
	UserID   string    `json:"u,omitempty"`
	KeyHash  string    `json:"k,omitempty"`
	Admin    bool      `json:"a,omitempty"`
	Expires  time.Time `json:"e"`
	// Calls are the functions of the caller's database this invocation may
	// ctx.call (its function.yaml `calls`); Depth counts the nested calls
	// above it; Inv is its own invocation id, the parent of what it calls.
	Calls []string `json:"c,omitempty"`
	Depth int      `json:"d,omitempty"`
	Inv   string   `json:"i,omitempty"`
	// Job and Attempt say which job attempt this run is, so it may report its
	// steps (ctx.step): only for an async function's job.
	Job     string `json:"j,omitempty"`
	Attempt int    `json:"t,omitempty"`
	// Email says the function may send custom emails (function.yaml `email: true`).
	Email bool `json:"m,omitempty"`
}

CallbackClaims are what a callback token carries: the function, the caller it acts as (a user, an API key, both for a key acting on behalf of a user, or neither for an anonymous caller) or full access (Admin), and when it expires (the invocation's deadline).

func VerifyCallback

func VerifyCallback(key []byte, token string, now time.Time) (CallbackClaims, error)

VerifyCallback checks a callback token's signature and expiry.

type Caller

type Caller struct {
	User *Principal  // the user whose rules apply
	Key  *APIKey     // set for API key callers
	Func *FuncCaller // set for a function's callbacks
}

Caller is whoever makes a request to a realm with auth enabled: a user with a session, a server-side caller with an API key, an API key acting on behalf of a user (both set), or anonymous (neither). A function calling back (Func set) acts as the caller that invoked it, or, with admin access, as itself.

func (Caller) Actor

func (c Caller) Actor() string

Actor names the caller for logs: the subject, and for an API key acting on behalf of a user, "key:<name> as user:<id>".

func (Caller) BypassesRules

func (c Caller) BypassesRules() bool

BypassesRules reports whether the caller has full access: an API key that isn't acting on behalf of a user, or a function's admin access.

func (Caller) Subject

func (c Caller) Subject() string

Subject is who the caller acts as, for ownership fields: "user:<id>", "key:<name>" or "anonymous".

type CheckJob added in v0.8.0

type CheckJob struct {
	Collections []string // "<database>/<collection>", resolved when the check started
	Database    string   // the scope asked for: "" every database
	Collection  string   // "" every collection of Database
	Limit       int      // invalid documents listed per collection
}

CheckJob is what a schema check's job holds: what to check and how much to list.

type CheckProblem added in v0.8.0

type CheckProblem struct {
	Path   string
	Reason string
}

CheckProblem is one way a document fails its schema: a JSON path and the rule that failed, never the value.

type CheckReport added in v0.8.0

type CheckReport struct {
	Database, Collection string
	JobID                string
	StartedAt            time.Time
	FinishedAt           time.Time
	Scanned              int64
	Invalid              int64
	// Complete is false when the scan stopped before the end: StoppedBy says why
	// ("limit" when Limit invalid documents were found, "time" for CheckMaxDuration).
	Complete  bool
	StoppedBy string
	Limit     int
	// SchemaHash identifies the schema the documents were checked against.
	SchemaHash string
	Documents  []CheckedDocument // up to Limit
}

CheckReport is the latest finished schema check of one collection.

type CheckedDocument added in v0.8.0

type CheckedDocument struct {
	ID       string
	Deleted  bool // soft-deleted (in the trash)
	Problems []CheckProblem
	// MoreProblems counts the problems left out past MaxCheckProblems.
	MoreProblems int
}

CheckedDocument is a document that failed its collection's schema.

type CustomEmail added in v0.4.0

type CustomEmail struct {
	Function string // <database>/<name> of the function that asked
	To       []string
	CC       []string
	BCC      []string
	Data     map[string]any
}

CustomEmail is what a function's email job carries.

type EmailJob added in v0.4.0

type EmailJob struct {
	Kind       string
	UserID     string
	Locale     string
	RedirectTo string // checked when the request was made; bound to the token the worker creates
	// To says whose address the message goes to: "" the user's current one,
	// "pending" the address they asked to change to, "previous" the one
	// before the last change.
	To string
	// Notice marks a message that only informs: no token, no link.
	Notice bool
	// InvitationID is the invitation an invitation email is for (there is no
	// user yet): the worker takes the address from it.
	InvitationID string
	// Custom is set on an email a function asked for with ctx.email.send. Unlike
	// the account emails it stores what the function gave: the recipients'
	// addresses and the data for the template, for as long as jobs are kept.
	Custom *CustomEmail
}

EmailJob is what an email job stores: which email, for whom, and nothing that works as a credential.

type EmailLimitedError added in v0.4.0

type EmailLimitedError struct {
	RetryAfter time.Duration
	// Scope says which limit it was: "recipient", "ip", "function" or
	// "invocation".
	Scope string
}

EmailLimitedError means an email limit was reached: nothing was queued.

func (*EmailLimitedError) Error added in v0.4.0

func (e *EmailLimitedError) Error() string

type EmailRequest added in v0.4.0

type EmailRequest struct {
	Kind       string // an email kind (verify-email, ...)
	UserID     string
	Address    string // the recipient, for the per-recipient limits only (never stored readable)
	Locale     string
	RedirectTo string // already checked against the realm's allowed redirects
	ClientIP   string // who caused it, for the per-IP limit; "" when nobody did
	RequestID  string
	// To, Notice and InvitationID are the fields of the same name of EmailJob.
	To           string
	Notice       bool
	InvitationID string
	// Custom marks an email a function asked for; Invocation is the
	// invocation that asked (the parent of the job, and what the per-invocation
	// cap counts). Address is then the user's, for to_user; Custom holds the
	// addresses of the others.
	Custom     *CustomEmail
	Invocation string
	Depth      int
}

EmailRequest asks for one email to be sent to a user.

type EmailToken added in v0.4.0

type EmailToken struct {
	Hash       string // hex SHA-256 of the token; the record's id
	Purpose    string // verify-email, reset-password, change-email, revert-email-change or invitation
	UserID     string
	RedirectTo string // where the page after the link may send the user (already checked)
	// Address is the address the token acts on: the new one for change-email,
	// the previous one for revert-email-change, the invited one for an
	// invitation. Empty for the others.
	Address string
	// InvitationID is the invitation an invitation token accepts (UserID is empty then).
	InvitationID string
	CreatedAt    time.Time
	ExpiresAt    time.Time
	UsedAt       time.Time // zero until redeemed
}

EmailToken is the record of a token sent in an email link (verifying an address, resetting a password, ...). backd stores only the SHA-256 hash of the token: the token itself exists only in memory and in the message handed to the delivery function.

type EraseJob added in v0.4.0

type EraseJob struct {
	UserID string
	Email  string
	Counts map[string]int64 // "<database>/<collection>/<operation>" → documents
}

EraseJob is what an erase job carries: whose data to erase, the email to match fields that hold it (cleared when the job finishes), and what has been done so far.

An erase job is not a function: it has no executor, a worker applies the collections' policies itself.

type ErrCheckRunning added in v0.8.0

type ErrCheckRunning struct{ JobID string }

ErrCheckRunning means a schema check is already queued or running in the realm; JobID is its job.

func (*ErrCheckRunning) Error added in v0.8.0

func (e *ErrCheckRunning) Error() string

type FileDeletion added in v0.8.0

type FileDeletion struct {
	Key       string
	Reason    string // replaced, cleared, removed, abandoned…
	Attempts  int
	NotBefore time.Time // not claimed before this
	CreatedAt time.Time
}

FileDeletion is an object to delete from the bucket.

type FileDeletionStats added in v0.8.0

type FileDeletionStats struct {
	Queued   int       // objects waiting to be deleted
	Retrying int       // of them, the ones that failed at least once
	Oldest   time.Time // when the oldest was queued; zero when empty
}

FileDeletionStats describes the deletion queue.

type FileJournalEntry added in v0.8.0

type FileJournalEntry struct {
	ID         string // the file's id (fl_…)
	Database   string
	Collection string
	Field      string
	DocumentID string // "" for an upload no document references yet
	Key        string // the object's key
	Caller     string // who uploaded it, for display (user:<id>, key:<name>, func:…)
	Size       int64
	Status     string
	CreatedAt  time.Time
	UpdatedAt  time.Time
	ExpiresAt  time.Time

	// A pending upload is one made before the document that will hold it exists
	// (or without the document being touched): it is attached by naming it, with
	// its token, in a write. Its details are kept here until then.
	Pending      bool
	Direct       bool      // a direct upload: the client sends the bytes to the bucket and completes it
	TokenHash    string    // SHA-256 of the secret token the creator holds
	Owner        string    // the user who made it; "" for an anonymous caller
	CallerKey    string    // who the open-uploads limit counts it for
	PendingUntil time.Time // unused, it expires then (also set for a direct upload to a document)
	Name         string
	Type         string
	SHA256       string
	UploadedAt   time.Time
	// Image is a pending upload's image details as JSON (width, height and the state of each
	// declared version), for the document that will hold it.
	Image string

	// A multipart direct upload (a file above one signed PUT): the storage's upload id, the
	// part size and the SHA-256 of each part as declared at the start. SHA256 is then their
	// composite, which storage can verify.
	MultipartID string
	PartSize    int64
	PartSHA256  []string
}

FileJournalEntry is one upload.

type FuncCaller

type FuncCaller struct {
	Name  string // <realm>/<database>/<function>
	Admin bool   // ctx.admin.db: full access, recorded as the function
	// What the invocation behind the token may do next (see CallbackClaims).
	Calls      []string
	Depth      int
	Invocation string
	Email      bool   // ctx.email.send
	Job        string // the job attempt behind the token, if any (see CallbackClaims)
	Attempt    int
	Expires    time.Time
}

FuncCaller marks a caller as a function acting through a callback token.

func FuncCallerOf added in v0.4.0

func FuncCallerOf(c CallbackClaims) *FuncCaller

FuncCallerOf is the function behind a verified callback token.

type Hasher

type Hasher struct {
	Params Argon2Params
	// contains filtered or unexported fields
}

Hasher hashes and verifies passwords with argon2id. At most a fixed number of hashes run at once; callers queue for a slot until their context ends, which bounds memory under a flood of requests.

func NewHasher

func NewHasher(concurrency int, params Argon2Params) *Hasher

NewHasher returns a Hasher running at most concurrency hashes at once (DefaultHashConcurrency when concurrency < 1).

func (*Hasher) Concurrency

func (h *Hasher) Concurrency() int

Concurrency is how many hashes may run at once.

func (*Hasher) Hash

func (h *Hasher) Hash(ctx context.Context, password string) (string, error)

Hash returns the PHC-format argon2id hash of an already checked password.

func (*Hasher) Hashes added in v0.7.0

func (h *Hasher) Hashes() uint64

Hashes is how many passwords this Hasher has hashed (Verify doesn't count). Tests use it to show that a code path hashes, instead of timing it.

func (*Hasher) Verify

func (h *Hasher) Verify(ctx context.Context, password, encoded string) (bool, error)

Verify reports whether password matches encoded. The password is normalized like CheckPassword does. Parameters are read from encoded, so hashes made with older parameters keep working.

type IdempotencyRecord

type IdempotencyRecord struct {
	ID          string // see IdempotencyID
	Function    string // <database>/<name>, for display
	CallerActor string
	InputHash   string     // sha256 of the exact input bytes
	Mode        string     // sync | async
	Status      string     // running | done
	Result      *JobResult // nil until done; a sync call's outcome
	JobID       string     // set once done, for async; "" for sync
	CreatedAt   time.Time
	ExpiresAt   time.Time
}

IdempotencyRecord ties one Idempotency-Key to its outcome, scoped to the function and the caller: two different callers can never collide on the same client-chosen key, even if they happen to pick the same string. Never stores input or output for keyless calls — this is the one deliberate exception, for calls that declare a key.

type Identity

type Identity struct {
	ID           string
	UserID       string
	Provider     string
	Subject      string // unique per provider
	PasswordHash string // ProviderPassword only: argon2id, PHC string format
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

Identity is one way a user signs in.

type ImageJob added in v0.8.1

type ImageJob struct {
	Collection string
	Field      string
	DocumentID string
	FileID     string
	// Version, when set, makes just that version, whatever its state: a function asked
	// for it. Params are the parameters to make it with, as JSON ("" means the declared
	// ones); the job's result carries the outcome for the function waiting on it.
	Version string
	Params  string
	// Scan makes this a job over a whole field (backd files regenerate): every file
	// whose copies no longer match what the field declares is made again, for Version only
	// when it is set. MissingOnly leaves the copies that exist alone. Rate is how many files
	// a second at most (0 for the default). DocumentID and FileID are empty.
	Scan        bool
	MissingOnly bool
	Rate        int
}

ImageJob is what an image-versions job carries: the file, and where it is. The job's Database is the file's database and its Function the collection.

type InvalidLocaleError added in v0.4.0

type InvalidLocaleError struct{ Allowed []string }

InvalidLocaleError means a language the realm doesn't list was asked for.

func (*InvalidLocaleError) Error added in v0.4.0

func (e *InvalidLocaleError) Error() string

type Invitation

type Invitation struct {
	ID        string
	TokenHash string
	Email     string // "" if anyone holding the token may use it
	CreatedBy string // "key:<name>"
	CreatedAt time.Time
	ExpiresAt time.Time
}

Invitation lets one person sign up in a realm with `signup: invite`. Only a hash of its token is stored.

type InvocationFilter

type InvocationFilter struct {
	Function, RequestID string
	Since, Until        time.Time
	Limit, Skip         int
}

InvocationFilter selects invocation records. Zero fields match everything.

type InvocationRecord

type InvocationRecord struct {
	ID         string
	At         time.Time
	ExpiresAt  time.Time // removed after this (the realm's log retention)
	Function   string    // <database>/<name>
	Actor      string    // "user:<id>", "key:<name>", "anonymous", or "func:<name> as ..."
	Mode       string    // sync today; async, webhook later
	Status     string    // executor.Status* (ok, function_error, timeout, memory, cpu, crash, ...)
	Code       string    // the function's own error code, when Status is function_error
	DurationMS int64
	RequestID  string
	JobID      string // async jobs (roadmap F11); empty until then
	ParentID   string // the invocation that called this one (ctx.call); empty otherwise
	Origin     string // http, function, cron, admin or backd:<event>
	Logs       []LogLine
	// Steps are what the call reported with ctx.step and ctx.progress, closed
	// when it ended; StepsOmitted counts those dropped from the middle.
	Steps        []Step
	StepsOmitted int
}

InvocationRecord is one function call, recorded in the realm's system database (roadmap F10). Inputs and outputs are never stored — only what's needed to debug what happened and read the function's own logs.

type Job

type Job struct {
	ID             string
	Database       string
	Function       string // name only; Database plus this is the function
	Input          json.RawMessage
	CallerActor    string // for display: "user:<id>", "key:<name>", "anonymous"
	CallerUserID   string // "" unless the caller was a signed-in user
	CallerKeyHash  string // "" unless the caller was an API key
	Scheduled      bool   // created by the function's cron schedule, acts as the function itself
	ActsAsFunction bool   // run as the function itself, with full access (a scheduled function run by hand)
	Origin         string // http, function (ctx.call), cron, admin or backd:<event>
	ParentID       string // the invocation that queued it with ctx.call; empty otherwise
	RerunOf        string // the finished job an administrator re-ran to make this one; empty otherwise
	Depth          int    // nested calls above it
	// Email is set on an email job: backd's own request to send a message, which
	// a worker renders and hands to the realm's delivery function. It holds no
	// message and no token.
	Email *EmailJob
	// Erase is set on an erase job (origin backd:account.erase).
	Erase *EraseJob
	// Check is set on a schema check's job (origin CheckOrigin).
	Check *CheckJob
	// Image is set on the job that makes a file's image versions (origin ImageOrigin).
	Image *ImageJob
	// Exclusive, when set, lets only one job of that name be unfinished at a
	// time: enqueuing another fails with ErrJobExclusive.
	Exclusive     string
	TimeoutMS     int64 // the function's timeout at enqueue time, for the worker's lease
	RequestID     string
	Status        string    // queued | running | done
	Attempts      int       // times a worker claimed it
	Failures      int       // attempts that ended in a failure worth retrying
	NextAttemptAt time.Time // when a retry may be claimed; zero unless one is waiting
	CreatedAt     time.Time
	CompletedAt   time.Time // zero until done
	ExpiresAt     time.Time
	Result        *JobResult // nil until done
	// Steps are what the running (or last) attempt reported with ctx.step and
	// ctx.progress, the last being the current one; StepsOmitted counts those
	// left out of the middle when there were more than MaxSteps. A new attempt
	// starts with none.
	Steps        []Step
	StepsOmitted int
}

Job is one async function call (roadmap F11), stored in the realm's system database until it's claimed, run, and its result read or expired. Unlike sync calls, whose input and output are never stored, a job's input and result are — there's nowhere else to keep them until the caller reads them back.

type JobFilter

type JobFilter struct {
	Database, Function string    // Function is the name only; "" matches every function
	Origin             string    // exact origin, such as function:main/ship; "" matches every one
	EraseUser          string    // the erase jobs of this user
	Status             string    // queued, running or done
	Scheduled          *bool     // nil: both; true: cron runs only; false: called ones only
	Since, Until       time.Time // on created_at: from Since, before Until
	Limit, Skip        int
}

JobFilter selects jobs to list. Zero fields match everything.

type JobResult

type JobResult struct {
	Status     string
	Output     json.RawMessage
	Code       string
	Message    string
	Details    json.RawMessage
	DurationMS int64
	// HTTPStatus is the function's own chosen status (ctx.error's first
	// argument) when Status is "function_error"; unused otherwise. A
	// job never answers HTTP itself, but an idempotent sync replay
	// (roadmap F12) needs it to reconstruct the original response.
	HTTPStatus int
}

JobResult is how a job's run ended: the same vocabulary as a sync call's executor.Result (executor.Status*), flattened here so this package doesn't need to import the executor.

type KeyOptions

type KeyOptions struct {
	TTL      time.Duration     // 0: never expires
	Role     KeyRole           // "" means data
	Networks registry.Networks // where the key may be used from; empty: anywhere
	Scopes   Scopes            // what a data key reaches; empty: everything
}

KeyOptions are the settings of a new API key.

type KeyRole

type KeyRole string

KeyRole is what an API key may do.

const (
	// KeyRoleData keys reach data routes, bypassing rules, and may act on
	// behalf of a user. The default: what services need.
	KeyRoleData KeyRole = "data"
	// KeyRoleAdmin keys also reach the admin API (/_admin): users, roles,
	// invitations and API keys. For management tooling only.
	KeyRoleAdmin KeyRole = "admin"
)

func ParseKeyRole

func ParseKeyRole(s string) (KeyRole, error)

ParseKeyRole returns the role named s ("" means data).

type LogLine

type LogLine struct {
	Level string
	Line  string
}

LogLine is one line of a function's captured console output, already masked by the executor.

type PolicyError

type PolicyError struct{ Reason string }

PolicyError is a password that doesn't meet the realm's policy.

func (*PolicyError) Error

func (e *PolicyError) Error() string

type Principal

type Principal struct {
	User    User
	Session Session
}

Principal is the authenticated caller of a request.

type RoleReport

type RoleReport struct {
	// DBOnly maps emails to roles assigned only in the database, which a
	// rebuild from realm.yaml wouldn't restore.
	DBOnly map[string][]string
	// Undeclared maps emails to roles realm.yaml no longer declares.
	Undeclared map[string][]string
	// NetworksDBOnly lists users with network restrictions that realm.yaml
	// doesn't set, which a rebuild from realm.yaml wouldn't restore.
	NetworksDBOnly []string
	// Admins counts the people who can use the admin API with a session:
	// emails seeded into an admin role in realm.yaml, and users holding
	// one in the database, each counted once.
	Admins int
}

RoleReport lists role assignments that realm.yaml doesn't account for.

type ScheduleState added in v0.8.0

type ScheduleState struct {
	Database, Function string
	Paused             bool
	ChangedAt          time.Time
	ChangedBy          string // the actor that last paused or resumed it
}

ScheduleState is the runtime state of a scheduled function: whether an administrator paused it. A function with no state is running. The state is kept after a resume, because ChangedAt keeps a run that fell inside the pause from being made up at the moment of the resume.

type Scope added in v0.5.0

type Scope struct {
	Op       ScopeOp
	Database string // "" is every database
	Name     string // a collection (read, write) or a function (call); "" is all of the database
}

Scopes limit what a data API key reaches, so a leaked key exposes only what it was made for. A key without scopes keeps full access (every key made before scopes existed); a key with scopes may do exactly what they grant and nothing else. A grant is `ops` or `ops:target`:

read                  read every collection
read:blog             read the collections of the database blog
write:blog/posts      create, update and delete in blog/posts
call                  call every function
call:main             call the functions of the database main
call:main/export      call that function (and read its jobs)

write means create, update and delete; it doesn't include read. The targets are relative to the key's realm.

func ParseScope added in v0.5.0

func ParseScope(s string) (Scope, error)

ParseScope parses one grant.

func (Scope) String added in v0.5.0

func (sc Scope) String() string

type ScopeOp added in v0.5.0

type ScopeOp string

ScopeOp is the kind of access a grant gives.

const (
	ScopeRead  ScopeOp = "read"
	ScopeWrite ScopeOp = "write"
	ScopeCall  ScopeOp = "call"
)

type Scopes added in v0.5.0

type Scopes []Scope

Scopes is the list of grants of a key.

func ParseScopes added in v0.5.0

func ParseScopes(list []string) (Scopes, error)

ParseScopes parses grants; duplicates collapse, and the order is kept.

func (Scopes) Allows added in v0.5.0

func (s Scopes) Allows(op ScopeOp, database, name string) bool

Allows reports whether op on database/name is granted. A key without scopes allows everything.

func (Scopes) Limited added in v0.5.0

func (s Scopes) Limited() bool

Limited reports whether the key has scopes at all: without, it has full access.

func (Scopes) Strings added in v0.5.0

func (s Scopes) Strings() []string

Strings writes the grants as they are parsed.

type Secret

type Secret struct {
	Database   string // "": realm scope
	Name       string
	Ciphertext string // base64
	Nonce      string // base64
	KeyID      string // fingerprint of the key it was encrypted with
	CreatedAt  time.Time
	UpdatedAt  time.Time
	UpdatedBy  string
}

Secret is one encrypted value, bound to a realm (implicitly, by which Users it's stored through), a scope and a name. Database empty means the realm scope; otherwise it's the database scope.

type SecretCache

type SecretCache struct {
	// contains filtered or unexported fields
}

SecretCache holds decrypted secret values (and confirmed-missing lookups) for secretCacheTTL, shared across every realm's Users: NewSecretCache once, assigned to each Users.Cache. Each key includes the realm, so it's safe for every realm to share one instance.

func NewSecretCache

func NewSecretCache() *SecretCache

NewSecretCache returns an empty cache.

type SecretCipher

type SecretCipher struct {
	KeyID string
	// contains filtered or unexported fields
}

SecretCipher encrypts and decrypts secret values with AES-256-GCM, under a key derived from the operator's master key (never the master key's raw bytes directly, so an accidental log of the derived key alone can't be turned back into the configured secret). KeyID is a fingerprint safe to store and log: it identifies which master key a value was encrypted with, for rotation, without revealing it.

func NewSecretCipher

func NewSecretCipher(raw []byte) (*SecretCipher, error)

NewSecretCipher derives a cipher from raw master key material (any length, at least 32 characters is enforced by settings). The same raw material always derives the same cipher and KeyID.

func (*SecretCipher) Open

func (c *SecretCipher) Open(ciphertext, nonce, keyID string) (string, error)

Open decrypts a value sealed with a cipher whose KeyID matches keyID.

func (*SecretCipher) Seal

func (c *SecretCipher) Seal(plaintext string) (ciphertext, nonce string, err error)

Seal encrypts plaintext, returning base64 ciphertext and nonce.

type SecretMeta

type SecretMeta struct {
	Database  string
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
	UpdatedBy string
}

SecretMeta is a secret without its value, as listed: names, scope and who last changed it, never the value.

type Session

type Session struct {
	ID         string // public id, used to list and revoke sessions
	UserID     string
	TokenHash  string
	CreatedAt  time.Time
	LastUsedAt time.Time
	ExpiresAt  time.Time
}

Session is a signed-in session. Only a hash of its token is stored.

type SignupRequest added in v0.4.0

type SignupRequest struct {
	Email, Password, Invitation string
	Locales                     []string // most wanted first
	RedirectTo                  string   // already checked against the realm's allowed redirects
	ClientIP, RequestID         string
}

SignupRequest is a sign-up from a client.

type SignupResult added in v0.4.0

type SignupResult struct {
	Principal Principal
	Token     string
	Pending   bool
}

SignupResult is what a sign-up gives back: a session, or (Pending) none because the realm wants the address verified first.

type Step added in v0.8.0

type Step struct {
	N          int
	Name       string
	Status     string // StepRunning, StepDone, StepFailed, StepTimedOut or StepCancelled
	StartedAt  time.Time
	EndedAt    time.Time // zero while running
	DurationMS int64
	Current    float64
	Total      *float64 // nil when the step has no known size
	Message    string
	UpdatedAt  time.Time
}

Step is one named step of an attempt, with how far it got.

func CloseSteps added in v0.8.0

func CloseSteps(steps []Step, status string, at time.Time) []Step

CloseSteps ends the step still running, if any, with status and the time at. It returns steps itself when none is running.

type StorageUsage added in v0.8.0

type StorageUsage struct {
	Realm StorageUsageTotals
	Users []UserUsage
}

StorageUsage is a realm's running totals: all of it, and the users holding the most.

type StorageUsageTotals added in v0.8.0

type StorageUsageTotals struct {
	Bytes int64
	Files int64
}

StorageUsageTotals is bytes and files a scope holds.

type Store

type Store interface {
	// CreateUser inserts a user; ErrEmailTaken if the email exists.
	CreateUser(ctx context.Context, u User) error
	UserByEmail(ctx context.Context, email string) (User, error)
	// UserByID returns the user with that id; ErrNotFound if none.
	UserByID(ctx context.Context, id string) (User, error)
	ListUsers(ctx context.Context) ([]User, error)
	// ListUsersPage returns one page of users sorted by email: the users whose
	// email sorts after `after` (all when empty), skipping skip of them, at most
	// limit; hasMore says whether more follow. Emails are unique, so `after` is
	// an exact position. It never reads more than the page. A non-empty contains
	// keeps the users whose email holds it (emails are stored lower-case, so
	// pass it lower-case): a scan of the email index, not of the documents.
	ListUsersPage(ctx context.Context, contains, after string, skip, limit int) (users []User, hasMore bool, err error)
	// AddRoles adds roles the user doesn't have yet, setting UpdatedAt.
	AddRoles(ctx context.Context, userID string, roles []string, now time.Time) error
	// RemoveRole removes a role from the user, setting UpdatedAt.
	RemoveRole(ctx context.Context, userID, role string, now time.Time) error
	// UpdateUser applies upd and sets UpdatedAt to now.
	UpdateUser(ctx context.Context, id string, upd UserUpdate, now time.Time) error
	// ListUnverifiedUsers returns up to limit users whose address is still
	// unverified and who were created before the given time, oldest first.
	ListUnverifiedUsers(ctx context.Context, createdBefore time.Time, limit int) ([]User, error)
	// EraseUser turns the user into a tombstone: it deletes their sessions and
	// sign-in methods, and sets email (the unique placeholder), ErasedAt,
	// disabled and verified, with no roles, locale, networks or pending and
	// previous addresses. Repeating it is harmless.
	EraseUser(ctx context.Context, id, placeholderEmail string, now time.Time) error
	// DeleteEmailTokensOfUser removes the user's email tokens.
	DeleteEmailTokensOfUser(ctx context.Context, userID string) error
	// DeleteEmailJobsOfUser removes the email jobs addressed to the user.
	DeleteEmailJobsOfUser(ctx context.Context, userID string) error
	// AddEraseCount adds n to the erase job's count under key
	// ("<database>/<collection>/<operation>"), atomically.
	AddEraseCount(ctx context.Context, jobID, key string, n int64) error
	// ClearEraseEmail removes the email an erase job held.
	ClearEraseEmail(ctx context.Context, jobID string) error
	// ReopenJob queues a job that ended in failure again, with a fresh
	// attempt count and expiry; false when it isn't a failed job.
	ReopenJob(ctx context.Context, id string, expiresAt time.Time) (bool, error)
	// DeleteUser removes the user with their identities and sessions.
	DeleteUser(ctx context.Context, id string) error
	// PutIdentity creates the identity, or, when one with the same provider
	// and subject exists, updates its credentials and UpdatedAt (keeping its
	// ID and CreatedAt).
	PutIdentity(ctx context.Context, id Identity) error
	Identity(ctx context.Context, provider, subject string) (Identity, error)
	// DeleteSessions revokes every session of the user and says how many.
	DeleteSessions(ctx context.Context, userID string) (int64, error)

	CreateSession(ctx context.Context, s Session) error
	// SessionByTokenHash returns the session and its user; ErrNotFound if
	// either doesn't exist.
	SessionByTokenHash(ctx context.Context, hash string) (Session, User, error)
	// TouchSession records a use of the session and its new expiry.
	TouchSession(ctx context.Context, id string, lastUsed, expires time.Time) error
	// ListSessions returns the user's sessions, newest first.
	ListSessions(ctx context.Context, userID string) ([]Session, error)
	// DeleteSession revokes one session of the user; ErrNotFound if the
	// user has no session with that id.
	DeleteSession(ctx context.Context, userID, id string) error
	// DeleteOtherSessions revokes every session of the user except keepID
	// and says how many.
	DeleteOtherSessions(ctx context.Context, userID, keepID string) (int64, error)

	// CreateAPIKey stores a key; ErrKeyNameTaken if the name exists.
	CreateAPIKey(ctx context.Context, k APIKey) error
	// APIKeyByHash returns the key with that hash; ErrKeyNotFound if none.
	APIKeyByHash(ctx context.Context, hash string) (APIKey, error)
	ListAPIKeys(ctx context.Context) ([]APIKey, error)
	// DeleteAPIKey removes the named key; ErrKeyNotFound if none.
	DeleteAPIKey(ctx context.Context, name string) error
	// TouchAPIKey records a use of the key.
	TouchAPIKey(ctx context.Context, hash string, at time.Time) error

	// AppendAudit adds a record to the audit trail, which is never
	// modified: records are only removed when they expire.
	AppendAudit(ctx context.Context, rec AuditRecord) error
	// ListAudit returns records matching f, newest first, and whether
	// more follow.
	ListAudit(ctx context.Context, f AuditFilter) ([]AuditRecord, bool, error)

	CreateInvitation(ctx context.Context, inv Invitation) error
	// ClaimInvitation atomically deletes and returns the unexpired
	// invitation with that token hash; ErrNotFound if none.
	ClaimInvitation(ctx context.Context, hash string, now time.Time) (Invitation, error)
	// ClaimInvitationByID is ClaimInvitation for an invitation known by its
	// id (one sent by email: nobody holds its own token).
	ClaimInvitationByID(ctx context.Context, id string, now time.Time) (Invitation, error)
	// ListInvitations returns all invitations, newest first.
	ListInvitations(ctx context.Context) ([]Invitation, error)
	// DeleteInvitation removes an invitation by id; ErrNotFound if none.
	DeleteInvitation(ctx context.Context, id string) error

	// LoginAttempts returns the failure counter of key (zero if none).
	LoginAttempts(ctx context.Context, key string) (Attempts, error)
	// RecordLoginFailure increments key's counter atomically, setting its
	// last failure to at; the counter is deleted at expires.
	RecordLoginFailure(ctx context.Context, key string, at, expires time.Time) error
	// ClearLoginAttempts deletes key's counter.
	ClearLoginAttempts(ctx context.Context, key string) error
	// AcquireLoginLock takes the lock of key for owner until the given time,
	// atomically: it is free when nobody holds it or its lease ended at or
	// before now. false means another owner holds it. The lock serializes the
	// password checks of one account across every instance.
	AcquireLoginLock(ctx context.Context, key, owner string, now, until time.Time) (bool, error)
	// ReleaseLoginLock frees the lock if owner still holds it.
	ReleaseLoginLock(ctx context.Context, key, owner string) error

	// IncrementCounter atomically increments key's counter (creating it,
	// or restarting it at 1 if its window has already ended) and returns
	// the new count and when it resets. The same generic "N events per
	// window" primitive login throttling uses, reused for function rate
	// limits (roadmap F9), which is why it lives in the same collection.
	IncrementCounter(ctx context.Context, key string, at, expires time.Time) (count int, resetAt time.Time, err error)

	// RecordInvocation appends a function call record (roadmap F10).
	// backd never updates or deletes them; MongoDB removes them when
	// they expire.
	RecordInvocation(ctx context.Context, r InvocationRecord) error
	// ListInvocations returns records matching f, newest first, and
	// whether more follow.
	ListInvocations(ctx context.Context, f InvocationFilter) ([]InvocationRecord, bool, error)

	// EnqueueJob stores a new async job (roadmap F11), queued for a worker.
	EnqueueJob(ctx context.Context, j Job) error
	// ClaimJob atomically claims the oldest job that's never been claimed,
	// or whose lease has expired, leasing it to workerID until at plus
	// the job's own timeout plus margin. found is false when there is
	// none to claim.
	ClaimJob(ctx context.Context, workerID string, at time.Time, margin time.Duration) (j Job, found bool, err error)
	// RetryJob counts a failed attempt and queues the job again, not to be
	// claimed before notBefore (it is still the same job: same id, same input).
	RetryJob(ctx context.Context, id string, notBefore time.Time) error
	// CompleteJob records a job's result; expiresAt starts its retention
	// countdown (MongoDB removes it once past).
	CompleteJob(ctx context.Context, id string, result JobResult, completedAt, expiresAt time.Time) error
	// CancelJob ends a job that isn't done yet with the given result (status
	// "cancelled"), clearing its lease. It reports whether it did: false when
	// the job was already done (or is unknown).
	CancelJob(ctx context.Context, id string, result JobResult, steps []Step, completedAt, expiresAt time.Time) (bool, error)
	// ListJobs returns the jobs matching f, newest first, and whether
	// more follow.
	ListJobs(ctx context.Context, f JobFilter) ([]Job, bool, error)
	// JournalFile records an upload; SetFileJournalStatus moves it to a new status
	// (with the document that references it, if any) and sets when the record
	// expires; StaleFileJournal lists uploads still writing or stored that were
	// last updated before the given time.
	JournalFile(ctx context.Context, e FileJournalEntry) error
	SetFileJournalStatus(ctx context.Context, id, status, documentID string, at, expiresAt time.Time) error
	StaleFileJournal(ctx context.Context, before, now time.Time, limit int) ([]FileJournalEntry, error)
	// FileJournalEntry returns one upload's record (ErrNotFound); the others are
	// the bookkeeping of pending uploads: completing one, claiming it once by its
	// token hash (stored → attaching, ErrNotFound when it can't be taken), giving
	// a claim back, and counting a caller's open ones.
	FileJournalEntry(ctx context.Context, id string) (FileJournalEntry, error)
	CompletePendingUpload(ctx context.Context, id, name, contentType, sha256sum string, size int64, uploadedAt time.Time, image string, at time.Time) error
	ClaimPendingUpload(ctx context.Context, id, tokenHash, documentID string, now time.Time) (FileJournalEntry, error)
	// ClaimDirectUpload is the same for a direct upload waiting for its bytes (writing →
	// attaching); ReleaseUpload moves a claimed upload back to the given status.
	ClaimDirectUpload(ctx context.Context, id, tokenHash string, now time.Time) (FileJournalEntry, error)
	ReleaseUpload(ctx context.Context, id, status string, at time.Time) error
	CountOpenPendingUploads(ctx context.Context, callerKey string, now time.Time) (int, error)
	// QueueFileDeletion adds an object to delete (one per key); ClaimFileDeletions
	// returns up to limit due ones and holds them until lease; CompleteFileDeletion
	// removes one; RetryFileDeletion counts a failed attempt and holds it until
	// notBefore.
	QueueFileDeletion(ctx context.Context, d FileDeletion) error
	// AddStorageUsage adds to the realm's running totals and, for a non-empty owner,
	// the user's; StorageUsage reads them; FileDeletionStats describes the queue.
	AddStorageUsage(ctx context.Context, owner string, bytes, files int64) error
	StorageUsage(ctx context.Context, limit int) (StorageUsage, error)
	// StorageUsageOf returns the realm's totals and one user's (zero for an owner of "").
	StorageUsageOf(ctx context.Context, owner string) (realm, user StorageUsageTotals, err error)
	FileDeletionStats(ctx context.Context) (FileDeletionStats, error)
	ClaimFileDeletions(ctx context.Context, now, lease time.Time, limit int) ([]FileDeletion, error)
	CompleteFileDeletion(ctx context.Context, key string) error
	RetryFileDeletion(ctx context.Context, key string, notBefore time.Time) error
	// RenewJobLease moves the lease of a running job's attempt to until; false
	// when the job no longer runs that attempt.
	RenewJobLease(ctx context.Context, id string, attempt int, until time.Time) (bool, error)
	// SetCheckReport stores a collection's latest schema check report, replacing
	// the previous one; ListCheckReports and GetCheckReport read them (documents
	// false leaves the lists of invalid documents out).
	SetCheckReport(ctx context.Context, r CheckReport) error
	ListCheckReports(ctx context.Context, documents bool) ([]CheckReport, error)
	GetCheckReport(ctx context.Context, database, collection string) (CheckReport, bool, error)
	// ImageDeclarations returns, by "<database>/<collection>.<field>/<version>", the
	// fingerprint of each declared image version as it was when a worker last looked;
	// SetImageDeclaration records one.
	ImageDeclarations(ctx context.Context) (map[string]string, error)
	SetImageDeclaration(ctx context.Context, key, fingerprint string) error
	// SetJobSteps replaces the steps of a running job's attempt; false when the
	// job is not running that attempt (so a late write changes nothing).
	SetJobSteps(ctx context.Context, id string, attempt int, steps []Step, omitted int) (bool, error)
	// GetJob returns one job by id; found is false if there is none.
	GetJob(ctx context.Context, id string) (j Job, found bool, err error)

	// CreateEmailToken stores a token's hash with its purpose and user.
	CreateEmailToken(ctx context.Context, t EmailToken) error
	// GetEmailToken returns the token with this hash whatever its state, or
	// ErrInvalidToken when there is none.
	GetEmailToken(ctx context.Context, hash string) (EmailToken, error)
	// RedeemEmailToken atomically marks the token with this hash and purpose
	// used, if it is unused and unexpired at now, and returns it; otherwise
	// ErrInvalidToken.
	RedeemEmailToken(ctx context.Context, hash, purpose string, now time.Time) (EmailToken, error)
	// InvalidateEmailTokens marks used every unused token of the user and
	// purpose.
	InvalidateEmailTokens(ctx context.Context, userID, purpose string, now time.Time) error

	// ClaimIdempotency (roadmap F12) atomically inserts rec if its id
	// isn't already used, or returns the existing record (running or
	// done) if it is. claimed is false in the second case.
	ClaimIdempotency(ctx context.Context, rec IdempotencyRecord) (existing IdempotencyRecord, claimed bool, err error)
	// CompleteIdempotency stores a claimed key's outcome: result for a
	// sync call, jobID for async (mutually exclusive). expiresAt starts
	// its retention countdown.
	CompleteIdempotency(ctx context.Context, id string, result *JobResult, jobID string, completedAt, expiresAt time.Time) error
	// ReleaseIdempotency removes a claim that shouldn't be remembered
	// (an infrastructure failure, not a real outcome), so a retry can
	// go through cleanly instead of being stuck "running" forever.
	ReleaseIdempotency(ctx context.Context, id string) error

	// SetScheduleState stores a scheduled function's state, replacing any.
	SetScheduleState(ctx context.Context, st ScheduleState) error
	// ListScheduleStates returns every stored state.
	ListScheduleStates(ctx context.Context) ([]ScheduleState, error)
	// UpsertSecret creates or replaces the secret at (database, name),
	// keeping its original CreatedAt on an update.
	UpsertSecret(ctx context.Context, s Secret) error
	// SecretByScope returns one secret; ErrSecretNotFound if none.
	SecretByScope(ctx context.Context, database, name string) (Secret, error)
	// ListSecrets returns every secret's metadata, sorted by database
	// (realm scope, "", first) then name.
	ListSecrets(ctx context.Context) ([]Secret, error)
	// DeleteSecret removes one; ErrSecretNotFound if none.
	DeleteSecret(ctx context.Context, database, name string) error
}

Store persists users, identities and sessions of one realm.

type ThrottledError

type ThrottledError struct{ RetryAfter time.Duration }

ThrottledError means the caller must wait before trying again.

func (*ThrottledError) Error

func (e *ThrottledError) Error() string

type User

type User struct {
	ID            string
	Email         string // normalized: trimmed, lowercased
	EmailVerified bool
	// PendingEmail is an address the user asked to change to and hasn't
	// confirmed yet; PreviousEmail is the one before the last change, kept
	// so the old address can be told and can undo it.
	PendingEmail  string
	PreviousEmail string
	// ErasedAt is when an administrator erased the user: what is left is a
	// tombstone, the id with an anonymized email and no way to sign in.
	ErasedAt time.Time
	Roles    []string
	Disabled bool
	// Locale is the user's language: one the realm lists (see
	// RealmSettings.Languages). Empty for users from before languages existed;
	// those use the realm's default.
	Locale string
	// AdminNetworks restrict the user's admin API requests; LoginNetworks
	// their login and every request with their session. Empty: no
	// restriction beyond the realm's.
	AdminNetworks registry.Networks
	LoginNetworks registry.Networks
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

User is a realm user. It holds only identity data; profiles belong in application collections.

type UserUpdate

type UserUpdate struct {
	// Email changes the address (ErrEmailTaken when another user has it).
	Email         *string
	PendingEmail  *string // set; an empty string clears it
	PreviousEmail *string // set; an empty string clears it
	EmailVerified *bool
	Disabled      *bool
	Locale        *string
	AdminNetworks *registry.Networks // set (empty clears)
	LoginNetworks *registry.Networks // set (empty clears)
}

UserUpdate changes the set fields of a user.

type UserUsage added in v0.8.0

type UserUsage struct {
	UserID string
	StorageUsageTotals
}

UserUsage is what the files of the documents a user owns add up to.

type Users

type Users struct {
	Store    Store
	Hasher   *Hasher
	Settings registry.RealmSettings
	Now      func() time.Time // defaults to time.Now
	Realm    string           // the realm's name, for logs
	Log      *slog.Logger     // audit records are also logged here; nil: not logged

	// Cipher encrypts and decrypts secrets (roadmap F5); nil when
	// BACKD_SECRETS_KEY isn't configured, in which case every secret
	// resolves as missing. Cache holds decrypted values briefly, shared
	// across every realm's Users (NewSecretCache); nil disables caching.
	Cipher *SecretCipher
	Cache  *SecretCache

	// Metrics counts sessions and limits; nil turns it off.
	Metrics *metrics.Metrics
	// contains filtered or unexported fields
}

Users manages the users of one realm.

func (*Users) AbandonedUploads added in v0.8.0

func (s *Users) AbandonedUploads(ctx context.Context, limit int) ([]FileJournalEntry, error)

AbandonedUploads returns uploads left writing, stored or attaching for longer than the grace period, and pending uploads nobody used before they expired.

func (*Users) AcceptInvitation added in v0.4.0

func (s *Users) AcceptInvitation(ctx context.Context, token, password, locale string) (EmailToken, error)

AcceptInvitation uses the link of an emailed invitation: it creates the account with the chosen password and the invited address, already verified (only its owner received the link). It starts no session. The password is checked first, so a refused one leaves the link usable. Every way the link can be wrong, or the address being registered meanwhile, is ErrInvalidToken.

func (*Users) AddRole

func (s *Users) AddRole(ctx context.Context, email, role string) error

AddRole assigns a declared role to a user.

func (*Users) ApplyRoleSeeds

func (s *Users) ApplyRoleSeeds(ctx context.Context) (int, error)

ApplyRoleSeeds adds the roles realm.yaml assigns to existing users. It never removes roles. Users who don't exist yet get theirs when created. It returns how many users were updated.

func (*Users) Audit

func (s *Users) Audit(ctx context.Context, action, target string, details map[string]any)

Audit appends a record to the realm's audit trail and writes it to the log. A record that can't be stored is logged as an error; the action it describes has already happened, so it isn't undone.

func (*Users) AuditAs

func (s *Users) AuditAs(ctx context.Context, actor, action, target string, details map[string]any)

AuditAs is Audit with an explicit actor (when not empty), for events whose actor isn't the caller yet, such as a login.

func (*Users) AuditTrail

func (s *Users) AuditTrail(ctx context.Context, f AuditFilter) ([]AuditRecord, bool, error)

AuditTrail returns records matching f, newest first, and whether more follow.

func (*Users) Authenticate

func (s *Users) Authenticate(ctx context.Context, token string) (Principal, error)

Authenticate resolves a session token to its user. It extends the session's idle timeout, writing at most once per minute.

func (*Users) AuthenticateKey

func (s *Users) AuthenticateKey(ctx context.Context, key string) (APIKey, error)

AuthenticateKey resolves an API key. It records the use at most once a minute.

func (*Users) CallbackCaller

func (s *Users) CallbackCaller(ctx context.Context, c CallbackClaims) (Caller, error)

CallbackCaller resolves a verified token into the caller it acts as, checking the user and key still exist and may act: a user disabled or a key revoked during the invocation loses access at once.

func (*Users) CancelJob added in v0.8.0

func (s *Users) CancelJob(ctx context.Context, id string) (Job, error)

CancelJob ends a queued or running function job: it is done, with the result "cancelled", at once. A worker running it notices and stops the run (what the function already did stays done). ErrJobFinished if it ended first, ErrNotFound if there is no such job.

func (*Users) ChangeEmailAsAdmin added in v0.4.0

func (s *Users) ChangeEmailAsAdmin(ctx context.Context, userID, newAddress string) (User, error)

ChangeEmailAsAdmin changes a user's address at once, as an administrator vouching for it: the new address counts as verified, every session of the user ends, the old address gets the message with the link to undo it and the new one is told. It needs the realm to send email (the notifications).

func (*Users) ChangePassword

func (s *Users) ChangePassword(ctx context.Context, p Principal, current, next string) error

ChangePassword replaces the caller's password after checking the current one, and ends all of their other sessions.

func (*Users) CheckReport added in v0.8.0

func (s *Users) CheckReport(ctx context.Context, database, collection string) (CheckReport, error)

CheckReport returns one collection's latest report; ErrNotFound if it was never checked.

func (*Users) CheckReports added in v0.8.0

func (s *Users) CheckReports(ctx context.Context, documents bool) ([]CheckReport, error)

CheckReports returns the latest report of every collection checked, sorted by database and collection. With documents false the lists of invalid documents are left out.

func (*Users) ClaimDirectUpload added in v0.8.0

func (s *Users) ClaimDirectUpload(ctx context.Context, id, token string) (FileJournalEntry, error)

ClaimDirectUpload takes a direct upload to verify and complete it, once: held as attaching until it is done or released. ErrNotFound when the token doesn't match, the upload isn't one that waits for its bytes, or it expired.

func (*Users) ClaimFileDeletions added in v0.8.0

func (s *Users) ClaimFileDeletions(ctx context.Context, limit int) ([]FileDeletion, error)

ClaimFileDeletions returns up to limit deletions that are due, holding them for FileDeletionLease so no other worker takes them meanwhile.

func (*Users) ClaimIdempotency

func (s *Users) ClaimIdempotency(ctx context.Context, rec IdempotencyRecord) (IdempotencyRecord, bool, error)

ClaimIdempotency atomically claims a key for a new call, or returns the existing record (running or done) if one already exists.

func (*Users) ClaimJob

func (s *Users) ClaimJob(ctx context.Context, workerID string) (Job, bool, error)

ClaimJob atomically claims the oldest claimable job: never claimed, or whose lease has expired (a dead worker's jobs are picked up again), leasing it to workerID for the function's own timeout plus a margin.

func (*Users) ClaimPendingUpload added in v0.8.0

func (s *Users) ClaimPendingUpload(ctx context.Context, id, token, documentID string) (FileJournalEntry, error)

ClaimPendingUpload takes a completed, unexpired pending upload whose token matches, once: it is held as attaching to the document until it is attached or released. ErrNotFound when there is none to take.

func (*Users) Clock added in v0.4.0

func (s *Users) Clock() time.Time

Clock is the service's current time (its Now, or the real one).

func (*Users) CompleteIdempotency

func (s *Users) CompleteIdempotency(ctx context.Context, id string, result *JobResult, jobID string) error

CompleteIdempotency stores a claimed key's outcome.

func (*Users) CompleteJob

func (s *Users) CompleteJob(ctx context.Context, id string, result JobResult) error

CompleteJob records a job's result and starts its retention countdown.

func (*Users) CompletePendingUpload added in v0.8.0

func (s *Users) CompletePendingUpload(ctx context.Context, id, name, contentType, sha256sum string, size int64, uploadedAt time.Time, image string) error

CompletePendingUpload records what was stored: the upload is now ready to be attached.

func (*Users) ConfirmEmailChange added in v0.4.0

func (s *Users) ConfirmEmailChange(ctx context.Context, token string) (EmailToken, error)

ConfirmEmailChange uses the link sent to the new address: the address changes (and counts as verified: the owner just read that mailbox), every session of the user ends, and the old address is told, with a link to undo it. Every way the token can be wrong, or the address having been taken meanwhile, is ErrInvalidToken.

func (*Users) Create

func (s *Users) Create(ctx context.Context, email string, password *string) (User, error)

Create adds a user, as an administrator, with the address already verified. With password nil the user has no password identity and can't sign in with a password until one is set.

func (*Users) CreateAPIKey

func (s *Users) CreateAPIKey(ctx context.Context, name string, opts KeyOptions) (APIKey, string, error)

CreateAPIKey creates a key named name with opts. The key itself is returned only here.

func (*Users) CreateInvitation

func (s *Users) CreateInvitation(ctx context.Context, email string, ttl time.Duration, createdBy string) (Invitation, string, error)

CreateInvitation creates an invitation valid for ttl (0: the default), optionally bound to email. The token is returned only here.

func (*Users) DeactivateAccount added in v0.4.0

func (s *Users) DeactivateAccount(ctx context.Context, p Principal, password string) error

DeactivateAccount handles a user deleting their own account: they must give their password, and the account is disabled and its sessions end. Nothing is erased: that is an administrator's action (Erase).

func (*Users) Delete

func (s *Users) Delete(ctx context.Context, email string) error

Delete removes the user, their identities and sessions. Documents they own are kept.

func (*Users) DeleteSecret

func (s *Users) DeleteSecret(ctx context.Context, database, name string) error

DeleteSecret removes the value at scope and name; ErrSecretNotFound if none. Functions that declare it start answering secret_missing again.

func (*Users) EnqueueCompletionJob added in v0.8.0

func (s *Users) EnqueueCompletionJob(ctx context.Context, finished Job, target string, timeoutMS int64, input json.RawMessage) (Job, bool, error)

EnqueueCompletionJob queues the notification of a job's end (on_complete): a job of function target in the same database, with input as its input, run with no caller (it has whatever its own function.yaml gives it). Its id comes from the finished job, so a second attempt to queue it for the same job does nothing; it returns false then. timeoutMS is the target's timeout.

func (*Users) EnqueueImageJob added in v0.8.1

func (s *Users) EnqueueImageJob(ctx context.Context, database string, e ImageJob, requestID string) error

EnqueueImageJob queues the making of a file's pending versions. A file has at most one such job at a time: asking again while one is queued or running is not an error.

func (*Users) EnqueueImageOp added in v0.8.1

func (s *Users) EnqueueImageOp(ctx context.Context, database string, e ImageJob, requestID string) (Job, error)

EnqueueImageOp queues the making of one version for a function that waits for it. It returns ErrJobExclusive while another job of the file is queued or running: the caller waits and asks again.

func (*Users) EnqueueJob

func (s *Users) EnqueueJob(ctx context.Context, j Job) (Job, error)

EnqueueJob stores a new job, queued for a worker to claim, and returns it with its id and timestamps filled in.

func (*Users) EnqueueScheduledJob

func (s *Users) EnqueueScheduledJob(ctx context.Context, j Job, scheduledAt time.Time, skipOverlap bool) (Job, bool, error)

EnqueueScheduledJob enqueues the run of a scheduled function for scheduledAt, under an id derived from the function and that time, so however many workers try, exactly one job is created (roadmap F18). It returns the job, and false when the run already exists.

With skipOverlap, a function that still has an unfinished job (queued, waiting for a retry, or running) doesn't get a new run: the scheduled time is recorded as a job that is done at once with the result "skipped", so the job list shows it and no worker claims it.

func (*Users) Erase added in v0.4.0

func (s *Users) Erase(ctx context.Context, id, requestID string) (Job, error)

Erase starts erasing a user, as an administrator: it queues the erase job (which holds the id and the email the policies match by), then turns the user into a tombstone and deletes their email tokens and email jobs at once. A worker applies the collections' policies. Repeating it for a user whose erase job failed queues that job again; for one whose job is still going it returns the job; otherwise it is ErrAlreadyErased.

func (*Users) EraseJobFor added in v0.4.0

func (s *Users) EraseJobFor(ctx context.Context, userID string) (Job, bool, error)

EraseJobFor returns the latest erase job of a user.

func (*Users) FileAdded added in v0.8.0

func (s *Users) FileAdded(ctx context.Context, owner string, size int64) error

FileAdded counts a file a document now references, for the realm and, when the document has an owner, for them.

func (*Users) FileDeletionStats added in v0.8.0

func (s *Users) FileDeletionStats(ctx context.Context) (FileDeletionStats, error)

FileDeletionStats describes what waits to be deleted.

func (*Users) FileRemoved added in v0.8.0

func (s *Users) FileRemoved(ctx context.Context, owner string, size int64) error

FileRemoved counts a file no document references any more.

func (*Users) Find

func (s *Users) Find(ctx context.Context, email string) (User, error)

Find returns the user with that email, or ErrNotFound.

func (*Users) FinishFileDeletion added in v0.8.0

func (s *Users) FinishFileDeletion(ctx context.Context, key string) error

FinishFileDeletion removes a deletion that was carried out.

func (*Users) GetJob

func (s *Users) GetJob(ctx context.Context, id string) (Job, bool, error)

GetJob returns a job by id, for its caller to poll its status.

func (*Users) Identify

func (s *Users) Identify(ctx context.Context, credential string) (Caller, error)

Identify resolves a bearer credential (a session token or an API key) to its caller. An empty credential is the anonymous caller.

func (*Users) ImageDeclarations added in v0.8.1

func (s *Users) ImageDeclarations(ctx context.Context) (map[string]string, error)

ImageDeclarations returns the fingerprints recorded for the declared image versions.

func (*Users) InvitationByID added in v0.4.0

func (s *Users) InvitationByID(ctx context.Context, id string) (Invitation, bool, error)

InvitationByID returns an unexpired invitation by id.

func (*Users) Invitations

func (s *Users) Invitations(ctx context.Context) ([]Invitation, error)

Invitations lists the realm's unexpired invitations, newest first.

func (*Users) Invocations

func (s *Users) Invocations(ctx context.Context, f InvocationFilter) ([]InvocationRecord, bool, error)

Invocations returns invocation records matching f, newest first, and whether more follow.

func (*Users) IsSeeded

func (s *Users) IsSeeded(email, role string) bool

IsSeeded reports whether realm.yaml assigns role to email.

func (*Users) Jobs

func (s *Users) Jobs(ctx context.Context, f JobFilter) ([]Job, bool, error)

Jobs returns the jobs matching f, newest first, and whether more follow.

func (*Users) JournalDirectUpload added in v0.8.0

func (s *Users) JournalDirectUpload(ctx context.Context, e FileJournalEntry, ttl time.Duration) error

JournalDirectUpload records a direct upload that is starting: the client will send the bytes to the bucket and complete it before it expires after ttl. A pending one (e.Pending) is attached later by a write; the others belong to a document.

func (*Users) JournalPendingUpload added in v0.8.0

func (s *Users) JournalPendingUpload(ctx context.Context, e FileJournalEntry, ttl time.Duration) error

JournalPendingUpload records a pending upload that is starting; unused, it expires after ttl.

func (*Users) JournalUpload added in v0.8.0

func (s *Users) JournalUpload(ctx context.Context, e FileJournalEntry) error

JournalUpload records an upload that is starting.

func (*Users) List

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

List returns every user of the realm.

func (*Users) ListAPIKeys

func (s *Users) ListAPIKeys(ctx context.Context) ([]APIKey, error)

ListAPIKeys returns the realm's API keys, sorted by name.

func (*Users) ListPage added in v0.5.0

func (s *Users) ListPage(ctx context.Context, contains, after string, skip, limit int) ([]User, bool, error)

ListPage is List for one page, sorted by email; see Store.ListUsersPage.

func (*Users) ListSecrets

func (s *Users) ListSecrets(ctx context.Context) ([]SecretMeta, error)

ListSecrets returns every secret's metadata (never a value), sorted by database then name (realm-scope secrets, database "", first).

func (*Users) LocaleOf added in v0.4.0

func (s *Users) LocaleOf(u User) string

LocaleOf is the language of a user: theirs, or the realm's default for users from before languages existed.

func (*Users) Login

func (s *Users) Login(ctx context.Context, email, password, ip string) (Principal, string, error)

Login checks an email and password and starts a session. Every failure returns ErrInvalidCredentials after the same amount of hashing work, so neither the answer nor its timing reveals whether the email exists. Repeated failures for the email or from the client ip (may be empty) are throttled with a *ThrottledError.

func (*Users) Logout

func (s *Users) Logout(ctx context.Context, p Principal) error

Logout ends the caller's session.

func (*Users) LogoutAll

func (s *Users) LogoutAll(ctx context.Context, p Principal) error

LogoutAll ends every session of the caller, including the current one.

func (*Users) MaxSessionExpiry added in v0.5.0

func (s *Users) MaxSessionExpiry(u User, created time.Time) time.Time

MaxSessionExpiry is the latest a session started at created can last for this user: the absolute limit, whatever the idle timeout slides to.

func (*Users) NetworksInConfig

func (s *Users) NetworksInConfig(email string) bool

NetworksInConfig reports whether realm.yaml sets network restrictions for email; seeding would then overwrite changes made elsewhere.

func (*Users) NewEmailToken added in v0.4.0

func (s *Users) NewEmailToken(ctx context.Context, purpose, userID, redirectTo string, ttl time.Duration) (token string, expiresAt time.Time, err error)

NewEmailToken creates a token of 256 random bits for userID and purpose, valid for ttl, stores its hash, and returns the token itself. Whoever receives it must not store it.

func (*Users) NewEmailTokenFor added in v0.4.0

func (s *Users) NewEmailTokenFor(ctx context.Context, t EmailToken, ttl time.Duration) (token string, expiresAt time.Time, err error)

NewEmailTokenFor is NewEmailToken for a token that also carries an address or an invitation: t's Hash, CreatedAt and ExpiresAt are filled in.

func (*Users) OnBehalfOf

func (s *Users) OnBehalfOf(ctx context.Context, c Caller, userID string) (Caller, error)

OnBehalfOf makes an API key caller act as the user with id: that user's rules and ownership apply. It returns ErrNotFound for unknown users.

func (*Users) OpenPendingUploads added in v0.8.0

func (s *Users) OpenPendingUploads(ctx context.Context, callerKey string) (int, error)

OpenPendingUploads counts the unused pending uploads a caller holds.

func (*Users) PeekEmailToken added in v0.4.0

func (s *Users) PeekEmailToken(ctx context.Context, token, purpose string) (EmailToken, error)

PeekEmailToken checks a token for purpose without using it, so a page can show its form before the person acts. Every failure is ErrInvalidToken.

func (*Users) PendingUpload added in v0.8.0

func (s *Users) PendingUpload(ctx context.Context, id string) (FileJournalEntry, error)

PendingUpload returns a pending upload's record, or ErrNotFound.

func (*Users) PurgeUnverified added in v0.4.0

func (s *Users) PurgeUnverified(ctx context.Context) (int, error)

PurgeUnverified deletes accounts that never verified their address and are older than account.purge_unverified_after, and returns how many. Accounts with roles are never purged. Documents the accounts created stay, as with any deletion of a user.

func (*Users) QueueEmail added in v0.4.0

func (s *Users) QueueEmail(ctx context.Context, r EmailRequest) (Job, error)

QueueEmail applies the limits and queues an email job. The job holds the kind, the user, the locale and the redirect: no address, no message and no token. A worker creates the token, renders the message and calls the delivery function. It returns ErrEmailNotConfigured in a realm without email, and *EmailLimitedError when a limit is reached: callers without a signed-in user skip the send silently, so the answer reveals nothing; callers with one answer 429. Without a UserID nothing is queued, but the limits are counted all the same, so a request about an address that has no account does the same work as one that has.

func (*Users) QueueFileDeletion added in v0.8.0

func (s *Users) QueueFileDeletion(ctx context.Context, key, reason string) error

QueueFileDeletion queues an object for deletion; queueing the same key twice is one deletion.

func (*Users) RateLimit

func (s *Users) RateLimit(ctx context.Context, key string, limit int, window time.Duration) error

RateLimit increments key's counter and returns *ThrottledError if the resulting count is over limit within window: shared across every backd instance (roadmap F9), unlike concurrency limits (F8), because the counter lives in Store, not in process memory. Reuses the same "N events per window" primitive as login throttling (Store's IncrementCounter), under its own key namespace so the two never collide.

func (*Users) RecordInvocation

func (s *Users) RecordInvocation(ctx context.Context, rec InvocationRecord)

RecordInvocation stores one function call. A record that can't be stored is logged as an error; the call already happened and isn't undone by a failed write, the same philosophy as Audit.

func (*Users) RedeemEmailToken added in v0.4.0

func (s *Users) RedeemEmailToken(ctx context.Context, token, purpose string) (EmailToken, error)

RedeemEmailToken uses a token for purpose: it hashes it, finds it, checks purpose, expiry and use, and marks it used in one atomic step, so of two parallel redemptions exactly one succeeds. A success also invalidates the user's other outstanding tokens of the same purpose. Every failure is ErrInvalidToken.

func (*Users) ReleaseDirectUpload added in v0.8.0

func (s *Users) ReleaseDirectUpload(ctx context.Context, id string) error

ReleaseDirectUpload gives a claimed direct upload back, to wait for its bytes again.

func (*Users) ReleaseIdempotency

func (s *Users) ReleaseIdempotency(ctx context.Context, id string) error

ReleaseIdempotency removes a claim that shouldn't be remembered (the executor was unreachable or busy — nothing about the call's own outcome to replay), so a retry can go through cleanly.

func (*Users) ReleasePendingUpload added in v0.8.0

func (s *Users) ReleasePendingUpload(ctx context.Context, id string) error

ReleasePendingUpload gives a claimed pending upload back, when the write that claimed it didn't happen.

func (*Users) RemoveRole

func (s *Users) RemoveRole(ctx context.Context, email, role string) error

RemoveRole takes a role away from a user. An assignment seeded in realm.yaml comes back at the next seeding.

func (*Users) RenewJobLease added in v0.8.0

func (s *Users) RenewJobLease(ctx context.Context, id string, attempt int, d time.Duration) (bool, error)

RenewJobLease keeps a running job's lease for another d, for the worker's attempt; false when it no longer runs that attempt.

func (*Users) ReportRoles

func (s *Users) ReportRoles(ctx context.Context) (RoleReport, error)

ReportRoles compares the stored assignments with realm.yaml.

func (*Users) RequestEmailChange added in v0.4.0

func (s *Users) RequestEmailChange(ctx context.Context, p Principal, password, newAddress, redirectTo, ip, requestID string) error

RequestEmailChange starts a change of the signed-in user's own address: it needs the current password, remembers the new address as pending and sends a confirmation link to it. Nothing changes until that link is used. An address someone else already has is answered like any other (the request is accepted and nothing is sent), so this can't be used to find out who is registered. A reached email limit is an *EmailLimitedError: the caller is signed in, so it may be told.

func (*Users) RequestPasswordReset added in v0.4.0

func (s *Users) RequestPasswordReset(ctx context.Context, address, redirectTo, ip, requestID string) error

RequestPasswordReset asks for a reset email. It answers nothing about the account: an unknown or disabled address does the same work as a known one, minus the queued job. Requests are counted per address and per client (a *ThrottledError once past the limit, for every address alike), and the email limits apply on top.

func (*Users) RerunJob added in v0.8.0

func (s *Users) RerunJob(ctx context.Context, id string, timeoutMS int64) (Job, error)

RerunJob queues a new job for a finished function job: the same function, input and caller, as a job of its own (RerunOf says which). timeoutMS is the function's timeout now, for the new job's lease.

func (*Users) ResendVerification added in v0.4.0

func (s *Users) ResendVerification(ctx context.Context, address, redirectTo, ip, requestID string) error

ResendVerification asks again for the verification email of an address. It answers nothing about the account: an unknown, disabled or already verified address does the same work, minus the queued job, and every limit counts the same.

func (*Users) ResetPassword added in v0.4.0

func (s *Users) ResetPassword(ctx context.Context, token, password string) (EmailToken, error)

ResetPassword uses a reset token to set a new password. The password is checked first, so a refused one (*PolicyError) leaves the token usable. Success ends every session of the user, marks the address verified (the token proves the person reads that mailbox) and sends password-changed. It never starts a session. Every way the token can be wrong is ErrInvalidToken.

func (*Users) ResolveSecrets

func (s *Users) ResolveSecrets(ctx context.Context, refs []registry.SecretRef, database string) (values map[string]string, missing []string, err error)

ResolveSecrets decrypts the values a function declared (refs), for database (its own). It returns values keyed exactly as declared (SecretRef.String(): NAME or realm.NAME) and, separately, the refs with no stored value, so the caller can answer secret_missing. Decrypted values are cached for a short time, so a rotation or a changed value applies within a minute without restarting anything.

func (*Users) RetryFileDeletion added in v0.8.0

func (s *Users) RetryFileDeletion(ctx context.Context, key string, wait time.Duration) error

RetryFileDeletion keeps a deletion that failed, to be claimed again after wait.

func (*Users) RetryJob added in v0.4.0

func (s *Users) RetryJob(ctx context.Context, id string, delay time.Duration) error

RetryJob puts a job whose attempt failed back in the queue, to be claimed again once delay has passed.

func (*Users) RevertEmailChange added in v0.4.0

func (s *Users) RevertEmailChange(ctx context.Context, token string) (EmailToken, error)

RevertEmailChange uses the link sent to the old address: it restores that address, ends every session of the user and makes their password unusable, since whoever changed the address may know it, and sends a password reset link to the restored address. Every way the token can be wrong is ErrInvalidToken.

func (*Users) RevokeAPIKey

func (s *Users) RevokeAPIKey(ctx context.Context, name string) error

RevokeAPIKey deletes a key; it stops working at once.

func (*Users) RevokeInvitation

func (s *Users) RevokeInvitation(ctx context.Context, id string) error

RevokeInvitation deletes an invitation; ErrNotFound if none has that id.

func (*Users) RevokeSession

func (s *Users) RevokeSession(ctx context.Context, p Principal, id string) error

RevokeSession ends one of the caller's sessions; ErrNotFound if the caller has no session with that id.

func (*Users) RevokeUserSession added in v0.7.0

func (s *Users) RevokeUserSession(ctx context.Context, userID, id string) error

RevokeUserSession ends one session of a user, on an administrator's say-so; ErrNotFound if the user has no session with that id.

func (*Users) SaveCheckReport added in v0.8.0

func (s *Users) SaveCheckReport(ctx context.Context, r CheckReport) error

SaveCheckReport stores r as the collection's latest report, replacing the last.

func (*Users) ScheduleStates added in v0.8.0

func (s *Users) ScheduleStates(ctx context.Context) (map[string]ScheduleState, error)

ScheduleStates returns the state of every function that has ever been paused, keyed by "<database>/<function>".

func (*Users) SendInvitation added in v0.4.0

func (s *Users) SendInvitation(ctx context.Context, address string, ttl time.Duration, createdBy, redirectTo, locale, ip, requestID string) (Invitation, error)

SendInvitation is CreateInvitation for an invitation that backd emails to the address it is bound to: the person gets a link to a page where they choose a password. Nobody holds the invitation's own token, so no token is returned; the link's token is made by a worker when it sends the message, and only its hash is stored. A reached email limit removes the invitation again and is an *EmailLimitedError.

func (*Users) Sessions

func (s *Users) Sessions(ctx context.Context, p Principal) ([]Session, error)

Sessions lists the caller's unexpired sessions, newest first.

func (*Users) SetDisabled

func (s *Users) SetDisabled(ctx context.Context, email string, disabled bool) error

SetDisabled disables or re-enables the user. Disabling revokes all of their sessions.

func (*Users) SetEmailVerified

func (s *Users) SetEmailVerified(ctx context.Context, email string, verified bool) error

SetEmailVerified marks the user's email as verified or not.

func (*Users) SetImageDeclaration added in v0.8.1

func (s *Users) SetImageDeclaration(ctx context.Context, key, fingerprint string) error

SetImageDeclaration records a declared version's fingerprint.

func (*Users) SetJobSteps added in v0.8.0

func (s *Users) SetJobSteps(ctx context.Context, id string, attempt int, steps []Step, omitted int) (bool, error)

SetJobSteps stores the steps the attempt numbered attempt reported, replacing what the job held. It does nothing (false) when the job is not running that attempt any more: finished, cancelled, or claimed again by another worker.

func (*Users) SetLocale added in v0.4.0

func (s *Users) SetLocale(ctx context.Context, userID, locale string) (User, error)

SetLocale changes a user's language to one the realm lists (ignoring case; es-mx sets es-MX). Any other value is an *InvalidLocaleError naming the allowed ones: unlike sign-up, an explicit change isn't silently mapped.

func (*Users) SetNetworks

func (s *Users) SetNetworks(ctx context.Context, email string, admin, login registry.Networks) (User, error)

SetNetworks replaces the user's network restrictions: admin limits their admin API requests (within the realm's allowed networks), login their login and session use. Empty lists remove the restriction. Settings realm.yaml doesn't hold live only in the database.

func (*Users) SetPassword

func (s *Users) SetPassword(ctx context.Context, email, password string) error

SetPassword sets or replaces the user's password and revokes all of their sessions.

func (*Users) SetSchedulePaused added in v0.8.0

func (s *Users) SetSchedulePaused(ctx context.Context, database, function string, paused bool, by string) (ScheduleState, error)

SetSchedulePaused pauses or resumes a scheduled function's runs. Asking for the state it already has changes nothing.

func (*Users) SetSecret

func (s *Users) SetSecret(ctx context.Context, database, name, value, by string) error

SetSecret creates or replaces the value at scope (database, or "" for the realm) and name, encrypting it with Cipher. by is the actor for UpdatedBy (see Caller.Actor).

func (*Users) SetUploadStatus added in v0.8.0

func (s *Users) SetUploadStatus(ctx context.Context, id, status, documentID string) error

SetUploadStatus moves an upload through its states (JournalStored, then JournalAttached or JournalFailed).

func (*Users) SignUp added in v0.4.0

func (s *Users) SignUp(ctx context.Context, r SignupRequest) (SignupResult, error)

SignUp creates the account like Signup, and in a realm with email asks for the verification email. The realm may also require the address to be verified before any session: then there is none (Pending).

func (*Users) Signup

func (s *Users) Signup(ctx context.Context, email, password, invitation string, locales ...string) (Principal, string, error)

Signup creates a user with a password and signs them in. Realms with `signup: open` accept anyone; `signup: invite` needs a valid invitation token (used up by a successful sign-up); `signup: closed` refuses.

locales are the languages the person asked for, most wanted first (the request's locale, then Accept-Language); the user gets the best one the realm lists, or its default, silently.

func (*Users) StartRegeneration added in v0.8.1

func (s *Users) StartRegeneration(ctx context.Context, database string, e ImageJob, requestedBy, requestID string) (Job, error)

StartRegeneration queues the making again of the versions of a file field that no longer match what it declares (a scan job; see ImageJob.Scan). A field has at most one at a time.

func (*Users) StartSchemaCheck added in v0.8.0

func (s *Users) StartSchemaCheck(ctx context.Context, scope CheckJob, requestedBy, requestID string) (Job, error)

StartSchemaCheck queues a schema check of collections (each "<database>/<collection>"), or *ErrCheckRunning when one is already queued or running in the realm. scope is what was asked for (database and collection, either may be "").

func (*Users) StorageUsage added in v0.8.0

func (s *Users) StorageUsage(ctx context.Context, limit int) (StorageUsage, error)

StorageUsage returns the running totals: the realm's and the limit users holding the most.

func (*Users) StorageUsageOf added in v0.8.1

func (s *Users) StorageUsageOf(ctx context.Context, owner string) (realm, user StorageUsageTotals, err error)

StorageUsageOf returns the realm's totals and those of one user, for the quotas.

func (*Users) UploadEntry added in v0.8.0

func (s *Users) UploadEntry(ctx context.Context, id string) (FileJournalEntry, error)

UploadEntry returns an upload's record whatever kind it is, or ErrNotFound.

func (*Users) UserByID

func (s *Users) UserByID(ctx context.Context, id string) (User, error)

UserByID returns the user with that id, or ErrNotFound.

func (*Users) UserSessions added in v0.7.0

func (s *Users) UserSessions(ctx context.Context, userID string) ([]Session, error)

UserSessions lists a user's unexpired sessions, newest first (an administrator's view of someone else's).

func (*Users) VerifyEmail added in v0.4.0

func (s *Users) VerifyEmail(ctx context.Context, token string) (EmailToken, error)

VerifyEmail uses a verification token: it marks the address verified and, if the realm wants it, asks for the welcome email. It never starts a session. Every way the token can be wrong is ErrInvalidToken.

Directories

Path Synopsis
Package authtest provides an in-memory auth.Store for tests.
Package authtest provides an in-memory auth.Store for tests.

Jump to

Keyboard shortcuts

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