Documentation
¶
Overview ¶
Package passkeys is Rivolt's WebAuthn: passkeys as a second way to sign in to an email account, next to the password (Sprint 10; DA-approved design). The server runs the ceremonies (github.com/go-webauthn/webauthn) and keeps only public values: a credential id, a public key, a counter and a few flags. Kratos is not involved: a good assertion resolves to a Rivolt user, and the caller accepts the Hydra login exactly as the password path does.
Rules, from DA: the relying party is the configured RP ID with its origins matched exactly; attestation is "none" (no FIDO metadata service, no outbound call from the library); user verification is required on both ceremonies and checked in the authenticator data, not only requested; discoverable (resident) credentials are required so sign-in needs no username; a ceremony is single use and expires in five minutes; a signature counter that goes backwards is refused. Nothing it logs carries a credential id, public key, challenge or user handle.
Index ¶
- Constants
- Variables
- func NewHandle() []byte
- type Ceremony
- type Config
- type Credential
- type PGStore
- func (s *PGStore) Add(ctx context.Context, c Credential) error
- func (s *PGStore) CredentialByID(ctx context.Context, credentialID []byte) (Credential, bool, error)
- func (s *PGStore) Credentials(ctx context.Context, uid uuid.UUID) ([]Credential, error)
- func (s *PGStore) Delete(ctx context.Context, uid, id uuid.UUID) (bool, error)
- func (s *PGStore) PurgeCeremonies(ctx context.Context, now time.Time) (int64, error)
- func (s *PGStore) RecordUse(ctx context.Context, credentialID []byte, signCount uint32, backupState bool, ...) error
- func (s *PGStore) Rename(ctx context.Context, uid, id uuid.UUID, label string) (bool, error)
- func (s *PGStore) SaveCeremony(ctx context.Context, c Ceremony) error
- func (s *PGStore) TakeCeremony(ctx context.Context, id uuid.UUID, purpose string, now time.Time) (Ceremony, bool, error)
- func (s *PGStore) UserByHandle(ctx context.Context, handle []byte) (uuid.UUID, bool, error)
- func (s *PGStore) UserHandle(ctx context.Context, uid uuid.UUID) ([]byte, error)
- type Refused
- type Service
- func (s *Service) BeginLogin(ctx context.Context, hydraChallenge string) (uuid.UUID, *protocol.CredentialAssertion, error)
- func (s *Service) BeginRegistration(ctx context.Context, uid uuid.UUID, name, display string) (uuid.UUID, *protocol.CredentialCreation, error)
- func (s *Service) FinishLogin(ctx context.Context, ceremony uuid.UUID, hydraChallenge string, body []byte) (uuid.UUID, Credential, error)
- func (s *Service) FinishRegistration(ctx context.Context, uid uuid.UUID, ceremony uuid.UUID, body []byte, ...) (Credential, error)
- func (s *Service) SetClock(now func() time.Time)
- func (s *Service) Store() Store
- type Store
Constants ¶
const ( ReasonCeremony = "ceremony" // unknown, expired, used or someone else's ReasonUnknown = "unknown_credential" ReasonAssertion = "assertion" // the signature, origin, RP or challenge did not check out ReasonNoUV = "no_user_verification" ReasonSignCount = "sign_count" ReasonRegistration = "registration" )
Reasons.
const CeremonyTTL = 5 * time.Minute
CeremonyTTL is how long a registration or sign-in may take.
const MaxPerAccount = 10
MaxPerAccount is the most passkeys one account may hold.
Variables ¶
var ErrTooMany = errors.New("passkeys: too many passkeys on this account")
ErrTooMany is a registration refused because the account already holds MaxPerAccount passkeys.
Functions ¶
Types ¶
type Ceremony ¶
type Ceremony struct {
ID uuid.UUID
Purpose string // "register" or "login"
UserID uuid.UUID
HydraChallenge string
Session []byte // webauthn.SessionData as JSON
ExpiresAt time.Time
}
Ceremony is the stored state of a registration or sign-in in progress.
type Config ¶
type Config struct {
RPID string // e.g. "rivolt.dev"
RPName string // shown by the authenticator; default "Rivolt"
Origins []string // exact origins, e.g. "https://rivolt.dev"
}
Config is the relying party. Disabled (the zero value) unless RPID and Origins are set.
func ConfigFromEnv ¶
ConfigFromEnv reads RIVOLT_WEBAUTHN_RP_ID, RIVOLT_WEBAUTHN_RP_NAME and RIVOLT_WEBAUTHN_ORIGINS (comma separated).
type Credential ¶
type Credential struct {
ID uuid.UUID
UserID uuid.UUID
CredentialID []byte
PublicKey []byte
SignCount uint32
Transports []string
AAGUID []byte
BackupEligible bool
BackupState bool
Label string
CreatedAt time.Time
LastUsedAt *time.Time
}
Credential is one passkey as the server keeps it.
type PGStore ¶
type PGStore struct {
// contains filtered or unexported fields
}
PGStore keeps passkeys in Postgres. It works on the system pool: sign-in runs before there is a user in the request, and the tables carry tenant isolation for the per-user pool like every table with a user_id.
func (*PGStore) CredentialByID ¶
func (*PGStore) Credentials ¶
func (*PGStore) PurgeCeremonies ¶
func (*PGStore) SaveCeremony ¶
func (*PGStore) TakeCeremony ¶
func (*PGStore) UserByHandle ¶
type Refused ¶
type Refused struct{ Reason string }
Refusals. Each carries a fixed reason, safe as a metric label; to the person they all read alike.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service runs the ceremonies.
func (*Service) BeginLogin ¶
func (s *Service) BeginLogin(ctx context.Context, hydraChallenge string) (uuid.UUID, *protocol.CredentialAssertion, error)
BeginLogin starts a usernameless sign-in bound to a Hydra login challenge.
func (*Service) BeginRegistration ¶
func (s *Service) BeginRegistration(ctx context.Context, uid uuid.UUID, name, display string) (uuid.UUID, *protocol.CredentialCreation, error)
BeginRegistration starts adding a passkey to the account uid; name is the login (email) shown by the authenticator.
func (*Service) FinishLogin ¶
func (s *Service) FinishLogin(ctx context.Context, ceremony uuid.UUID, hydraChallenge string, body []byte) (uuid.UUID, Credential, error)
FinishLogin verifies the assertion in body for the ceremony and returns the account and the passkey used. Every failure is a Refused with a fixed reason; the caller answers them all alike.
func (*Service) FinishRegistration ¶
func (s *Service) FinishRegistration(ctx context.Context, uid uuid.UUID, ceremony uuid.UUID, body []byte, label, name, display string) (Credential, error)
FinishRegistration completes it: body is the browser's PublicKeyCredential JSON. Returns the stored passkey.
type Store ¶
type Store interface {
// UserHandle returns the account's WebAuthn user handle, creating a random one the first time.
UserHandle(ctx context.Context, uid uuid.UUID) ([]byte, error)
// UserByHandle finds the account a handle belongs to.
UserByHandle(ctx context.Context, handle []byte) (uuid.UUID, bool, error)
Credentials(ctx context.Context, uid uuid.UUID) ([]Credential, error)
// CredentialByID finds a credential by the authenticator's credential id.
CredentialByID(ctx context.Context, credentialID []byte) (Credential, bool, error)
Add(ctx context.Context, c Credential) error
// RecordUse stores the new signature counter and backup state and the time of use.
RecordUse(ctx context.Context, credentialID []byte, signCount uint32, backupState bool, at time.Time) error
Rename(ctx context.Context, uid, id uuid.UUID, label string) (bool, error)
Delete(ctx context.Context, uid, id uuid.UUID) (bool, error)
SaveCeremony(ctx context.Context, c Ceremony) error
// TakeCeremony returns an unexpired ceremony of the purpose and deletes it, so it can be used once.
TakeCeremony(ctx context.Context, id uuid.UUID, purpose string, now time.Time) (Ceremony, bool, error)
PurgeCeremonies(ctx context.Context, now time.Time) (int64, error)
}
Store is the passkeys' persistence (a Postgres store, or a fake in tests).
Directories
¶
| Path | Synopsis |
|---|---|
|
Package passkeytest is a software authenticator for tests: enough of one to make attestations ("none") and assertions that the WebAuthn library accepts, with the flags, origin and counter under the test's control.
|
Package passkeytest is a software authenticator for tests: enough of one to make attestations ("none") and assertions that the WebAuthn library accepts, with the flags, origin and counter under the test's control. |