Spaces:
Runtime error
Runtime error
| /* | |
| 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 <https://www.gnu.org/licenses/>. | |
| 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; | |
| } | |