From 964c069236dee0ebaf18d0a065bda5258cd38860 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 21:56:06 +0300 Subject: [PATCH] Don't enforce email verification at sign-in (yet); add CLAUDE.md - emailAndPassword.requireEmailVerification = false so users can sign in immediately; verification emails are still sent and /verify-email still works. Flip back to true to make it mandatory later (TODO noted in code). - Add backend/CLAUDE.md documenting stack, commands, the auth/schema generation workflow, and runtime gotchas. Co-Authored-By: Claude Opus 4.8 --- backend/CLAUDE.md | 64 +++++++++++++++++++++++++++++++++++++++++++++ backend/src/auth.ts | 6 ++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 backend/CLAUDE.md diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 0000000..ff4e048 --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,64 @@ +# CLAUDE.md + +Guidance for Claude Code when working in `backend/`. See the root `../CLAUDE.md` for the project +vision and `README.md` here for run/setup instructions. + +## What this is + +The temetro API: **TypeScript + Express 5 + Postgres (Drizzle ORM)**, with authentication and +multi-tenant clinics via **Better Auth**. Serves the `../frontend` app. ESM (`"type": "module"`), +Node ≥ 20. This is its own git repo — commit changes here with the +`Co-Authored-By: Claude Opus 4.8 ` trailer. + +## Commands + +```bash +npm run dev # tsx watch on http://localhost:4000 +npm run build # tsc -> dist/ +npm run typecheck # tsc --noEmit +npm run auth:generate # Better Auth CLI -> src/db/schema/auth.ts (re-run after auth config changes) +npm run db:generate # drizzle-kit: schema -> ./drizzle/*.sql migration +npm run db:migrate # drizzle-kit: apply migrations (local dev) +npm run db:apply # node dist/migrate.js — runtime migrator (used by Docker) +``` + +No test runner is configured. Verify by running the stack (`docker compose up`) and curling, or +`npm run dev` against a local Postgres. See `README.md`. + +## Architecture + +- **`src/auth.ts`** — the Better Auth config (the CLI auto-discovers it). Email/password, + organization plugin (clinics) with custom RBAC from **`src/lib/access.ts`** + (`owner`/`admin`/`member`/`viewer` + a `patient` resource). Mounted in `src/index.ts` via + `toNodeHandler(auth)` at `/api/auth/*`. +- **`src/db/`** — `index.ts` is the Drizzle client (no schema passed; we use the core query builder). + `schema/auth.ts` is **generated** by the Better Auth CLI; `schema/patients.ts` is hand-written and + references the generated `organization`/`user` tables. +- **`src/services/patients.ts`** maps DB rows ⇆ the canonical `Patient` shape + (`src/types/patient.ts`, mirrors `../frontend/lib/patients.ts`). **`src/routes/patients.ts`** is + org-scoped CRUD, gated by **`src/middleware/auth.ts`** (`requireAuth` → `requireOrg` → + `requirePermission`). +- **`src/lib/email.ts`** — `sendEmail` logs links to the console when SMTP is unset. + +## Gotchas / conventions + +- **Schema generation order (circular bootstrap):** `auth.ts` → `db/index.ts` must NOT pull in the + patient schema, so the Better Auth CLI can load `auth.ts` to (re)generate `schema/auth.ts`. After + any auth/plugin change: `npm run auth:generate` → `npm run db:generate` → `npm run db:migrate`. +- **Express 5** needs a named wildcard: `app.all("/api/auth/*splat", …)`, and the Better Auth handler + must be registered **before** `express.json()`. +- **Secure cookies** key off `BETTER_AUTH_URL` scheme (https), **not** `NODE_ENV` — otherwise login + breaks over `http://localhost` in the production-mode Docker stack. +- **Rate limiting** needs a client IP; behind no proxy we backfill `x-forwarded-for` from the socket + in `index.ts` so it still applies. +- **Env:** `src/env.ts` (zod) treats empty strings as unset (compose passes `${VAR:-}` as `""`) and + has dev defaults so the CLIs can load the config offline. +- **Email verification is wired but NOT enforced** at sign-in + (`emailAndPassword.requireEmailVerification: false` in `auth.ts`) — re-enable by flipping it to + `true` (and route signup back to `/verify-email` in the frontend). +- Docker: migrations run on container start (`node dist/migrate.js`); `docker-compose.yml` exposes a + configurable `POSTGRES_PORT` host port. + +## Not built yet + +The AI `/chat` endpoint (LLM proxy) and the signing / patient-owned-storage / approval flow. diff --git a/backend/src/auth.ts b/backend/src/auth.ts index 31bb994..7f66126 100644 --- a/backend/src/auth.ts +++ b/backend/src/auth.ts @@ -24,7 +24,11 @@ export const auth = betterAuth({ emailAndPassword: { enabled: true, - requireEmailVerification: true, + // TODO(verification): Email verification is intentionally NOT enforced for + // now so users can sign in immediately. Verification emails are still sent + // (sendOnSignUp below) and the /verify-email flow still works — flip this + // back to `true` to make verification mandatory before sign-in. + requireEmailVerification: false, minPasswordLength: 12, maxPasswordLength: 256, revokeSessionsOnPasswordReset: true,