capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
175 lines • 6.56 kB
JavaScript
import { BiometricErrorCode, BiometryType } from '../core/types';
import { arrayBufferToBase64, base64ToArrayBuffer, generateSessionId as generateSecureSessionId, } from '../utils/encoding';
import { createErrorResult } from '../utils/error-handler';
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: generateSecureSessionId(),
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 = arrayBufferToBase64(credential.rawId);
this.credentials.set(credentialId, credential);
this.saveCredentialId(credentialId);
return {
success: true,
biometryType: BiometryType.MULTIPLE,
sessionId: generateSecureSessionId(),
platform: 'web'
};
}
return {
success: false,
error: {
code: BiometricErrorCode.AUTHENTICATION_FAILED,
message: 'Failed to authenticate'
}
};
}
catch (error) {
return createErrorResult(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: 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;
}
}
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