Files
baya-monorepo/dev/shared-working-context/backend/handoff/after-backend-phase-2.md
T
hamid 3a51305343 backend phase 2: identity — phone-OTP auth, sessions & roles (REST)
- six REST endpoints (auth/request_otp, verify_otp, refresh, logout, me,
  me/select_role) wrapping the existing JWE/TOTP/RBAC engine
- usr.UserSessions with refresh-token rotation + stolen-token (replay)
  detection → revoke-all + 401; logout rotates the security stamp
- users extended: gender, national_id (enc, NULL until KYC),
  shahkar_verified_at (auto-reset on phone change), phone_hash UNIQUE,
  is_active, deleted_at + soft-delete filter; phone/email/national_id
  encrypted at rest via IFieldEncryptor value converter
- user_roles grant/revoke audit trail + global revoked filter; 7 roles
  seeded; admin sub-roles never self-assignable (403)
- ISmsSender seam (mock logs the OTP code) replaces the TODO log lines
- OperationResult/BaseController learned enveloped 401/403
- auth knobs as platform_configs rows (resend/attempts/session TTL)
- migration IdentitySessionsAndUserExtensions applied to the dev DB
- 24 new tests incl. Baya.Test.Api (WebApplicationFactory over SQLite);
  47 total green, zero new build warnings; swagger snapshot + contract
  (identity-auth.md), handoff, report, mocks-registry updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:34:11 +03:30

3.5 KiB

After backend-phase-2 — auth is live over REST

The marketplace has its front door: phone-OTP login, revocable sessions with refresh-token rotation + stolen-token detection, /me, and public role selection — all over REST, wrapping the pre-existing JWE/TOTP/RBAC engine (nothing was rebuilt). Contract: dev/contracts/domains/identity-auth.md; machine schema: dev/contracts/openapi/swagger.v1.json (refreshed — 22 paths).

What the frontend (f1-b2) can now build

  • Login flow: POST api/v1/auth/request_otpPOST api/v1/auth/verify_otp (camelCase bodies; exact snake_case paths per the contract — note request_otp, not otp/request).
  • Session handling: store the token pair; POST api/v1/auth/refresh rotates it (never reuse an old refresh token — replay = 401 + logout-everywhere); POST api/v1/auth/logout (send {}).
  • AuthContext roles + role router: GET api/v1/me returns masked phone, roles[], profile-completion flags (false until b3) and nurseVerificationStatus (not_started until b6). Fresh users have roles: [] → route to POST api/v1/me/select_role (customer/nurse, both allowed, 403 for anything else). Refresh tokens after role selection — role claims are baked into the access token.
  • Errors: 400 invalid phone/code (safe, non-enumerating message), 401 with the standard envelope (also written by the auth stack itself), 403 admin self-assign, 429 over the OTP/auth per-IP limits. The envelope is unchanged (camelCase body, snake_case URLs).

What's mocked

  • SMS delivery (ISmsSender🟡). The OTP code is written to the server log instead of a SIM. Local testing: call request_otp, read the code from the API console log, verify_otp with it.

Rules baked into the API (don't fight them client-side)

  • Phone is the only public credential; email is optional and never a login key.
  • One resend per auth_otp_resend_seconds (response says otpSent: false + wait time).
  • After auth_otp_max_attempts wrong codes, verification refuses until a fresh OTP is requested.
  • Logout rotates the security stamp: all devices' access tokens die; they recover via refresh.

Schema / migration

Migration 20260701222425_IdentitySessionsAndUserExtensions applied to the dev DB on top of b1's baseline: usr.Users gains Gender, NationalId (enc, NULL until b6 KYC), NationalIdVerifiedAt, ShahkarVerifiedAt (auto-reset on phone change), PhoneHash (UNIQUE), PhoneVerifiedAt, IsActive, DeletedAt (+ soft-delete filter); new usr.UserSessions; usr.UserRoles gains GrantedById/GrantedAt/RevokedAt (revoked grants filtered out globally). PhoneNumber/Email/NationalId are now encrypted at rest — never query them by equality; use PhoneHash. Roles seeded: customer, nurse, admin, support, finance, moderation, super_admin. Config keys added: auth_otp_resend_seconds (120), auth_otp_max_attempts (5), auth_session_ttl_days (30).

Follow-ups later phases must close

  • b3: profiles/patients/addresses/bank accounts; gender + names become settable; the /me profile-completion flags start reading real tables.
  • b6: Shahkar + KYC set NationalId/ShahkarVerifiedAt; nurseVerificationStatus becomes real.
  • Legacy UserRefreshTokens still backs the gRPC path only; retire it when gRPC moves to sessions (or gRPC is dropped).
  • ISmsSender → real gateway (see mocks-registry row).