Performance insights, Goals & Events, 2FA improvements, auth fixes #36

Merged
uz1mani merged 12 commits from staging into main 2026-02-25 19:41:07 +00:00
10 changed files with 130 additions and 66 deletions
Showing only changes of commit 801dc1d773 - Show all commits

View File

@@ -6,6 +6,7 @@ import api from '@/lib/api/client'
import { deriveAuthKey } from '@/lib/crypto/password'
import { deleteAccount, getUserSessions, revokeSession, updateUserPreferences, updateDisplayName } from '@/lib/api/user'
import { setup2FA, verify2FA, disable2FA, regenerateRecoveryCodes } from '@/lib/api/2fa'
import { registerPasskey, listPasskeys, deletePasskey } from '@/lib/api/webauthn'
export default function ProfileSettings() {
const { user, refresh, logout } = useAuth()
@@ -46,6 +47,9 @@ export default function ProfileSettings() {
onRegenerateRecoveryCodes={regenerateRecoveryCodes}
onGetSessions={getUserSessions}
onRevokeSession={revokeSession}
onRegisterPasskey={registerPasskey}
onListPasskeys={listPasskeys}
onDeletePasskey={deletePasskey}
onUpdatePreferences={updateUserPreferences}
deriveAuthKey={deriveAuthKey}
refreshUser={refresh}

54
lib/api/webauthn.ts Normal file
View File

@@ -0,0 +1,54 @@
/**
* WebAuthn / Passkey API client for settings (list, register, delete).
*/
import { startRegistration, type PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/browser'
import apiRequest from './client'
export interface BeginRegistrationResponse {
sessionId: string
creationOptions: {
publicKey: Record<string, unknown>
mediation?: string
}
}
export interface PasskeyCredential {
id: string
createdAt: string
}
export interface ListPasskeysResponse {
credentials: PasskeyCredential[]
}
export async function registerPasskey(): Promise<void> {
const { sessionId, creationOptions } = await apiRequest<BeginRegistrationResponse>(
'/auth/webauthn/register/begin',
{ method: 'POST' }
)
const optionsJSON = creationOptions?.publicKey
if (!optionsJSON) {
throw new Error('Invalid registration options')
}
const response = await startRegistration({
optionsJSON: optionsJSON as unknown as PublicKeyCredentialCreationOptionsJSON,
})
await apiRequest<{ message: string }>('/auth/webauthn/register/finish', {
method: 'POST',
body: JSON.stringify({ sessionId, response }),
})
}
export async function listPasskeys(): Promise<ListPasskeysResponse> {
return apiRequest<ListPasskeysResponse>('/auth/webauthn/credentials', {
method: 'GET',
})
}
export async function deletePasskey(credentialId: string): Promise<void> {
return apiRequest<void>(
`/auth/webauthn/credentials/${encodeURIComponent(credentialId)}`,
{ method: 'DELETE' }
)
}

7
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@ciphera-net/ui": "^0.0.64",
"@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0",
"@simplewebauthn/browser": "^13.2.2",
"@stripe/react-stripe-js": "^5.6.0",
"@stripe/stripe-js": "^8.7.0",
"axios": "^1.13.2",
@@ -2717,6 +2718,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@simplewebauthn/browser": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz",
"integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==",
"license": "MIT"
},
"node_modules/@stripe/react-stripe-js": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz",

View File

@@ -13,6 +13,7 @@
"@ciphera-net/ui": "^0.0.64",
"@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0",
"@simplewebauthn/browser": "^13.2.2",
"@stripe/react-stripe-js": "^5.6.0",
"@stripe/stripe-js": "^8.7.0",
"axios": "^1.13.2",