fix(auth): remove the ?demo=1 PIN bypass

The query string skipped the PIN gate on any URL. The PIN is now the
only way in. Docs updated; the login plan notes there is no fixed
cutover date while the app is in on-site testing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DEls 2026-09-26 17:37:20 +02:00
parent 46e9e3dbc4
commit 16652bb36c
5 changed files with 9 additions and 12 deletions

View file

@ -9,8 +9,8 @@ OCR-driven receipting. Includes an LLM chat ("Ask") with a read-only
tool layer over the same data, and a read-only MCP server.
> **Default login PIN is `2468`.** Change `NEXT_PUBLIC_APP_PIN` in
> production. Append `?demo=1` to any URL to bypass the PIN during
> development.
> production. There is no PIN bypass — the old `?demo=1` shortcut was
> removed on 2026-09-26.
---

View file

@ -127,8 +127,8 @@ held in `sessionStorage` after a client-side string compare.
- `AuthGate` (`src/components/auth-gate.tsx`) redirects to `/login` whenever
the flag is missing, but does so client-side — every server component
below it still runs on the server.
- Append `?demo=1` to any dashboard URL to bypass the PIN entirely
(`src/hooks/use-auth.tsx:27-30`).
- The `?demo=1` PIN bypass was removed on 2026-09-26 — the PIN is the
only way in.
- There is **no per-user identity anywhere in the application**. Every user
who knows the PIN sees every site's contract values, payroll rates, and
margins.

View file

@ -173,7 +173,8 @@ The dev server runs on **port 6004** (not 3000 — the user runs other
apps on 3000; do not change this back to 3000). The `--webpack` flag
is required because `@ducanh2912/next-pwa` is webpack-only.
Append `?demo=1` to any URL to skip the PIN gate.
Enter the PIN (`NEXT_PUBLIC_APP_PIN`, default `2468`) on `/login`. The
old `?demo=1` bypass was removed on 2026-09-26.
### 5.2 Production build
@ -221,7 +222,6 @@ breaks. This is left in-tree for context, not as a reference.
| Flag | Default | Effect when on | Effect when off |
| --- | --- | --- | --- |
| `ENABLE_CHAT` | unset (= `false`) | `/api/chat` and `/dashboard/chat` work | Both return 404 / `notFound()` |
| `?demo=1` query string | n/a | Skips the PIN gate on the current URL | Normal auth flow |
| `NEXT_PUBLIC_DEFAULT_THEME` | `light` | Initial theme before user preference loads | " |
| `CHAT_PROVIDER` | `anthropic` | `anthropic` if `ANTHROPIC_API_KEY` is set (or `ant auth login` profile); `minimax` / `openai` if `MINIMAX_API_KEY` is set |

View file

@ -42,6 +42,8 @@
### Cutover timeline
> **Update 2026-09-26:** the app is still in on-site testing and production is months away, so there is **no fixed cutover date**. Testers keep the shared PIN (shown on the login page on purpose). Build this when it's needed, keep `AUTH_MODE=pin` until testing is done, and use the dates below only as the shape of the rollout.
| When | What | Who |
| --- | --- | --- |
| Sat 26 Sep – Sun 27 Sep | Tasks 1–11 built and merged; deployed with `AUTH_MODE=pin` (no visible change) | Dev |
@ -819,7 +821,7 @@ export const PATCH = withUser(async (_req, { params }: { params: Promise<{ id: s
- [ ] **Step 1: Mode reaches the client.** `src/app/layout.tsx` (server) reads `authMode()` and passes `mode` into `<AuthProvider mode={mode}>`. `NEXT_PUBLIC_*` must **not** be used for the mode — a runtime env flip must not need a rebuild.
- [ ] **Step 2: Login page.** `mode === "pin"` → today's keypad, unchanged. `mode === "users"` → email + password form posting to `/api/auth/login`; on 200 `router.replace(resolveRedirectTarget(location.search))`; show the server's error text; disable the button while submitting; `autoComplete="username"` / `"current-password"` so phones offer to save it.
- [ ] **Step 3: Gate.** In users mode `AuthGate` renders children directly (the proxy already redirected), and the `?demo=1` bypass is **ignored** (only honoured in pin mode).
- [ ] **Step 3: Gate.** In users mode `AuthGate` renders children directly (the proxy already redirected). (The old `?demo=1` bypass was removed on 2026-09-26 — do not reintroduce it.)
- [ ] **Step 4: Sidebar footer.** Users mode: "Signed in as {name} · {ROLE_LABELS[role]}" + **Sign out** (POST `/api/auth/logout`, then `location.href = "/login"`). Pin mode: today's "Lock screen".
- [ ] **Step 5: Nav by permission.** Hide groups the role lacks (`workforce.view`, `plant.view`, `financial.view`; `chat.use` for Ask). In Phase 1 a site manager sees everything except Ask and Users.
- [ ] **Step 6:** Browser check both modes. Commit — `git commit -m "feat(auth): email login page and sign-out"`

View file

@ -23,11 +23,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
React.useEffect(() => {
try {
if (sessionStorage.getItem(PIN_VERIFIED_KEY) === "1") setAuthenticated(true);
// Demo bypass: append ?demo=1 to any dashboard URL to skip the PIN screen.
if (typeof window !== "undefined" && new URLSearchParams(window.location.search).get("demo") === "1") {
setAuthenticated(true);
sessionStorage.setItem(PIN_VERIFIED_KEY, "1");
}
} catch {
// ignore
}