admin / Synapse-Cortex
publicSelf Hosted ITSM Tool with RBAC/Tenanting and MFA
Synapse-Cortex / Synapse-Cortexv2 / frontend / src / pages / MfaEnroll.tsx
3149 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import { useEffect, useState, type FormEvent } from 'react' import { useNavigate } from 'react-router-dom' import { settingsApi } from '../api/settings' import { ApiError } from '../api/client' import { useAuth } from '../auth/AuthContext' import { usePageHeader } from '../layout/PageHeaderContext' export function MfaEnroll() { usePageHeader('Settings // Enable MFA') const [qr, setQr] = useState<string | null>(null) const [secret, setSecret] = useState('') const [code, setCode] = useState('') const [error, setError] = useState<string | null>(null) const [submitting, setSubmitting] = useState(false) const navigate = useNavigate() const { refresh } = useAuth() useEffect(() => { settingsApi.mfaEnrollInit().then((res) => { setQr(res.qr_data_url) setSecret(res.secret) }) }, []) const onSubmit = async (e: FormEvent) => { e.preventDefault() setError(null) setSubmitting(true) try { await settingsApi.mfaEnrollVerify(code) await refresh() navigate('/settings') } catch (err) { setError(err instanceof ApiError ? err.message : 'Invalid code.') } finally { setSubmitting(false) } } return ( <div className="max-w-md border border-cortex-border bg-cortex-panel rounded-md p-6"> {error && ( <div className="mb-4 text-xs border border-accent-red/40 bg-accent-red/10 text-accent-red px-3 py-2 rounded"> {error} </div> )} {qr && ( <div className="bg-white rounded p-3 flex justify-center mb-4"> <img src={qr} alt="MFA QR code" className="h-40 w-40" /> </div> )} <div className="text-[10px] text-cortex-muted uppercase tracking-wider mb-1">Manual entry code</div> <div className="font-mono text-xs break-all bg-cortex-panel2 border border-cortex-border rounded px-3 py-2 mb-4"> {secret} </div> <form onSubmit={onSubmit} className="space-y-3"> <div> <label className="block text-xs text-cortex-muted mb-1 uppercase tracking-wider"> Confirmation Code </label> <input required inputMode="numeric" maxLength={6} value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))} className="w-full bg-cortex-panel2 border border-cortex-border rounded px-3 py-2 font-mono text-lg tracking-widest focus:outline-none focus:border-accent-blue" /> </div> <div className="flex gap-3"> <button type="submit" disabled={submitting} className="bg-accent-blue text-cortex-bg font-display font-bold uppercase tracking-wider text-xs px-4 py-2 rounded hover:bg-accent-blue-bright transition disabled:opacity-50" > Confirm & Enable </button> <button type="button" onClick={() => navigate('/settings')} className="text-xs text-cortex-muted hover:text-accent-red transition" > Cancel </button> </div> </form> </div> ) } |