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 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 SessionToken(r *http.Request) (string, bool)
- func SetSessionCookie(w http.ResponseWriter, token string, secure bool)
- type Service
- func (s *Service) BeginLogin(ctx context.Context, username string) (*protocol.CredentialAssertion, error)
- func (s *Service) BeginRegistration(ctx context.Context, username, displayName string) (*protocol.CredentialCreation, error)
- func (s *Service) CreateSession(ctx context.Context, u *User) (token string, err 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)
- type Store
- func (s *Store) AddCredential(ctx context.Context, userID []byte, cred webauthn.Credential) 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) 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) Init(ctx context.Context) 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) RevokeUser(ctx context.Context, userID []byte) error
- func (s *Store) SaveCeremony(ctx context.Context, username, kind string, session webauthn.SessionData, ...) error
- func (s *Store) UpdateCredential(ctx context.Context, userID []byte, cred webauthn.Credential) 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 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 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 — everyone else gets a 401 JSON body, never the handler underneath.
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.
Types ¶
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. adminUsername names the one account that becomes an admin on registration — established here, at startup, rather than by whoever happens to register first.
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 string) (*protocol.CredentialCreation, error)
BeginRegistration starts a registration ceremony for a brand-new username. Returns ErrAlreadyRegistered if that username already has an account.
func (*Service) CreateSession ¶
CreateSession issues a session token for u, ready to set as a cookie.
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.
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) 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) 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) Init ¶
Init creates the schema if it doesn't already exist. Safe to call every startup.
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) 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.
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) 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.
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.