capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
200 lines • 10.4 kB
JavaScript
/**
* Convert various input formats to ArrayBuffer for WebAuthn API
*/
export function toArrayBuffer(data) {
if (!data)
return undefined;
if (data instanceof ArrayBuffer) {
return data;
}
if (data instanceof Uint8Array) {
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
}
if (typeof data === 'string') {
try {
// First try base64url decoding (WebAuthn standard)
const base64 = data
.replace(/-/g, '+')
.replace(/_/g, '/')
.padEnd(data.length + ((4 - (data.length % 4)) % 4), '=');
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
catch (_a) {
try {
// Fallback to regular base64 decoding
const binaryString = atob(data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
catch (_b) {
// If both fail, encode as UTF-8
return new TextEncoder().encode(data).buffer;
}
}
}
return undefined;
}
/**
* Convert ArrayBuffer to base64 string for storage
*/
export function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* Convert ArrayBuffer to base64url string (WebAuthn standard)
*/
export function arrayBufferToBase64URL(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
/**
* Generate a cryptographically secure random challenge
*/
export function generateChallenge() {
const challenge = new Uint8Array(32);
crypto.getRandomValues(challenge);
return challenge.buffer;
}
/**
* Merge user-provided WebAuthn create options with defaults
*/
export function mergeCreateOptions(userOptions, defaults) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
const challenge = toArrayBuffer(userOptions === null || userOptions === void 0 ? void 0 : userOptions.challenge) || generateChallenge();
const options = {
challenge,
rp: {
name: ((_a = userOptions === null || userOptions === void 0 ? void 0 : userOptions.rp) === null || _a === void 0 ? void 0 : _a.name) || ((_b = defaults === null || defaults === void 0 ? void 0 : defaults.rp) === null || _b === void 0 ? void 0 : _b.name) || window.location.hostname,
id: ((_c = userOptions === null || userOptions === void 0 ? void 0 : userOptions.rp) === null || _c === void 0 ? void 0 : _c.id) || ((_d = defaults === null || defaults === void 0 ? void 0 : defaults.rp) === null || _d === void 0 ? void 0 : _d.id) || window.location.hostname,
},
user: {
id: toArrayBuffer((_e = userOptions === null || userOptions === void 0 ? void 0 : userOptions.user) === null || _e === void 0 ? void 0 : _e.id) ||
(((_f = defaults === null || defaults === void 0 ? void 0 : defaults.user) === null || _f === void 0 ? void 0 : _f.id) instanceof ArrayBuffer ||
((_g = defaults === null || defaults === void 0 ? void 0 : defaults.user) === null || _g === void 0 ? void 0 : _g.id) instanceof Uint8Array
? toArrayBuffer(defaults.user.id)
: undefined) ||
crypto.getRandomValues(new Uint8Array(16)).buffer,
name: ((_h = userOptions === null || userOptions === void 0 ? void 0 : userOptions.user) === null || _h === void 0 ? void 0 : _h.name) ||
((_j = defaults === null || defaults === void 0 ? void 0 : defaults.user) === null || _j === void 0 ? void 0 : _j.name) ||
`user@${window.location.hostname}`,
displayName: ((_k = userOptions === null || userOptions === void 0 ? void 0 : userOptions.user) === null || _k === void 0 ? void 0 : _k.displayName) || ((_l = defaults === null || defaults === void 0 ? void 0 : defaults.user) === null || _l === void 0 ? void 0 : _l.displayName) || 'User',
},
pubKeyCredParams: (userOptions === null || userOptions === void 0 ? void 0 : userOptions.pubKeyCredParams) ||
(defaults === null || defaults === void 0 ? void 0 : defaults.pubKeyCredParams) || [
{ alg: -7, type: 'public-key' }, // ES256
{ alg: -257, type: 'public-key' }, // RS256
],
timeout: (userOptions === null || userOptions === void 0 ? void 0 : userOptions.timeout) || (defaults === null || defaults === void 0 ? void 0 : defaults.timeout) || 60000,
attestation: (userOptions === null || userOptions === void 0 ? void 0 : userOptions.attestation) || (defaults === null || defaults === void 0 ? void 0 : defaults.attestation) || 'none',
};
// Add optional properties if provided
if ((userOptions === null || userOptions === void 0 ? void 0 : userOptions.authenticatorSelection) || (defaults === null || defaults === void 0 ? void 0 : defaults.authenticatorSelection)) {
options.authenticatorSelection = Object.assign(Object.assign({}, defaults === null || defaults === void 0 ? void 0 : defaults.authenticatorSelection), userOptions === null || userOptions === void 0 ? void 0 : userOptions.authenticatorSelection);
}
if (userOptions === null || userOptions === void 0 ? void 0 : userOptions.attestationFormats) {
options.attestationFormats = userOptions.attestationFormats;
}
if (userOptions === null || userOptions === void 0 ? void 0 : userOptions.excludeCredentials) {
options.excludeCredentials = userOptions.excludeCredentials.map((cred) => (Object.assign(Object.assign({}, cred), { id: toArrayBuffer(cred.id) })));
}
if (userOptions === null || userOptions === void 0 ? void 0 : userOptions.extensions) {
options.extensions = userOptions.extensions;
}
if (userOptions === null || userOptions === void 0 ? void 0 : userOptions.hints) {
options.hints = userOptions.hints;
}
return options;
}
/**
* Merge user-provided WebAuthn get options with defaults
*/
export function mergeGetOptions(userOptions, defaults) {
const challenge = toArrayBuffer(userOptions === null || userOptions === void 0 ? void 0 : userOptions.challenge) || generateChallenge();
const options = {
challenge,
timeout: (userOptions === null || userOptions === void 0 ? void 0 : userOptions.timeout) || (defaults === null || defaults === void 0 ? void 0 : defaults.timeout) || 60000,
};
// Add optional properties if provided
if ((userOptions === null || userOptions === void 0 ? void 0 : userOptions.rpId) || (defaults === null || defaults === void 0 ? void 0 : defaults.rpId)) {
options.rpId = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.rpId) || (defaults === null || defaults === void 0 ? void 0 : defaults.rpId);
}
if ((userOptions === null || userOptions === void 0 ? void 0 : userOptions.allowCredentials) || (defaults === null || defaults === void 0 ? void 0 : defaults.allowCredentials)) {
const userCreds = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.allowCredentials) || [];
const defaultCreds = (defaults === null || defaults === void 0 ? void 0 : defaults.allowCredentials) || [];
options.allowCredentials = [...userCreds, ...defaultCreds].map((cred) => ({
type: 'public-key',
id: toArrayBuffer(cred.id),
transports: cred.transports,
}));
}
if ((userOptions === null || userOptions === void 0 ? void 0 : userOptions.userVerification) || (defaults === null || defaults === void 0 ? void 0 : defaults.userVerification)) {
options.userVerification =
(userOptions === null || userOptions === void 0 ? void 0 : userOptions.userVerification) || (defaults === null || defaults === void 0 ? void 0 : defaults.userVerification);
}
if ((userOptions === null || userOptions === void 0 ? void 0 : userOptions.extensions) || (defaults === null || defaults === void 0 ? void 0 : defaults.extensions)) {
options.extensions = Object.assign(Object.assign({}, defaults === null || defaults === void 0 ? void 0 : defaults.extensions), userOptions === null || userOptions === void 0 ? void 0 : userOptions.extensions);
}
if (userOptions === null || userOptions === void 0 ? void 0 : userOptions.hints) {
options.hints = userOptions.hints;
}
return options;
}
/**
* Store credential ID in localStorage for future authentication
*/
export function storeCredentialId(credentialId, userId) {
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) {
const key = userId
? `biometric_auth_cred_${userId}`
: 'biometric_auth_cred_default';
try {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : [];
}
catch (_a) {
return [];
}
}
/**
* Clear stored credential IDs
*/
export function clearStoredCredentialIds(userId) {
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));
}
}
//# sourceMappingURL=webauthn.js.map