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:
12 files changed
+361
-19
No files matched your search
@@ -8,6 +8,7 @@ import { ThemeToggle } from '@/components/shell/ThemeToggle';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { cx } from '@/lib/cx';
|
||||
import { signOutAction } from '@/server/auth/actions';
|
||||
import {
|
||||
isActive,
|
||||
NAVIGATION,
|
||||
@@ -19,9 +20,19 @@ import {
|
||||
|
||||
export interface AppShellProps {
|
||||
children: ReactNode;
|
||||
initials: string;
|
||||
fullName: string;
|
||||
roleName: string;
|
||||
accountName: string;
|
||||
}
|
||||
|
||||
export function AppShell({ children }: AppShellProps) {
|
||||
export function AppShell({
|
||||
children,
|
||||
initials,
|
||||
fullName,
|
||||
roleName,
|
||||
accountName,
|
||||
}: AppShellProps) {
|
||||
const pathname = usePathname();
|
||||
const section = sectionForPath(pathname);
|
||||
|
||||
@@ -67,11 +78,17 @@ export function AppShell({ children }: AppShellProps) {
|
||||
</Button>
|
||||
<ThemeToggle />
|
||||
<span
|
||||
aria-hidden
|
||||
className="flex size-7 items-center justify-center rounded-full bg-accent-soft text-xs font-semibold text-accent-soft-ink"
|
||||
title={`${fullName} · ${roleName}`}
|
||||
>
|
||||
MR
|
||||
<span aria-hidden>{initials}</span>
|
||||
<span className="sr-only">{`${fullName}, ${roleName}`}</span>
|
||||
</span>
|
||||
<form action={signOutAction}>
|
||||
<Button size="sm" type="submit">
|
||||
Déconnexion
|
||||
</Button>
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<div className="flex min-h-0 flex-1">
|
||||
@@ -86,7 +103,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
<p className="border-t border-line-1 px-2.5 pt-3 text-micro leading-relaxed text-ink-3">
|
||||
Instance auto-hébergée
|
||||
<br />
|
||||
Maison Rivage · 34 établissements
|
||||
{accountName}
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in new issue
Block a user