Skip to content

Commit

Permalink
feat(protocol): credential mediation
Browse files Browse the repository at this point in the history
This adds support for credential mediation.
  • Loading branch information
james-d-elliott committed Dec 24, 2024
1 parent 7e1caf7 commit 677f7ac
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 7 deletions.
30 changes: 30 additions & 0 deletions protocol/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,36 @@ type AttestedCredentialData struct {
CredentialPublicKey []byte `json:"public_key"`
}

// CredentialMediationRequirement represents mediation requirements for clients. When making a request via get(options)
// or create(options), developers can set a case-by-case requirement for user mediation by choosing the appropriate
// CredentialMediationRequirement enum value.
//
// See https://www.w3.org/TR/credential-management-1/#mediation-requirements
type CredentialMediationRequirement string

const (
// MediationSilent indicates user mediation is suppressed for the given operation. If the operation can be performed
// without user involvement, wonderful. If user involvement is necessary, then the operation will return null rather
// than involving the user.
MediationSilent CredentialMediationRequirement = "silent"

// MediationOptional indicates if credentials can be handed over for a given operation without user mediation, they
// will be. If user mediation is required, then the user agent will involve the user in the decision.
MediationOptional CredentialMediationRequirement = "optional"

// MediationConditional indicates for get(), discovered credentials are presented to the user in a non-modal dialog
// along with an indication of the origin which is requesting credentials. If the user makes a gesture outside of
// the dialog, the dialog closes without resolving or rejecting the Promise returned by the get() method and without
// causing a user-visible error condition. If the user makes a gesture that selects a credential, that credential is
// returned to the caller. The prevent silent access flag is treated as being true regardless of its actual value:
// the conditional behavior always involves user mediation of some sort if applicable credentials are discovered.
MediationConditional CredentialMediationRequirement = "conditional"

// MediationRequired indicates the user agent will not hand over credentials without user mediation, even if the
// prevent silent access flag is unset for an origin.
MediationRequired CredentialMediationRequirement = "required"
)

// AuthenticatorAttachment represents the IDL enum of the same name, and is used as part of the Authenticator Selection
// Criteria.
//
Expand Down
6 changes: 4 additions & 2 deletions protocol/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
)

type CredentialCreation struct {
Response PublicKeyCredentialCreationOptions `json:"publicKey"`
Response PublicKeyCredentialCreationOptions `json:"publicKey"`
Mediation CredentialMediationRequirement `json:"mediation,omitempty"`
}

type CredentialAssertion struct {
Response PublicKeyCredentialRequestOptions `json:"publicKey"`
Response PublicKeyCredentialRequestOptions `json:"publicKey"`
Mediation CredentialMediationRequirement `json:"mediation,omitempty"`
}

// PublicKeyCredentialCreationOptions represents the IDL of the same name.
Expand Down
22 changes: 17 additions & 5 deletions webauthn/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ type DiscoverableUserHandler func(rawID, userHandle []byte) (user User, err erro
//
// Specification: §5.5. Options for Assertion Generation (https://www.w3.org/TR/webauthn/#dictionary-assertion-options)
func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.BeginMediatedLogin(user, "", opts...)
}

// BeginDiscoverableLogin begins a client-side discoverable login, previously known as Resident Key logins.
func (webauthn *WebAuthn) BeginDiscoverableLogin(opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.beginLogin(nil, nil, "", opts...)
}

// BeginMediatedLogin is similar to BeginLogin however it also allows specifying a credential mediation requirement.
func (webauthn *WebAuthn) BeginMediatedLogin(user User, mediation protocol.CredentialMediationRequirement, opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
credentials := user.WebAuthnCredentials()

if len(credentials) == 0 { // If the user does not have any credentials, we cannot perform an assertion.
Expand All @@ -43,15 +53,16 @@ func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.
allowedCredentials[i] = credential.Descriptor()
}

return webauthn.beginLogin(user.WebAuthnID(), allowedCredentials, opts...)
return webauthn.beginLogin(user.WebAuthnID(), allowedCredentials, mediation, opts...)
}

// BeginDiscoverableLogin begins a client-side discoverable login, previously known as Resident Key logins.
func (webauthn *WebAuthn) BeginDiscoverableLogin(opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.beginLogin(nil, nil, opts...)
// BeginDiscoverableMediatedLogin begins a client-side discoverable login with a mediation requirement, previously known
// as Resident Key logins.
func (webauthn *WebAuthn) BeginDiscoverableMediatedLogin(mediation protocol.CredentialMediationRequirement, opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.beginLogin(nil, nil, mediation, opts...)
}

func (webauthn *WebAuthn) beginLogin(userID []byte, allowedCredentials []protocol.CredentialDescriptor, opts ...LoginOption) (assertion *protocol.CredentialAssertion, session *SessionData, err error) {
func (webauthn *WebAuthn) beginLogin(userID []byte, allowedCredentials []protocol.CredentialDescriptor, mediation protocol.CredentialMediationRequirement, opts ...LoginOption) (assertion *protocol.CredentialAssertion, session *SessionData, err error) {
if err = webauthn.Config.validate(); err != nil {
return nil, nil, fmt.Errorf(errFmtConfigValidate, err)
}
Expand All @@ -62,6 +73,7 @@ func (webauthn *WebAuthn) beginLogin(userID []byte, allowedCredentials []protoco
UserVerification: webauthn.Config.AuthenticatorSelection.UserVerification,
AllowedCredentials: allowedCredentials,
},
Mediation: mediation,
}

for _, opt := range opts {
Expand Down
6 changes: 6 additions & 0 deletions webauthn/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ type RegistrationOption func(*protocol.PublicKeyCredentialCreationOptions)

// BeginRegistration generates a new set of registration data to be sent to the client and authenticator.
func (webauthn *WebAuthn) BeginRegistration(user User, opts ...RegistrationOption) (creation *protocol.CredentialCreation, session *SessionData, err error) {
return webauthn.BeginMediatedRegistration(user, "", opts...)
}

// BeginMediatedRegistration is similar to BeginRegistration however it also allows specifying a credential mediation requirement.
func (webauthn *WebAuthn) BeginMediatedRegistration(user User, mediation protocol.CredentialMediationRequirement, opts ...RegistrationOption) (creation *protocol.CredentialCreation, session *SessionData, err error) {
if err = webauthn.Config.validate(); err != nil {
return nil, nil, fmt.Errorf(errFmtConfigValidate, err)
}
Expand Down Expand Up @@ -64,6 +69,7 @@ func (webauthn *WebAuthn) BeginRegistration(user User, opts ...RegistrationOptio
AuthenticatorSelection: webauthn.Config.AuthenticatorSelection,
Attestation: webauthn.Config.AttestationPreference,
},
Mediation: mediation,
}

for _, opt := range opts {
Expand Down

0 comments on commit 677f7ac

Please sign in to comment.