Skip to content

feat(users): Phase 7b — admin user management + email invitations - #174

Merged
alovladi007 merged 1 commit into
mainfrom
feat/account-admin
Aug 4, 2026
Merged

feat(users): Phase 7b — admin user management + email invitations#174
alovladi007 merged 1 commit into
mainfrom
feat/account-admin

Conversation

@alovladi007

Copy link
Copy Markdown
Owner

Why

After bootstrap_admin, an admin had no way to add a second user — the users API was GET-only. Second of the two Phase 7 (account lifecycle) PRs, building on #173's token + mailer foundation.

What

Backend

  • POST /api/users/ (admin only) — creates an active, passwordless user and emails a 7-day single-use set-password link; the shared /reset-password page terminates both flows (&welcome=1). Duplicate email in org → 409.
  • PATCH /api/users/{id} (admin only, own org) — name/role/is_active with self-protection: you cannot demote or deactivate your own account, so an org can never brick its last admin. Cross-org ids are 404 (RLS + explicit filter).
  • Two container-vs-test import realities found by live testing: pydantic EmailStr needs email-validator (not shipped) → plain-str shape check; issue_password_token must be imported relatively (.auth) since the package is app.api in the container.

Frontend/system/users gains an Invite dialog (email/name/role) and, in the user details dialog, a role select + activate/deactivate button on the real PATCH; backend errors (409, self-protection 400s) surface verbatim.

Plumbing — compose lims env passes through SMTP_* + APP_BASE_URL (unset = honest log-only dev mode); k8s lims Deployment reads them from the optional smtp-credentials secret (template + runbook decision row added). The platform deploys and runs without email configured — it logs instead of sending.

Proof

  • 10 new API tests (invite lifecycle including completion through the reset endpoint, RBAC 403s, duplicate 409, self-protection 400s, cross-org 404): lims suite 90 passed
  • Live on the dev stack: invite 201 → link from service log → set password 204 → invitee logs in 200; engineer's invite attempt 403; invited-but-not-completed accounts cannot log in
  • tsc clean, smoke 4/4, docker compose config valid, both overlays render

🤖 Generated with Claude Code

After bootstrap_admin, an admin had no way to add a second user: the
users API was GET-only. This completes org administration on the
Phase 7a token/mailer foundation.

Backend (services/lims/app/api/users.py):
- POST /api/users/ (admin only): creates an ACTIVE, passwordless user
  and emails a 7-day single-use set-password link (the shared
  /reset-password page terminates both reset and invite flows via
  &welcome=1). Duplicate email in org → 409. Plain-str email shape
  check, NOT pydantic EmailStr — that type imports email-validator,
  which the lims image doesn't ship (found live: reload crash).
- PATCH /api/users/{id} (admin only, own org): name/role/is_active.
  Self-protection: you cannot demote or deactivate your own account,
  so an org can never brick its last admin. Cross-org ids are 404.
- issue_password_token imported RELATIVELY (.auth): the package is
  `app.api` in the container and `services.lims.app.api` under tests —
  the absolute spelling 500'd in the container (found live).

Frontend (/system/users):
- Invite dialog (email/name/role) on the real POST; role select +
  activate/deactivate in the details dialog on the real PATCH; backend
  errors (409, self-protection 400s) surface verbatim; react-query
  invalidation keeps the list fresh.

Plumbing:
- compose lims env: SMTP_* + APP_BASE_URL passthrough (unset = honest
  log-only dev mode).
- k8s lims Deployment: APP_BASE_URL + SMTP_* from the OPTIONAL
  smtp-credentials secret (template + runbook decision row added) —
  the platform deploys and runs without email, it just logs instead.

Proof: 10 new API tests (invite lifecycle incl. completion via the
reset endpoint, RBAC 403s, 409, self-protection 400s, cross-org 404) —
lims suite 90 passed. LIVE on the dev stack: invite 201 → link from
log → set password 204 → invitee logs in 200; engineer invite 403.
tsc clean; smoke 4/4; compose config valid; overlays render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alovladi007
alovladi007 merged commit ae7a326 into main Aug 4, 2026
18 checks passed
@alovladi007
alovladi007 deleted the feat/account-admin branch August 4, 2026 01:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant