capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
235 lines • 8.64 kB
JavaScript
import { BiometricErrorCode, BiometryType } from '../core/types';
export class WebAdapter {
constructor() {
this.platform = 'web';
this.credentials = new Map();
// Set default Relying Party info
this.rpId = window.location.hostname;
this.rpName = document.title || 'Biometric Authentication';
}
async isAvailable() {
// Check if WebAuthn is supported
if (!window.PublicKeyCredential) {
return false;
}
// Check if platform authenticator is available
try {
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
return available;
}
catch (_a) {
return false;
}
}
async getSupportedBiometrics() {
if (!(await this.isAvailable())) {
return [];
}
// WebAuthn doesn't provide specific biometry types
// Return generic "multiple" as modern devices support various methods
return [BiometryType.MULTIPLE];
}
async authenticate(options) {
var _a;
try {
// Check if WebAuthn is available
if (!(await this.isAvailable())) {
return {
success: false,
error: {
code: BiometricErrorCode.BIOMETRIC_UNAVAILABLE,
message: 'WebAuthn is not available on this device'
}
};
}
const webOptions = ((_a = options === null || options === void 0 ? void 0 : options.platform) === null || _a === void 0 ? void 0 : _a.web) || {};
// Try to get existing credential first
const existingCredential = await this.getExistingCredential(webOptions);
if (existingCredential) {
return {
success: true,
biometryType: BiometryType.MULTIPLE,
sessionId: this.generateSessionId(),
platform: 'web'
};
}
// If no existing credential, create a new one
const credential = await this.createCredential((options === null || options === void 0 ? void 0 : options.reason) || 'Authentication required', webOptions);
if (credential) {
// Store credential for future use
const credentialId = this.arrayBufferToBase64(credential.rawId);
this.credentials.set(credentialId, credential);
this.saveCredentialId(credentialId);
return {
success: true,
biometryType: BiometryType.MULTIPLE,
sessionId: this.generateSessionId(),
platform: 'web'
};
}
return {
success: false,
error: {
code: BiometricErrorCode.AUTHENTICATION_FAILED,
message: 'Failed to authenticate'
}
};
}
catch (error) {
return this.handleError(error);
}
}
async deleteCredentials() {
this.credentials.clear();
localStorage.removeItem('biometric_credential_ids');
}
async hasCredentials() {
const storedIds = this.getStoredCredentialIds();
return storedIds.length > 0;
}
async getExistingCredential(options) {
const storedIds = this.getStoredCredentialIds();
if (storedIds.length === 0) {
return null;
}
try {
const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32));
const publicKeyOptions = {
challenge,
rpId: options.rpId || this.rpId,
timeout: options.timeout || 60000,
userVerification: options.userVerification || 'preferred',
allowCredentials: storedIds.map(id => ({
id: this.base64ToArrayBuffer(id),
type: 'public-key'
}))
};
const credential = await navigator.credentials.get({
publicKey: publicKeyOptions
});
return credential;
}
catch (_a) {
return null;
}
}
async createCredential(_reason, options) {
try {
const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32));
const userId = crypto.getRandomValues(new Uint8Array(32));
const publicKeyOptions = {
challenge,
rp: {
id: options.rpId || this.rpId,
name: options.rpName || this.rpName
},
user: {
id: userId,
name: 'user@' + this.rpId,
displayName: 'User'
},
pubKeyCredParams: [
{ type: 'public-key', alg: -7 }, // ES256
{ type: 'public-key', alg: -257 } // RS256
],
authenticatorSelection: options.authenticatorSelection || {
authenticatorAttachment: 'platform',
userVerification: 'preferred',
requireResidentKey: false,
residentKey: 'discouraged'
},
timeout: options.timeout || 60000,
attestation: options.attestation || 'none'
};
const credential = await navigator.credentials.create({
publicKey: publicKeyOptions
});
return credential;
}
catch (_a) {
return null;
}
}
handleError(error) {
let code = BiometricErrorCode.UNKNOWN_ERROR;
let message = 'An unknown error occurred';
if (error instanceof DOMException) {
switch (error.name) {
case 'NotAllowedError':
code = BiometricErrorCode.USER_CANCELLED;
message = 'User cancelled the authentication';
break;
case 'AbortError':
code = BiometricErrorCode.USER_CANCELLED;
message = 'Authentication was aborted';
break;
case 'SecurityError':
code = BiometricErrorCode.AUTHENTICATION_FAILED;
message = 'Security error during authentication';
break;
case 'InvalidStateError':
code = BiometricErrorCode.AUTHENTICATION_FAILED;
message = 'Invalid state for authentication';
break;
case 'NotSupportedError':
code = BiometricErrorCode.BIOMETRIC_UNAVAILABLE;
message = 'WebAuthn is not supported';
break;
default:
message = error.message || message;
}
}
else if (error instanceof Error) {
message = error.message;
}
return {
success: false,
error: {
code,
message,
details: error
}
};
}
generateSessionId() {
return Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
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);
}
base64ToArrayBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
getStoredCredentialIds() {
const stored = localStorage.getItem('biometric_credential_ids');
if (!stored) {
return [];
}
try {
return JSON.parse(stored);
}
catch (_a) {
return [];
}
}
saveCredentialId(id) {
const existing = this.getStoredCredentialIds();
if (!existing.includes(id)) {
existing.push(id);
localStorage.setItem('biometric_credential_ids', JSON.stringify(existing));
}
}
}
//# sourceMappingURL=WebAdapter.js.map