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