Google-SSO PWA for bistro employee clock-in/out, admin employee management, and stats with CSV export. Express + SQLite backend, React + Zustand frontend in the light-mono-tui design language. Multi-stage Dockerfile, compose.yaml for image-based deploys, nginx reverse-proxy template, and an OKF documentation bundle in docs/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2.3 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||
|---|---|---|---|---|---|---|---|
| Architecture Overview | Authentication & authorization | Google ID-token verification, the session cookie, and the admin/employee role split. |
|
2026-08-12T00:00:00Z |
Authentication & authorization
Sign-in
- The frontend loads the Google Identity Services script
(
https://accounts.google.com/gsi/client, infrontend/index.html) and renders the button infrontend/src/auth/GoogleButton.tsx. - Google returns a signed ID token to the browser (
credential). The frontend does not trust it — it POSTs it toPOST /api/auth/google. - The backend verifies the token server-side with
google-auth-library(backend/src/auth/google.ts,verifyGoogleIdToken), checking the audience againstGOOGLE_CLIENT_IDand requiringemail_verified.
Role resolution
Two independent checks decide who gets in and as what role
(backend/src/routes/auth.ts):
- Admin: the verified email is in the
ADMIN_EMAILSenv var (comma-separated, lowercased —backend/src/env.ts). Admins do not need a row in theemployeestable. - Employee: the verified email matches an active row in employees.
If neither matches, login is rejected with 403 ("This account is not registered as an EatMe employee"). This is how the employee whitelist is enforced — there's no self-service signup.
Session
On success the backend signs its own JWT (backend/src/auth/session.ts,
issueSession) containing { email, name, role, employeeId }, and sets it
as an httpOnly, SameSite=Lax cookie (eatme_session, 12h TTL). All
subsequent /api/* requests carry that cookie
(frontend/src/api/client.ts sends credentials: 'include'); the backend
never re-derives role from the DB on every request — it trusts the signed
cookie until it expires.
backend/src/auth/middleware.ts provides three guards used by the routers:
requireAuth— valid session cookie presentrequireAdmin—role === 'admin'requireEmployee—employeeId != null(blocks admins with no employee profile from hitting/api/attendance/*)