refactor(web): webauthn references (#5244)
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5246/head
parent
2733fc040c
commit
370585d1de
|
@ -52,7 +52,7 @@ func TestWebAuthnGetUser(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, user)
|
require.NotNil(t, user)
|
||||||
|
|
||||||
assert.Equal(t, []byte("john"), user.WebAuthnID())
|
assert.Equal(t, []byte{}, user.WebAuthnID())
|
||||||
assert.Equal(t, "john", user.WebAuthnName())
|
assert.Equal(t, "john", user.WebAuthnName())
|
||||||
assert.Equal(t, "john", user.Username)
|
assert.Equal(t, "john", user.Username)
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
|
||||||
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RPID: "https://example.com",
|
RPID: "example.com",
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Description: "Primary",
|
Description: "Primary",
|
||||||
KID: model.NewBase64([]byte("abc123")),
|
KID: model.NewBase64([]byte("abc123")),
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
// U2FDevice represents a users U2F device row in the database.
|
|
||||||
type U2FDevice struct {
|
|
||||||
ID int `db:"id"`
|
|
||||||
Username string `db:"username"`
|
|
||||||
Description string `db:"description"`
|
|
||||||
KeyHandle []byte `db:"key_handle"`
|
|
||||||
PublicKey []byte `db:"public_key"`
|
|
||||||
}
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,9 +20,13 @@ const (
|
||||||
|
|
||||||
// WebAuthnUser is an object to represent a user for the WebAuthn lib.
|
// WebAuthnUser is an object to represent a user for the WebAuthn lib.
|
||||||
type WebAuthnUser struct {
|
type WebAuthnUser struct {
|
||||||
Username string
|
ID int `db:"id"`
|
||||||
DisplayName string
|
RPID string `db:"rpid"`
|
||||||
Devices []WebAuthnDevice
|
Username string `db:"username"`
|
||||||
|
UserID string `db:"userid"`
|
||||||
|
DisplayName string `db:"-"`
|
||||||
|
|
||||||
|
Devices []WebAuthnDevice `db:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasFIDOU2F returns true if the user has any attestation type `fido-u2f` devices.
|
// HasFIDOU2F returns true if the user has any attestation type `fido-u2f` devices.
|
||||||
|
@ -37,7 +42,7 @@ func (w WebAuthnUser) HasFIDOU2F() bool {
|
||||||
|
|
||||||
// WebAuthnID implements the webauthn.User interface.
|
// WebAuthnID implements the webauthn.User interface.
|
||||||
func (w WebAuthnUser) WebAuthnID() []byte {
|
func (w WebAuthnUser) WebAuthnID() []byte {
|
||||||
return []byte(w.Username)
|
return []byte(w.UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnName implements the webauthn.User interface.
|
// WebAuthnName implements the webauthn.User interface.
|
||||||
|
@ -122,11 +127,11 @@ func NewWebAuthnDeviceFromCredential(rpid, username, description string, credent
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
Description: description,
|
Description: description,
|
||||||
KID: NewBase64(credential.ID),
|
KID: NewBase64(credential.ID),
|
||||||
PublicKey: credential.PublicKey,
|
|
||||||
AttestationType: credential.AttestationType,
|
AttestationType: credential.AttestationType,
|
||||||
|
Transport: strings.Join(transport, ","),
|
||||||
SignCount: credential.Authenticator.SignCount,
|
SignCount: credential.Authenticator.SignCount,
|
||||||
CloneWarning: credential.Authenticator.CloneWarning,
|
CloneWarning: credential.Authenticator.CloneWarning,
|
||||||
Transport: strings.Join(transport, ","),
|
PublicKey: credential.PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
aaguid, err := uuid.Parse(hex.EncodeToString(credential.Authenticator.AAGUID))
|
aaguid, err := uuid.Parse(hex.EncodeToString(credential.Authenticator.AAGUID))
|
||||||
|
@ -146,12 +151,12 @@ type WebAuthnDevice struct {
|
||||||
Username string `db:"username"`
|
Username string `db:"username"`
|
||||||
Description string `db:"description"`
|
Description string `db:"description"`
|
||||||
KID Base64 `db:"kid"`
|
KID Base64 `db:"kid"`
|
||||||
PublicKey []byte `db:"public_key"`
|
AAGUID uuid.NullUUID `db:"aaguid"`
|
||||||
AttestationType string `db:"attestation_type"`
|
AttestationType string `db:"attestation_type"`
|
||||||
Transport string `db:"transport"`
|
Transport string `db:"transport"`
|
||||||
AAGUID uuid.NullUUID `db:"aaguid"`
|
|
||||||
SignCount uint32 `db:"sign_count"`
|
SignCount uint32 `db:"sign_count"`
|
||||||
CloneWarning bool `db:"clone_warning"`
|
CloneWarning bool `db:"clone_warning"`
|
||||||
|
PublicKey []byte `db:"public_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSignInInfo adjusts the values of the WebAuthnDevice after a sign in.
|
// UpdateSignInInfo adjusts the values of the WebAuthnDevice after a sign in.
|
||||||
|
@ -172,8 +177,8 @@ func (d *WebAuthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastUsed provides LastUsedAt as a *time.Time instead of sql.NullTime.
|
// DataValueLastUsedAt provides LastUsedAt as a *time.Time instead of sql.NullTime.
|
||||||
func (d *WebAuthnDevice) LastUsed() *time.Time {
|
func (d *WebAuthnDevice) DataValueLastUsedAt() *time.Time {
|
||||||
if d.LastUsedAt.Valid {
|
if d.LastUsedAt.Valid {
|
||||||
value := time.Unix(d.LastUsedAt.Time.Unix(), int64(d.LastUsedAt.Time.Nanosecond()))
|
value := time.Unix(d.LastUsedAt.Time.Unix(), int64(d.LastUsedAt.Time.Nanosecond()))
|
||||||
|
|
||||||
|
@ -183,22 +188,43 @@ func (d *WebAuthnDevice) LastUsed() *time.Time {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToData converts this WebAuthnDevice into the data format for exporting etc.
|
// DataValueAAGUID provides AAGUID as a *string instead of uuid.NullUUID.
|
||||||
|
func (d *WebAuthnDevice) DataValueAAGUID() *string {
|
||||||
|
if d.AAGUID.Valid {
|
||||||
|
value := d.AAGUID.UUID.String()
|
||||||
|
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *WebAuthnDevice) ToData() WebAuthnDeviceData {
|
func (d *WebAuthnDevice) ToData() WebAuthnDeviceData {
|
||||||
return WebAuthnDeviceData{
|
o := WebAuthnDeviceData{
|
||||||
|
ID: d.ID,
|
||||||
CreatedAt: d.CreatedAt,
|
CreatedAt: d.CreatedAt,
|
||||||
LastUsedAt: d.LastUsed(),
|
LastUsedAt: d.DataValueLastUsedAt(),
|
||||||
RPID: d.RPID,
|
RPID: d.RPID,
|
||||||
Username: d.Username,
|
Username: d.Username,
|
||||||
Description: d.Description,
|
Description: d.Description,
|
||||||
KID: d.KID.String(),
|
KID: d.KID.String(),
|
||||||
PublicKey: base64.StdEncoding.EncodeToString(d.PublicKey),
|
AAGUID: d.DataValueAAGUID(),
|
||||||
AttestationType: d.AttestationType,
|
AttestationType: d.AttestationType,
|
||||||
Transport: d.Transport,
|
|
||||||
AAGUID: d.AAGUID.UUID.String(),
|
|
||||||
SignCount: d.SignCount,
|
SignCount: d.SignCount,
|
||||||
CloneWarning: d.CloneWarning,
|
CloneWarning: d.CloneWarning,
|
||||||
|
PublicKey: base64.StdEncoding.EncodeToString(d.PublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.Transport != "" {
|
||||||
|
o.Transports = strings.Split(d.Transport, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the WebAuthnDevice in a JSON friendly manner.
|
||||||
|
func (d *WebAuthnDevice) MarshalJSON() (data []byte, err error) {
|
||||||
|
return json.Marshal(d.ToData())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalYAML marshals this model into YAML.
|
// MarshalYAML marshals this model into YAML.
|
||||||
|
@ -220,12 +246,14 @@ func (d *WebAuthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||||
|
|
||||||
var aaguid uuid.UUID
|
var aaguid uuid.UUID
|
||||||
|
|
||||||
if aaguid, err = uuid.Parse(o.AAGUID); err != nil {
|
if o.AAGUID != nil {
|
||||||
return err
|
if aaguid, err = uuid.Parse(*o.AAGUID); err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if aaguid.ID() != 0 {
|
if aaguid.ID() != 0 {
|
||||||
d.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
|
d.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var kid []byte
|
var kid []byte
|
||||||
|
@ -241,7 +269,7 @@ func (d *WebAuthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||||
d.Username = o.Username
|
d.Username = o.Username
|
||||||
d.Description = o.Description
|
d.Description = o.Description
|
||||||
d.AttestationType = o.AttestationType
|
d.AttestationType = o.AttestationType
|
||||||
d.Transport = o.Transport
|
d.Transport = strings.Join(o.Transports, ",")
|
||||||
d.SignCount = o.SignCount
|
d.SignCount = o.SignCount
|
||||||
d.CloneWarning = o.CloneWarning
|
d.CloneWarning = o.CloneWarning
|
||||||
|
|
||||||
|
@ -254,18 +282,62 @@ func (d *WebAuthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||||
|
|
||||||
// WebAuthnDeviceData represents a WebAuthn Device in the database storage.
|
// WebAuthnDeviceData represents a WebAuthn Device in the database storage.
|
||||||
type WebAuthnDeviceData struct {
|
type WebAuthnDeviceData struct {
|
||||||
CreatedAt time.Time `yaml:"created_at"`
|
ID int `json:"id" yaml:"-"`
|
||||||
LastUsedAt *time.Time `yaml:"last_used_at"`
|
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
|
||||||
RPID string `yaml:"rpid"`
|
LastUsedAt *time.Time `json:"last_used_at,omitempty" yaml:"last_used_at,omitempty"`
|
||||||
Username string `yaml:"username"`
|
RPID string `json:"rpid" yaml:"rpid"`
|
||||||
Description string `yaml:"description"`
|
Username string `json:"-" yaml:"username"`
|
||||||
KID string `yaml:"kid"`
|
Description string `json:"description" yaml:"description"`
|
||||||
PublicKey string `yaml:"public_key"`
|
KID string `json:"kid" yaml:"kid"`
|
||||||
AttestationType string `yaml:"attestation_type"`
|
AAGUID *string `json:"aaguid,omitempty" yaml:"aaguid,omitempty"`
|
||||||
Transport string `yaml:"transport"`
|
AttestationType string `json:"attestation_type" yaml:"attestation_type"`
|
||||||
AAGUID string `yaml:"aaguid"`
|
Transports []string `json:"transports" yaml:"transports"`
|
||||||
SignCount uint32 `yaml:"sign_count"`
|
SignCount uint32 `json:"sign_count" yaml:"sign_count"`
|
||||||
CloneWarning bool `yaml:"clone_warning"`
|
CloneWarning bool `json:"clone_warning" yaml:"clone_warning"`
|
||||||
|
PublicKey string `json:"public_key" yaml:"public_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *WebAuthnDeviceData) ToDevice() (device *WebAuthnDevice, err error) {
|
||||||
|
device = &WebAuthnDevice{
|
||||||
|
CreatedAt: d.CreatedAt,
|
||||||
|
RPID: d.RPID,
|
||||||
|
Username: d.Username,
|
||||||
|
Description: d.Description,
|
||||||
|
AttestationType: d.AttestationType,
|
||||||
|
Transport: strings.Join(d.Transports, ","),
|
||||||
|
SignCount: d.SignCount,
|
||||||
|
CloneWarning: d.CloneWarning,
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.PublicKey, err = base64.StdEncoding.DecodeString(d.PublicKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var aaguid uuid.UUID
|
||||||
|
|
||||||
|
if d.AAGUID != nil {
|
||||||
|
if aaguid, err = uuid.Parse(*d.AAGUID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if aaguid.ID() != 0 {
|
||||||
|
device.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid []byte
|
||||||
|
|
||||||
|
if kid, err = base64.StdEncoding.DecodeString(d.KID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
device.KID = NewBase64(kid)
|
||||||
|
|
||||||
|
if d.LastUsedAt != nil {
|
||||||
|
device.LastUsedAt = sql.NullTime{Valid: true, Time: *d.LastUsedAt}
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnDeviceExport represents a WebAuthnDevice export file.
|
// WebAuthnDeviceExport represents a WebAuthnDevice export file.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export enum SecondFactorMethod {
|
export enum SecondFactorMethod {
|
||||||
TOTP = 1,
|
TOTP = 1,
|
||||||
Webauthn,
|
WebAuthn,
|
||||||
MobilePush,
|
MobilePush,
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ export enum AttestationResult {
|
||||||
FailureSyntax,
|
FailureSyntax,
|
||||||
FailureSupport,
|
FailureSupport,
|
||||||
FailureUnknown,
|
FailureUnknown,
|
||||||
FailureWebauthnNotSupported,
|
FailureWebAuthnNotSupported,
|
||||||
FailureToken,
|
FailureToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ export enum AssertionResult {
|
||||||
FailureSyntax,
|
FailureSyntax,
|
||||||
FailureUnknown,
|
FailureUnknown,
|
||||||
FailureUnknownSecurity,
|
FailureUnknownSecurity,
|
||||||
FailureWebauthnNotSupported,
|
FailureWebAuthnNotSupported,
|
||||||
FailureChallenge,
|
FailureChallenge,
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export function toEnum(method: Method2FA): SecondFactorMethod {
|
||||||
case "totp":
|
case "totp":
|
||||||
return SecondFactorMethod.TOTP;
|
return SecondFactorMethod.TOTP;
|
||||||
case "webauthn":
|
case "webauthn":
|
||||||
return SecondFactorMethod.Webauthn;
|
return SecondFactorMethod.WebAuthn;
|
||||||
case "mobile_push":
|
case "mobile_push":
|
||||||
return SecondFactorMethod.MobilePush;
|
return SecondFactorMethod.MobilePush;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ export function toString(method: SecondFactorMethod): Method2FA {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case SecondFactorMethod.TOTP:
|
case SecondFactorMethod.TOTP:
|
||||||
return "totp";
|
return "totp";
|
||||||
case SecondFactorMethod.Webauthn:
|
case SecondFactorMethod.WebAuthn:
|
||||||
return "webauthn";
|
return "webauthn";
|
||||||
case SecondFactorMethod.MobilePush:
|
case SecondFactorMethod.MobilePush:
|
||||||
return "mobile_push";
|
return "mobile_push";
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
PublicKeyCredentialJSON,
|
PublicKeyCredentialJSON,
|
||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
PublicKeyCredentialRequestOptionsStatus,
|
PublicKeyCredentialRequestOptionsStatus,
|
||||||
} from "@models/Webauthn";
|
} from "@models/WebAuthn";
|
||||||
import {
|
import {
|
||||||
OptionalDataServiceResponse,
|
OptionalDataServiceResponse,
|
||||||
ServiceResponse,
|
ServiceResponse,
|
|
@ -9,9 +9,9 @@ import { IdentityToken } from "@constants/SearchParams";
|
||||||
import { useNotifications } from "@hooks/NotificationsContext";
|
import { useNotifications } from "@hooks/NotificationsContext";
|
||||||
import { useQueryParam } from "@hooks/QueryParam";
|
import { useQueryParam } from "@hooks/QueryParam";
|
||||||
import LoginLayout from "@layouts/LoginLayout";
|
import LoginLayout from "@layouts/LoginLayout";
|
||||||
import { AttestationResult } from "@models/Webauthn";
|
import { AttestationResult } from "@models/WebAuthn";
|
||||||
import { FirstFactorPath } from "@services/Api";
|
import { FirstFactorPath } from "@services/Api";
|
||||||
import { performAttestationCeremony } from "@services/Webauthn";
|
import { performAttestationCeremony } from "@services/WebAuthn";
|
||||||
|
|
||||||
const RegisterWebAuthn = function () {
|
const RegisterWebAuthn = function () {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
@ -53,7 +53,7 @@ const RegisterWebAuthn = function () {
|
||||||
"The attestation challenge was rejected as malformed or incompatible by your browser.",
|
"The attestation challenge was rejected as malformed or incompatible by your browser.",
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case AttestationResult.FailureWebauthnNotSupported:
|
case AttestationResult.FailureWebAuthnNotSupported:
|
||||||
createErrorNotification("Your browser does not support the WebAuthN protocol.");
|
createErrorNotification("Your browser does not support the WebAuthN protocol.");
|
||||||
break;
|
break;
|
||||||
case AttestationResult.FailureUserConsent:
|
case AttestationResult.FailureUserConsent:
|
||||||
|
|
|
@ -143,7 +143,7 @@ const LoginPortal = function (props: Props) {
|
||||||
if (configuration.available_methods.size === 0) {
|
if (configuration.available_methods.size === 0) {
|
||||||
redirect(AuthenticatedRoute, false);
|
redirect(AuthenticatedRoute, false);
|
||||||
} else {
|
} else {
|
||||||
if (userInfo.method === SecondFactorMethod.Webauthn) {
|
if (userInfo.method === SecondFactorMethod.WebAuthn) {
|
||||||
redirect(`${SecondFactorRoute}${SecondFactorWebAuthnSubRoute}`);
|
redirect(`${SecondFactorRoute}${SecondFactorWebAuthnSubRoute}`);
|
||||||
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
|
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
|
||||||
redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
|
redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
|
||||||
|
|
|
@ -39,12 +39,12 @@ const MethodSelectionDialog = function (props: Props) {
|
||||||
onClick={() => props.onClick(SecondFactorMethod.TOTP)}
|
onClick={() => props.onClick(SecondFactorMethod.TOTP)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{props.methods.has(SecondFactorMethod.Webauthn) && props.webauthnSupported ? (
|
{props.methods.has(SecondFactorMethod.WebAuthn) && props.webauthnSupported ? (
|
||||||
<MethodItem
|
<MethodItem
|
||||||
id="webauthn-option"
|
id="webauthn-option"
|
||||||
method={translate("Security Key - WebAuthN")}
|
method={translate("Security Key - WebAuthN")}
|
||||||
icon={<FingerTouchIcon size={32} />}
|
icon={<FingerTouchIcon size={32} />}
|
||||||
onClick={() => props.onClick(SecondFactorMethod.Webauthn)}
|
onClick={() => props.onClick(SecondFactorMethod.WebAuthn)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{props.methods.has(SecondFactorMethod.MobilePush) ? (
|
{props.methods.has(SecondFactorMethod.MobilePush) ? (
|
||||||
|
|
|
@ -19,11 +19,11 @@ import { UserInfo } from "@models/UserInfo";
|
||||||
import { initiateTOTPRegistrationProcess, initiateWebAuthnRegistrationProcess } from "@services/RegisterDevice";
|
import { initiateTOTPRegistrationProcess, initiateWebAuthnRegistrationProcess } from "@services/RegisterDevice";
|
||||||
import { AuthenticationLevel } from "@services/State";
|
import { AuthenticationLevel } from "@services/State";
|
||||||
import { setPreferred2FAMethod } from "@services/UserInfo";
|
import { setPreferred2FAMethod } from "@services/UserInfo";
|
||||||
import { isWebAuthnSupported } from "@services/Webauthn";
|
import { isWebAuthnSupported } from "@services/WebAuthn";
|
||||||
import MethodSelectionDialog from "@views/LoginPortal/SecondFactor/MethodSelectionDialog";
|
import MethodSelectionDialog from "@views/LoginPortal/SecondFactor/MethodSelectionDialog";
|
||||||
import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswordMethod";
|
import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswordMethod";
|
||||||
import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod";
|
import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod";
|
||||||
import WebauthnMethod from "@views/LoginPortal/SecondFactor/WebauthnMethod";
|
import WebAuthnMethod from "@views/LoginPortal/SecondFactor/WebAuthnMethod";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
authenticationLevel: AuthenticationLevel;
|
authenticationLevel: AuthenticationLevel;
|
||||||
|
@ -41,12 +41,12 @@ const SecondFactorForm = function (props: Props) {
|
||||||
const [methodSelectionOpen, setMethodSelectionOpen] = useState(false);
|
const [methodSelectionOpen, setMethodSelectionOpen] = useState(false);
|
||||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||||
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
||||||
const [webauthnSupported, setWebauthnSupported] = useState(false);
|
const [stateWebAuthnSupported, setStateWebAuthnSupported] = useState(false);
|
||||||
const { t: translate } = useTranslation();
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWebauthnSupported(isWebAuthnSupported());
|
setStateWebAuthnSupported(isWebAuthnSupported());
|
||||||
}, [setWebauthnSupported]);
|
}, [setStateWebAuthnSupported]);
|
||||||
|
|
||||||
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>) => {
|
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
|
@ -90,7 +90,7 @@ const SecondFactorForm = function (props: Props) {
|
||||||
<MethodSelectionDialog
|
<MethodSelectionDialog
|
||||||
open={methodSelectionOpen}
|
open={methodSelectionOpen}
|
||||||
methods={props.configuration.available_methods}
|
methods={props.configuration.available_methods}
|
||||||
webauthnSupported={webauthnSupported}
|
webauthnSupported={stateWebAuthnSupported}
|
||||||
onClose={() => setMethodSelectionOpen(false)}
|
onClose={() => setMethodSelectionOpen(false)}
|
||||||
onClick={handleMethodSelected}
|
onClick={handleMethodSelected}
|
||||||
/>
|
/>
|
||||||
|
@ -126,10 +126,10 @@ const SecondFactorForm = function (props: Props) {
|
||||||
<Route
|
<Route
|
||||||
path={SecondFactorWebAuthnSubRoute}
|
path={SecondFactorWebAuthnSubRoute}
|
||||||
element={
|
element={
|
||||||
<WebauthnMethod
|
<WebAuthnMethod
|
||||||
id="webauthn-method"
|
id="webauthn-method"
|
||||||
authenticationLevel={props.authenticationLevel}
|
authenticationLevel={props.authenticationLevel}
|
||||||
// Whether the user has a Webauthn device registered already
|
// Whether the user has a WebAuthn device registered already
|
||||||
registered={props.userInfo.has_webauthn}
|
registered={props.userInfo.has_webauthn}
|
||||||
onRegisterClick={initiateRegistration(initiateWebAuthnRegistrationProcess)}
|
onRegisterClick={initiateRegistration(initiateWebAuthnRegistrationProcess)}
|
||||||
onSignInError={(err) => createErrorNotification(err.message)}
|
onSignInError={(err) => createErrorNotification(err.message)}
|
||||||
|
|
|
@ -11,13 +11,13 @@ import { useIsMountedRef } from "@hooks/Mounted";
|
||||||
import { useQueryParam } from "@hooks/QueryParam";
|
import { useQueryParam } from "@hooks/QueryParam";
|
||||||
import { useTimer } from "@hooks/Timer";
|
import { useTimer } from "@hooks/Timer";
|
||||||
import { useWorkflow } from "@hooks/Workflow";
|
import { useWorkflow } from "@hooks/Workflow";
|
||||||
import { AssertionResult } from "@models/Webauthn";
|
import { AssertionResult } from "@models/WebAuthn";
|
||||||
import { AuthenticationLevel } from "@services/State";
|
import { AuthenticationLevel } from "@services/State";
|
||||||
import {
|
import {
|
||||||
getAssertionPublicKeyCredentialResult,
|
getAssertionPublicKeyCredentialResult,
|
||||||
getAssertionRequestOptions,
|
getAssertionRequestOptions,
|
||||||
postAssertionPublicKeyCredentialResult,
|
postAssertionPublicKeyCredentialResult,
|
||||||
} from "@services/Webauthn";
|
} from "@services/WebAuthn";
|
||||||
import IconWithContext from "@views/LoginPortal/SecondFactor/IconWithContext";
|
import IconWithContext from "@views/LoginPortal/SecondFactor/IconWithContext";
|
||||||
import MethodContainer, { State as MethodContainerState } from "@views/LoginPortal/SecondFactor/MethodContainer";
|
import MethodContainer, { State as MethodContainerState } from "@views/LoginPortal/SecondFactor/MethodContainer";
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export interface Props {
|
||||||
onSignInSuccess: (redirectURL: string | undefined) => void;
|
onSignInSuccess: (redirectURL: string | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebauthnMethod = function (props: Props) {
|
const WebAuthnMethod = function (props: Props) {
|
||||||
const signInTimeout = 30;
|
const signInTimeout = 30;
|
||||||
const [state, setState] = useState(State.WaitTouch);
|
const [state, setState] = useState(State.WaitTouch);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
@ -86,7 +86,7 @@ const WebauthnMethod = function (props: Props) {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case AssertionResult.FailureWebauthnNotSupported:
|
case AssertionResult.FailureWebAuthnNotSupported:
|
||||||
onSignInErrorCallback(new Error("Your browser does not support the WebAuthN protocol."));
|
onSignInErrorCallback(new Error("Your browser does not support the WebAuthN protocol."));
|
||||||
break;
|
break;
|
||||||
case AssertionResult.FailureUnknownSecurity:
|
case AssertionResult.FailureUnknownSecurity:
|
||||||
|
@ -179,7 +179,7 @@ const WebauthnMethod = function (props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WebauthnMethod;
|
export default WebAuthnMethod;
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
icon: {
|
icon: {
|
Loading…
Reference in New Issue