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

8.8 KiB

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

  • 0.1 Confirm current access_requests, users, roles, edge_projects, user_project_access, and subscriptions schema in migrations.
  • 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.
  • 0.4 Decide implementation location: private helpers in handlers/access_requests.rs vs new access_request_provisioning.rs module.
  • 0.5 Keep existing request submission, review, reject, and login behavior unchanged outside this change.

Phase 1: Backend Schema

  • 1.1 Add migration for access_requests.provisioned_user_id, provisioned_project_id, and provisioned_at.
  • 1.2 Add migration for access_request_invitations with token_hash, email status, expiration, consumed/revoked fields, and indexes.
  • 1.3 Ensure migration is additive and rollback-safe; do not drop existing request data.
  • 1.4 Update backend row/response types for access requests to include provisioning and latest invitation summary fields.
  • 1.5 Add query helper to fetch latest invitation summary per request without exposing raw tokens.

Phase 2: Backend Token And Mailer Foundations

  • 2.1 Add token generation helper: 32 random bytes, base64url without padding.
  • 2.2 Add token hashing helper using SHA-256 or stronger stable digest.
  • 2.3 Add tests proving stored hash differs from raw token and token length/encoding is URL-safe.
  • 2.4 Add generic SMTP mailer helper using existing SMTP_HOST, SMTP_USER, SMTP_PASS, SMTP_PORT, and SMTP_FROM env vars.
  • 2.5 Add activation email body builder for inactive/pending users.
  • 2.6 Add access-ready email body builder for existing active users.
  • 2.7 Ensure raw activation tokens are never logged or written to audit metadata.

Phase 3: Backend Approval Provisioning

  • 3.1 Refactor approve_access_request to run a transaction locking the request row.
  • 3.2 Reject approval unless request status is pending or in_review.
  • 3.3 Create edge_projects from deterministic request-derived defaults, including metadata with request id, NIT, contact, phone, and position.
  • 3.4 Resolve project name conflicts by appending a stable short request-id suffix.
  • 3.5 Lookup existing user by normalized email.
  • 3.6 For no existing user: create role admin user with is_active=false and an unusable random Argon2 password hash.
  • 3.7 For existing active customer user: preserve password and active state.
  • 3.8 For existing inactive customer user: preserve inactive state and create activation invitation.
  • 3.9 For existing provider-role user with same email: fail approval with clear conflict error.
  • 3.10 Insert or reactivate user_project_access with project_role='owner'.
  • 3.11 Insert subscriptions row with status='trial' and trial_ends_at = now + 7 days before billing reads can create an active default.
  • 3.12 Update access_requests with approved/provisioned fields and reviewer metadata.
  • 3.13 Create activation invitation for inactive users with 7-day expiry.
  • 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.
  • 3.15 Commit transaction before SMTP send.
  • 3.16 Send activation/access-ready email and update email_status, email_sent_at, or email_error.
  • 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

  • 4.1 Register public POST /api/auth/invitations/activate route.
  • 4.2 Optionally register public GET /api/auth/invitations/validate?token=... route for frontend preflight.
  • 4.3 Implement activation token validation: hash lookup, approved request, not expired, not consumed, not revoked, user/project exists.
  • 4.4 Implement password validation and Argon2 hashing for activation.
  • 4.5 Activate user and mark invitation consumed_at in one transaction.
  • 4.6 Register provider-only POST /api/platform/access-requests/{id}/invitation/resend route.
  • 4.7 Resend endpoint revokes all previous unconsumed invitations for the request.
  • 4.8 Resend endpoint creates a fresh 7-day invitation and sends email.
  • 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.

  • 5.1 Test approving pending request provisions project, customer admin, project access, trial subscription, and invitation.
  • 5.2 Test approving in-review request follows same provisioning path.
  • 5.3 Test approved/rejected request cannot be re-approved.
  • 5.4 Test existing active customer user is linked without password reset or deactivation.
  • 5.5 Test existing inactive user gets a fresh activation invitation.
  • 5.6 Test provider-role duplicate email blocks approval.
  • 5.7 Test SMTP failure leaves request approved/provisioned and marks email failed.
  • 5.8 Test raw token is not persisted in invitation rows or audit metadata.
  • 5.9 Test expired, consumed, and revoked tokens cannot activate.
  • 5.10 Test resend revokes previous unconsumed tokens.

Phase 6: Frontend Admin Activation

  • 6.1 Add /activate route before protected admin routes.
  • 6.2 Add /auth/invitations to AUTH_BYPASS_ENDPOINTS.
  • 6.3 Add activation API functions under features/auth/api.
  • 6.4 Add activation schema with password and confirmation validation.
  • 6.5 Create ActivatePage with states: validating token, invalid/expired, password form, success.
  • 6.6 Ensure activation token is read from URL and not stored in auth store/local storage.
  • 6.7 On success, show clear path to /login; do not auto-login in this change.
  • 6.8 Add focused tests for valid token form, invalid token, and success state.

Phase 7: Frontend Console Provisioning UX

  • 7.1 Extend access-request API types with provisioned user/project and invitation summary fields.
  • 7.2 Add resend invitation API function.
  • 7.3 Update request detail panel to show provisioned project, customer admin, activation email status, sent time, expiration, and consumed state.
  • 7.4 Keep approve button single-click; backend handles automatic project defaults.
  • 7.5 Show resend as primary action only when email failed or invitation expired/unconsumed.
  • 7.6 Show successful approval toast that distinguishes provisioned + email sent vs provisioned + email failed.
  • 7.7 Add focused tests for provisioning status and resend action visibility.

Phase 8: Docs And Deployment

  • 8.1 Document INVITATION_BASE_URL=https://app.omnioil.app in deployment docs.
  • 8.2 Document required SMTP_* variables for approval/activation email.
  • 8.3 Document that initial projects start with 7-day trial and payment enforcement is future billing lifecycle work.
  • 8.4 Update .env.example if present; do not commit real secrets.
  • 8.5 Add operator note: SMTP failure does not rollback approval; use Console resend.

Phase 9: Verification

  • 9.1 Run targeted backend tests for access request provisioning and invitation activation.
  • 9.2 Run targeted frontend-admin tests for activation page.
  • 9.3 Run targeted frontend-console tests for access request detail/resend.
  • 9.4 Run lint for touched frontends.
  • 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.