"use client" import { useEffect, useMemo, useRef, useState } from "react" import { Loader2 } from "lucide-react" import { AuthConnectButton } from "@/components/auth/auth-connect-button" import { PasswordFieldBlock } from "@/components/auth/password-field-block" import { isSignupCredentialsStep, SignupCredentialsFields, } from "@/components/auth/signup-credentials-fields" import { buildSignupEmail } from "@/lib/auth/signup-platform" import { AUTH_FLOW_SLUGS } from "@/lib/auth/auth-flow-slugs" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import type { FlowChallenge } from "@/lib/auth/flow-api" import { flowRedirectUrl } from "@/lib/auth/flow-api" import { FLOW_STAGE_REGISTRY, isKnownFlowComponent, readPrimaryAction, } from "@/components/auth/flow-stage-registry" import { PasswordStage, UserLoginStage } from "@/components/auth/flow-stages/password-stage" import { WebAuthnStage } from "@/components/auth/flow-stages/webauthn-stage" import { SourceOAuthStage } from "@/components/auth/flow-stages/source-oauth-stage" import { AccessDeniedStage, FlowRedirectStage, } from "@/components/auth/flow-stages/access-denied-stage" import { AuthenticatorValidateStage } from "@/components/auth/flow-stages/authenticator-validate-stage" import { CaptchaStage } from "@/components/auth/flow-stages/captcha-stage" import { FlowFrameStage } from "@/components/auth/flow-stages/flow-frame-stage" FLOW_STAGE_REGISTRY["ak-stage-password"] = PasswordStage FLOW_STAGE_REGISTRY["ak-stage-user-login"] = UserLoginStage FLOW_STAGE_REGISTRY["ak-stage-authenticator-webauthn"] = WebAuthnStage FLOW_STAGE_REGISTRY["ak-stage-authenticator-validate"] = AuthenticatorValidateStage FLOW_STAGE_REGISTRY["ak-stage-captcha"] = CaptchaStage FLOW_STAGE_REGISTRY["ak-stage-source"] = SourceOAuthStage FLOW_STAGE_REGISTRY["ak-stage-access-denied"] = AccessDeniedStage FLOW_STAGE_REGISTRY["xak-flow-redirect"] = FlowRedirectStage FLOW_STAGE_REGISTRY["xak-flow-frame"] = FlowFrameStage type PromptField = { field_key: string label?: string type?: string required?: boolean placeholder?: string initial_value?: string choices?: Array<{ label?: string; value?: string }> } type FlowChallengeFormProps = { challenge: FlowChallenge | null component: string submitting: boolean fieldErrors?: Record flowSlug?: string onSubmit: (payload: Record) => void | Promise } export function FlowChallengeForm({ challenge, component, submitting, fieldErrors = {}, flowSlug, onSubmit, }: FlowChallengeFormProps) { const [values, setValues] = useState>({}) const [signupCanSubmit, setSignupCanSubmit] = useState(true) const initializedComponentRef = useRef(null) const promptFields = useMemo(() => readPromptFields(challenge), [challenge]) const primaryAction = readPrimaryAction(challenge) const signupCredentials = flowSlug === AUTH_FLOW_SLUGS.enrollment && component === "ak-stage-prompt" && isSignupCredentialsStep(promptFields) const RegistryStage = FLOW_STAGE_REGISTRY[component] useEffect(() => { if (!signupCredentials) { setSignupCanSubmit(true) } }, [signupCredentials]) useEffect(() => { if (initializedComponentRef.current === component) return initializedComponentRef.current = component const next: Record = {} if (component === "ak-stage-prompt") { for (const field of promptFields) { if (field.type === "hidden" || field.type === "static") continue next[field.field_key] = field.initial_value ?? "" } } if (component === "ak-stage-identification") { next.uid_field = "" if (readPasswordFields(challenge)) { next.password = "" } } if (component === "ak-stage-email") { next.email = "" } if (component === "ak-stage-authenticator-validate") { next.code = "" } if (component === "ak-stage-password") { next.password = "" } setValues(next) }, [challenge, component, promptFields]) async function handleSubmit(event: React.FormEvent) { event.preventDefault() const payload: Record = { component } if (component === "ak-stage-prompt") { for (const field of promptFields) { const key = field.field_key if (field.type === "static") continue if (field.type === "hidden") { if (key === "email" && signupCredentials) { payload[key] = buildSignupEmail(values.username ?? "") } else { payload[key] = field.initial_value ?? "" } continue } payload[key] = values[key] ?? "" } } else if (component === "ak-stage-identification") { payload.uid_field = values.uid_field ?? "" if (readPasswordFields(challenge)) { payload.password = values.password ?? "" } } else if (component === "ak-stage-email") { payload.email = values.email ?? "" } else if (component === "ak-stage-password") { payload.password = values.password ?? "" } else if (!RegistryStage) { Object.assign(payload, values) } await onSubmit(payload) } if (!component) { return null } const autoSubmitStage = component === "ak-stage-user-login" || component === "ak-stage-authenticator-webauthn" || component === "xak-flow-redirect" || component === "xak-flow-frame" || (component === "ak-stage-source" && Boolean(flowRedirectUrl(challenge))) if (component === "ak-stage-access-denied") { return } if (RegistryStage) { if (component === "ak-stage-password") { return (
setValues((prev) => ({ ...prev, [key]: value }))} onSubmit={onSubmit} /> {submitting ? ( <> Patientez… ) : ( primaryAction )} ) } return ( setValues((prev) => ({ ...prev, [key]: value }))} onSubmit={onSubmit} /> ) } return (
{component === "ak-stage-prompt" ? ( signupCredentials ? ( setValues((prev) => ({ ...prev, [key]: value })) } /> ) : ( setValues((prev) => ({ ...prev, [key]: value })) } /> ) ) : null} {component === "ak-stage-identification" ? ( setValues((prev) => ({ ...prev, [key]: value })) } /> ) : null} {component === "ak-stage-email" ? ( setValues((prev) => ({ ...prev, email: value }))} required /> ) : null} {!isKnownFlowComponent(component) ? (

Étape non prise en charge ({component}). Utilisez le portail Authentik.

) : null} {isKnownFlowComponent(component) && !autoSubmitStage ? ( {submitting ? ( <> Patientez… ) : ( primaryAction )} ) : null} ) } function PromptFields({ fields, values, fieldErrors, onChange, }: { fields: PromptField[] values: Record fieldErrors: Record onChange: (key: string, value: string) => void }) { return (
{fields.map((field) => { if (field.type === "hidden") return null if (field.type === "static") { return (

{field.label} {field.initial_value ? ( {" "} {field.initial_value} ) : null}

) } const inputType = field.type === "password" ? "password" : field.type === "email" ? "email" : "text" if (field.type === "file") { return (
{ const file = event.target.files?.[0] if (!file) { onChange(field.field_key, "") return } const reader = new FileReader() reader.onload = () => { onChange(field.field_key, String(reader.result ?? "")) } reader.readAsDataURL(file) }} />
) } if (field.type === "password") { return ( onChange(field.field_key, value)} /> ) } return ( onChange(field.field_key, value)} required={field.required} autoComplete={autoCompleteFor(field.field_key, field.type)} /> ) })}
) } function IdentificationFields({ challenge, values, fieldErrors, onChange, }: { challenge: FlowChallenge | null values: Record fieldErrors: Record onChange: (key: string, value: string) => void }) { const withPassword = readPasswordFields(challenge) const userFields = readUserFields(challenge) const label = userFields.includes("email") && !userFields.includes("username") ? "Adresse e-mail" : "Identifiant" return (
onChange("uid_field", value)} required /> {withPassword ? ( onChange("password", value)} /> ) : null}
) } function FieldBlock({ id, label, type = "text", placeholder, value, error, onChange, required, autoComplete, inputMode, }: { id: string label: string type?: string placeholder?: string value: string error?: string onChange: (value: string) => void required?: boolean autoComplete?: string inputMode?: React.HTMLAttributes["inputMode"] }) { return (
onChange(event.target.value)} className="h-10 rounded-lg" /> {error ? (

{error}

) : null}
) } function readPromptFields(challenge: FlowChallenge | null): PromptField[] { const raw = challenge?.fields if (!Array.isArray(raw)) return [] return raw.filter(isPromptField) } function isPromptField(value: unknown): value is PromptField { return ( typeof value === "object" && value !== null && typeof (value as PromptField).field_key === "string" ) } function readPasswordFields(challenge: FlowChallenge | null): boolean { return challenge?.password_fields === true } function readUserFields(challenge: FlowChallenge | null): string[] { const raw = challenge?.user_fields if (!Array.isArray(raw)) return ["username"] return raw.filter((v): v is string => typeof v === "string") } function autoCompleteFor(fieldKey: string, type?: string): string | undefined { if (type === "password") { return fieldKey.includes("repeat") ? "new-password" : "new-password" } if (fieldKey === "username") return "username" if (fieldKey === "email" || fieldKey.includes("email")) return "email" if (fieldKey === "name") return "name" if (fieldKey.includes("phone")) return "tel" return undefined }