feat: add support for excluding PRF extensions in credential options

This commit is contained in:
shuaiplus
2026-06-28 14:02:51 +08:00
parent 4cd9ad00d2
commit 31cfd19b6b
+23 -2
View File
@@ -136,6 +136,19 @@ function withPrfExtension(
}; };
} }
function withoutPrfExtension<T extends PublicKeyCredentialCreationOptions | PublicKeyCredentialRequestOptions>(options: T): T {
const extensions = { ...(((options as any).extensions || {}) as Record<string, unknown>) };
delete extensions.prf;
if (!Object.keys(extensions).length) {
const { extensions: _extensions, ...rest } = options as any;
return rest as T;
}
return {
...options,
extensions: extensions as any,
};
}
function readPrfFirstResult(credential: PublicKeyCredential): ArrayBuffer | undefined { function readPrfFirstResult(credential: PublicKeyCredential): ArrayBuffer | undefined {
const result = (credential.getClientExtensionResults() as any).prf?.results?.first; const result = (credential.getClientExtensionResults() as any).prf?.results?.first;
return result instanceof ArrayBuffer ? result : undefined; return result instanceof ArrayBuffer ? result : undefined;
@@ -171,6 +184,14 @@ async function getPublicKeyCredentialWithPrf(
salt: Uint8Array, salt: Uint8Array,
credentialIds: string[] = [] credentialIds: string[] = []
): Promise<PublicKeyCredential> { ): Promise<PublicKeyCredential> {
const baseOptions = withoutPrfExtension(options);
if (!(await canRequestPrfExtension())) {
const credential = await navigator.credentials.get({ publicKey: baseOptions });
if (!(credential instanceof PublicKeyCredential)) {
throw new Error(t('txt_no_passkey_selected'));
}
return credential;
}
const attempts = credentialIds.length const attempts = credentialIds.length
? [ ? [
buildCredentialPrfExtension(salt, credentialIds), buildCredentialPrfExtension(salt, credentialIds),
@@ -181,7 +202,7 @@ async function getPublicKeyCredentialWithPrf(
for (let index = 0; index < attempts.length; index += 1) { for (let index = 0; index < attempts.length; index += 1) {
try { try {
const credential = await navigator.credentials.get({ const credential = await navigator.credentials.get({
publicKey: withPrfExtension(options, attempts[index]), publicKey: withPrfExtension(baseOptions, attempts[index]),
}); });
if (!(credential instanceof PublicKeyCredential)) { if (!(credential instanceof PublicKeyCredential)) {
throw new Error(t('txt_no_passkey_selected')); throw new Error(t('txt_no_passkey_selected'));
@@ -287,7 +308,7 @@ export async function createAccountPasskeyCredential(
if (!window.PublicKeyCredential || !navigator.credentials) { if (!window.PublicKeyCredential || !navigator.credentials) {
throw new Error(t('txt_passkey_browser_not_supported')); throw new Error(t('txt_passkey_browser_not_supported'));
} }
const nativeOptions = cloneCreationOptions(response.options); const nativeOptions = withoutPrfExtension(cloneCreationOptions(response.options));
const createWithOptions = async (options: PublicKeyCredentialCreationOptions): Promise<PublicKeyCredential> => { const createWithOptions = async (options: PublicKeyCredentialCreationOptions): Promise<PublicKeyCredential> => {
const credential = await navigator.credentials.create({ publicKey: options }); const credential = await navigator.credentials.create({ publicKey: options });
if (!(credential instanceof PublicKeyCredential)) { if (!(credential instanceof PublicKeyCredential)) {