/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ export function base64UrlToBuffer(base64url) { if (!base64url) return new ArrayBuffer(0); let padding = '='.repeat((4 - (base64url.length % 4)) % 4); const base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const buffer = new ArrayBuffer(rawData.length); const uintArray = new Uint8Array(buffer); for (let i = 0; i < rawData.length; i += 1) { uintArray[i] = rawData.charCodeAt(i); } return buffer; } export function bufferToBase64Url(buffer) { if (!buffer) return ''; const uintArray = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < uintArray.byteLength; i += 1) { binary += String.fromCharCode(uintArray[i]); } return window .btoa(binary) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/g, ''); } export function prepareCredentialCreationOptions(payload) { const options = payload?.publicKey || payload?.PublicKey || payload?.response || payload?.Response; if (!options) { throw new Error('无法从服务端响应中解析 Passkey 注册参数'); } const publicKey = { ...options, challenge: base64UrlToBuffer(options.challenge), user: { ...options.user, id: base64UrlToBuffer(options.user?.id), }, }; if (Array.isArray(options.excludeCredentials)) { publicKey.excludeCredentials = options.excludeCredentials.map((item) => ({ ...item, id: base64UrlToBuffer(item.id), })); } if ( Array.isArray(options.attestationFormats) && options.attestationFormats.length === 0 ) { delete publicKey.attestationFormats; } return publicKey; } export function prepareCredentialRequestOptions(payload) { const options = payload?.publicKey || payload?.PublicKey || payload?.response || payload?.Response; if (!options) { throw new Error('无法从服务端响应中解析 Passkey 登录参数'); } const publicKey = { ...options, challenge: base64UrlToBuffer(options.challenge), }; if (Array.isArray(options.allowCredentials)) { publicKey.allowCredentials = options.allowCredentials.map((item) => ({ ...item, id: base64UrlToBuffer(item.id), })); } return publicKey; } export function buildRegistrationResult(credential) { if (!credential) return null; const { response } = credential; const transports = typeof response.getTransports === 'function' ? response.getTransports() : undefined; return { id: credential.id, rawId: bufferToBase64Url(credential.rawId), type: credential.type, authenticatorAttachment: credential.authenticatorAttachment, response: { attestationObject: bufferToBase64Url(response.attestationObject), clientDataJSON: bufferToBase64Url(response.clientDataJSON), transports, }, clientExtensionResults: credential.getClientExtensionResults?.() ?? {}, }; } export function buildAssertionResult(assertion) { if (!assertion) return null; const { response } = assertion; return { id: assertion.id, rawId: bufferToBase64Url(assertion.rawId), type: assertion.type, authenticatorAttachment: assertion.authenticatorAttachment, response: { authenticatorData: bufferToBase64Url(response.authenticatorData), clientDataJSON: bufferToBase64Url(response.clientDataJSON), signature: bufferToBase64Url(response.signature), userHandle: response.userHandle ? bufferToBase64Url(response.userHandle) : null, }, clientExtensionResults: assertion.getClientExtensionResults?.() ?? {}, }; } export async function isPasskeySupported() { if (typeof window === 'undefined' || !window.PublicKeyCredential) { return false; } if ( typeof window.PublicKeyCredential.isConditionalMediationAvailable === 'function' ) { try { const available = await window.PublicKeyCredential.isConditionalMediationAvailable(); if (available) return true; } catch (error) { // ignore } } if ( typeof window.PublicKeyCredential .isUserVerifyingPlatformAuthenticatorAvailable === 'function' ) { try { return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); } catch (error) { return false; } } return true; }