capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
181 lines (160 loc) • 5.42 kB
text/typescript
import { WebAuthnCreateOptions, WebAuthnGetOptions } from '../definitions';
import {
toArrayBuffer,
generateChallenge,
} from './encoding';
// Re-export encoding utilities for backward compatibility
export {
toArrayBuffer,
arrayBufferToBase64,
arrayBufferToBase64URL,
base64ToArrayBuffer,
base64URLToArrayBuffer,
generateChallenge,
generateSessionId,
} from './encoding';
/**
* Merge user-provided WebAuthn create options with defaults
*/
export function mergeCreateOptions(
userOptions?: WebAuthnCreateOptions,
defaults?: Partial<PublicKeyCredentialCreationOptions>
): PublicKeyCredentialCreationOptions {
const challenge =
toArrayBuffer(userOptions?.challenge) || generateChallenge();
const options: PublicKeyCredentialCreationOptions = {
challenge,
rp: {
name:
userOptions?.rp?.name || defaults?.rp?.name || window.location.hostname,
id: userOptions?.rp?.id || defaults?.rp?.id || window.location.hostname,
},
user: {
id:
toArrayBuffer(userOptions?.user?.id) ||
(defaults?.user?.id instanceof ArrayBuffer ||
defaults?.user?.id instanceof Uint8Array
? toArrayBuffer(defaults.user.id)
: undefined) ||
crypto.getRandomValues(new Uint8Array(16)).buffer,
name:
userOptions?.user?.name ||
defaults?.user?.name ||
`user@${window.location.hostname}`,
displayName:
userOptions?.user?.displayName || defaults?.user?.displayName || 'User',
},
pubKeyCredParams: userOptions?.pubKeyCredParams ||
defaults?.pubKeyCredParams || [
{ alg: -7, type: 'public-key' }, // ES256
{ alg: -257, type: 'public-key' }, // RS256
],
timeout: userOptions?.timeout || defaults?.timeout || 60000,
attestation: userOptions?.attestation || defaults?.attestation || 'none',
};
// Add optional properties if provided
if (userOptions?.authenticatorSelection || defaults?.authenticatorSelection) {
options.authenticatorSelection = {
...defaults?.authenticatorSelection,
...userOptions?.authenticatorSelection,
};
}
if (userOptions?.attestationFormats) {
(options as { attestationFormats?: string[] }).attestationFormats = userOptions.attestationFormats;
}
if (userOptions?.excludeCredentials) {
options.excludeCredentials = userOptions.excludeCredentials.map((cred) => ({
...cred,
id: toArrayBuffer(cred.id)!,
}));
}
if (userOptions?.extensions) {
options.extensions = userOptions.extensions;
}
if (userOptions?.hints) {
(options as { hints?: string[] }).hints = userOptions.hints;
}
return options;
}
/**
* Merge user-provided WebAuthn get options with defaults
*/
export function mergeGetOptions(
userOptions?: WebAuthnGetOptions,
defaults?: Partial<PublicKeyCredentialRequestOptions>
): PublicKeyCredentialRequestOptions {
const challenge =
toArrayBuffer(userOptions?.challenge) || generateChallenge();
const options: PublicKeyCredentialRequestOptions = {
challenge,
timeout: userOptions?.timeout || defaults?.timeout || 60000,
};
// Add optional properties if provided
if (userOptions?.rpId || defaults?.rpId) {
options.rpId = userOptions?.rpId || defaults?.rpId;
}
if (userOptions?.allowCredentials || defaults?.allowCredentials) {
const userCreds = userOptions?.allowCredentials || [];
const defaultCreds = defaults?.allowCredentials || [];
options.allowCredentials = [...userCreds, ...defaultCreds].map((cred) => ({
type: 'public-key' as PublicKeyCredentialType,
id: toArrayBuffer(cred.id as string | ArrayBuffer | Uint8Array)!,
transports: cred.transports,
})) as PublicKeyCredentialDescriptor[];
}
if (userOptions?.userVerification || defaults?.userVerification) {
options.userVerification =
userOptions?.userVerification || defaults?.userVerification;
}
if (userOptions?.extensions || defaults?.extensions) {
options.extensions = {
...defaults?.extensions,
...userOptions?.extensions,
};
}
if (userOptions?.hints) {
(options as { hints?: string[] }).hints = userOptions.hints;
}
return options;
}
/**
* Store credential ID in localStorage for future authentication
*/
export function storeCredentialId(credentialId: string, userId?: string): void {
const key = userId
? `biometric_auth_cred_${userId}`
: 'biometric_auth_cred_default';
const existingCreds = getStoredCredentialIds(userId);
if (!existingCreds.includes(credentialId)) {
existingCreds.push(credentialId);
localStorage.setItem(key, JSON.stringify(existingCreds));
}
}
/**
* Get stored credential IDs from localStorage
*/
export function getStoredCredentialIds(userId?: string): string[] {
const key = userId
? `biometric_auth_cred_${userId}`
: 'biometric_auth_cred_default';
try {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
}
/**
* Clear stored credential IDs
*/
export function clearStoredCredentialIds(userId?: string): void {
if (userId) {
localStorage.removeItem(`biometric_auth_cred_${userId}`);
} else {
// Clear all credential keys
const keys = Object.keys(localStorage).filter((key) =>
key.startsWith('biometric_auth_cred_')
);
keys.forEach((key) => localStorage.removeItem(key));
}
}