Smail/frontend/src/utils/webauthn.js
smail f60d3b0880
Some checks are pending
Security Checks / security (push) Waiting to run
整理前后端与测试构建目录并完善配置和安全引导
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
2026-09-05 19:19:52 +08:00

92 lines
3.2 KiB
JavaScript

function normalizeBase64Url(value) {
return String(value || '').replace(/-/g, '+').replace(/_/g, '/');
}
export function base64UrlToBuffer(value) {
const normalized = normalizeBase64Url(value);
const padded = normalized + '='.repeat((4 - normalized.length % 4) % 4);
const binary = atob(padded);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) {
bytes[index] = binary.charCodeAt(index);
}
return bytes.buffer;
}
export function bufferToBase64Url(value) {
if (value === null || value === undefined) return null;
const bytes = value instanceof ArrayBuffer
? new Uint8Array(value)
: new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
let binary = '';
for (let index = 0; index < bytes.length; index += 1) {
binary += String.fromCharCode(bytes[index]);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function normalizeDescriptors(items) {
if (!Array.isArray(items)) return [];
return items.map(item => ({...item, id: base64UrlToBuffer(item.id)}));
}
export function normalizeCreationOptions(options) {
const source = options?.publicKey || options?.response || options;
if (!source?.challenge || !source?.user?.id) {
throw new Error('安全密钥参数无效');
}
return {
...source,
challenge: base64UrlToBuffer(source.challenge),
user: {...source.user, id: base64UrlToBuffer(source.user.id)},
excludeCredentials: normalizeDescriptors(source.excludeCredentials),
};
}
export function normalizeRequestOptions(options) {
const source = options?.publicKey || options?.response || options;
if (!source?.challenge) {
throw new Error('安全密钥参数无效');
}
return {
...source,
challenge: base64UrlToBuffer(source.challenge),
allowCredentials: normalizeDescriptors(source.allowCredentials),
};
}
export function serializeCredential(credential) {
if (!credential) throw new Error('安全密钥参数无效');
const response = credential.response;
const result = {
id: credential.id,
rawId: bufferToBase64Url(credential.rawId),
type: credential.type,
authenticatorAttachment: credential.authenticatorAttachment || undefined,
clientExtensionResults: credential.getClientExtensionResults?.() || {},
response: {
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
},
};
if ('attestationObject' in response) {
result.response.attestationObject = bufferToBase64Url(response.attestationObject);
if (typeof response.getTransports === 'function') {
result.response.transports = response.getTransports();
}
}
if ('authenticatorData' in response) {
result.response.authenticatorData = bufferToBase64Url(response.authenticatorData);
result.response.signature = bufferToBase64Url(response.signature);
result.response.userHandle = bufferToBase64Url(response.userHandle);
}
return result;
}
export function webAuthnSupported() {
return typeof window !== 'undefined'
&& window.isSecureContext
&& typeof navigator.credentials?.create === 'function'
&& typeof navigator.credentials?.get === 'function'
&& typeof window.PublicKeyCredential !== 'undefined';
}