mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: implement account passkey functionality
- Added functions for managing account passkeys including creation, listing, updating, and deletion. - Introduced login methods using account passkeys with options for direct unlock and login-only modes. - Enhanced error handling and response parsing for passkey-related API calls. - Updated UI styles for account passkey management components. - Added new translations for account passkey features in multiple languages. - Modified network status handling to improve service reachability checks.
This commit is contained in:
@@ -4,27 +4,40 @@ import {
|
||||
deleteAllAuthorizedDevices,
|
||||
deleteAuthorizedDevice,
|
||||
deriveLoginHash,
|
||||
deleteAccountPasskey as deleteAccountPasskeyApi,
|
||||
enableAccountPasskeyDirectUnlock as enableAccountPasskeyDirectUnlockApi,
|
||||
getCurrentDeviceIdentifier,
|
||||
getApiKey,
|
||||
getAccountPasskeyAttestationOptions,
|
||||
getAccountPasskeyUpdateAssertionOptions,
|
||||
getTotpRecoveryCode,
|
||||
listAccountPasskeys,
|
||||
rotateApiKey,
|
||||
revokeAuthorizedDeviceTrust,
|
||||
revokeAllAuthorizedDeviceTrust,
|
||||
saveAccountPasskey,
|
||||
setTotp,
|
||||
trustAuthorizedDevicePermanently,
|
||||
updateAuthorizedDeviceName,
|
||||
updateProfile,
|
||||
} from '@/lib/api/auth';
|
||||
import {
|
||||
assertAccountPasskey,
|
||||
buildAccountPasskeyPrfKeySet,
|
||||
buildAccountPasskeyPrfKeySetFromPrfKey,
|
||||
createAccountPasskeyCredential,
|
||||
} from '@/lib/account-passkeys';
|
||||
import { t } from '@/lib/i18n';
|
||||
import type { AppConfirmState } from '@/components/AppGlobalOverlays';
|
||||
import type { AuthedFetch } from '@/lib/api/shared';
|
||||
import type { AuthorizedDevice, Profile } from '@/lib/types';
|
||||
import type { AccountPasskeyCredential, AuthorizedDevice, Profile, SessionState } from '@/lib/types';
|
||||
|
||||
type Notify = (type: 'success' | 'error' | 'warning', text: string) => void;
|
||||
|
||||
interface UseAccountSecurityActionsOptions {
|
||||
authedFetch: AuthedFetch;
|
||||
profile: Profile | null;
|
||||
session: SessionState | null;
|
||||
defaultKdfIterations: number;
|
||||
disableTotpPassword: string;
|
||||
clearDisableTotpDialog: () => void;
|
||||
@@ -40,6 +53,7 @@ export default function useAccountSecurityActions(options: UseAccountSecurityAct
|
||||
const {
|
||||
authedFetch,
|
||||
profile,
|
||||
session,
|
||||
defaultKdfIterations,
|
||||
disableTotpPassword,
|
||||
clearDisableTotpDialog,
|
||||
@@ -170,6 +184,68 @@ export default function useAccountSecurityActions(options: UseAccountSecurityAct
|
||||
return key;
|
||||
},
|
||||
|
||||
async listAccountPasskeys(): Promise<AccountPasskeyCredential[]> {
|
||||
return listAccountPasskeys(authedFetch);
|
||||
},
|
||||
|
||||
async createAccountPasskey(name: string, masterPassword: string, directUnlock: boolean): Promise<AccountPasskeyCredential> {
|
||||
if (!profile) throw new Error(t('txt_profile_unavailable'));
|
||||
const normalizedPassword = String(masterPassword || '');
|
||||
if (!normalizedPassword) throw new Error(t('txt_master_password_is_required'));
|
||||
const normalizedName = String(name || '').trim() || t('txt_account_passkey');
|
||||
const derived = await deriveLoginHash(profile.email, normalizedPassword, defaultKdfIterations);
|
||||
const options = await getAccountPasskeyAttestationOptions(authedFetch, derived.hash);
|
||||
const pending = await createAccountPasskeyCredential(options);
|
||||
let keySet = null;
|
||||
if (directUnlock) {
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error(t('txt_vault_key_unavailable'));
|
||||
keySet = await buildAccountPasskeyPrfKeySet(pending, {
|
||||
symEncKey: session.symEncKey,
|
||||
symMacKey: session.symMacKey,
|
||||
});
|
||||
}
|
||||
const credential = await saveAccountPasskey(authedFetch, {
|
||||
name: normalizedName,
|
||||
token: pending.token,
|
||||
deviceResponse: pending.request,
|
||||
supportsPrf: pending.supportsPrf,
|
||||
keySet,
|
||||
});
|
||||
onNotify('success', t('txt_account_passkey_saved'));
|
||||
return credential;
|
||||
},
|
||||
|
||||
async enableAccountPasskeyDirectUnlock(id: string, masterPassword: string): Promise<void> {
|
||||
if (!profile) throw new Error(t('txt_profile_unavailable'));
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error(t('txt_vault_key_unavailable'));
|
||||
if (!String(id || '').trim()) throw new Error(t('txt_account_passkey_not_found'));
|
||||
const normalizedPassword = String(masterPassword || '');
|
||||
if (!normalizedPassword) throw new Error(t('txt_master_password_is_required'));
|
||||
const derived = await deriveLoginHash(profile.email, normalizedPassword, defaultKdfIterations);
|
||||
const options = await getAccountPasskeyUpdateAssertionOptions(authedFetch, derived.hash, id);
|
||||
const assertion = await assertAccountPasskey(options);
|
||||
if (!assertion.prfKey) throw new Error(t('txt_account_passkey_prf_not_available'));
|
||||
const keySet = await buildAccountPasskeyPrfKeySetFromPrfKey(assertion.prfKey, {
|
||||
symEncKey: session.symEncKey,
|
||||
symMacKey: session.symMacKey,
|
||||
});
|
||||
await enableAccountPasskeyDirectUnlockApi(authedFetch, {
|
||||
token: assertion.token,
|
||||
deviceResponse: assertion.deviceResponse,
|
||||
keySet,
|
||||
});
|
||||
onNotify('success', t('txt_account_passkey_direct_unlock_enabled'));
|
||||
},
|
||||
|
||||
async deleteAccountPasskey(id: string, masterPassword: string): Promise<void> {
|
||||
if (!profile) throw new Error(t('txt_profile_unavailable'));
|
||||
const normalizedPassword = String(masterPassword || '');
|
||||
if (!normalizedPassword) throw new Error(t('txt_master_password_is_required'));
|
||||
const derived = await deriveLoginHash(profile.email, normalizedPassword, defaultKdfIterations);
|
||||
await deleteAccountPasskeyApi(authedFetch, id, derived.hash);
|
||||
onNotify('success', t('txt_account_passkey_deleted'));
|
||||
},
|
||||
|
||||
async refreshAuthorizedDevices() {
|
||||
await refetchAuthorizedDevices();
|
||||
},
|
||||
@@ -304,6 +380,8 @@ export default function useAccountSecurityActions(options: UseAccountSecurityAct
|
||||
onProfileUpdated,
|
||||
onSetConfirm,
|
||||
profile,
|
||||
session?.symEncKey,
|
||||
session?.symMacKey,
|
||||
refetchAuthorizedDevices,
|
||||
refetchTotpStatus,
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user