Files
OmniOilPersonal/openspec/changes/notify-approved-access-requests/tasks.md
2026-05-14 08:56:48 -05:00

122 lines
8.8 KiB
Markdown

# Tasks: Provision Approved Access Requests
## Review Workload Forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | 900-1,400 across backend, Console, Admin, docs/tests |
| 400-line budget risk | High |
| Chained PRs recommended | Yes |
| Suggested split | PR 1 backend data/provisioning/email -> PR 2 Admin activation -> PR 3 Console status/resend/docs/tests |
## Phase 0: Baseline And Safety
- [x] 0.1 Confirm current `access_requests`, `users`, `roles`, `edge_projects`, `user_project_access`, and `subscriptions` schema in migrations.
- [x] 0.2 Confirm roles `admin`, `platform_admin`, and `platform_operator` exist in target environments.
- [ ] 0.3 Confirm production env can provide `INVITATION_BASE_URL=https://app.omnioil.app` and `SMTP_*` values.
- [x] 0.4 Decide implementation location: private helpers in `handlers/access_requests.rs` vs new `access_request_provisioning.rs` module.
- [x] 0.5 Keep existing request submission, review, reject, and login behavior unchanged outside this change.
## Phase 1: Backend Schema
- [x] 1.1 Add migration for `access_requests.provisioned_user_id`, `provisioned_project_id`, and `provisioned_at`.
- [x] 1.2 Add migration for `access_request_invitations` with `token_hash`, email status, expiration, consumed/revoked fields, and indexes.
- [x] 1.3 Ensure migration is additive and rollback-safe; do not drop existing request data.
- [x] 1.4 Update backend row/response types for access requests to include provisioning and latest invitation summary fields.
- [x] 1.5 Add query helper to fetch latest invitation summary per request without exposing raw tokens.
## Phase 2: Backend Token And Mailer Foundations
- [x] 2.1 Add token generation helper: 32 random bytes, base64url without padding.
- [x] 2.2 Add token hashing helper using SHA-256 or stronger stable digest.
- [x] 2.3 Add tests proving stored hash differs from raw token and token length/encoding is URL-safe.
- [x] 2.4 Add generic SMTP mailer helper using existing `SMTP_HOST`, `SMTP_USER`, `SMTP_PASS`, `SMTP_PORT`, and `SMTP_FROM` env vars.
- [x] 2.5 Add activation email body builder for inactive/pending users.
- [x] 2.6 Add access-ready email body builder for existing active users.
- [x] 2.7 Ensure raw activation tokens are never logged or written to audit metadata.
## Phase 3: Backend Approval Provisioning
- [x] 3.1 Refactor `approve_access_request` to run a transaction locking the request row.
- [x] 3.2 Reject approval unless request status is `pending` or `in_review`.
- [x] 3.3 Create `edge_projects` from deterministic request-derived defaults, including metadata with request id, NIT, contact, phone, and position.
- [x] 3.4 Resolve project name conflicts by appending a stable short request-id suffix.
- [x] 3.5 Lookup existing user by normalized email.
- [x] 3.6 For no existing user: create role `admin` user with `is_active=false` and an unusable random Argon2 password hash.
- [x] 3.7 For existing active customer user: preserve password and active state.
- [x] 3.8 For existing inactive customer user: preserve inactive state and create activation invitation.
- [x] 3.9 For existing provider-role user with same email: fail approval with clear conflict error.
- [x] 3.10 Insert or reactivate `user_project_access` with `project_role='owner'`.
- [x] 3.11 Insert `subscriptions` row with `status='trial'` and `trial_ends_at = now + 7 days` before billing reads can create an active default.
- [x] 3.12 Update `access_requests` with approved/provisioned fields and reviewer metadata.
- [x] 3.13 Create activation invitation for inactive users with 7-day expiry.
- [x] 3.14 For existing active users, decide whether to create an invitation row for email tracking or a separate notification-only record; implement consistently with design.
- [x] 3.15 Commit transaction before SMTP send.
- [x] 3.16 Send activation/access-ready email and update `email_status`, `email_sent_at`, or `email_error`.
- [x] 3.17 Add audit events for approval, project provisioning, user creation/linking, trial creation, invitation creation, email sent, email failed, and activation.
## Phase 4: Backend Activation And Resend Endpoints
- [x] 4.1 Register public `POST /api/auth/invitations/activate` route.
- [x] 4.2 Optionally register public `GET /api/auth/invitations/validate?token=...` route for frontend preflight.
- [x] 4.3 Implement activation token validation: hash lookup, approved request, not expired, not consumed, not revoked, user/project exists.
- [x] 4.4 Implement password validation and Argon2 hashing for activation.
- [x] 4.5 Activate user and mark invitation `consumed_at` in one transaction.
- [x] 4.6 Register provider-only `POST /api/platform/access-requests/{id}/invitation/resend` route.
- [x] 4.7 Resend endpoint revokes all previous unconsumed invitations for the request.
- [x] 4.8 Resend endpoint creates a fresh 7-day invitation and sends email.
- [x] 4.9 Resend endpoint records audit and email status.
## Phase 5: Backend Tests
Backend DB integration evidence: `services/backend-api/tests/access_request_db_integration.rs` calls the real `approve_access_request`, `activate_invitation`, and `resend_access_request_invitation` handlers/routes against a migrated disposable Timescale/PostgreSQL fixture. Evidence command passed against Docker test DB `omnioil_access_request_test` on port `55432`: `$env:DATABASE_URL='postgres://omnioil_test:omnioil_test_password@127.0.0.1:55432/omnioil_access_request_test'; cargo test -p backend-api --test access_request_db_integration -- --ignored --nocapture` -> 5/5 passed, including non-provider/customer `admin` denial for the provider resend route without token rotation. Focused unit evidence also covers activation-link construction and platform-role predicates.
- [x] 5.1 Test approving pending request provisions project, customer admin, project access, trial subscription, and invitation.
- [x] 5.2 Test approving in-review request follows same provisioning path.
- [x] 5.3 Test approved/rejected request cannot be re-approved.
- [x] 5.4 Test existing active customer user is linked without password reset or deactivation.
- [x] 5.5 Test existing inactive user gets a fresh activation invitation.
- [x] 5.6 Test provider-role duplicate email blocks approval.
- [x] 5.7 Test SMTP failure leaves request approved/provisioned and marks email failed.
- [x] 5.8 Test raw token is not persisted in invitation rows or audit metadata.
- [x] 5.9 Test expired, consumed, and revoked tokens cannot activate.
- [x] 5.10 Test resend revokes previous unconsumed tokens.
## Phase 6: Frontend Admin Activation
- [x] 6.1 Add `/activate` route before protected admin routes.
- [x] 6.2 Add `/auth/invitations` to `AUTH_BYPASS_ENDPOINTS`.
- [x] 6.3 Add activation API functions under `features/auth/api`.
- [x] 6.4 Add activation schema with password and confirmation validation.
- [x] 6.5 Create `ActivatePage` with states: validating token, invalid/expired, password form, success.
- [x] 6.6 Ensure activation token is read from URL and not stored in auth store/local storage.
- [x] 6.7 On success, show clear path to `/login`; do not auto-login in this change.
- [x] 6.8 Add focused tests for valid token form, invalid token, and success state.
## Phase 7: Frontend Console Provisioning UX
- [x] 7.1 Extend access-request API types with provisioned user/project and invitation summary fields.
- [x] 7.2 Add resend invitation API function.
- [x] 7.3 Update request detail panel to show provisioned project, customer admin, activation email status, sent time, expiration, and consumed state.
- [x] 7.4 Keep approve button single-click; backend handles automatic project defaults.
- [x] 7.5 Show resend as primary action only when email failed or invitation expired/unconsumed.
- [x] 7.6 Show successful approval toast that distinguishes provisioned + email sent vs provisioned + email failed.
- [x] 7.7 Add focused tests for provisioning status and resend action visibility.
## Phase 8: Docs And Deployment
- [x] 8.1 Document `INVITATION_BASE_URL=https://app.omnioil.app` in deployment docs.
- [x] 8.2 Document required `SMTP_*` variables for approval/activation email.
- [x] 8.3 Document that initial projects start with 7-day trial and payment enforcement is future billing lifecycle work.
- [x] 8.4 Update `.env.example` if present; do not commit real secrets.
- [x] 8.5 Add operator note: SMTP failure does not rollback approval; use Console resend.
## Phase 9: Verification
- [x] 9.1 Run targeted backend tests for access request provisioning and invitation activation.
- [x] 9.2 Run targeted frontend-admin tests for activation page.
- [x] 9.3 Run targeted frontend-console tests for access request detail/resend.
- [x] 9.4 Run lint for touched frontends.
- [x] 9.5 Run targeted `cargo test` for backend modules. `cargo fmt -- --check` remains documented as external repo-wide formatting drift outside this feature.
- [ ] 9.6 Manually verify local happy path with stubbed SMTP if available: submit request -> approve -> activate -> login.