capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
203 lines (177 loc) • 6.33 kB
text/typescript
import {
BiometricAuthAdapter,
BiometricAuthOptions,
BiometricAuthResult,
BiometricErrorCode,
BiometryType
} from '../core/types';
import { generateSessionId } from '../utils/encoding';
import { createErrorResult } from '../utils/error-handler';
/**
* Electron Adapter for biometric authentication
* Supports:
* - macOS: Touch ID via Electron's systemPreferences API
* - Windows: Windows Hello via WebAuthn API
*/
export class ElectronAdapter implements BiometricAuthAdapter {
platform = 'electron';
private windowsHelloAvailable: boolean | null = null;
constructor() {
// Electron-specific initialization
}
/**
* Check if Windows Hello is available
*/
private async checkWindowsHello(): Promise<boolean> {
// Cache the result since this check can be expensive
if (this.windowsHelloAvailable !== null) {
return this.windowsHelloAvailable;
}
try {
// Check if PublicKeyCredential API is available (WebAuthn)
if (typeof PublicKeyCredential !== 'undefined' &&
typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function') {
this.windowsHelloAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
return this.windowsHelloAvailable;
}
} catch {
// Windows Hello not available
}
this.windowsHelloAvailable = false;
return false;
}
async isAvailable(): Promise<boolean> {
try {
// Check if we're in Electron main or renderer process
if (typeof process !== 'undefined' && process.versions && process.versions.electron) {
// In Electron, we can use TouchID on macOS
if (process.platform === 'darwin') {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const electronModule = require('electron');
const { systemPreferences } = electronModule.remote || electronModule;
return systemPreferences.canPromptTouchID();
}
// On Windows, check for Windows Hello via WebAuthn
if (process.platform === 'win32') {
return await this.checkWindowsHello();
}
return false;
}
return false;
} catch {
return false;
}
}
async getSupportedBiometrics(): Promise<BiometryType[]> {
if (!(await this.isAvailable())) {
return [];
}
// On macOS, we support Touch ID
if (process.platform === 'darwin') {
return [BiometryType.TOUCH_ID];
}
// On Windows, Windows Hello supports multiple biometric types
if (process.platform === 'win32') {
// Windows Hello can use fingerprint, face, or PIN
// We report FINGERPRINT as the primary type, but it could also be face recognition
return [BiometryType.FINGERPRINT, BiometryType.FACE_ID];
}
return [];
}
/**
* Authenticate using Windows Hello via WebAuthn API
*/
private async authenticateWithWindowsHello(options?: BiometricAuthOptions): Promise<BiometricAuthResult> {
try {
// Generate a random challenge
const challenge = new Uint8Array(32);
crypto.getRandomValues(challenge);
// Get WebAuthn options from the options object
const webAuthnGet = options?.webAuthnOptions?.get;
const platformWeb = options?.platform?.web;
// Create a credential request for platform authenticator (Windows Hello)
const publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions = {
challenge: challenge.buffer,
timeout: webAuthnGet?.timeout || platformWeb?.timeout || options?.sessionTimeout || 60000,
userVerification: 'required',
rpId: webAuthnGet?.rpId || platformWeb?.rpId || (typeof window !== 'undefined' ? window.location.hostname : 'localhost'),
};
// Request authentication
const credential = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
}) as PublicKeyCredential | null;
if (!credential) {
return {
success: false,
error: {
code: BiometricErrorCode.AUTHENTICATION_FAILED,
message: 'Windows Hello authentication failed - no credential returned'
}
};
}
// Authentication successful
return {
success: true,
biometryType: BiometryType.FINGERPRINT, // Windows Hello primary type
sessionId: generateSessionId(),
platform: 'electron',
};
} catch (error) {
return createErrorResult(error, 'windows');
}
}
async authenticate(options?: BiometricAuthOptions): Promise<BiometricAuthResult> {
try {
if (!(await this.isAvailable())) {
return {
success: false,
error: {
code: BiometricErrorCode.BIOMETRIC_UNAVAILABLE,
message: 'Biometric authentication is not available'
}
};
}
// macOS Touch ID
if (process.platform === 'darwin') {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const electronModule = require('electron');
const { systemPreferences } = electronModule.remote || electronModule;
try {
await systemPreferences.promptTouchID(
options?.reason || 'authenticate with Touch ID'
);
return {
success: true,
biometryType: BiometryType.TOUCH_ID,
sessionId: generateSessionId(),
platform: 'electron'
};
} catch (touchIdError) {
return createErrorResult(touchIdError, 'electron');
}
}
// Windows Hello
if (process.platform === 'win32') {
return await this.authenticateWithWindowsHello(options);
}
return {
success: false,
error: {
code: BiometricErrorCode.PLATFORM_NOT_SUPPORTED,
message: 'Platform not supported'
}
};
} catch (error) {
return createErrorResult(error, 'electron');
}
}
async deleteCredentials(): Promise<void> {
// Electron doesn't store biometric credentials
// This is a no-op
}
async hasCredentials(): Promise<boolean> {
// In Electron, we don't store credentials
// Return true if biometrics are available
return await this.isAvailable();
}
}