# Session: 2026-06-17 — Phase 2 (Accounts & roles) ## Goal Add worker/admin roles, admin-creates-users, and role-based data scoping to the backend. Backend-only this phase; the React admin panel stays Phase 3. ## Decisions (confirmed by maintainer this session) 1. **Role mechanism** → better-auth `admin()` plugin (`defaultRole: 'worker'`, `adminRoles: ['admin']`). Gives `createUser` / `listUsers` / `setRole` server+client APIs with access control for free (roadmap Decision #6 "don't hand-roll security"). 2. **Public sign-up** → **closed** (`emailAndPassword.disableSignUp: true`). Admin creates users. Worker client becomes login-only (Registreren toggle removed). Dev seed still creates a dev worker **and** a dev admin via `auth.api.createUser` (bypasses disableSignUp server-side). 3. **Phase 2 client scope** → backend-only. Admin UI is Phase 3. Only worker-client change: drop self-signup. ## Key better-auth facts established (read from installed v1.6.18 source) - `admin/schema.mjs`: plugin adds `user.role/banned/banReason/banExpires` + `session.impersonatedBy`. - `admin/routes.mjs` `createUser`: `if (!session && (ctx.request || ctx.headers)) throw UNAUTHORIZED` → calling `auth.api.createUser({ body })` with **no headers** skips the admin check ⇒ usable for seeding/tests. - `api/routes/sign-up.mjs:143`: throws `BAD_REQUEST` when `emailAndPassword.disableSignUp` is set (sign-in unaffected). createUser is a separate endpoint, so it still works. - Admin endpoints auto-mount under `/api/auth/admin/*` via the existing `/api/auth/*` handler. ## Work done Implemented Phase 2 task-by-task per `docs/plans/phase-2-accounts-roles.md` (TDD throughout): - **Task 1 — Shared contracts.** Added `Role` enum (`worker | admin`), optional `user_name` / `user_email` on `WorkSession` (admin cross-user joins only), and an `AdminUser` contract in `packages/shared/src/index.ts`. - **Task 2 — Test helpers.** Centralized auth on `apps/api/test/helpers.ts` (`createTestUser` / `authToken` / `bearer` / `seedActivity`) via server-side `auth.api.createUser`, removing every test's dependency on the public sign-up route so it can be closed. - **Task 3 — Admin plugin + close sign-up.** Wired better-auth's `admin()` plugin (`defaultRole: 'worker'`, `adminRoles: ['admin']`), set `disableSignUp: true`, added the admin-plugin columns (`user.role/banned/banReason/banExpires`, `session.impersonatedBy`) and migration `0002`. New test asserts public sign-up is rejected. - **Task 4 — Role-aware helper + activity lockdown.** `getSessionUser` now returns role; added `isAdmin`; extracted `toWorkSession` into `apps/api/src/lib/work-session.ts`; gated activity POST/PUT/DELETE to admins (GET stays open to any authenticated user). - **Task 5 — Admin router.** `apps/api/src/routes/admin.ts` exposes admin-only `GET /api/admin/sessions` and `/api/admin/sessions/active` (all users, joined with activity + user name/email); 401 unauthenticated, 403 non-admin. - **Task 6 — Dev seed.** Seeds dev worker `worker@solelog.local` + dev admin `admin@solelog.local` via `auth.api.createUser`, dev-only and idempotent. - **Task 7 — Worker client.** Removed self-signup: dropped `signUp` from the API client and `AuthContext`, made `Login` login-only (no Registreren toggle). - **Task 8 — Docs, lint, verification.** Updated this log, the roadmap status, and the worker README (both dev logins; self-registration closed). Ran lint/format/typecheck and both test suites green, plus the live HTTP smoke test proving the role rules. ## Plane - Epic + tasks created under SoleLog (SL). See plan doc for the mapping. ## Next Run the build (workflow), verify live (admin can manage users + see all sessions; worker cannot; sign-up closed), then update roadmap status to Phase 2 = Done.