import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; /** Secrets stay in this mounted component, never in page props or remembered state. */ export default function TwoFactorManagement({ enabled: initialEnabled, pending: initialPending, }: { enabled: boolean; pending: boolean; }) { const [enabled, setEnabled] = useState(initialEnabled); const [pending, setPending] = useState(initialPending); const [secret, setSecret] = useState(''); const [code, setCode] = useState(''); const [codes, setCodes] = useState([]); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [confirm, setConfirm] = useState<'disable' | 'regenerate' | null>( null, ); async function request( path: string, method = 'GET', data?: Record, ) { const token = document.cookie .split('; ') .find((cookie) => cookie.startsWith('XSRF-TOKEN=')) ?.slice(11); const response = await fetch(path, { method, credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json', ...(method !== 'GET' ? { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': decodeURIComponent(token ?? ''), } : {}), }, ...(data ? { body: JSON.stringify(data) } : {}), }); if (response.status === 423) throw new Error( 'Confirme novamente a palavra-passe, abrindo esta página outra vez.', ); if (!response.ok) { const body = await response.json().catch(() => null); throw new Error( response.status === 422 ? (body?.errors?.code?.[0] ?? 'Confirme os dados introduzidos.') : 'Não foi possível concluir. Actualize a página e tente novamente.', ); } return response.status === 204 ? null : response.json(); } async function run(action: () => Promise) { setBusy(true); setError(''); try { await action(); } catch (failure) { setError( failure instanceof Error ? failure.message : 'Não foi possível concluir.', ); } finally { setBusy(false); } } const loadSecret = async () => { const data = await request('/user/two-factor-secret-key'); setSecret(data.secretKey); }; const loadCodes = async () => { setCodes(await request('/user/two-factor-recovery-codes')); }; return (

Autenticação de dois factores

{enabled ? 'Activa. Nos próximos acessos será pedido o código da aplicação ou um código de recuperação.' : pending ? 'Adesão por confirmar. O segundo factor só será activado depois de confirmar um código válido.' : 'Acrescente um código da aplicação autenticadora à palavra-passe desta conta.'}

{error && (

{error}{' '} Reabrir segurança da conta

)} {!enabled && !pending && ( )} {pending && !secret && ( )} {pending && secret && ( <>

Na aplicação autenticadora, adicione uma conta com esta chave manual. Não partilhe esta chave.

{secret}
{ event.preventDefault(); void run(async () => { await request( '/user/confirmed-two-factor-authentication', 'POST', { code }, ); setEnabled(true); setPending(false); setSecret(''); setCode(''); await loadCodes(); }); }} >
)} {enabled && (
)} {codes.length > 0 && (

Guarde estes códigos num local privado. Cada um permite recuperar um acesso e só pode ser usado uma vez.

    {codes.map((value) => (
  • {value}
  • ))}
)} {(enabled || pending) && ( )} {confirm && (

{confirm === 'disable' ? 'Esta conta deixará de exigir o segundo factor. A palavra-passe permanece necessária.' : 'Os códigos anteriores deixarão de funcionar. Guarde os novos códigos depois de confirmar.'}

)}
); }