import { useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Button, Input } from '@gouvfr-lasuite/cunningham-react'
import { useFlow } from '../../api/flows'
import { useSessionStore } from '../../stores/session'
import FlowForm from '../../components/FlowNodes/FlowForm'
export default function SecurityPage() {
const [params] = useSearchParams()
const flowId = params.get('flow')
const { needs2faSetup } = useSessionStore()
// New users arriving from recovery flow: redirect to onboarding wizard
if (needs2faSetup) {
window.location.href = '/onboarding'
return
Redirecting to account setup...
}
// If redirected back from Kratos with a flow param, show the result
if (flowId) {
return (
Security
)
}
return (
Security
{needs2faSetup && (
You must set up two-factor authentication before you can use this app.
Configure an authenticator app or security key below.
)}
Two-Factor Authentication
Add a second layer of security to your account using an authenticator app, security key, or backup codes.
)
}
function PasswordSection() {
const [expanded, setExpanded] = useState(false)
const [password, setPassword] = useState('')
const [confirm, setConfirm] = useState('')
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
const mismatch = confirm.length > 0 && password !== confirm
const handleSubmit = async () => {
if (!password || password !== confirm) return
setSaving(true)
setMessage(null)
try {
const flowResp = await fetch('/kratos/self-service/settings/browser', {
credentials: 'include',
headers: { Accept: 'application/json' },
})
if (!flowResp.ok) throw new Error('Failed to create settings flow')
const flow = await flowResp.json()
const csrfToken = flow.ui.nodes.find(
(n: { attributes: { name: string } }) => n.attributes.name === 'csrf_token'
)?.attributes?.value ?? ''
const submitResp = await fetch(flow.ui.action, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'include',
body: new URLSearchParams({
csrf_token: csrfToken,
method: 'password',
password,
}),
})
if (submitResp.ok || submitResp.status === 422) {
const result = await submitResp.json()
const errorMsg = result.ui?.messages?.find((m: { type: string }) => m.type === 'error')
if (errorMsg) {
setMessage({ type: 'error', text: errorMsg.text })
} else {
setMessage({ type: 'success', text: 'Password changed successfully.' })
setPassword('')
setConfirm('')
setExpanded(false)
}
} else {
throw new Error('Failed to change password')
}
} catch (err) {
setMessage({ type: 'error', text: String(err) })
} finally {
setSaving(false)
}
}
if (!expanded) {
return (
<>
{message && (
{message.text}
)}
>
)
}
return (
{message && (
{message.text}
)}
) => setPassword(e.target.value)}
fullWidth
/>
) => setConfirm(e.target.value)}
state={mismatch ? 'error' : undefined}
text={mismatch ? 'Passwords do not match' : undefined}
fullWidth
/>
)
}
function MfaSection({ method, title, description }: { method: string; title: string; description: string }) {
const [flowId, setFlowId] = useState(null)
const [loading, setLoading] = useState(false)
const [expanded, setExpanded] = useState(false)
const startFlow = async () => {
setLoading(true)
try {
const resp = await fetch('/kratos/self-service/settings/browser', {
credentials: 'include',
headers: { Accept: 'application/json' },
})
if (resp.ok) {
const flow = await resp.json()
setFlowId(flow.id)
setExpanded(true)
}
} catch {
// ignore
} finally {
setLoading(false)
}
}
return (
{!expanded && (
)}
{expanded && flowId && (
)}
)
}
function SettingsFlow({ flowId, only, exclude }: { flowId: string; only?: string; exclude?: string[] }) {
const { data: flow, isLoading, error } = useFlow('settings', flowId)
if (isLoading) return Loading...
if (error) return Error: {String(error)}
if (!flow) return null
return
}