Skip to content

security: just-in-time (JIT) time-bound access requests #378

Description

@babltiga

Background

Proposed for v1.3. AccessFlow approves individual queries and supports admin-set static expiry on grants (permissions.expires_at), but there is no self-service workflow for a user to request temporary, scoped access (e.g. "write to analytics for 4 hours") that is granted on approval and automatically revoked on expiry. Just-in-time access is the defining capability of the JIT/PAM category (StrongDM, HashiCorp Boundary/Vault, Satori, ConductorOne, Opal) and is the single largest gap between AccessFlow and that category.

Scope

Let users raise an access-grant request that flows through the existing approval engine and, once approved, materialises as a time-boxed permissions row that a scheduled job revokes on expiry.

  • New table access_grant_request (Flyway V56+): id UUID PK, organization_id, requester_id, datasource_id, requested capabilities (can_read/can_write/can_ddl), optional allowed_schemas/allowed_tables, duration (ISO-8601), justification, status (snake_case enum access_grant_status: PENDING/APPROVED/REJECTED/EXPIRED/REVOKED/CANCELLED), expires_at, granted_permission_id NULL, timestamps. UUID PK, TIMESTAMPTZ.
  • Approval reuses the existing reviewer-eligibility + self-approval-block logic — a requester can never approve their own request. Routed through the same review_plans / datasource_reviewers machinery the query review queue uses.
  • On final approval, the permission service writes a permissions row with expires_at = now + duration; on rejection/cancel nothing is granted.
  • New scheduling job AccessGrantExpiryJob (mirror QueryTimeoutJob): scans for grants past expires_at, revokes them, writes audit + notification. @SchedulerLock (ShedLock/Redis), ISO-8601 fixedDelayString cadence property under accessflow.security.*, per the CLAUDE.md scheduled-job rules.
  • REST: POST /api/v1/access-requests, GET /api/v1/access-requests (mine), GET /admin/access-requests (review queue), POST /admin/access-requests/{id}/{approve,reject}, DELETE /api/v1/access-requests/{id} (requester cancel). Paginated per docs/04-api-spec.md.
  • Audit: new actions ACCESS_REQUEST_SUBMITTED/APPROVED/REJECTED/CANCELLED, ACCESS_GRANT_EXPIRED/REVOKED.
  • Notifications: reuse the dispatcher fanout (reviewers on submit; requester on decision/expiry) + WebSocket access_request.* events.
  • Frontend: a "Request access" page + an admin "Access requests" queue alongside ReviewQueuePage; surface active time-boxed grants and remaining TTL on DatasourceSettingsPage.

Acceptance criteria

  • Flyway migration V56+ (new table + enum; columns nullable or defaulted).
  • Approval path reuses reviewer eligibility and blocks self-approval at the service layer (not just UI).
  • Approved request materialises a permissions row with expires_at; AccessGrantExpiryJob revokes on expiry with @SchedulerLock + audit.
  • REST endpoints with Bean Validation + frontend validation parity (CLAUDE.md validation parity rule).
  • No hardcoded user-facing strings (messages.properties + en.json); i18n parity tests pass.
  • Unit + integration (Testcontainers) tests ≥ 90% line coverage; ApplicationModulesTest + ApiPackageDependencyTest green.
  • Playwright spec under e2e/tests/: request → approve → grant visible → (expiry) revoke.
  • docs/03-data-model.md, docs/04-api-spec.md, docs/05-backend.md, docs/06-frontend.md, docs/07-security.md, docs/12-roadmap.md (add v1.3) and website/ updated.

Pointers

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions