# 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.