mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 01:03:35 +00:00
[EC-598] feat: only show fallback options if supported
This commit is contained in:
@@ -10,7 +10,8 @@ export class WebauthnUtils {
|
|||||||
static mapCredentialCreationOptions(
|
static mapCredentialCreationOptions(
|
||||||
options: CredentialCreationOptions,
|
options: CredentialCreationOptions,
|
||||||
origin: string,
|
origin: string,
|
||||||
sameOriginWithAncestors: boolean
|
sameOriginWithAncestors: boolean,
|
||||||
|
fallbackSupported: boolean
|
||||||
): CreateCredentialParams {
|
): CreateCredentialParams {
|
||||||
const keyOptions = options.publicKey;
|
const keyOptions = options.publicKey;
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ export class WebauthnUtils {
|
|||||||
},
|
},
|
||||||
timeout: keyOptions.timeout,
|
timeout: keyOptions.timeout,
|
||||||
sameOriginWithAncestors,
|
sameOriginWithAncestors,
|
||||||
|
fallbackSupported,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,8 @@ export class WebauthnUtils {
|
|||||||
static mapCredentialRequestOptions(
|
static mapCredentialRequestOptions(
|
||||||
options: CredentialRequestOptions,
|
options: CredentialRequestOptions,
|
||||||
origin: string,
|
origin: string,
|
||||||
sameOriginWithAncestors: boolean
|
sameOriginWithAncestors: boolean,
|
||||||
|
fallbackSupported: boolean
|
||||||
): AssertCredentialParams {
|
): AssertCredentialParams {
|
||||||
const keyOptions = options.publicKey;
|
const keyOptions = options.publicKey;
|
||||||
|
|
||||||
@@ -108,6 +111,7 @@ export class WebauthnUtils {
|
|||||||
userVerification: keyOptions.userVerification,
|
userVerification: keyOptions.userVerification,
|
||||||
timeout: keyOptions.timeout,
|
timeout: keyOptions.timeout,
|
||||||
sameOriginWithAncestors,
|
sameOriginWithAncestors,
|
||||||
|
fallbackSupported,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,22 @@ navigator.credentials.create = async (
|
|||||||
options?: CredentialCreationOptions,
|
options?: CredentialCreationOptions,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<Credential> => {
|
): Promise<Credential> => {
|
||||||
|
const fallbackSupported =
|
||||||
|
(options?.publicKey?.authenticatorSelection.authenticatorAttachment === "platform" &&
|
||||||
|
browserNativeWebauthnPlatformAuthenticatorSupport) ||
|
||||||
|
(options?.publicKey?.authenticatorSelection.authenticatorAttachment !== "platform" &&
|
||||||
|
browserNativeWebauthnSupport);
|
||||||
try {
|
try {
|
||||||
const response = await messenger.request(
|
const response = await messenger.request(
|
||||||
{
|
{
|
||||||
type: MessageType.CredentialCreationRequest,
|
type: MessageType.CredentialCreationRequest,
|
||||||
// TODO: Fix sameOriginWithAncestors!
|
// TODO: Fix sameOriginWithAncestors!
|
||||||
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin, true),
|
data: WebauthnUtils.mapCredentialCreationOptions(
|
||||||
|
options,
|
||||||
|
window.location.origin,
|
||||||
|
true,
|
||||||
|
fallbackSupported
|
||||||
|
),
|
||||||
},
|
},
|
||||||
abortController
|
abortController
|
||||||
);
|
);
|
||||||
@@ -71,7 +81,7 @@ navigator.credentials.create = async (
|
|||||||
|
|
||||||
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
|
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && error.fallbackRequested) {
|
if (error && error.fallbackRequested && fallbackSupported) {
|
||||||
return await browserCredentials.create(options);
|
return await browserCredentials.create(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +93,18 @@ navigator.credentials.get = async (
|
|||||||
options?: CredentialRequestOptions,
|
options?: CredentialRequestOptions,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<Credential> => {
|
): Promise<Credential> => {
|
||||||
|
const fallbackSupported = browserNativeWebauthnSupport;
|
||||||
try {
|
try {
|
||||||
const response = await messenger.request(
|
const response = await messenger.request(
|
||||||
{
|
{
|
||||||
type: MessageType.CredentialGetRequest,
|
type: MessageType.CredentialGetRequest,
|
||||||
// TODO: Fix sameOriginWithAncestors!
|
// TODO: Fix sameOriginWithAncestors!
|
||||||
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin, true),
|
data: WebauthnUtils.mapCredentialRequestOptions(
|
||||||
|
options,
|
||||||
|
window.location.origin,
|
||||||
|
true,
|
||||||
|
fallbackSupported
|
||||||
|
),
|
||||||
},
|
},
|
||||||
abortController
|
abortController
|
||||||
);
|
);
|
||||||
@@ -99,7 +115,7 @@ navigator.credentials.get = async (
|
|||||||
|
|
||||||
return WebauthnUtils.mapCredentialAssertResult(response.result);
|
return WebauthnUtils.mapCredentialAssertResult(response.result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && error.fallbackRequested) {
|
if (error && error.fallbackRequested && fallbackSupported) {
|
||||||
return await browserCredentials.get(options);
|
return await browserCredentials.get(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,12 @@
|
|||||||
You do not have a matching login for this site.
|
You do not have a matching login for this site.
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="abort(true)">
|
<button
|
||||||
|
*ngIf="data.fallbackSupported"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
(click)="abort(true)"
|
||||||
|
>
|
||||||
Use browser built-in
|
Use browser built-in
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="abort(false)">Abort</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="abort(false)">Abort</button>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
interface ViewData {
|
interface ViewData {
|
||||||
message: BrowserFido2Message;
|
message: BrowserFido2Message;
|
||||||
showUnsupportedVerification: boolean;
|
showUnsupportedVerification: boolean;
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -108,11 +109,10 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
return {
|
return {
|
||||||
message,
|
message,
|
||||||
showUnsupportedVerification:
|
showUnsupportedVerification:
|
||||||
(message.type === "ConfirmNewCredentialRequest" ||
|
"userVerification" in message &&
|
||||||
message.type === "ConfirmNewNonDiscoverableCredentialRequest" ||
|
|
||||||
message.type === "PickCredentialRequest") &&
|
|
||||||
message.userVerification &&
|
message.userVerification &&
|
||||||
!(await this.passwordRepromptService.enabled()),
|
!(await this.passwordRepromptService.enabled()),
|
||||||
|
fallbackSupported: "fallbackSupported" in message && message.fallbackSupported,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
takeUntil(this.destroy$)
|
takeUntil(this.destroy$)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type BrowserFido2Message = { sessionId: string } & (
|
|||||||
type: "PickCredentialRequest";
|
type: "PickCredentialRequest";
|
||||||
cipherIds: string[];
|
cipherIds: string[];
|
||||||
userVerification: boolean;
|
userVerification: boolean;
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "PickCredentialResponse";
|
type: "PickCredentialResponse";
|
||||||
@@ -59,6 +60,7 @@ export type BrowserFido2Message = { sessionId: string } & (
|
|||||||
credentialName: string;
|
credentialName: string;
|
||||||
userName: string;
|
userName: string;
|
||||||
userVerification: boolean;
|
userVerification: boolean;
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "ConfirmNewCredentialResponse";
|
type: "ConfirmNewCredentialResponse";
|
||||||
@@ -69,6 +71,7 @@ export type BrowserFido2Message = { sessionId: string } & (
|
|||||||
credentialName: string;
|
credentialName: string;
|
||||||
userName: string;
|
userName: string;
|
||||||
userVerification: boolean;
|
userVerification: boolean;
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "ConfirmNewNonDiscoverableCredentialResponse";
|
type: "ConfirmNewNonDiscoverableCredentialResponse";
|
||||||
@@ -78,9 +81,11 @@ export type BrowserFido2Message = { sessionId: string } & (
|
|||||||
| {
|
| {
|
||||||
type: "InformExcludedCredentialRequest";
|
type: "InformExcludedCredentialRequest";
|
||||||
existingCipherIds: string[];
|
existingCipherIds: string[];
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "InformCredentialNotFoundRequest";
|
type: "InformCredentialNotFoundRequest";
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "AbortRequest";
|
type: "AbortRequest";
|
||||||
@@ -94,17 +99,29 @@ export type BrowserFido2Message = { sessionId: string } & (
|
|||||||
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
|
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
|
||||||
constructor(private popupUtilsService: PopupUtilsService) {}
|
constructor(private popupUtilsService: PopupUtilsService) {}
|
||||||
|
|
||||||
async newSession(abortController?: AbortController): Promise<Fido2UserInterfaceSession> {
|
async newSession(
|
||||||
return await BrowserFido2UserInterfaceSession.create(this.popupUtilsService, abortController);
|
fallbackSupported: boolean,
|
||||||
|
abortController?: AbortController
|
||||||
|
): Promise<Fido2UserInterfaceSession> {
|
||||||
|
return await BrowserFido2UserInterfaceSession.create(
|
||||||
|
this.popupUtilsService,
|
||||||
|
fallbackSupported,
|
||||||
|
abortController
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSession {
|
export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSession {
|
||||||
static async create(
|
static async create(
|
||||||
popupUtilsService: PopupUtilsService,
|
popupUtilsService: PopupUtilsService,
|
||||||
|
fallbackSupported: boolean,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<BrowserFido2UserInterfaceSession> {
|
): Promise<BrowserFido2UserInterfaceSession> {
|
||||||
return new BrowserFido2UserInterfaceSession(popupUtilsService, abortController);
|
return new BrowserFido2UserInterfaceSession(
|
||||||
|
popupUtilsService,
|
||||||
|
fallbackSupported,
|
||||||
|
abortController
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sendMessage(msg: BrowserFido2Message) {
|
static sendMessage(msg: BrowserFido2Message) {
|
||||||
@@ -121,6 +138,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private readonly popupUtilsService: PopupUtilsService,
|
private readonly popupUtilsService: PopupUtilsService,
|
||||||
|
private readonly fallbackSupported: boolean,
|
||||||
readonly abortController = new AbortController(),
|
readonly abortController = new AbortController(),
|
||||||
readonly sessionId = Utils.newGuid()
|
readonly sessionId = Utils.newGuid()
|
||||||
) {
|
) {
|
||||||
@@ -182,6 +200,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
cipherIds,
|
cipherIds,
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
userVerification,
|
userVerification,
|
||||||
|
fallbackSupported: this.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.send(data);
|
await this.send(data);
|
||||||
@@ -201,6 +220,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
credentialName,
|
credentialName,
|
||||||
userName,
|
userName,
|
||||||
userVerification,
|
userVerification,
|
||||||
|
fallbackSupported: this.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.send(data);
|
await this.send(data);
|
||||||
@@ -220,6 +240,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
credentialName,
|
credentialName,
|
||||||
userName,
|
userName,
|
||||||
userVerification,
|
userVerification,
|
||||||
|
fallbackSupported: this.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.send(data);
|
await this.send(data);
|
||||||
@@ -233,6 +254,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
type: "InformExcludedCredentialRequest",
|
type: "InformExcludedCredentialRequest",
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
existingCipherIds,
|
existingCipherIds,
|
||||||
|
fallbackSupported: this.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.send(data);
|
await this.send(data);
|
||||||
@@ -243,6 +265,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
const data: BrowserFido2Message = {
|
const data: BrowserFido2Message = {
|
||||||
type: "InformCredentialNotFoundRequest",
|
type: "InformCredentialNotFoundRequest",
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
|
fallbackSupported: this.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.send(data);
|
await this.send(data);
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ export interface Fido2AuthenticatorMakeCredentialsParams {
|
|||||||
/** The effective resident key requirement for credential creation, a Boolean value determined by the client. */
|
/** The effective resident key requirement for credential creation, a Boolean value determined by the client. */
|
||||||
requireResidentKey: boolean;
|
requireResidentKey: boolean;
|
||||||
requireUserVerification: boolean;
|
requireUserVerification: boolean;
|
||||||
|
/** Forwarded to user interface */
|
||||||
|
fallbackSupported: boolean;
|
||||||
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
|
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
|
||||||
// requireUserPresence: true; // Always required
|
// requireUserPresence: true; // Always required
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,8 @@ export interface Fido2AuthenticatorGetAssertionParams {
|
|||||||
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
|
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
|
||||||
// requireUserPresence: boolean; // Always required
|
// requireUserPresence: boolean; // Always required
|
||||||
extensions: unknown;
|
extensions: unknown;
|
||||||
|
/** Forwarded to user interface */
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Fido2AuthenticatorGetAssertionResult {
|
export interface Fido2AuthenticatorGetAssertionResult {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface CreateCredentialParams {
|
|||||||
id: string; // b64 encoded
|
id: string; // b64 encoded
|
||||||
displayName: string;
|
displayName: string;
|
||||||
};
|
};
|
||||||
|
fallbackSupported: boolean;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ export interface AssertCredentialParams {
|
|||||||
userVerification?: UserVerification;
|
userVerification?: UserVerification;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
sameOriginWithAncestors: boolean;
|
sameOriginWithAncestors: boolean;
|
||||||
|
fallbackSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AssertCredentialResult {
|
export interface AssertCredentialResult {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ export interface PickCredentialParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Fido2UserInterfaceService {
|
export abstract class Fido2UserInterfaceService {
|
||||||
newSession: (abortController?: AbortController) => Promise<Fido2UserInterfaceSession>;
|
newSession: (
|
||||||
|
fallbackSupported: boolean,
|
||||||
|
abortController?: AbortController
|
||||||
|
) => Promise<Fido2UserInterfaceSession>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Fido2UserInterfaceSession {
|
export abstract class Fido2UserInterfaceSession {
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
params: Fido2AuthenticatorMakeCredentialsParams,
|
params: Fido2AuthenticatorMakeCredentialsParams,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<Fido2AuthenticatorMakeCredentialResult> {
|
): Promise<Fido2AuthenticatorMakeCredentialResult> {
|
||||||
const userInterfaceSession = await this.userInterface.newSession(abortController);
|
const userInterfaceSession = await this.userInterface.newSession(
|
||||||
|
params.fallbackSupported,
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
|
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
|
||||||
@@ -211,7 +214,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
params: Fido2AuthenticatorGetAssertionParams,
|
params: Fido2AuthenticatorGetAssertionParams,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<Fido2AuthenticatorGetAssertionResult> {
|
): Promise<Fido2AuthenticatorGetAssertionResult> {
|
||||||
const userInterfaceSession = await this.userInterface.newSession(abortController);
|
const userInterfaceSession = await this.userInterface.newSession(
|
||||||
|
params.fallbackSupported,
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
|||||||
id: Fido2Utils.stringToBuffer(params.user.id),
|
id: Fido2Utils.stringToBuffer(params.user.id),
|
||||||
displayName: params.user.displayName,
|
displayName: params.user.displayName,
|
||||||
},
|
},
|
||||||
|
fallbackSupported: params.fallbackSupported,
|
||||||
};
|
};
|
||||||
let makeCredentialResult;
|
let makeCredentialResult;
|
||||||
try {
|
try {
|
||||||
@@ -226,6 +227,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
|||||||
hash: clientDataHash,
|
hash: clientDataHash,
|
||||||
allowCredentialDescriptorList,
|
allowCredentialDescriptorList,
|
||||||
extensions: {},
|
extensions: {},
|
||||||
|
fallbackSupported: params.fallbackSupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
let getAssertionResult;
|
let getAssertionResult;
|
||||||
|
|||||||
Reference in New Issue
Block a user