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>
This commit is contained in:
135
maternal-web/lib/api/biometric.ts
Normal file
135
maternal-web/lib/api/biometric.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
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;
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user