capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
235 lines • 8.38 kB
JavaScript
import { Capacitor } from '@capacitor/core';
export class SessionManager {
/**
* Generate a cryptographically secure random token
*/
static generateSecureToken() {
if (Capacitor.isNativePlatform()) {
// On native platforms, this is handled by the native implementation
return '';
}
// For web, use crypto API
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
}
/**
* Store session data securely
*/
static async storeSession(session) {
if (Capacitor.isNativePlatform()) {
// Native platforms handle this internally
return;
}
// For web, use sessionStorage with encryption
const encryptedData = await this.encrypt(JSON.stringify(session));
sessionStorage.setItem(this.SESSION_KEY, encryptedData);
}
/**
* Retrieve session data
*/
static async getSession() {
if (Capacitor.isNativePlatform()) {
// Native platforms handle this internally
return null;
}
const encryptedData = sessionStorage.getItem(this.SESSION_KEY);
if (!encryptedData) {
return null;
}
try {
const decryptedData = await this.decrypt(encryptedData);
const session = JSON.parse(decryptedData);
// Check if session is expired
if (session.expiresAt < Date.now()) {
await this.clearSession();
return null;
}
return session;
}
catch (error) {
console.error('Failed to decrypt session:', error);
await this.clearSession();
return null;
}
}
/**
* Clear session data
*/
static async clearSession() {
if (Capacitor.isNativePlatform()) {
// Native platforms handle this internally
return;
}
sessionStorage.removeItem(this.SESSION_KEY);
}
/**
* Check if session is valid
*/
static async isSessionValid() {
const session = await this.getSession();
return session !== null && session.expiresAt > Date.now();
}
/**
* Extend session expiration
*/
static async extendSession(durationMs) {
const session = await this.getSession();
if (!session) {
return false;
}
session.expiresAt = Date.now() + durationMs;
await this.storeSession(session);
return true;
}
/**
* Simple encryption using Web Crypto API (for demonstration)
* In production, use a proper encryption library or service
*/
static async encrypt(data) {
if (!crypto.subtle) {
// Fallback to base64 if crypto.subtle is not available
return btoa(data);
}
try {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
// Generate encryption key from string
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(this.ENCRYPTION_KEY), { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey']);
const key = await crypto.subtle.deriveKey({
name: 'PBKDF2',
salt: encoder.encode('biometric-auth-salt'),
iterations: 100000,
hash: 'SHA-256',
}, keyMaterial, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
// Generate IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt
const encryptedData = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, dataBuffer);
// Combine IV and encrypted data
const combined = new Uint8Array(iv.length + encryptedData.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encryptedData), iv.length);
// Convert to base64
return btoa(String.fromCharCode(...combined));
}
catch (error) {
console.error('Encryption failed:', error);
// Fallback to base64
return btoa(data);
}
}
/**
* Simple decryption using Web Crypto API
*/
static async decrypt(encryptedData) {
if (!crypto.subtle) {
// Fallback from base64 if crypto.subtle is not available
return atob(encryptedData);
}
try {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// Convert from base64
const combined = Uint8Array.from(atob(encryptedData), (c) => c.charCodeAt(0));
// Extract IV and encrypted data
const iv = combined.slice(0, 12);
const encrypted = combined.slice(12);
// Generate decryption key
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(this.ENCRYPTION_KEY), { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey']);
const key = await crypto.subtle.deriveKey({
name: 'PBKDF2',
salt: encoder.encode('biometric-auth-salt'),
iterations: 100000,
hash: 'SHA-256',
}, keyMaterial, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
// Decrypt
const decryptedData = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted);
return decoder.decode(decryptedData);
}
catch (error) {
console.error('Decryption failed:', error);
// Try fallback from base64
return atob(encryptedData);
}
}
}
SessionManager.SESSION_KEY = 'biometric_auth_session';
SessionManager.ENCRYPTION_KEY = 'biometric_auth_encryption';
/**
* Credential storage utilities
*/
export class CredentialManager {
/**
* Store credential securely
*/
static async storeCredential(credentialId, credentialData, encrypt = true) {
const key = `${this.CREDENTIAL_PREFIX}${credentialId}`;
const dataStr = JSON.stringify(credentialData);
if (encrypt && crypto.subtle) {
const encryptedData = await SessionManager['encrypt'](dataStr);
localStorage.setItem(key, encryptedData);
}
else {
localStorage.setItem(key, dataStr);
}
}
/**
* Retrieve credential
*/
static async getCredential(credentialId, decrypt = true) {
const key = `${this.CREDENTIAL_PREFIX}${credentialId}`;
const storedData = localStorage.getItem(key);
if (!storedData) {
return null;
}
try {
if (decrypt && crypto.subtle) {
const decryptedData = await SessionManager['decrypt'](storedData);
return JSON.parse(decryptedData);
}
else {
return JSON.parse(storedData);
}
}
catch (error) {
console.error('Failed to retrieve credential:', error);
return null;
}
}
/**
* Delete credential
*/
static deleteCredential(credentialId) {
const key = `${this.CREDENTIAL_PREFIX}${credentialId}`;
localStorage.removeItem(key);
}
/**
* List all credential IDs
*/
static listCredentialIds() {
const ids = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key === null || key === void 0 ? void 0 : key.startsWith(this.CREDENTIAL_PREFIX)) {
ids.push(key.substring(this.CREDENTIAL_PREFIX.length));
}
}
return ids;
}
/**
* Clear all credentials
*/
static clearAllCredentials() {
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key === null || key === void 0 ? void 0 : key.startsWith(this.CREDENTIAL_PREFIX)) {
keys.push(key);
}
}
keys.forEach((key) => localStorage.removeItem(key));
}
}
CredentialManager.CREDENTIAL_PREFIX = 'biometric_credential_';
//# sourceMappingURL=session.js.map