Performance insights, Goals & Events, 2FA improvements, auth fixes #36
@@ -6,6 +6,7 @@ import api from '@/lib/api/client'
|
|||||||
import { deriveAuthKey } from '@/lib/crypto/password'
|
import { deriveAuthKey } from '@/lib/crypto/password'
|
||||||
import { deleteAccount, getUserSessions, revokeSession, updateUserPreferences, updateDisplayName } from '@/lib/api/user'
|
import { deleteAccount, getUserSessions, revokeSession, updateUserPreferences, updateDisplayName } from '@/lib/api/user'
|
||||||
import { setup2FA, verify2FA, disable2FA, regenerateRecoveryCodes } from '@/lib/api/2fa'
|
import { setup2FA, verify2FA, disable2FA, regenerateRecoveryCodes } from '@/lib/api/2fa'
|
||||||
|
import { registerPasskey, listPasskeys, deletePasskey } from '@/lib/api/webauthn'
|
||||||
|
|
||||||
export default function ProfileSettings() {
|
export default function ProfileSettings() {
|
||||||
const { user, refresh, logout } = useAuth()
|
const { user, refresh, logout } = useAuth()
|
||||||
@@ -46,6 +47,9 @@ export default function ProfileSettings() {
|
|||||||
onRegenerateRecoveryCodes={regenerateRecoveryCodes}
|
onRegenerateRecoveryCodes={regenerateRecoveryCodes}
|
||||||
onGetSessions={getUserSessions}
|
onGetSessions={getUserSessions}
|
||||||
onRevokeSession={revokeSession}
|
onRevokeSession={revokeSession}
|
||||||
|
onRegisterPasskey={registerPasskey}
|
||||||
|
onListPasskeys={listPasskeys}
|
||||||
|
onDeletePasskey={deletePasskey}
|
||||||
onUpdatePreferences={updateUserPreferences}
|
onUpdatePreferences={updateUserPreferences}
|
||||||
deriveAuthKey={deriveAuthKey}
|
deriveAuthKey={deriveAuthKey}
|
||||||
refreshUser={refresh}
|
refreshUser={refresh}
|
||||||
|
|||||||
54
lib/api/webauthn.ts
Normal file
54
lib/api/webauthn.ts
Normal 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
7
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@ciphera-net/ui": "^0.0.64",
|
"@ciphera-net/ui": "^0.0.64",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
"@stripe/react-stripe-js": "^5.6.0",
|
"@stripe/react-stripe-js": "^5.6.0",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
@@ -2717,6 +2718,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@stripe/react-stripe-js": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@ciphera-net/ui": "^0.0.64",
|
"@ciphera-net/ui": "^0.0.64",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
"@stripe/react-stripe-js": "^5.6.0",
|
"@stripe/react-stripe-js": "^5.6.0",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user