diff --git a/apps/desktop/src/components/DeviceRegistration.tsx b/apps/desktop/src/components/DeviceRegistration.tsx deleted file mode 100644 index ddc4f6f..0000000 --- a/apps/desktop/src/components/DeviceRegistration.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import type { DeviceRecord } from '@chat-app/shared/auth'; -import { extractErrorCode } from '@chat-app/shared/i18n'; -import { useCallback, useId, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { detectDesktopPlatform, registerCurrentDevice } from '../lib/device'; -import { AlertIcon, ArrowRightIcon, LockIcon, ShieldIcon, SpinnerIcon } from './icons'; - -interface Props { - userId: string; - defaultName: string; - onRegistered: (device: DeviceRecord) => void; -} - -export function DeviceRegistration({ userId, defaultName, onRegistered }: Props) { - const { t } = useTranslation(['auth', 'errors']); - const nameId = useId(); - const [name, setName] = useState(defaultName); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - - const handleSubmit = useCallback( - async (e: React.FormEvent) => { - e.preventDefault(); - const trimmed = name.trim(); - if (!trimmed) return; - setBusy(true); - setError(null); - try { - const device = await registerCurrentDevice({ userId, name: trimmed }); - onRegistered(device); - } catch (err: unknown) { - const code = extractErrorCode(err); - if (code) { - setError(t(`errors:${code}`, { defaultValue: t('errors:generic') })); - } else if (err instanceof Error) { - setError(err.message); - } else { - setError(t('errors:generic')); - } - } finally { - setBusy(false); - } - }, - [name, userId, onRegistered, t], - ); - - const platform = detectDesktopPlatform(); - - return ( -
-
-
- -
-
-

{t('auth:device.title')}

-

{t('auth:device.subtitle')}

-
-
- -
- - setName(e.target.value)} - placeholder={t('auth:device.name_placeholder')} - className="w-full rounded-lg border border-white/10 bg-ink-800 px-3 py-2.5 text-sm text-white placeholder-neutral-500 transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-500/40" - /> -

{t('auth:device.name_hint')}

-
- -
-
- - {t('auth:device.security_note_dev')} -
-
- - - - {error && ( -
- -

{error}

-
- )} - -

- {t('auth:device.device_platform', { defaultValue: 'Platform' })}: {platform} -

-
- ); -} diff --git a/apps/desktop/src/components/DeviceRestore.tsx b/apps/desktop/src/components/DeviceRestore.tsx deleted file mode 100644 index 2cc5499..0000000 --- a/apps/desktop/src/components/DeviceRestore.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { - type DeviceRecord, - restoreDeviceFromServerRecord, -} from '@chat-app/shared/auth'; -import { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { - decodePrivateKeyFromBackup, - importDeviceBackup, - normalizeRecoveryCode, -} from '../lib/deviceBackup'; -import { devLocalSecretStore } from '../lib/secretStore'; -import { supabase } from '../lib/supabase'; -import { writeLocalDeviceId } from '../lib/device'; -import { AlertIcon, ArrowRightIcon, LockIcon, ShieldIcon, SpinnerIcon } from './icons'; - -interface Props { - userId: string; - onRestored: (device: DeviceRecord) => void; -} - -// Restores a device from a user-provided backup string. The backup embeds -// userId + deviceId + X25519 private key; we verify userId matches the current -// session, confirm the device row still exists server-side, and then re-seed -// the local vault + cached deviceId so the app treats this install as the -// original device (conv-key bundles stay valid, no "awaiting" state). -export function DeviceRestore({ userId, onRestored }: Props) { - const { t } = useTranslation(['app', 'errors']); - const [backup, setBackup] = useState(''); - const [passphrase, setPassphrase] = useState(''); - const [mode, setMode] = useState<'passphrase' | 'recovery'>('passphrase'); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - - const handleSubmit = useCallback( - async (e: React.FormEvent) => { - e.preventDefault(); - if (!backup.trim() || passphrase.length < 1 || busy) return; - setBusy(true); - setError(null); - let privateKey: Uint8Array | null = null; - try { - const secret = - mode === 'recovery' ? normalizeRecoveryCode(passphrase) : passphrase; - const payload = await importDeviceBackup(backup.trim(), secret); - privateKey = decodePrivateKeyFromBackup(payload); - - const device = await restoreDeviceFromServerRecord({ - client: supabase, - secretStore: devLocalSecretStore, - userId: payload.userId, - deviceId: payload.deviceId, - privateKey, - }); - - // Cache deviceId locally so findExistingDevice picks it up on next load. - writeLocalDeviceId(userId, device.id); - - onRestored(device); - } catch (err: unknown) { - setError(err instanceof Error ? err.message : String(err)); - } finally { - if (privateKey) { - for (let i = 0; i < privateKey.length; i++) privateKey[i] = 0; - } - setBusy(false); - } - }, - [backup, passphrase, mode, userId, onRestored, busy], - ); - - return ( -
-
-
- -
-
-

- {t('app:backup.restore_title', { defaultValue: 'Backup wiederherstellen' })} -

-

- {t('app:backup.restore_subtitle', { - defaultValue: 'Bringe einen zuvor erstellten Backup-String + Passphrase mit.', - })} -

-
-
- -
-
- -