Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): credential mediation #361

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading