Wire authentication into the application

Adds the sign-in screen, sign-out, and a server-side guard on every
application route. The guard lives in the layout rather than the proxy
because the proxy cannot query the database to check whether a session
was revoked — and revocation is the reason sessions are stored there.

Sign-in returns one message for an unknown account and for a wrong
password, and verifies a dummy hash when the account does not exist, so
neither the wording nor the timing enumerates staff addresses. An
end-to-end test compares the two messages rather than trusting the
code to keep them aligned.

The shell now shows the signed-in person and their role from the
database instead of hardcoded initials.

Playwright signs in once in a setup project and shares the cookie;
argon2 is deliberately slow, and logging in per test would also drive
the shared failed-attempt counter toward a lockout. The seed resets
that counter so repeated local runs cannot lock the demo account.

Two test locators had to be scoped to the form: Next's route announcer
carries role="alert" and an empty string, which silently satisfied the
assertion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
This commit is contained in:
Claude committed 2026-08-07 22:45:24 +00:00
1 parent 11763142d5
commit 2c0e9e8dd4
12 files changed
+361 -19

No files matched your search

+24 -2
View File
@@ -1,7 +1,29 @@
import { redirect } from 'next/navigation';
import type { ReactNode } from 'react';
import { AppShell } from '@/components/shell/AppShell';
import { currentSession } from '@/server/auth/session';
export default function AppLayout({ children }: { children: ReactNode }) {
return <AppShell>{children}</AppShell>;
/**
* Toutes les routes applicatives passent par ici.
*
* Le contrôle est fait côté serveur, dans le layout, et non dans le proxy : le
* proxy ne peut pas interroger la base pour vérifier qu'une session n'a pas été
* révoquée, et c'est précisément la révocation qui justifie de tenir les
* sessions en base (matrice n° 23).
*/
export default async function AppLayout({ children }: { children: ReactNode }) {
const session = await currentSession();
if (!session) redirect('/connexion');
return (
<AppShell
initials={session.user.initials}
fullName={`${session.user.firstName} ${session.user.lastName}`}
roleName={session.roleName}
accountName={session.accountName}
>
{children}
</AppShell>
);
}