Documentation
¶
Overview ¶
Package auth is passkey (WebAuthn) registration and login: no password ever stored or transmitted, a session cookie once a ceremony succeeds, and the store both ceremonies and sessions persist to (SQLite).
Index ¶
- Constants
- Variables
- func BearerToken(r *http.Request) (string, bool)
- func ClearSessionCookie(w http.ResponseWriter, secure bool)
- func IsHTTPS(r *http.Request) bool
- func RequireAdmin(next http.Handler) http.Handler
- func RequireAuth(store *Store) func(http.Handler) http.Handler
- func RequireSession(store *Store) func(http.Handler) http.Handler
- func SessionToken(r *http.Request) (string, bool)
- func SetSessionCookie(w http.ResponseWriter, token string, secure bool)
- func WithAccessLogUsername(ctx context.Context) (context.Context, *string)
- type APIToken
- type Credential
- type Invite
- type RequestLogFilter
- type RequestLogRow
- type Service
- func (s *Service) BeginAddCredential(ctx context.Context, u *User) (*protocol.CredentialCreation, error)
- func (s *Service) BeginLogin(ctx context.Context, username string) (*protocol.CredentialAssertion, error)
- func (s *Service) BeginRegistration(ctx context.Context, username, displayName, inviteToken string) (*protocol.CredentialCreation, error)
- func (s *Service) CreateInvite(ctx context.Context, requester *User, username, displayName string) (token string, invite *Invite, err error)
- func (s *Service) CreateSession(ctx context.Context, u *User) (token string, err error)
- func (s *Service) FinishAddCredential(ctx context.Context, u *User, label string, r *http.Request) (*Credential, error)
- func (s *Service) FinishLogin(ctx context.Context, username string, r *http.Request) (*User, error)
- func (s *Service) FinishRegistration(ctx context.Context, username string, r *http.Request) (*User, error)
- func (s *Service) HasAnyRegisteredUser(ctx context.Context) (bool, error)
- type Store
- func (s *Store) AddCredential(ctx context.Context, userID []byte, cred webauthn.Credential) error
- func (s *Store) ConsumeInviteIfValid(ctx context.Context, token, username string) (*Invite, error)
- func (s *Store) CreateAPIToken(ctx context.Context, userID []byte, label string, expiresAt time.Time) (raw string, tok *APIToken, err error)
- func (s *Store) CreateInvite(ctx context.Context, username, displayName string, createdBy []byte, ...) (raw string, invite *Invite, err error)
- func (s *Store) CreateSession(ctx context.Context, userID []byte, ttl time.Duration) (token string, err error)
- func (s *Store) CreateUser(ctx context.Context, username, displayName string, isAdmin bool) (*User, error)
- func (s *Store) DeleteAPIToken(ctx context.Context, userID []byte, id string) error
- func (s *Store) DeleteCeremony(ctx context.Context, username, kind string) error
- func (s *Store) DeleteSession(ctx context.Context, token string) error
- func (s *Store) DeleteUnregisteredUser(ctx context.Context, username string) error
- func (s *Store) DeleteUser(ctx context.Context, userID []byte) error
- func (s *Store) GetUserByID(ctx context.Context, id []byte) (*User, error)
- func (s *Store) GetUserByUsername(ctx context.Context, username string) (*User, error)
- func (s *Store) HasAnyRegisteredUser(ctx context.Context) (bool, error)
- func (s *Store) Init(ctx context.Context) error
- func (s *Store) ListAPITokens(ctx context.Context, userID []byte) ([]*APIToken, error)
- func (s *Store) ListCredentials(ctx context.Context, userID []byte) ([]*Credential, error)
- func (s *Store) ListOutstandingInvites(ctx context.Context) ([]*Invite, error)
- func (s *Store) ListRequests(ctx context.Context, filter RequestLogFilter) ([]RequestLogRow, error)
- func (s *Store) ListUsers(ctx context.Context) ([]*User, error)
- func (s *Store) LoadCeremony(ctx context.Context, username, kind string) (webauthn.SessionData, error)
- func (s *Store) RecordRequest(ctx context.Context, row RequestLogRow, maxEntries int) error
- func (s *Store) RemoveCredential(ctx context.Context, userID []byte, credID string) error
- func (s *Store) RevokeInvite(ctx context.Context, id string) error
- func (s *Store) RevokeUser(ctx context.Context, userID []byte) error
- func (s *Store) SaveCeremony(ctx context.Context, username, kind string, session webauthn.SessionData, ...) error
- func (s *Store) SetCredentialLabel(ctx context.Context, userID []byte, credID []byte, label string) error
- func (s *Store) UpdateCredential(ctx context.Context, userID []byte, cred webauthn.Credential) error
- func (s *Store) UserForAPIToken(ctx context.Context, raw string) (*User, error)
- func (s *Store) UserForSession(ctx context.Context, token string) (*User, error)
- type User
Constants ¶
const CookieName = "forge_dashboard_session"
CookieName is the session cookie forge-dashboard sets and reads. Its value is an opaque token — Store.UserForSession is what resolves it to a user, never anything decoded client-side.
const SessionTTL = 30 * 24 * time.Hour
SessionTTL is how long an issued session stays valid.
Variables ¶
var ErrAlreadyRegistered = errors.New("auth: username already registered")
ErrAlreadyRegistered is returned by BeginRegistration when the username is already taken.
var ErrInvalidInvite = errors.New("auth: invalid or expired invite")
ErrInvalidInvite is returned by BeginRegistration when an invite is required (any account already exists) and the presented token is missing, unknown, expired, already consumed, or issued for a different username than the one being registered — deliberately one sentinel for every reason rather than one each, so a caller can't fingerprint which specific check failed.
var ErrLastCredential = errors.New("auth: can't remove the account's last remaining passkey")
ErrLastCredential is returned by RemoveCredential when credID is userID's only remaining passkey — this app is WebAuthn-only with no password fallback, so deleting it would lock the account out entirely (#355).
var ErrNotAdmin = errors.New("auth: admin access required")
ErrNotAdmin is returned by an admin-only Service method when the requester isn't the designated admin.
var ErrNotFound = errors.New("auth: not found")
ErrNotFound is returned by a lookup that found nothing — a sentinel rather than a typed error, since every caller only ever needs to know which kind, not reach anything out of it.
Functions ¶
func BearerToken ¶ added in v0.64.0
BearerToken reads an "Authorization: Bearer <token>" header from r, if any — a personal API token, checked by RequireAuth as a fallback when there's no session cookie, so a script can authenticate the same way a signed-in browser does with no WebAuthn ceremony to perform.
func ClearSessionCookie ¶
func ClearSessionCookie(w http.ResponseWriter, secure bool)
ClearSessionCookie expires the session cookie immediately — logout.
func IsHTTPS ¶
IsHTTPS reports whether r itself arrived over TLS, directly or via a reverse proxy that sets the standard forwarded-proto header (Traefik, in front of this service's own deployment, terminates TLS and always sets it) — the deciding factor for whether the session cookie can carry Secure without locking out a plain-HTTP local dev run.
func RequireAdmin ¶ added in v0.9.0
RequireAdmin wraps next so it only ever runs for the designated admin — everyone else gets a 403. Compose it inside RequireAuth (RequireAuth(store)(RequireAdmin(next))), since it reads the user RequireAuth already put in context rather than looking one up itself.
func RequireAuth ¶
RequireAuth wraps next so it only ever runs for a request carrying a valid, unexpired session cookie or a valid personal API token (Authorization: Bearer <token>) — everyone else gets a 401 JSON body, never the handler underneath. Cookie checked first, Bearer as the fallback: the common case (a browser) never pays for a second lookup, and a script sending both would be unusual enough that "cookie wins" is a fine tiebreak rather than something worth its own rule.
func RequireSession ¶ added in v0.133.2
RequireSession is RequireAuth for state that belongs to the browser alone: only a session cookie gets in, and a personal API token is answered with a 403 rather than being treated as the user at a keyboard (#1000). A request with no credentials at all still gets the same 401 as everywhere else.
func SessionToken ¶
SessionToken reads the session cookie from r, if any.
func SetSessionCookie ¶
func SetSessionCookie(w http.ResponseWriter, token string, secure bool)
SetSessionCookie sets token as the session cookie on w. secure mirrors whether the request itself arrived over HTTPS — see isHTTPS, called by the handler that has the *http.Request this cookie is a response to.
func WithAccessLogUsername ¶ added in v0.77.0
WithAccessLogUsername returns a context carrying a pointer RequireAuth fills in with the resolved username, once it resolves one, plus that same pointer for the caller to read back later (#371).
An access-log middleware has to wrap outside RequireAuth — a rejected or unauthenticated request still needs logging — so by the time it can log anything, whatever RequireAuth put in *its own* request's context is already gone: r.WithContext returns a new *http.Request rather than mutating the caller's, so a value added deeper in the chain never becomes visible on the outer handler's own (now-stale) request. What does survive the round trip is a pointer carried through that context: writing to what it points at is visible to anyone still holding the same pointer, however many WithContext copies sit in between — which is exactly what the caller here keeps hold of.
Types ¶
type APIToken ¶ added in v0.64.0
type APIToken struct {
ID string
Label string
CreatedAt time.Time
// ExpiresAt is always set (#356: mandatory at creation, no "never
// expires" option) — UserForAPIToken rejects a token once this
// passes, the same way it rejects one it's never heard of. A row
// from before this field existed reads back as the zero Time, which
// is always in the past, so it's rejected too rather than treated as
// permanently valid just because the schema changed underneath it.
ExpiresAt time.Time
// LastUsedAt is nil until the token first authenticates a request —
// the same "hasn't happened yet, not zero-value" shape
// dashboard.RateLimit's own nilable fields use elsewhere in this
// codebase.
LastUsedAt *time.Time
}
APIToken is a personal API token's own metadata — never the raw value, which only Store.CreateAPIToken ever returns, once, at creation time. Matches components.schemas.APIToken.
type Credential ¶ added in v0.73.0
Credential is one registered passkey's own display metadata (#355) — never the credential itself, which stays on the authenticator that created it and never reaches this server at all. ID is base64url of the underlying webauthn.Credential.ID, matching how a credential ID already appears everywhere else in a WebAuthn ceremony (excludeCredentials, allowCredentials) rather than inventing a second encoding.
type Invite ¶ added in v0.86.0
type Invite struct {
ID string
Username string
DisplayName string
// CreatedBy is the admin's own WebAuthn user handle — the same raw
// bytes User.ID holds — not a display name, so it survives that
// admin's own account being renamed (usernames have no such
// guarantee elsewhere in this schema, but nothing renames a username
// today either; matching User.ID's shape is just the safer default).
CreatedBy []byte
ExpiresAt time.Time
// ConsumedAt is nil for an outstanding invite — set once by either a
// completed registration (ConsumeInviteIfValid) or an admin's own
// revoke (RevokeInvite), which share the same column since this
// schema has no separate "revoked" state to track.
ConsumedAt *time.Time
}
Invite is a single-use, admin-issued registration invite — the same "hashed secret, shown raw exactly once" shape APIToken already uses (design.md's explicit precedent). ID is the invite's own stored token_hash, never the raw token itself: SHA-256 of a uniformly random secret can't be reversed (the same reasoning hashAPIToken's own doc comment gives for api_tokens), so it's safe to hand back to a caller as a stable identifier — it's what ListOutstandingInvites/RevokeInvite key on, not something that can complete a registration on its own.
type RequestLogFilter ¶ added in v0.90.0
RequestLogFilter narrows ListRequests. A zero Filter (both fields empty) returns every row.
type RequestLogRow ¶ added in v0.90.0
type RequestLogRow struct {
ID int64
LoggedAt time.Time
Forge string
AccountID string // "" when no per-account credential made this request
Method string
Endpoint string
StatusCode int // 0 when the request never got a response at all
Outcome string // "success" or a dashboard.ForgeErrorKind string
// RateLimitLimit/RateLimitRemaining/RateLimitResetsAt/RateLimitCost
// are all nil when the response carried no rate-limit fields at all —
// not every request reports these, and a zero would misread as a
// real, reported zero-remaining budget.
RateLimitLimit *int
RateLimitRemaining *int
RateLimitResetsAt *time.Time
RateLimitCost *int
}
RequestLogRow is one outbound-forge-request record, in the plain, dependency-free shape this store persists and returns — internal/ requestlog owns the richer Entry type callers actually work with (dashboard.Forge, a typed rate-limit struct) and translates to/from this shape at its own SQLiteRecorder boundary, rather than this store importing internal/requestlog's types directly: internal/requestlog already depends on this package (its SQLiteRecorder wraps *Store), so the reverse import would be a cycle.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service drives WebAuthn registration and login ceremonies against the configured relying party, persisting every step through Store.
func NewService ¶
NewService returns a Service.
func (*Service) BeginAddCredential ¶ added in v0.73.0
func (s *Service) BeginAddCredential(ctx context.Context, u *User) (*protocol.CredentialCreation, error)
BeginAddCredential starts a registration ceremony for an additional passkey on u's already-authenticated account (#355) — the same underlying WebAuthn ceremony BeginRegistration drives for a brand-new account, but against a user that already exists and, critically, already has credentials: the library's own excludeCredentials (built from u.WebAuthnCredentials()) is what stops a browser from letting someone re-register the same authenticator as a second, redundant entry.
func (*Service) BeginLogin ¶
func (s *Service) BeginLogin(ctx context.Context, username string) (*protocol.CredentialAssertion, error)
BeginLogin starts a login ceremony for an existing username.
func (*Service) BeginRegistration ¶
func (s *Service) BeginRegistration(ctx context.Context, username, displayName, inviteToken string) (*protocol.CredentialCreation, error)
BeginRegistration starts a registration ceremony for a brand-new username. Returns ErrAlreadyRegistered if that username already has an account.
The very first registration on a fresh instance bootstraps its admin and needs no invite — HasAnyRegisteredUser is false, so inviteToken is never even looked at. Every registration after that requires inviteToken to be a valid, unexpired, unconsumed invite issued for exactly username (ErrInvalidInvite otherwise), and the invite's own displayName wins over whatever the caller passed in — the admin who issued the invite chose it, not the anonymous request completing the ceremony.
func (*Service) CreateInvite ¶ added in v0.86.0
func (s *Service) CreateInvite(ctx context.Context, requester *User, username, displayName string) (token string, invite *Invite, err error)
CreateInvite generates a new single-use registration invite for username/displayName, issued by requester — ErrNotAdmin if requester isn't the designated admin, ErrAlreadyRegistered if username already has a completed registration (an abandoned, credential-less username is still fine to invite, same as it's still fine to self-reclaim via BeginRegistration).
func (*Service) CreateSession ¶
CreateSession issues a session token for u, ready to set as a cookie.
func (*Service) FinishAddCredential ¶ added in v0.73.0
func (s *Service) FinishAddCredential(ctx context.Context, u *User, label string, r *http.Request) (*Credential, error)
FinishAddCredential completes a ceremony BeginAddCredential started, attaching a new passkey labeled label to u's account. label is required — #355's whole point is that a nickname is prompted for at registration time, not derived from the authenticator.
func (*Service) FinishLogin ¶
FinishLogin completes a login ceremony, persisting the authenticator's bumped signature counter so a cloned authenticator is caught on a later login.
func (*Service) FinishRegistration ¶
func (s *Service) FinishRegistration(ctx context.Context, username string, r *http.Request) (*User, error)
FinishRegistration completes a registration ceremony begun for username, attaching the new passkey to the account BeginRegistration already created.
func (*Service) HasAnyRegisteredUser ¶ added in v0.86.0
HasAnyRegisteredUser reports whether any account has completed registration — an exported wrapper around the Store method of the same name, reachable from outside this package for the public registration-status endpoint (unauthenticated, so it has no *User of its own to check admin status against the way CreateInvite does).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists users, their passkey credentials, in-flight WebAuthn ceremonies, and sessions. It owns no driver of its own — cmd wires up whichever database/sql driver is registered (modernc.org/sqlite) and hands Store a plain *sql.DB.
func (*Store) AddCredential ¶
AddCredential appends cred to userID's stored credentials — a brand-new passkey, from a just-finished registration ceremony.
func (*Store) ConsumeInviteIfValid ¶ added in v0.86.0
ConsumeInviteIfValid atomically checks that token hashes to an outstanding (unconsumed, unexpired) invite issued for exactly username, and marks it consumed in the same statement — a single UPDATE with every validity condition in its WHERE clause, so a concurrent attempt to spend the same token twice can never both succeed: SQLite serializes writes, and only the first one actually matches consumed_at IS NULL. Returns ErrNotFound uniformly for an unknown token, a username mismatch, an already-consumed invite, or an expired one — deliberately not distinguishing which, the same "don't leak which check failed" reasoning ErrInvalidInvite's own doc comment gives at the Service layer that wraps this.
func (*Store) CreateAPIToken ¶ added in v0.64.0
func (s *Store) CreateAPIToken(ctx context.Context, userID []byte, label string, expiresAt time.Time) (raw string, tok *APIToken, err error)
CreateAPIToken generates a brand-new personal API token for userID, labeled label, valid until expiresAt (#356 — mandatory, no "never expires" option; the caller, internal/api's handler, enforces the future/366-day-cap validation before this is ever called). raw is the only time the actual credential is ever returned — only its hash is stored, so losing it means generating a new one, the same "show once" handling a real secret needs.
func (*Store) CreateInvite ¶ added in v0.86.0
func (s *Store) CreateInvite(ctx context.Context, username, displayName string, createdBy []byte, ttl time.Duration) (raw string, invite *Invite, err error)
CreateInvite generates a brand-new single-use registration invite for username/displayName, issued by createdBy (an admin's own user ID), valid for ttl. raw is the only time the actual token is ever returned — only its hash is stored, so a leaked database dump can't be replayed into a registration (the same reasoning CreateAPIToken's own doc comment gives).
func (*Store) CreateSession ¶
func (s *Store) CreateSession(ctx context.Context, userID []byte, ttl time.Duration) (token string, err error)
CreateSession issues a new opaque session token for userID, valid for ttl.
func (*Store) CreateUser ¶
func (s *Store) CreateUser(ctx context.Context, username, displayName string, isAdmin bool) (*User, error)
CreateUser registers a brand-new account with no credentials yet — AddCredential attaches the first one once the registration ceremony that follows actually succeeds.
func (*Store) DeleteAPIToken ¶ added in v0.64.0
DeleteAPIToken revokes id, scoped to userID so one user can never revoke another's token by guessing an id — idempotent, same as DeleteUser's own sessions cleanup: deleting one that's already gone, or never belonged to userID, is not an error.
func (*Store) DeleteCeremony ¶
DeleteCeremony clears the in-flight ceremony once it's finished, win or lose — it's single-use either way.
func (*Store) DeleteSession ¶
DeleteSession revokes a session token — logout, or an admin revoking another user's session.
func (*Store) DeleteUnregisteredUser ¶ added in v0.7.1
DeleteUnregisteredUser removes username's account if — and only if — it has no credentials attached: an abandoned registration (begun, never finished, so nothing was ever actually attached to the username) rather than a real account. A no-op if the account has since gained a credential, or doesn't exist at all.
func (*Store) DeleteUser ¶ added in v0.9.0
DeleteUser removes the account and every session it holds — irreversible, and the caller's job to also clean up anything outside this store (saved forge credentials, a running dashboard refresh).
func (*Store) GetUserByID ¶
GetUserByID returns ErrNotFound when no such user is registered. id is the raw WebAuthn user handle, the same bytes User.ID holds.
func (*Store) GetUserByUsername ¶
GetUserByUsername returns ErrNotFound when no such user is registered.
func (*Store) HasAnyRegisteredUser ¶ added in v0.11.0
HasAnyRegisteredUser reports whether any account has completed registration — a row with no credentials (BeginRegistration begun, never finished) doesn't count. The first username to make this true is the one that becomes admin.
func (*Store) Init ¶
Init creates the schema if it doesn't already exist. Safe to call every startup.
func (*Store) ListAPITokens ¶ added in v0.64.0
ListAPITokens returns userID's own live tokens, oldest first.
func (*Store) ListCredentials ¶ added in v0.73.0
ListCredentials returns userID's own passkeys, oldest first — driven by the account's real webauthn.Credential list (the source of truth for what can actually authenticate), not credential_metadata alone, so a credential somehow missing its label row still appears rather than silently vanishing from the list. defaultLabel fills that gap: a credential from before this feature existed reads back with no metadata row at all.
func (*Store) ListOutstandingInvites ¶ added in v0.86.0
ListOutstandingInvites returns every invite that's neither consumed nor expired, soonest-expiring first — for the admin area's own list.
func (*Store) ListRequests ¶ added in v0.90.0
func (s *Store) ListRequests(ctx context.Context, filter RequestLogFilter) ([]RequestLogRow, error)
ListRequests returns every row matching filter, newest first. An empty Filter field means "don't filter on this." Never returns a nil slice for an empty result — an empty, non-nil one — so a caller can range over it with no nil check.
func (*Store) ListUsers ¶ added in v0.9.0
ListUsers returns every registered account, for the admin area.
func (*Store) LoadCeremony ¶
func (s *Store) LoadCeremony(ctx context.Context, username, kind string) (webauthn.SessionData, error)
LoadCeremony returns ErrNotFound when there's no in-flight ceremony of that kind for username, or it already expired.
func (*Store) RecordRequest ¶ added in v0.90.0
RecordRequest persists row, then deletes the oldest rows past maxEntries in the same transaction — the caller (internal/requestlog.SQLiteRecorder) owns the retention cap, not this store, so changing it needs no change here.
func (*Store) RemoveCredential ¶ added in v0.73.0
RemoveCredential deletes credID from userID's account, scoped to that user so one account's own credential id can never be guessed into deleting another's — the same shape DeleteAPIToken's own scoping uses. Idempotent for an id that's unknown or already gone (matching DeleteAPIToken), but never for the account's last one: that's always ErrLastCredential, not silently skipped, so a caller (or a stale list somehow down to one) can't accidentally lock the account out.
func (*Store) RevokeInvite ¶ added in v0.86.0
RevokeInvite marks id (an invite's own ID — its stored token_hash, from CreateInvite/ListOutstandingInvites, never the raw token) consumed so it can never be used to register — the row itself is kept, the same "lingers, but harmlessly" trade-off design.md accepts for a genuinely consumed invite, rather than a DELETE. Unlike DeleteAPIToken's own idempotent delete, revoking an id that's already gone, already consumed, or was never issued is ErrNotFound, not a silent no-op: an admin revoking from a list they're looking at should be told when the row they clicked isn't outstanding anymore, rather than have the button silently do nothing.
func (*Store) RevokeUser ¶ added in v0.9.0
RevokeUser signs userID out everywhere and clears every registered passkey, without deleting the account itself — they have to register a new passkey from scratch to get back in, same as BeginRegistration reclaiming an abandoned one. Also revokes every API token: "signed out everywhere" has to mean everywhere a request can authenticate as this user, not just the session-cookie path.
func (*Store) SaveCeremony ¶
func (s *Store) SaveCeremony(ctx context.Context, username, kind string, session webauthn.SessionData, ttl time.Duration) error
SaveCeremony records the in-flight WebAuthn session data for username's registration or login attempt, replacing any earlier one of the same kind — a user can only ever be partway through one ceremony at a time.
func (*Store) SetCredentialLabel ¶ added in v0.73.0
func (s *Store) SetCredentialLabel(ctx context.Context, userID []byte, credID []byte, label string) error
SetCredentialLabel names credID (userID's own passkey) label — called once, right after AddCredential, for both the very first passkey a brand-new account registers and every one added afterward (#355), so Settings always has something to show even for an account that predates this feature having a UI of its own.
func (*Store) UpdateCredential ¶
func (s *Store) UpdateCredential(ctx context.Context, userID []byte, cred webauthn.Credential) error
UpdateCredential replaces the stored credential sharing cred's ID — FinishLogin bumps the authenticator's signature counter on every successful login, and that has to be persisted to catch a cloned authenticator on a later login.
func (*Store) UserForAPIToken ¶ added in v0.64.0
UserForAPIToken returns the user raw belongs to, or ErrNotFound — the Bearer-token equivalent of UserForSession. An expired token (or a legacy row with no recorded expiry at all — see APIToken.ExpiresAt) is rejected the same way an unknown one is, not a distinct error a caller could special-case into still trusting it (#356). Also records this as the token's most recent use, best-effort: a failure to persist that doesn't fail a request the token has already authenticated.
type User ¶
type User struct {
ID []byte
Username string
DisplayName string
IsAdmin bool
Credentials []webauthn.Credential
CreatedAt time.Time
}
User is a registered account. It implements webauthn.User directly, so it can be handed straight to the library's Begin/Finish calls with no adapter.
func UserFromContext ¶
UserFromContext returns the authenticated user a RequireAuth-wrapped handler is running for. Only ever called from inside such a handler, so the zero value (found false) means RequireAuth itself has a bug, not a condition a caller needs to handle gracefully.
func (*User) WebAuthnCredentials ¶
func (u *User) WebAuthnCredentials() []webauthn.Credential
WebAuthnCredentials satisfies webauthn.User with every passkey on file.
func (*User) WebAuthnDisplayName ¶
WebAuthnDisplayName satisfies webauthn.User with the account's display name.
func (*User) WebAuthnID ¶
WebAuthnID satisfies webauthn.User with the account's random handle.
func (*User) WebAuthnName ¶
WebAuthnName satisfies webauthn.User with the account's username.