Files
solelog/docs/sessions/2026-06-17-phase-2-accounts-roles.md

79 lines
4.7 KiB
Markdown

# 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.
## Follow-up (maintainer feedback, same day)
Two UX gaps surfaced once Phase 2 landed, both fixed in commit `1631c16`:
1. **No logout** — the worker client never surfaced `AuthContext.signOut`.
2. **Workers saw uneditable Settings** — the Instellingen tab *was* activity management, which
Phase 2 made admin-only, so every add/edit/delete 403'd for workers.
Decision (confirmed): the worker app stays worker-only. Replaced the Instellingen tab with an
**Account** screen (signed-in name/email via `/api/me` + an **Uitloggen** button), deleted the
activity-management `Settings` screen and its now-unused mutation hooks
(`useCreateActivity/useUpdateActivity/useDeleteActivity`; `useActivities` read stays for the
Stopwatch picker). Activity management belongs to the **Phase 3 admin app**. No backend change.
Worker: 22 tests + typecheck + `vite build` green.
## Plane
- Epic + tasks created under SoleLog (SL). See plan doc for the mapping.
- Follow-up item (logout + Account screen) created and marked Done.
## 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.