mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-21 05:10: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:
@@ -94,10 +94,6 @@ export interface AppMainRoutesProps {
|
||||
onEnableTotp: (secret: string, token: string) => Promise<void>;
|
||||
onOpenDisableTotp: () => void;
|
||||
onGetRecoveryCode: (masterPassword: string) => Promise<string>;
|
||||
passkeys: Array<{ id: string; name: string; creationDate: string; lastUsedDate: string | null }>;
|
||||
onCreatePasskey: (name: string) => Promise<void>;
|
||||
onRenamePasskey: (id: string, name: string) => Promise<void>;
|
||||
onDeletePasskey: (id: string) => Promise<void>;
|
||||
onRefreshAuthorizedDevices: () => Promise<void>;
|
||||
onRevokeDeviceTrust: (device: AuthorizedDevice) => void;
|
||||
onRemoveDevice: (device: AuthorizedDevice) => void;
|
||||
@@ -229,10 +225,6 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
|
||||
onOpenDisableTotp={props.onOpenDisableTotp}
|
||||
onGetRecoveryCode={props.onGetRecoveryCode}
|
||||
onNotify={props.onNotify}
|
||||
passkeys={props.passkeys}
|
||||
onCreatePasskey={props.onCreatePasskey}
|
||||
onRenamePasskey={props.onRenamePasskey}
|
||||
onDeletePasskey={props.onDeletePasskey}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'preact/hooks';
|
||||
import { ArrowLeft, Eye, EyeOff, Fingerprint, LogIn, LogOut, Unlock, UserPlus } from 'lucide-preact';
|
||||
import { ArrowLeft, Eye, EyeOff, LogIn, LogOut, Unlock, UserPlus } from 'lucide-preact';
|
||||
import StandalonePageFrame from '@/components/StandalonePageFrame';
|
||||
import { t } from '@/lib/i18n';
|
||||
|
||||
@@ -30,7 +30,6 @@ interface AuthViewsProps {
|
||||
onChangeRegister: (next: RegisterValues) => void;
|
||||
onChangeUnlock: (password: string) => void;
|
||||
onSubmitLogin: () => void;
|
||||
onSubmitPasskey: () => void;
|
||||
onSubmitRegister: () => void;
|
||||
onSubmitUnlock: () => void;
|
||||
onGotoLogin: () => void;
|
||||
@@ -38,7 +37,6 @@ interface AuthViewsProps {
|
||||
onLogout: () => void;
|
||||
onTogglePasswordHint: () => void;
|
||||
onShowLockedPasswordHint: () => void;
|
||||
passkeySupported: boolean;
|
||||
}
|
||||
|
||||
function PasswordField(props: {
|
||||
@@ -108,12 +106,6 @@ export default function AuthViews(props: AuthViewsProps) {
|
||||
<Unlock size={16} className="btn-icon" />
|
||||
{unlockBusy ? t('txt_unlocking') : t('txt_unlock')}
|
||||
</button>
|
||||
{props.passkeySupported && (
|
||||
<button type="button" className="btn btn-secondary full" onClick={props.onSubmitPasskey} disabled={unlockBusy}>
|
||||
<Fingerprint size={16} className="btn-icon" />
|
||||
Passkey 解锁
|
||||
</button>
|
||||
)}
|
||||
<div className="or">{t('txt_or')}</div>
|
||||
<button type="button" className="btn btn-secondary full" onClick={props.onLogout} disabled={unlockBusy}>
|
||||
<LogOut size={16} className="btn-icon" />
|
||||
@@ -251,12 +243,6 @@ export default function AuthViews(props: AuthViewsProps) {
|
||||
<LogIn size={16} className="btn-icon" />
|
||||
{loginBusy ? t('txt_logging_in') : t('txt_log_in')}
|
||||
</button>
|
||||
{props.passkeySupported && (
|
||||
<button type="button" className="btn btn-secondary full" onClick={props.onSubmitPasskey} disabled={loginBusy}>
|
||||
<Fingerprint size={16} className="btn-icon" />
|
||||
Passkey 登录
|
||||
</button>
|
||||
)}
|
||||
<div className="or">{t('txt_or')}</div>
|
||||
<button type="button" className="btn btn-secondary full" onClick={props.onGotoRegister} disabled={loginBusy}>
|
||||
<UserPlus size={16} className="btn-icon" />
|
||||
|
||||
@@ -15,10 +15,6 @@ interface SettingsPageProps {
|
||||
onOpenDisableTotp: () => void;
|
||||
onGetRecoveryCode: (masterPassword: string) => Promise<string>;
|
||||
onNotify?: (type: 'success' | 'error', text: string) => void;
|
||||
passkeys: Array<{ id: string; name: string; creationDate: string; lastUsedDate: string | null }>;
|
||||
onCreatePasskey: (name: string) => Promise<void>;
|
||||
onRenamePasskey: (id: string, name: string) => Promise<void>;
|
||||
onDeletePasskey: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
function randomBase32Secret(length: number): string {
|
||||
@@ -52,10 +48,6 @@ export default function SettingsPage(props: SettingsPageProps) {
|
||||
const [totpLocked, setTotpLocked] = useState(props.totpEnabled);
|
||||
const [recoveryMasterPassword, setRecoveryMasterPassword] = useState('');
|
||||
const [recoveryCode, setRecoveryCode] = useState('');
|
||||
const [passkeyName, setPasskeyName] = useState('');
|
||||
const [renamePasskey, setRenamePasskey] = useState<{ id: string; name: string } | null>(null);
|
||||
const [renamePasskeyName, setRenamePasskeyName] = useState('');
|
||||
const [deletePasskey, setDeletePasskey] = useState<{ id: string; name: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.totpEnabled) {
|
||||
@@ -102,21 +94,6 @@ export default function SettingsPage(props: SettingsPageProps) {
|
||||
return parsed.toLocaleString();
|
||||
}
|
||||
|
||||
async function confirmRenamePasskey(): Promise<void> {
|
||||
if (!renamePasskey) return;
|
||||
const nextName = renamePasskeyName.trim();
|
||||
if (!nextName) return;
|
||||
await props.onRenamePasskey(renamePasskey.id, nextName);
|
||||
setRenamePasskey(null);
|
||||
setRenamePasskeyName('');
|
||||
}
|
||||
|
||||
async function confirmDeletePasskey(): Promise<void> {
|
||||
if (!deletePasskey) return;
|
||||
await props.onDeletePasskey(deletePasskey.id);
|
||||
setDeletePasskey(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="stack">
|
||||
<section className="card">
|
||||
@@ -172,91 +149,6 @@ export default function SettingsPage(props: SettingsPageProps) {
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<h3>Passkey</h3>
|
||||
<div className="field-grid">
|
||||
<label className="field">
|
||||
<span>名称</span>
|
||||
<input className="input" value={passkeyName} onInput={(e) => setPasskeyName((e.currentTarget as HTMLInputElement).value)} placeholder="例如:MacBook Touch ID" />
|
||||
</label>
|
||||
<div className="field" style={{ alignSelf: 'end' }}>
|
||||
<button type="button" className="btn btn-primary" disabled={!passkeyName.trim()} onClick={() => void props.onCreatePasskey(passkeyName.trim()).then(() => setPasskeyName(''))}>
|
||||
创建 Passkey
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="muted-inline" style={{ marginBottom: 8 }}>最多 5 个,支持重命名和删除。</p>
|
||||
<div className="stack" style={{ gap: 6 }}>
|
||||
{props.passkeys.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
border: '1px solid var(--line)',
|
||||
borderRadius: 10,
|
||||
padding: '10px 12px',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<strong>{item.name}</strong>
|
||||
<span style={{ marginLeft: 'auto', fontSize: 12, opacity: 0.72 }}>
|
||||
创建于 {formatDateTime(item.creationDate)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary small"
|
||||
onClick={() => {
|
||||
setRenamePasskey({ id: item.id, name: item.name });
|
||||
setRenamePasskeyName(item.name);
|
||||
}}
|
||||
>
|
||||
{t('txt_edit')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger small"
|
||||
onClick={() => setDeletePasskey({ id: item.id, name: item.name })}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{!props.passkeys.length && <div className="empty">暂无 Passkey</div>}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ConfirmDialog
|
||||
open={!!renamePasskey}
|
||||
title={t('txt_edit')}
|
||||
message={t('txt_enter_a_folder_name')}
|
||||
confirmText={t('txt_save')}
|
||||
cancelText={t('txt_cancel')}
|
||||
onConfirm={() => void confirmRenamePasskey()}
|
||||
onCancel={() => {
|
||||
setRenamePasskey(null);
|
||||
setRenamePasskeyName('');
|
||||
}}
|
||||
>
|
||||
<label className="field">
|
||||
<span>{t('txt_name')}</span>
|
||||
<input className="input" value={renamePasskeyName} onInput={(e) => setRenamePasskeyName((e.currentTarget as HTMLInputElement).value)} />
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
|
||||
<ConfirmDialog
|
||||
open={!!deletePasskey}
|
||||
title={t('txt_delete')}
|
||||
message={deletePasskey ? `确认删除 Passkey「${deletePasskey.name}」吗?` : ''}
|
||||
variant="warning"
|
||||
danger
|
||||
confirmText={t('txt_delete')}
|
||||
cancelText={t('txt_cancel')}
|
||||
onConfirm={() => void confirmDeletePasskey()}
|
||||
onCancel={() => setDeletePasskey(null)}
|
||||
/>
|
||||
|
||||
<section className="card">
|
||||
<div className="settings-twofactor-grid">
|
||||
<div className="settings-subcard">
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
draftFromCipher,
|
||||
buildCipherDuplicateSignature,
|
||||
firstCipherUri,
|
||||
firstPasskeyCreationTime,
|
||||
isCipherVisibleInArchive,
|
||||
isCipherVisibleInNormalVault,
|
||||
isCipherVisibleInTrash,
|
||||
@@ -352,7 +351,6 @@ export default function VaultPage(props: VaultPageProps) {
|
||||
() => filteredCiphers.slice(virtualRange.start, virtualRange.end),
|
||||
[filteredCiphers, virtualRange.start, virtualRange.end]
|
||||
);
|
||||
const passkeyCreatedAt = firstPasskeyCreationTime(selectedCipher);
|
||||
const selectedAttachments = useMemo(
|
||||
() => (Array.isArray(selectedCipher?.attachments) ? selectedCipher.attachments : []),
|
||||
[selectedCipher]
|
||||
@@ -973,7 +971,6 @@ function folderName(id: string | null | undefined): string {
|
||||
repromptApprovedCipherId={repromptApprovedCipherId}
|
||||
showPassword={showPassword}
|
||||
totpLive={totpLive}
|
||||
passkeyCreatedAt={passkeyCreatedAt}
|
||||
hiddenFieldVisibleMap={hiddenFieldVisibleMap}
|
||||
folderName={folderName}
|
||||
onOpenReprompt={() => setRepromptOpen(true)}
|
||||
|
||||
@@ -20,7 +20,6 @@ interface VaultDetailViewProps {
|
||||
repromptApprovedCipherId: string | null;
|
||||
showPassword: boolean;
|
||||
totpLive: { code: string; remain: number } | null;
|
||||
passkeyCreatedAt: string | null;
|
||||
hiddenFieldVisibleMap: Record<number, boolean>;
|
||||
folderName: (id: string | null | undefined) => string;
|
||||
downloadingAttachmentKey: string;
|
||||
@@ -136,15 +135,6 @@ export default function VaultDetailView(props: VaultDetailViewProps) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!!props.passkeyCreatedAt && (
|
||||
<div className="kv-row">
|
||||
<span className="kv-label">{t('txt_passkey')}</span>
|
||||
<div className="kv-main">
|
||||
<strong>{t('txt_passkey_created_at_value', { value: formatHistoryTime(props.passkeyCreatedAt) })}</strong>
|
||||
</div>
|
||||
<div className="kv-actions" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -194,9 +194,6 @@ export function buildCipherDuplicateSignature(cipher: Cipher): string {
|
||||
uri: valueOrFallback(uri.decUri ?? uri.uri),
|
||||
match: uri.match ?? null,
|
||||
})),
|
||||
fido2Credentials: (cipher.login.fido2Credentials || []).map((credential) => ({
|
||||
creationDate: valueOrFallback(credential.creationDate),
|
||||
})),
|
||||
}
|
||||
: null,
|
||||
card: cipher.card
|
||||
@@ -265,7 +262,6 @@ export function createEmptyDraft(type: number): VaultDraft {
|
||||
loginPassword: '',
|
||||
loginTotp: '',
|
||||
loginUris: [createEmptyLoginUri()],
|
||||
loginFido2Credentials: [],
|
||||
cardholderName: '',
|
||||
cardNumber: '',
|
||||
cardBrand: '',
|
||||
@@ -314,9 +310,6 @@ export function draftFromCipher(cipher: Cipher): VaultDraft {
|
||||
uri: x.decUri || x.uri || '',
|
||||
match: x.match ?? null,
|
||||
}));
|
||||
draft.loginFido2Credentials = Array.isArray(cipher.login.fido2Credentials)
|
||||
? cipher.login.fido2Credentials.map((credential) => ({ ...credential }))
|
||||
: [];
|
||||
if (!draft.loginUris.length) draft.loginUris = [createEmptyLoginUri()];
|
||||
}
|
||||
if (cipher.card) {
|
||||
@@ -413,16 +406,6 @@ export function creationTimeValue(cipher: Cipher): number {
|
||||
return Number.isFinite(time) ? time : 0;
|
||||
}
|
||||
|
||||
export function firstPasskeyCreationTime(cipher: Cipher | null): string | null {
|
||||
const credentials = cipher?.login?.fido2Credentials;
|
||||
if (!Array.isArray(credentials) || credentials.length === 0) return null;
|
||||
for (const credential of credentials) {
|
||||
const raw = String(credential?.creationDate || '').trim();
|
||||
if (raw) return raw;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const failedIconHosts = new Set<string>();
|
||||
|
||||
export function VaultListIcon({ cipher }: { cipher: Cipher }) {
|
||||
|
||||
Reference in New Issue
Block a user