From eccf74d0e868b180afe4b02258d96ab7275ea352 Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Tue, 21 Jan 2025 16:30:59 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=9A=A7=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 10 +++ packages/applications/ssr/package.json | 2 + .../ssr/src/components/atoms/form/Form.tsx | 3 + .../abandon/demander/DemanderAbandon.form.tsx | 7 +- .../abandon/demander/DemanderAbandon.page.tsx | 9 ++- packages/applications/ssr/src/middleware.ts | 65 +++++++++++++++++-- 6 files changed, 87 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdc725fb67..acffd925f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3477,6 +3477,15 @@ "node": ">=10.0.0" } }, + "node_modules/@edge-csrf/nextjs": { + "version": "2.5.3-cloudflare-rc1", + "resolved": "https://registry.npmjs.org/@edge-csrf/nextjs/-/nextjs-2.5.3-cloudflare-rc1.tgz", + "integrity": "sha512-sH8HKl2s/zFkIgXcVzODljHsBhKW7LN2gXeUNEQTSP31Chy40ryjR7iTf0MA/DtPhOpXUkd6IBUMMF820sK+rA==", + "license": "MIT", + "peerDependencies": { + "next": "^13.0.0 || ^14.0.0 || ^15.0.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.12.0", "license": "MIT", @@ -28959,6 +28968,7 @@ "version": "0.1.0", "dependencies": { "@codegouvfr/react-dsfr": "^1.9.11", + "@edge-csrf/nextjs": "^2.5.3-cloudflare-rc1", "@emotion/react": "*", "@emotion/server": "*", "@emotion/styled": "*", diff --git a/packages/applications/ssr/package.json b/packages/applications/ssr/package.json index ef4b7c7c80..2be5dcc7b0 100644 --- a/packages/applications/ssr/package.json +++ b/packages/applications/ssr/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@codegouvfr/react-dsfr": "^1.9.11", + "@edge-csrf/nextjs": "^2.5.3-cloudflare-rc1", "@emotion/react": "*", "@emotion/server": "*", "@emotion/styled": "*", @@ -25,6 +26,7 @@ "@potentiel-applications/api-documentation": "*", "@potentiel-applications/bootstrap": "*", "@potentiel-applications/document-builder": "*", + "@potentiel-applications/feature-flags": "*", "@potentiel-applications/request-context": "*", "@potentiel-applications/routes": "*", "@potentiel-domain/appel-offre": "*", diff --git a/packages/applications/ssr/src/components/atoms/form/Form.tsx b/packages/applications/ssr/src/components/atoms/form/Form.tsx index 08d9377e30..f3d03a31af 100644 --- a/packages/applications/ssr/src/components/atoms/form/Form.tsx +++ b/packages/applications/ssr/src/components/atoms/form/Form.tsx @@ -23,6 +23,7 @@ export type FormProps = { onError?: FormHTMLAttributes['onError']; onInvalid?: FormHTMLAttributes['onInvalid']; successMessage?: string; + csrfToken?: string; }; export const Form: FC = ({ @@ -37,6 +38,7 @@ export const Form: FC = ({ actions, onError, onInvalid, + csrfToken, }) => { const [state, formAction] = useFormState(action, { status: undefined, @@ -49,6 +51,7 @@ export const Form: FC = ({ return ( // eslint-disable-next-line react/no-unknown-property
+ {heading && {heading}} diff --git a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx index d7802f8ef8..49b4abc729 100644 --- a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx +++ b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx @@ -12,9 +12,13 @@ import { demanderAbandonAction, DemanderAbandonFormKeys } from './demanderAbando export type DemanderAbandonFormProps = { identifiantProjet: string; + csrfToken: string; }; -export const DemanderAbandonForm: FC = ({ identifiantProjet }) => { +export const DemanderAbandonForm: FC = ({ + identifiantProjet, + csrfToken, +}) => { const [validationErrors, setValidationErrors] = useState< ValidationErrors >({}); @@ -23,6 +27,7 @@ export const DemanderAbandonForm: FC = ({ identifiantP action={demanderAbandonAction} onValidationError={(validationErrors) => setValidationErrors(validationErrors)} actions={Demander l'abandon} + csrfToken={csrfToken} > diff --git a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx index c603fdf772..fbd168232c 100644 --- a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx +++ b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx @@ -1,5 +1,6 @@ import Alert from '@codegouvfr/react-dsfr/Alert'; import { FC } from 'react'; +import { headers } from 'next/headers'; import { ProjetBanner } from '@/components/molecules/projet/ProjetBanner'; import { ColumnPageTemplate } from '@/components/templates/ColumnPage.template'; @@ -7,15 +8,19 @@ import { Heading1 } from '@/components/atoms/headings'; import { DemanderAbandonForm, DemanderAbandonFormProps } from './DemanderAbandon.form'; -export type DemanderAbandonPageProps = DemanderAbandonFormProps; +export type DemanderAbandonPageProps = Omit; export const DemanderAbandonPage: FC = ({ identifiantProjet }) => { + const csrfToken = headers().get('X-CSRF-Token') || 'no_token'; + return ( } heading={Demander l'abandon du projet} leftColumn={{ - children: , + children: ( + + ), }} rightColumn={{ children: ( diff --git a/packages/applications/ssr/src/middleware.ts b/packages/applications/ssr/src/middleware.ts index 24445c829a..848ac11f07 100644 --- a/packages/applications/ssr/src/middleware.ts +++ b/packages/applications/ssr/src/middleware.ts @@ -1,13 +1,66 @@ -import { withAuth } from 'next-auth/middleware'; +import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs'; +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; +import { NextMiddlewareResult } from 'next/dist/server/web/types'; +import { getToken } from 'next-auth/jwt'; -export default withAuth({ - // NB: importing Routes is not working in the middleware - pages: { signIn: '/auth/signIn' }, - callbacks: { - authorized: ({ token }) => !!token?.utilisateur, +export type CustomMiddleware = ( + request: NextRequest, + event: NextFetchEvent, + response: NextResponse, +) => NextMiddlewareResult | Promise; + +type MiddlewareFactory = (middleware: CustomMiddleware) => CustomMiddleware; + +export const chain = (functions: MiddlewareFactory[], index = 0): CustomMiddleware => { + const current = functions[index]; + + if (current) { + const next = chain(functions, index + 1); + return current(next); + } + + return (request: NextRequest, event: NextFetchEvent, response: NextResponse) => { + return response; + }; +}; + +const csrfProtect = createCsrfProtect({ + cookie: { + sameSite: true, + secure: true, }, }); +function withCSRF(middleware: CustomMiddleware) { + return async (request: NextRequest, event: NextFetchEvent) => { + const response = NextResponse.next(); + + try { + await csrfProtect(request, response); + } catch (err) { + if (err instanceof CsrfError) { + return NextResponse.redirect(new URL('/error', request.url)); + } + throw err; + } + + return middleware(request, event, response); + }; +} + +function withNextAuth(middleware: CustomMiddleware) { + return async (request: NextRequest, event: NextFetchEvent) => { + const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); + if (!token) { + return NextResponse.redirect(new URL('/auth/signIn', request.url)); + } + + return middleware(request, event, NextResponse.next()); + }; +} + +export default chain([withNextAuth, withCSRF]); + export const config = { // do not run middleware for paths matching one of following matcher: [ From 3839bcfe904d932928940903693fce38563fa25f Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Wed, 22 Jan 2025 16:52:45 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Recup=C3=A9ration=20to?= =?UTF-8?q?ken=20depuis=20composant=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applications/ssr/src/app/csrf/page.tsx | 3 + .../ssr/src/components/atoms/form/Form.tsx | 19 +++++- .../abandon/demander/DemanderAbandon.form.tsx | 7 +- .../abandon/demander/DemanderAbandon.page.tsx | 9 +-- packages/applications/ssr/src/middleware.ts | 65 ++----------------- .../applications/ssr/src/middlewares/chain.ts | 16 +++++ .../ssr/src/middlewares/middleware.ts | 10 +++ .../ssr/src/middlewares/withCSRF.ts | 32 +++++++++ .../ssr/src/middlewares/withNextAuth.ts | 15 +++++ 9 files changed, 99 insertions(+), 77 deletions(-) create mode 100644 packages/applications/ssr/src/app/csrf/page.tsx create mode 100644 packages/applications/ssr/src/middlewares/chain.ts create mode 100644 packages/applications/ssr/src/middlewares/middleware.ts create mode 100644 packages/applications/ssr/src/middlewares/withCSRF.ts create mode 100644 packages/applications/ssr/src/middlewares/withNextAuth.ts diff --git a/packages/applications/ssr/src/app/csrf/page.tsx b/packages/applications/ssr/src/app/csrf/page.tsx new file mode 100644 index 0000000000..8aace062f6 --- /dev/null +++ b/packages/applications/ssr/src/app/csrf/page.tsx @@ -0,0 +1,3 @@ +export default function CSRF() { + return <>; +} diff --git a/packages/applications/ssr/src/components/atoms/form/Form.tsx b/packages/applications/ssr/src/components/atoms/form/Form.tsx index f3d03a31af..c1f5da342a 100644 --- a/packages/applications/ssr/src/components/atoms/form/Form.tsx +++ b/packages/applications/ssr/src/components/atoms/form/Form.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FC, FormHTMLAttributes, ReactNode } from 'react'; +import { FC, FormHTMLAttributes, ReactNode, useEffect, useState } from 'react'; import { useFormState } from 'react-dom'; import { formAction, ValidationErrors } from '@/utils/formAction'; @@ -23,7 +23,6 @@ export type FormProps = { onError?: FormHTMLAttributes['onError']; onInvalid?: FormHTMLAttributes['onInvalid']; successMessage?: string; - csrfToken?: string; }; export const Form: FC = ({ @@ -38,8 +37,22 @@ export const Form: FC = ({ actions, onError, onInvalid, - csrfToken, }) => { + const [csrfToken, setCsrfToken] = useState(''); + + useEffect(() => { + const fetchCSRFToken = async () => { + const response = await fetch('/csrf', { + method: 'HEAD', + }); + + const tokenFromHeader = response.headers.get('csrf_token'); + setCsrfToken(tokenFromHeader ?? 'empty_token'); + }; + + fetchCSRFToken(); + }, []); + const [state, formAction] = useFormState(action, { status: undefined, }); diff --git a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx index 49b4abc729..d7802f8ef8 100644 --- a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx +++ b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.form.tsx @@ -12,13 +12,9 @@ import { demanderAbandonAction, DemanderAbandonFormKeys } from './demanderAbando export type DemanderAbandonFormProps = { identifiantProjet: string; - csrfToken: string; }; -export const DemanderAbandonForm: FC = ({ - identifiantProjet, - csrfToken, -}) => { +export const DemanderAbandonForm: FC = ({ identifiantProjet }) => { const [validationErrors, setValidationErrors] = useState< ValidationErrors >({}); @@ -27,7 +23,6 @@ export const DemanderAbandonForm: FC = ({ action={demanderAbandonAction} onValidationError={(validationErrors) => setValidationErrors(validationErrors)} actions={Demander l'abandon} - csrfToken={csrfToken} > diff --git a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx index fbd168232c..c603fdf772 100644 --- a/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx +++ b/packages/applications/ssr/src/components/pages/abandon/demander/DemanderAbandon.page.tsx @@ -1,6 +1,5 @@ import Alert from '@codegouvfr/react-dsfr/Alert'; import { FC } from 'react'; -import { headers } from 'next/headers'; import { ProjetBanner } from '@/components/molecules/projet/ProjetBanner'; import { ColumnPageTemplate } from '@/components/templates/ColumnPage.template'; @@ -8,19 +7,15 @@ import { Heading1 } from '@/components/atoms/headings'; import { DemanderAbandonForm, DemanderAbandonFormProps } from './DemanderAbandon.form'; -export type DemanderAbandonPageProps = Omit; +export type DemanderAbandonPageProps = DemanderAbandonFormProps; export const DemanderAbandonPage: FC = ({ identifiantProjet }) => { - const csrfToken = headers().get('X-CSRF-Token') || 'no_token'; - return ( } heading={Demander l'abandon du projet} leftColumn={{ - children: ( - - ), + children: , }} rightColumn={{ children: ( diff --git a/packages/applications/ssr/src/middleware.ts b/packages/applications/ssr/src/middleware.ts index 848ac11f07..6cc39c2cf6 100644 --- a/packages/applications/ssr/src/middleware.ts +++ b/packages/applications/ssr/src/middleware.ts @@ -1,69 +1,12 @@ -import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs'; -import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; -import { NextMiddlewareResult } from 'next/dist/server/web/types'; -import { getToken } from 'next-auth/jwt'; - -export type CustomMiddleware = ( - request: NextRequest, - event: NextFetchEvent, - response: NextResponse, -) => NextMiddlewareResult | Promise; - -type MiddlewareFactory = (middleware: CustomMiddleware) => CustomMiddleware; - -export const chain = (functions: MiddlewareFactory[], index = 0): CustomMiddleware => { - const current = functions[index]; - - if (current) { - const next = chain(functions, index + 1); - return current(next); - } - - return (request: NextRequest, event: NextFetchEvent, response: NextResponse) => { - return response; - }; -}; - -const csrfProtect = createCsrfProtect({ - cookie: { - sameSite: true, - secure: true, - }, -}); - -function withCSRF(middleware: CustomMiddleware) { - return async (request: NextRequest, event: NextFetchEvent) => { - const response = NextResponse.next(); - - try { - await csrfProtect(request, response); - } catch (err) { - if (err instanceof CsrfError) { - return NextResponse.redirect(new URL('/error', request.url)); - } - throw err; - } - - return middleware(request, event, response); - }; -} - -function withNextAuth(middleware: CustomMiddleware) { - return async (request: NextRequest, event: NextFetchEvent) => { - const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); - if (!token) { - return NextResponse.redirect(new URL('/auth/signIn', request.url)); - } - - return middleware(request, event, NextResponse.next()); - }; -} +import { chain } from './middlewares/chain'; +import { withNextAuth } from './middlewares/withNextAuth'; +import { withCSRF } from './middlewares/withCSRF'; export default chain([withNextAuth, withCSRF]); export const config = { // do not run middleware for paths matching one of following matcher: [ - '/((?!api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|illustrations|$).*)', + '/((?!api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|illustrations|error|$).*)', ], }; diff --git a/packages/applications/ssr/src/middlewares/chain.ts b/packages/applications/ssr/src/middlewares/chain.ts new file mode 100644 index 0000000000..69554ab360 --- /dev/null +++ b/packages/applications/ssr/src/middlewares/chain.ts @@ -0,0 +1,16 @@ +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; + +import { CustomMiddleware, MiddlewareFactory } from './middleware'; + +export const chain = (functions: MiddlewareFactory[], index = 0): CustomMiddleware => { + const current = functions[index]; + + if (current) { + const next = chain(functions, index + 1); + return current(next); + } + + return (_request: NextRequest, _event: NextFetchEvent, response: NextResponse) => { + return response; + }; +}; diff --git a/packages/applications/ssr/src/middlewares/middleware.ts b/packages/applications/ssr/src/middlewares/middleware.ts new file mode 100644 index 0000000000..c625e01f70 --- /dev/null +++ b/packages/applications/ssr/src/middlewares/middleware.ts @@ -0,0 +1,10 @@ +import { NextMiddlewareResult } from 'next/dist/server/web/types'; +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; + +export type CustomMiddleware = ( + request: NextRequest, + event: NextFetchEvent, + response: NextResponse, +) => NextMiddlewareResult | Promise; + +export type MiddlewareFactory = (middleware: CustomMiddleware) => CustomMiddleware; diff --git a/packages/applications/ssr/src/middlewares/withCSRF.ts b/packages/applications/ssr/src/middlewares/withCSRF.ts new file mode 100644 index 0000000000..aea7529586 --- /dev/null +++ b/packages/applications/ssr/src/middlewares/withCSRF.ts @@ -0,0 +1,32 @@ +import { createCsrfProtect, CsrfError } from '@edge-csrf/nextjs'; +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; + +import { CustomMiddleware } from './middleware'; + +const csrfProtect = createCsrfProtect({ + cookie: { + sameSite: true, + secure: true, + httpOnly: true, + }, + token: { + responseHeader: 'csrf_token', + }, +}); + +export function withCSRF(middleware: CustomMiddleware) { + return async (request: NextRequest, event: NextFetchEvent) => { + const response = NextResponse.next(); + + try { + await csrfProtect(request, response); + } catch (err) { + if (err instanceof CsrfError) { + return NextResponse.redirect(new URL('/error', request.url)); + } + throw err; + } + + return middleware(request, event, response); + }; +} diff --git a/packages/applications/ssr/src/middlewares/withNextAuth.ts b/packages/applications/ssr/src/middlewares/withNextAuth.ts new file mode 100644 index 0000000000..e0f5178243 --- /dev/null +++ b/packages/applications/ssr/src/middlewares/withNextAuth.ts @@ -0,0 +1,15 @@ +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; +import { getToken } from 'next-auth/jwt'; + +import { CustomMiddleware } from './middleware'; + +export function withNextAuth(middleware: CustomMiddleware) { + return async (request: NextRequest, event: NextFetchEvent) => { + const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); + if (!token) { + return NextResponse.redirect(new URL('/auth/signIn', request.url)); + } + + return middleware(request, event, NextResponse.next()); + }; +} From 454020d17ce118b4d5f3073762a4d81d6a70cdaf Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Thu, 23 Jan 2025 10:20:00 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=90=9B=20Fix=20after=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/applications/ssr/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/applications/ssr/package.json b/packages/applications/ssr/package.json index 2be5dcc7b0..111298024e 100644 --- a/packages/applications/ssr/package.json +++ b/packages/applications/ssr/package.json @@ -26,7 +26,6 @@ "@potentiel-applications/api-documentation": "*", "@potentiel-applications/bootstrap": "*", "@potentiel-applications/document-builder": "*", - "@potentiel-applications/feature-flags": "*", "@potentiel-applications/request-context": "*", "@potentiel-applications/routes": "*", "@potentiel-domain/appel-offre": "*", From 6341de2cf1ddbb5e03897ac1511c0b22adae244c Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Fri, 24 Jan 2025 11:22:42 +0100 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=90=9B=20Fix=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssr/src/components/atoms/form/Form.tsx | 6 ++++++ .../applications/ssr/src/middlewares/withCSRF.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/applications/ssr/src/components/atoms/form/Form.tsx b/packages/applications/ssr/src/components/atoms/form/Form.tsx index c1f5da342a..35244a301e 100644 --- a/packages/applications/ssr/src/components/atoms/form/Form.tsx +++ b/packages/applications/ssr/src/components/atoms/form/Form.tsx @@ -2,6 +2,7 @@ import { FC, FormHTMLAttributes, ReactNode, useEffect, useState } from 'react'; import { useFormState } from 'react-dom'; +import { useRouter } from 'next/navigation'; import { formAction, ValidationErrors } from '@/utils/formAction'; @@ -57,6 +58,11 @@ export const Form: FC = ({ status: undefined, }); + if (!state) { + const router = useRouter(); + router.push('/error'); + } + if (state.status === 'validation-error' && onValidationError) { onValidationError(state.errors); } diff --git a/packages/applications/ssr/src/middlewares/withCSRF.ts b/packages/applications/ssr/src/middlewares/withCSRF.ts index aea7529586..589e90070d 100644 --- a/packages/applications/ssr/src/middlewares/withCSRF.ts +++ b/packages/applications/ssr/src/middlewares/withCSRF.ts @@ -22,6 +22,18 @@ export function withCSRF(middleware: CustomMiddleware) { await csrfProtect(request, response); } catch (err) { if (err instanceof CsrfError) { + const isAction = request.method === 'POST' && request.headers.has('Next-Action'); + if (isAction) { + return NextResponse.json( + { + status: 'failed', + }, + { + status: 403, + statusText: 'Invalid CSRF token', + }, + ); + } return NextResponse.redirect(new URL('/error', request.url)); } throw err; From 44feebcd3bd524be3a3ca5c0df05558b56146602 Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Mon, 27 Jan 2025 09:25:54 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=92=A1=20Ajout=20commentaire=20sur=20?= =?UTF-8?q?page=20CSRF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/applications/ssr/src/app/csrf/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/applications/ssr/src/app/csrf/page.tsx b/packages/applications/ssr/src/app/csrf/page.tsx index 8aace062f6..35da6d1e4b 100644 --- a/packages/applications/ssr/src/app/csrf/page.tsx +++ b/packages/applications/ssr/src/app/csrf/page.tsx @@ -1,3 +1,5 @@ +// Cette page est utilisé pour la récupératoin du token CSRF +// Merci de ne pas la supprimer ou l'utiliser pour autre chose export default function CSRF() { return <>; } From 5d7aea8ba32a1deb082eb560c2ee07b3f201bdd5 Mon Sep 17 00:00:00 2001 From: Sylvain Pontoreau Date: Mon, 27 Jan 2025 17:52:53 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20hook=20hors=20c?= =?UTF-8?q?ondition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/applications/ssr/src/components/atoms/form/Form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/applications/ssr/src/components/atoms/form/Form.tsx b/packages/applications/ssr/src/components/atoms/form/Form.tsx index 35244a301e..81572c483f 100644 --- a/packages/applications/ssr/src/components/atoms/form/Form.tsx +++ b/packages/applications/ssr/src/components/atoms/form/Form.tsx @@ -40,6 +40,7 @@ export const Form: FC = ({ onInvalid, }) => { const [csrfToken, setCsrfToken] = useState(''); + const router = useRouter(); useEffect(() => { const fetchCSRFToken = async () => { @@ -59,7 +60,6 @@ export const Form: FC = ({ }); if (!state) { - const router = useRouter(); router.push('/error'); }