- 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>
136 lines
4.0 KiB
TypeScript
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;
|
|
}
|
|
},
|
|
};
|