mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
Refactor: Remove passkey-related functionality and types
- Deleted passkey-related interfaces and types from index.ts and types.ts. - Removed passkey handling from App component, including related state and functions. - Cleaned up API calls in auth.ts, removing passkey registration and login functions. - Updated vault and import formats to eliminate passkey references. - Removed passkey support checks and UI elements from AuthViews and SettingsPage. - Cleaned up unused passkey helper functions and constants. - Adjusted related components and hooks to ensure consistent functionality without passkey support.
This commit is contained in:
@@ -8,7 +8,6 @@ import type {
|
||||
TokenSuccess,
|
||||
} from '../types';
|
||||
import { parseJson, type AuthedFetch, type SessionSetter } from './shared';
|
||||
import { createPasskeyCredential, requestPasskeyAssertion } from '../passkey';
|
||||
|
||||
const SESSION_KEY = 'nodewarden.web.session.v4';
|
||||
const DEVICE_IDENTIFIER_KEY = 'nodewarden.web.device.identifier.v1';
|
||||
@@ -27,14 +26,6 @@ export interface PreloginKdfConfig {
|
||||
kdfParallelism: number | null;
|
||||
}
|
||||
|
||||
export interface AccountPasskey {
|
||||
id: string;
|
||||
name: string;
|
||||
creationDate: string;
|
||||
revisionDate: string;
|
||||
lastUsedDate: string | null;
|
||||
}
|
||||
|
||||
function randomHex(length: number): string {
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(Math.max(1, Math.ceil(length / 2))));
|
||||
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('').slice(0, length);
|
||||
@@ -206,84 +197,6 @@ export async function refreshAccessToken(refreshToken: string): Promise<TokenSuc
|
||||
return json || null;
|
||||
}
|
||||
|
||||
export async function listAccountPasskeys(authedFetch: AuthedFetch): Promise<AccountPasskey[]> {
|
||||
const resp = await authedFetch('/api/accounts/passkeys');
|
||||
if (!resp.ok) throw new Error('Failed to load passkeys');
|
||||
const body = (await parseJson<{ data?: AccountPasskey[] }>(resp)) || {};
|
||||
return Array.isArray(body.data) ? body.data : [];
|
||||
}
|
||||
|
||||
export async function registerAccountPasskey(authedFetch: AuthedFetch, name: string, session: SessionState): Promise<void> {
|
||||
const beginResp = await authedFetch('/api/accounts/passkeys/begin-registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{}',
|
||||
});
|
||||
if (!beginResp.ok) throw new Error('Failed to start passkey registration');
|
||||
const begin = (await parseJson<{ challengeId: string; publicKey: Record<string, any> }>(beginResp)) || {};
|
||||
if (!begin.challengeId || !begin.publicKey) throw new Error('Invalid registration challenge');
|
||||
|
||||
const credential = await createPasskeyCredential(begin.publicKey);
|
||||
const finishResp = await authedFetch('/api/accounts/passkeys/finish-registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
challengeId: begin.challengeId,
|
||||
name,
|
||||
wrappedVaultKeys: JSON.stringify({
|
||||
symEncKey: session.symEncKey || '',
|
||||
symMacKey: session.symMacKey || '',
|
||||
}),
|
||||
credential,
|
||||
}),
|
||||
});
|
||||
if (!finishResp.ok) {
|
||||
const err = await parseJson<TokenError>(finishResp);
|
||||
throw new Error(err?.error_description || err?.error || 'Failed to finish passkey registration');
|
||||
}
|
||||
}
|
||||
|
||||
export async function renameAccountPasskey(authedFetch: AuthedFetch, passkeyId: string, name: string): Promise<void> {
|
||||
const resp = await authedFetch(`/api/accounts/passkeys/${passkeyId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!resp.ok) throw new Error('Failed to rename passkey');
|
||||
}
|
||||
|
||||
export async function deleteAccountPasskey(authedFetch: AuthedFetch, passkeyId: string): Promise<void> {
|
||||
const resp = await authedFetch(`/api/accounts/passkeys/${passkeyId}`, { method: 'DELETE' });
|
||||
if (!resp.ok && resp.status !== 204) throw new Error('Failed to delete passkey');
|
||||
}
|
||||
|
||||
export async function loginWithPasskey(email?: string, totpCode?: string): Promise<TokenSuccess | TokenError> {
|
||||
const beginResp = await fetch('/identity/passkeys/begin-login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: String(email || '').trim().toLowerCase() || undefined }),
|
||||
});
|
||||
if (!beginResp.ok) return ((await parseJson<TokenError>(beginResp)) || {});
|
||||
const begin = (await parseJson<{ challengeId: string; publicKey: Record<string, any> }>(beginResp)) || {};
|
||||
if (!begin.challengeId || !begin.publicKey) return { error: 'Passkey challenge missing' };
|
||||
|
||||
const credential = await requestPasskeyAssertion(begin.publicKey);
|
||||
const finishResp = await fetch('/identity/passkeys/finish-login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
challengeId: begin.challengeId,
|
||||
credential,
|
||||
deviceIdentifier: getOrCreateDeviceIdentifier(),
|
||||
deviceName: guessDeviceName(),
|
||||
deviceType: '14',
|
||||
twoFactorToken: totpCode || undefined,
|
||||
}),
|
||||
});
|
||||
const result = (await parseJson<TokenSuccess & TokenError>(finishResp)) || {};
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function registerAccount(args: {
|
||||
email: string;
|
||||
name: string;
|
||||
|
||||
@@ -392,56 +392,6 @@ function toIsoDateOrNow(value: unknown): string {
|
||||
return parsed.toISOString();
|
||||
}
|
||||
|
||||
async function encryptMaybeFidoValue(
|
||||
value: unknown,
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array,
|
||||
fallback = ''
|
||||
): Promise<string> {
|
||||
const normalized = String(value ?? '').trim() || fallback;
|
||||
if (looksLikeCipherString(normalized)) return normalized;
|
||||
return encryptBw(new TextEncoder().encode(normalized), enc, mac);
|
||||
}
|
||||
|
||||
async function encryptMaybeNullableFidoValue(
|
||||
value: unknown,
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array
|
||||
): Promise<string | null> {
|
||||
const normalized = String(value ?? '').trim();
|
||||
if (!normalized) return null;
|
||||
if (looksLikeCipherString(normalized)) return normalized;
|
||||
return encryptBw(new TextEncoder().encode(normalized), enc, mac);
|
||||
}
|
||||
|
||||
async function normalizeFido2Credentials(
|
||||
credentials: Array<Record<string, unknown>> | null | undefined,
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array
|
||||
): Promise<Array<Record<string, unknown>> | null> {
|
||||
if (!Array.isArray(credentials) || credentials.length === 0) return null;
|
||||
const out: Array<Record<string, unknown>> = [];
|
||||
for (const credential of credentials) {
|
||||
if (!credential || typeof credential !== 'object') continue;
|
||||
out.push({
|
||||
credentialId: await encryptMaybeFidoValue(credential.credentialId, enc, mac),
|
||||
keyType: await encryptMaybeFidoValue(credential.keyType, enc, mac, 'public-key'),
|
||||
keyAlgorithm: await encryptMaybeFidoValue(credential.keyAlgorithm, enc, mac, 'ECDSA'),
|
||||
keyCurve: await encryptMaybeFidoValue(credential.keyCurve, enc, mac, 'P-256'),
|
||||
keyValue: await encryptMaybeFidoValue(credential.keyValue, enc, mac),
|
||||
rpId: await encryptMaybeFidoValue(credential.rpId, enc, mac),
|
||||
rpName: await encryptMaybeNullableFidoValue(credential.rpName, enc, mac),
|
||||
userHandle: await encryptMaybeNullableFidoValue(credential.userHandle, enc, mac),
|
||||
userName: await encryptMaybeNullableFidoValue(credential.userName, enc, mac),
|
||||
userDisplayName: await encryptMaybeNullableFidoValue(credential.userDisplayName, enc, mac),
|
||||
counter: await encryptMaybeFidoValue(credential.counter, enc, mac, '0'),
|
||||
discoverable: await encryptMaybeFidoValue(credential.discoverable, enc, mac, 'false'),
|
||||
creationDate: toIsoDateOrNow(credential.creationDate),
|
||||
});
|
||||
}
|
||||
return out.length ? out : null;
|
||||
}
|
||||
|
||||
async function getCipherKeys(
|
||||
cipher: Cipher | null,
|
||||
userEnc: Uint8Array,
|
||||
@@ -490,15 +440,10 @@ async function buildCipherPayload(
|
||||
}
|
||||
|
||||
if (type === 1) {
|
||||
const existingFido2 =
|
||||
cipher?.login && Array.isArray((cipher.login as any).fido2Credentials)
|
||||
? (cipher.login as any).fido2Credentials
|
||||
: draft.loginFido2Credentials;
|
||||
payload.login = {
|
||||
username: await encryptTextValue(draft.loginUsername, keys.enc, keys.mac),
|
||||
password: await encryptTextValue(draft.loginPassword, keys.enc, keys.mac),
|
||||
totp: await encryptTextValue(draft.loginTotp, keys.enc, keys.mac),
|
||||
fido2Credentials: await normalizeFido2Credentials(existingFido2, keys.enc, keys.mac),
|
||||
uris: await encryptUris(draft.loginUris || [], keys.enc, keys.mac),
|
||||
};
|
||||
} else if (type === 3) {
|
||||
|
||||
Reference in New Issue
Block a user