127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { Management, button, control } from '@/pages/management/shared';
|
|
import { profileJson } from '@/lib/profile-json';
|
|
import { createOperationKey } from '@/lib/operation-key';
|
|
type Plan = {
|
|
approval_id: string;
|
|
requested_by: number;
|
|
second_user_id: number;
|
|
reason: string;
|
|
expires_at: string;
|
|
consumed: boolean;
|
|
expired: boolean;
|
|
code_current: boolean;
|
|
can_confirm: boolean;
|
|
mfa_confirmed_in_session: boolean;
|
|
hash: string;
|
|
target: { database: string; port: number };
|
|
};
|
|
export default function BootstrapConfirmation(plan: Plan) {
|
|
const [key] = useState(createOperationKey);
|
|
const [reason, setReason] = useState('');
|
|
const [confirmed, setConfirmed] = useState(false);
|
|
const [busy, setBusy] = useState(false);
|
|
const [done, setDone] = useState(plan.consumed);
|
|
const [message, setMessage] = useState('');
|
|
async function confirm() {
|
|
setBusy(true);
|
|
setMessage('');
|
|
try {
|
|
await profileJson(
|
|
'/management/profile-install/bootstrap/' +
|
|
plan.approval_id +
|
|
'/confirm',
|
|
{
|
|
hash: plan.hash,
|
|
confirm: confirmed,
|
|
idempotency_key: key,
|
|
reason,
|
|
},
|
|
);
|
|
setDone(true);
|
|
setMessage(
|
|
'Dupla inicializada por confirmação cruzada. Apenas estas duas contas foram designadas; nenhuma flag foi alterada. Inicie uma nova sessão.',
|
|
);
|
|
} catch (error) {
|
|
setMessage(
|
|
error instanceof Error
|
|
? error.message
|
|
: 'Resposta indisponível. Recupere este pedido antes de repetir.',
|
|
);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
return (
|
|
<Management title="Confirmar os dois titulares">
|
|
<p>
|
|
Base {plan.target.database}:{plan.target.port}
|
|
</p>
|
|
<p>
|
|
Pedido apresentado pela conta {plan.requested_by}; confirmação
|
|
reservada à conta {plan.second_user_id}.
|
|
</p>
|
|
<p>Fundamento do pedido: {plan.reason}</p>
|
|
<p>Expira em {plan.expires_at}.</p>
|
|
<p>
|
|
O pedido não atribui privilégios. A confirmação é uma operação
|
|
distinta, com MFA e auditoria. Não altera a regra de
|
|
substituição posterior.
|
|
</p>
|
|
{plan.expired && (
|
|
<p role="alert">
|
|
Pedido expirado. Prepare novo pedido autorizado.
|
|
</p>
|
|
)}
|
|
{!plan.code_current && (
|
|
<p role="alert">
|
|
Revisão ou destino alterados. Este pedido não pode ser
|
|
confirmado.
|
|
</p>
|
|
)}
|
|
{!plan.mfa_confirmed_in_session && (
|
|
<p role="alert">Conclua MFA nesta sessão antes de confirmar.</p>
|
|
)}
|
|
{!plan.can_confirm && !done && (
|
|
<p>Esta conta pode consultar, mas não confirmar o pedido.</p>
|
|
)}
|
|
<label className="block">
|
|
Fundamento da confirmação
|
|
<textarea
|
|
className={control}
|
|
minLength={20}
|
|
maxLength={1000}
|
|
value={reason}
|
|
onChange={(e) => setReason(e.target.value)}
|
|
/>
|
|
</label>
|
|
<label className="block">
|
|
<input
|
|
type="checkbox"
|
|
checked={confirmed}
|
|
onChange={(e) => setConfirmed(e.target.checked)}
|
|
/>{' '}
|
|
Confirmo apenas estas duas contas e esta base
|
|
</label>
|
|
<button
|
|
className={button}
|
|
disabled={
|
|
busy ||
|
|
done ||
|
|
!plan.can_confirm ||
|
|
!plan.mfa_confirmed_in_session ||
|
|
!confirmed ||
|
|
reason.length < 20
|
|
}
|
|
onClick={() => void confirm()}
|
|
>
|
|
Confirmar designação cruzada
|
|
</button>
|
|
<p role="status">
|
|
{done ? 'Pedido consumido; não reinicializar. ' : ''}
|
|
{message}
|
|
</p>
|
|
</Management>
|
|
);
|
|
}
|