Files
maternal-app/maternal-web/lib/api/biometric.ts
Andrei 6c8a50b910 Add biometric authentication enrollment UI
- Create biometric API client with WebAuthn methods
- Add BiometricSettings component for credential management
- Support Face ID, Touch ID, Windows Hello enrollment
- Display list of enrolled credentials with metadata
- Add/remove/rename biometric credentials
- Check browser and platform authenticator support
- Integrate into settings page with animations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 22:30:09 +00:00

136 lines
4.0 KiB
TypeScript

import axios from 'axios';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020';
export interface BiometricCredential {
id: string;
friendlyName?: string;
deviceType?: string;
createdAt: Date;
lastUsed?: Date;
backedUp: boolean;
}
export const biometricApi = {
// Check if user has biometric credentials
async hasCredentials(): Promise<{ success: boolean; hasCredentials: boolean }> {
const response = await axios.get(`${API_BASE_URL}/api/v1/auth/biometric/has-credentials`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
});
return response.data;
},
// Get all biometric credentials for current user
async getCredentials(): Promise<{ success: boolean; credentials: BiometricCredential[] }> {
const response = await axios.get(`${API_BASE_URL}/api/v1/auth/biometric/credentials`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
});
return response.data;
},
// Get registration options (start enrollment process)
async getRegistrationOptions(friendlyName?: string): Promise<any> {
const response = await axios.post(
`${API_BASE_URL}/api/v1/auth/biometric/register/options`,
{ friendlyName },
{
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
}
);
return response.data;
},
// Verify registration response (complete enrollment)
async verifyRegistration(
response: any,
friendlyName?: string
): Promise<{ success: boolean; credentialId: string; message: string }> {
const verifyResponse = await axios.post(
`${API_BASE_URL}/api/v1/auth/biometric/register/verify`,
{ response, friendlyName },
{
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
}
);
return verifyResponse.data;
},
// Get authentication options (start login process)
async getAuthenticationOptions(email?: string): Promise<any> {
const response = await axios.post(`${API_BASE_URL}/api/v1/auth/biometric/authenticate/options`, {
email,
});
return response.data;
},
// Verify authentication response (complete login)
async verifyAuthentication(
response: any,
email?: string,
deviceInfo?: { deviceId: string; platform: string }
): Promise<{ success: boolean; message: string; user: any; tokens: any }> {
const verifyResponse = await axios.post(
`${API_BASE_URL}/api/v1/auth/biometric/authenticate/verify`,
{ response, email, deviceInfo }
);
return verifyResponse.data;
},
// Delete a credential
async deleteCredential(credentialId: string): Promise<{ success: boolean; message: string }> {
const response = await axios.delete(
`${API_BASE_URL}/api/v1/auth/biometric/credentials/${credentialId}`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
}
);
return response.data;
},
// Update credential name
async updateCredentialName(
credentialId: string,
friendlyName: string
): Promise<{ success: boolean; message: string }> {
const response = await axios.patch(
`${API_BASE_URL}/api/v1/auth/biometric/credentials/${credentialId}`,
{ friendlyName },
{
headers: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
}
);
return response.data;
},
// Check if WebAuthn is supported in this browser
isSupported(): boolean {
return (
typeof window !== 'undefined' &&
window.PublicKeyCredential !== undefined &&
typeof window.PublicKeyCredential === 'function'
);
},
// Check if platform authenticator (Face ID, Touch ID, Windows Hello) is available
async isPlatformAuthenticatorAvailable(): Promise<boolean> {
if (!this.isSupported()) return false;
try {
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
} catch {
return false;
}
},
};