| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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) { |
| | |
| | } |
| | } |
| | if ( |
| | typeof window.PublicKeyCredential |
| | .isUserVerifyingPlatformAuthenticatorAvailable === 'function' |
| | ) { |
| | try { |
| | return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); |
| | } catch (error) { |
| | return false; |
| | } |
| | } |
| | return true; |
| | } |
| |
|