mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
parent
66d93f6911
commit
87374dfa89
5 changed files with 252 additions and 84 deletions
|
|
@ -147,9 +147,41 @@
|
|||
"message": "Invalid URL provided.",
|
||||
"description": "Invalid URL provided."
|
||||
},
|
||||
"errorMessagePasskeysNonResidentKeysNotSupported": {
|
||||
"message": "Non-Resident Keys are not supported.",
|
||||
"description": "Non-Resident Keys are not supported."
|
||||
"errorMessagePasskeysOriginNotAllowed": {
|
||||
"message": "Origin is empty or not allowed.",
|
||||
"description": "Origin is empty or not allowed."
|
||||
},
|
||||
"errorMessagePasskeysDomainNotValid": {
|
||||
"message": "Effective domain is not a valid domain.",
|
||||
"description": "Effective domain is not a valid domain."
|
||||
},
|
||||
"errorMessagePasskeysDomainRpIdMismatch": {
|
||||
"message": "Origin and RP ID do not match.",
|
||||
"description": "Origin and RP ID do not match."
|
||||
},
|
||||
"errorMessagePasskeysNoSupportedAlgorithms": {
|
||||
"message": "No supported algorithms were provided.",
|
||||
"description": "No supported algorithms were provided."
|
||||
},
|
||||
"errorMessagePasskeysWaitforLifeTimer": {
|
||||
"message": "Wait for timer to expire.",
|
||||
"description": "Wait for timer to expire."
|
||||
},
|
||||
"errorMessagePasskeysUnknownError": {
|
||||
"message": "Unknown Passkeys error.",
|
||||
"description": "Unknown Passkeys error."
|
||||
},
|
||||
"errorMessagePasskeysInvalidChallenge": {
|
||||
"message": "Challenge is shorter than required minimum length.",
|
||||
"description": "Challenge is shorter than required minimum length."
|
||||
},
|
||||
"errorMessagePasskeysInvalidUserId": {
|
||||
"message": "user.id does not match the required length.",
|
||||
"description": "user.id does not match the required length."
|
||||
},
|
||||
"errorMessagePasskeysContextIsNotSecure": {
|
||||
"message": "Context is not secure.",
|
||||
"description": "Context is not secure."
|
||||
},
|
||||
"errorNotConnected": {
|
||||
"message": "Not connected to KeePassXC.",
|
||||
|
|
|
|||
|
|
@ -26,12 +26,21 @@ const kpErrors = {
|
|||
NO_GROUPS_FOUND: 16,
|
||||
CANNOT_CREATE_NEW_GROUP: 17,
|
||||
NO_VALID_UUID_PROVIDED: 18,
|
||||
ACCESS_TO_ALL_ENTRIES_DENIED: 19,
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED: 20,
|
||||
PASSKEYS_CREDENTIAL_IS_EXCLUDED: 21,
|
||||
PASSKEYS_REQUEST_CANCELED: 22,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION: 23,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY: 24,
|
||||
PASSKEYS_INVALID_URL_PROVIDED: 25,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED: 26,
|
||||
PASSKEYS_DOMAIN_IS_NOT_VALID: 27,
|
||||
PASSKEYS_DOMAIN_RPID_MISMATCH: 28,
|
||||
PASSKEYS_NO_SUPPORTED_ALGORITHMS: 29,
|
||||
PASSKEYS_WAIT_FOR_LIFETIMER: 30,
|
||||
PASSKEYS_UNKNOWN_ERROR: 31,
|
||||
PASSKEYS_INVALID_CHALLENGE: 32,
|
||||
PASSKEYS_INVALID_USER_ID: 33,
|
||||
|
||||
errorMessages: {
|
||||
0: { msg: tr('errorMessageUnknown') },
|
||||
|
|
@ -60,7 +69,14 @@ const kpErrors = {
|
|||
23: { msg: tr('errorMessagePasskeysInvalidUserVerification') },
|
||||
24: { msg: tr('errorMessagePasskeysEmptyPublicKey') },
|
||||
25: { msg: tr('errorMessagePasskeysInvalidUrlProvided') },
|
||||
26: { msg: tr('errorMessagePasskeysNonResidentKeysNotSupported') },
|
||||
26: { msg: tr('errorMessagePasskeysOriginNotAllowed') },
|
||||
27: { msg: tr('errorMessagePasskeysDomainNotValid') },
|
||||
28: { msg: tr('errorMessagePasskeysDomainRpIdMismatch') },
|
||||
29: { msg: tr('errorMessagePasskeysNoSupportedAlgorithms') },
|
||||
30: { msg: tr('errorMessagePasskeysWaitforLifeTimer') },
|
||||
31: { msg: tr('errorMessagePasskeysUnknownError') },
|
||||
32: { msg: tr('errorMessagePasskeysInvalidChallenge') },
|
||||
33: { msg: tr('errorMessagePasskeysInvalidUserId') },
|
||||
},
|
||||
|
||||
getError(errorCode) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
const PASSKEYS_NO_LOGINS_FOUND = 15;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 21;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 30;
|
||||
|
||||
// Contains already called method names
|
||||
const _called = {};
|
||||
_called.automaticRedetectCompleted = false;
|
||||
|
|
@ -806,35 +810,70 @@ kpxc.enablePasskeys = function() {
|
|||
passkeys.src = browser.runtime.getURL('content/passkeys.js');
|
||||
document.documentElement.appendChild(passkeys);
|
||||
|
||||
const startTimer = function(timeout) {
|
||||
return setTimeout(() => {
|
||||
throw new DOMException('lifetimeTimer has expired', 'NotAllowedError');
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
const stopTimer = function(lifetimeTimer) {
|
||||
if (lifetimeTimer) {
|
||||
clearTimeout(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
const letTimerRunOut = function (errorCode) {
|
||||
return (
|
||||
errorCode === PASSKEYS_WAIT_FOR_LIFETIMER ||
|
||||
errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED ||
|
||||
errorCode === PASSKEYS_NO_LOGINS_FOUND
|
||||
);
|
||||
};
|
||||
|
||||
const sendResponse = async function(command, publicKey, callback) {
|
||||
const lifetimeTimer = startTimer(publicKey?.timeout);
|
||||
|
||||
const ret = await sendMessage(command, [ publicKey, window.location.origin ]);
|
||||
if (ret) {
|
||||
let errorMessage;
|
||||
if (ret.response && ret.response.errorCode) {
|
||||
errorMessage = await sendMessage('get_error_message', ret.response.errorCode);
|
||||
// Do not create a notification for this error
|
||||
if (ret?.response?.errorCode !== PASSKEYS_WAIT_FOR_LIFETIMER) {
|
||||
kpxcUI.createNotification('error', errorMessage);
|
||||
}
|
||||
|
||||
if (letTimerRunOut(ret?.response?.errorCode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const responsePublicKey = callback(ret.response);
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(responsePublicKey, ret.response?.errorCode, errorMessage);
|
||||
stopTimer(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('kpxc-passkeys-request', async (ev) => {
|
||||
if (!window.isSecureContext) {
|
||||
kpxcUI.createNotification('error', tr('errorMessagePasskeysContextIsNotSecure'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.detail.action === 'passkeys_create') {
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialCreationOptions(ev.detail.publicKey);
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialCreationOptions(
|
||||
ev.detail.publicKey,
|
||||
ev.detail.sameOriginWithAncestors,
|
||||
);
|
||||
logDebug('publicKey', publicKey);
|
||||
|
||||
const ret = await sendMessage('passkeys_register', [ publicKey, window.location.origin ]);
|
||||
if (ret) {
|
||||
if (ret.response && ret.response.errorCode) {
|
||||
const errorMessage = await sendMessage('get_error_message', ret.response.errorCode);
|
||||
kpxcUI.createNotification('error', errorMessage);
|
||||
}
|
||||
|
||||
const responsePublicKey = kpxcPasskeysUtils.parsePublicKeyCredential(ret.response);
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(responsePublicKey);
|
||||
}
|
||||
await sendResponse('passkeys_register', publicKey, kpxcPasskeysUtils.parsePublicKeyCredential);
|
||||
} else if (ev.detail.action === 'passkeys_get') {
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialRequestOptions(ev.detail.publicKey);
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialRequestOptions(
|
||||
ev.detail.publicKey,
|
||||
ev.detail.sameOriginWithAncestors,
|
||||
);
|
||||
logDebug('publicKey', publicKey);
|
||||
|
||||
const ret = await sendMessage('passkeys_get', [ publicKey, window.location.origin ]);
|
||||
if (ret) {
|
||||
if (ret.response && ret.response.errorCode) {
|
||||
const errorMessage = await sendMessage('get_error_message', ret.response.errorCode);
|
||||
kpxcUI.createNotification('error', errorMessage);
|
||||
}
|
||||
|
||||
const responsePublicKey = kpxcPasskeysUtils.parseGetPublicKeyCredential(ret.response);
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(responsePublicKey);
|
||||
}
|
||||
await sendResponse('passkeys_get', publicKey, kpxcPasskeysUtils.parseGetPublicKeyCredential);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
const MINIMUM_TIMEOUT = 15000;
|
||||
const DEFAULT_TIMEOUT = 30000;
|
||||
const DISCOURAGED_TIMEOUT = 120000;
|
||||
|
||||
const stringToArrayBuffer = function(str) {
|
||||
const arr = Uint8Array.from(str, c => c.charCodeAt(0));
|
||||
return arr.buffer;
|
||||
|
|
@ -16,10 +20,17 @@ const arrayBufferToBase64 = function(buf) {
|
|||
return window.btoa(str).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
|
||||
};
|
||||
|
||||
// Error checks for both registration and authentication
|
||||
const checkErrors = function(pkOptions) {
|
||||
if (pkOptions.sameOriginWithAncestors !== undefined && pkOptions.sameOriginWithAncestors === false) {
|
||||
throw new DOMException('Cross-origin register is not allowed.', DOMException.NotAllowedError);
|
||||
const checkErrors = function(pkOptions, sameOriginWithAncestors) {
|
||||
if (!pkOptions) {
|
||||
throw new Error('No publicKey configuration options were provided');
|
||||
}
|
||||
|
||||
if (pkOptions.signal && pkOptions.signal.aborted) {
|
||||
throw new DOMException('Abort signalled', DOMException.AbortError);
|
||||
}
|
||||
|
||||
if (!sameOriginWithAncestors) {
|
||||
throw new DOMException('Cross-origin register or authentication is not allowed.', DOMException.NotAllowedError);
|
||||
}
|
||||
|
||||
if (pkOptions.challenge.length < 16) {
|
||||
|
|
@ -27,54 +38,43 @@ const checkErrors = function(pkOptions) {
|
|||
}
|
||||
};
|
||||
|
||||
const getTimeout = function(userVerification, timeout) {
|
||||
if (!timeout || Number(timeout) === 0 || isNaN(Number(timeout))) {
|
||||
return userVerification === 'discouraged' ? DISCOURAGED_TIMEOUT : DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Note: A suggested reasonable range for the timeout member of options is 15 seconds to 120 seconds.
|
||||
if (Number(timeout) < MINIMUM_TIMEOUT || Number(timeout) > DISCOURAGED_TIMEOUT) {
|
||||
return DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return Number(timeout);
|
||||
};
|
||||
|
||||
const kpxcPasskeysUtils = {};
|
||||
|
||||
// Sends response from KeePassXC back to the injected script
|
||||
kpxcPasskeysUtils.sendPasskeysResponse = function(publicKey) {
|
||||
const response = { publicKey: publicKey, fallback: kpxc.settings.passkeysFallback };
|
||||
kpxcPasskeysUtils.sendPasskeysResponse = function(publicKey, errorCode, errorMessage) {
|
||||
const response = errorCode
|
||||
? { errorCode: errorCode, errorMessage: errorMessage }
|
||||
: { publicKey: publicKey, fallback: kpxc.settings.passkeysFallback };
|
||||
const details = isFirefox() ? cloneInto(response, document.defaultView) : response;
|
||||
document.dispatchEvent(new CustomEvent('kpxc-passkeys-response', { detail: details }));
|
||||
};
|
||||
|
||||
// Create a new object with base64 strings for KeePassXC
|
||||
kpxcPasskeysUtils.buildCredentialCreationOptions = function(pkOptions) {
|
||||
kpxcPasskeysUtils.buildCredentialCreationOptions = function(pkOptions, sameOriginWithAncestors) {
|
||||
try {
|
||||
checkErrors(pkOptions);
|
||||
|
||||
if (pkOptions.user.id && (pkOptions.user.id.length < 1 || pkOptions.user.id.length > 64)) {
|
||||
throw new TypeError('user.id does not match the required length.');
|
||||
}
|
||||
|
||||
if (!pkOptions.rp.id) {
|
||||
pkOptions.rp.id = window.location.hostname;
|
||||
pkOptions.rp.name = window.location.hostname;
|
||||
} else if (!window.location.hostname.endsWith(pkOptions.rp.id)) {
|
||||
throw new DOMException('Site domain differs from RP ID', DOMException.SecurityError);
|
||||
}
|
||||
|
||||
if (!pkOptions.pubKeyCredParams || pkOptions.pubKeyCredParams.length === 0) {
|
||||
pkOptions.pubKeyCredParams.push({
|
||||
'type': 'public-key',
|
||||
'alg': -7
|
||||
});
|
||||
pkOptions.pubKeyCredParams.push({
|
||||
'type': 'public-key',
|
||||
'alg': -257
|
||||
});
|
||||
}
|
||||
checkErrors(pkOptions, sameOriginWithAncestors);
|
||||
|
||||
const publicKey = {};
|
||||
publicKey.attestation = pkOptions.attestation || 'none';
|
||||
publicKey.authenticatorSelection = pkOptions.authenticatorSelection || { userVerification: 'preferred' };
|
||||
if (!publicKey.authenticatorSelection.userVerification) {
|
||||
publicKey.authenticatorSelection.userVerification = 'preferred';
|
||||
}
|
||||
|
||||
publicKey.attestation = pkOptions?.attestation;
|
||||
publicKey.authenticatorSelection = pkOptions?.authenticatorSelection;
|
||||
publicKey.challenge = arrayBufferToBase64(pkOptions.challenge);
|
||||
publicKey.extensions = pkOptions.extensions;
|
||||
publicKey.pubKeyCredParams = pkOptions.pubKeyCredParams;
|
||||
publicKey.rp = pkOptions.rp;
|
||||
publicKey.timeout = pkOptions.timeout;
|
||||
publicKey.extensions = pkOptions?.extensions;
|
||||
publicKey.pubKeyCredParams = pkOptions?.pubKeyCredParams;
|
||||
publicKey.rp = pkOptions?.rp;
|
||||
publicKey.timeout = getTimeout(publicKey?.authenticatorSelection?.userVerification, pkOptions?.timeout);
|
||||
|
||||
publicKey.excludeCredentials = [];
|
||||
if (pkOptions.excludeCredentials && pkOptions.excludeCredentials.length > 0) {
|
||||
|
|
@ -101,21 +101,17 @@ kpxcPasskeysUtils.buildCredentialCreationOptions = function(pkOptions) {
|
|||
};
|
||||
|
||||
// Create a new object with base64 strings for KeePassXC
|
||||
kpxcPasskeysUtils.buildCredentialRequestOptions = function(pkOptions) {
|
||||
kpxcPasskeysUtils.buildCredentialRequestOptions = function(pkOptions, sameOriginWithAncestors) {
|
||||
try {
|
||||
checkErrors(pkOptions);
|
||||
|
||||
if (!pkOptions.rpId) {
|
||||
pkOptions.rpId = window.location.hostname;
|
||||
} else if (!window.location.hostname.endsWith(pkOptions.rpId)) {
|
||||
throw new DOMException('Site domain differs from RP ID', DOMException.SecurityError);
|
||||
}
|
||||
checkErrors(pkOptions, sameOriginWithAncestors);
|
||||
|
||||
const publicKey = {};
|
||||
publicKey.challenge = arrayBufferToBase64(pkOptions.challenge);
|
||||
publicKey.rpId = pkOptions.rpId;
|
||||
publicKey.timeout = pkOptions.timeout;
|
||||
publicKey.userVerification = pkOptions.userVerification || 'preferred';
|
||||
publicKey.enterpriseAttestationPossible = false;
|
||||
publicKey.extensions = pkOptions?.extensions;
|
||||
publicKey.rpId = pkOptions?.rpId;
|
||||
publicKey.timeout = getTimeout(publicKey?.userVerification, pkOptions?.timeout);
|
||||
publicKey.userVerification = pkOptions?.userVerification;
|
||||
|
||||
publicKey.allowCredentials = [];
|
||||
if (pkOptions.allowCredentials && pkOptions.allowCredentials.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
const PASSKEYS_ATTESTATION_NOT_SUPPORTED = 20;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 21;
|
||||
const PASSKEYS_REQUEST_CANCELED = 22;
|
||||
const PASSKEYS_INVALID_USER_VERIFICATION = 23;
|
||||
const PASSKEYS_EMPTY_PUBLIC_KEY = 24;
|
||||
const PASSKEYS_INVALID_URL_PROVIDED = 25;
|
||||
const PASSKEYS_ORIGIN_NOT_ALLOWED = 26;
|
||||
const PASSKEYS_DOMAIN_IS_NOT_VALID = 27;
|
||||
const PASSKEYS_DOMAIN_RPID_MISMATCH = 28;
|
||||
const PASSKEYS_NO_SUPPORTED_ALGORITHMS = 29;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 30;
|
||||
const PASSKEYS_UNKNOWN_ERROR = 31;
|
||||
const PASSKEYS_INVALID_CHALLENGE = 32;
|
||||
const PASSKEYS_INVALID_USER_ID = 33;
|
||||
|
||||
// Posts a message to extension's content script and waits for response
|
||||
const postMessageToExtension = function(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -22,6 +37,55 @@ const postMessageToExtension = function(request) {
|
|||
});
|
||||
};
|
||||
|
||||
const isSameOriginWithAncestors = function() {
|
||||
try {
|
||||
return window.self.origin === window.top.origin;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Throws errors to a correct exceptions
|
||||
const handleError = function(errorCode, errorMessage) {
|
||||
if ((!errorCode && !errorMessage) || errorCode === PASSKEYS_REQUEST_CANCELED) {
|
||||
// No error or canceled by user. Stop the timer but throw no exception. Fallback with be called instead.
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_WAIT_FOR_LIFETIMER || errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED) {
|
||||
// Timer handled in the content script
|
||||
return;
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_DOMAIN_RPID_MISMATCH, PASSKEYS_DOMAIN_IS_NOT_VALID ].includes(errorCode)) {
|
||||
throw new DOMException(errorMessage, DOMException.SECURITY_ERR);
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_NO_SUPPORTED_ALGORITHMS) {
|
||||
throw new DOMException(errorMessage, DOMException.NOT_SUPPORTED_ERR);
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_INVALID_CHALLENGE, PASSKEYS_INVALID_USER_ID ].includes(errorCode)) {
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED,
|
||||
PASSKEYS_INVALID_URL_PROVIDED,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY,
|
||||
PASSKEYS_UNKNOWN_ERROR,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED,
|
||||
].includes(errorCode)
|
||||
) {
|
||||
throw new DOMException(errorMessage, 'NotAllowedError');
|
||||
}
|
||||
|
||||
throw new DOMException(errorMessage, 'UnknownError');
|
||||
};
|
||||
|
||||
|
||||
(async () => {
|
||||
const originalCredentials = navigator.credentials;
|
||||
|
||||
|
|
@ -31,26 +95,41 @@ const postMessageToExtension = function(request) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const response = await postMessageToExtension({ action: 'passkeys_create', publicKey: options.publicKey });
|
||||
const sameOriginWithAncestors = isSameOriginWithAncestors();
|
||||
const response = await postMessageToExtension({
|
||||
action: 'passkeys_create',
|
||||
publicKey: options.publicKey,
|
||||
sameOriginWithAncestors: sameOriginWithAncestors,
|
||||
});
|
||||
|
||||
if (!response.publicKey) {
|
||||
handleError(response?.errorCode, response?.errorMessage);
|
||||
return response.fallback ? originalCredentials.create(options) : null;
|
||||
}
|
||||
|
||||
response.publicKey.getClientExtensionResults = () => {};
|
||||
response.publicKey.clientExtensionResults = () => {};
|
||||
response.publicKey.response.getTransports = () => [];
|
||||
return response.publicKey;
|
||||
},
|
||||
async get(options) {
|
||||
if (!options.publicKey) {
|
||||
if (!options.publicKey || options?.mediation === 'silent') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options.mediation === 'conditional') {
|
||||
if (options?.mediation === 'conditional') {
|
||||
return originalCredentials.get(options);
|
||||
}
|
||||
|
||||
const response = await postMessageToExtension({ action: 'passkeys_get', publicKey: options.publicKey });
|
||||
const sameOriginWithAncestors = isSameOriginWithAncestors();
|
||||
const response = await postMessageToExtension({
|
||||
action: 'passkeys_get',
|
||||
publicKey: options.publicKey,
|
||||
sameOriginWithAncestors: sameOriginWithAncestors,
|
||||
});
|
||||
|
||||
if (!response.publicKey) {
|
||||
handleError(response?.errorCode, response?.errorMessage);
|
||||
return response.fallback ? originalCredentials.get(options) : null;
|
||||
}
|
||||
|
||||
|
|
@ -61,13 +140,19 @@ const postMessageToExtension = function(request) {
|
|||
};
|
||||
|
||||
const isConditionalMediationAvailable = async() => false;
|
||||
const isUserVerifyingPlatformAuthenticatorAvailable = async() => true;
|
||||
|
||||
// Overwrite navigator.credentials and PublicKeyCredential.isConditionalMediationAvailable.
|
||||
// The latter requires user to select which device to use for authentication, but for now browsers cannot
|
||||
// select a software authenticator. This could be removed in the future.
|
||||
try {
|
||||
Object.defineProperty(navigator, 'credentials', { value: passkeysCredentials });
|
||||
Object.defineProperty(window.PublicKeyCredential, 'isConditionalMediationAvailable', { value: isConditionalMediationAvailable });
|
||||
Object.defineProperty(window.PublicKeyCredential, 'isConditionalMediationAvailable', {
|
||||
value: isConditionalMediationAvailable,
|
||||
});
|
||||
Object.defineProperty(window.PublicKeyCredential, 'isUserVerifyingPlatformAuthenticatorAvailable', {
|
||||
value: isUserVerifyingPlatformAuthenticatorAvailable,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Cannot override navigator.credentials: ', err);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue