Skip to content

Add access review campaigns#935

Draft
aureliensibiril wants to merge 90 commits intogetprobo:mainfrom
aureliensibiril:aureliensibiril/eng-136-access-review
Draft

Add access review campaigns#935
aureliensibiril wants to merge 90 commits intogetprobo:mainfrom
aureliensibiril:aureliensibiril/eng-136-access-review

Conversation

@aureliensibiril
Copy link
Contributor

@aureliensibiril aureliensibiril commented Mar 26, 2026

Summary

Add access review campaigns with full lifecycle management (create, start, close, cancel) and a source fetch worker that pulls account data from connected providers.

Access source drivers (18 providers)

Provider Connection Fields
Google Workspace OAuth2 Email, name, role, admin, MFA, last login, created at
Slack OAuth2 Email, name, role, job title, admin, MFA, bot support
Linear OAuth2 Email, name, role, admin, active, created at
Cloudflare API key Email, name, roles (concatenated), MFA, active
Brex API key Email, name, role, active
Tally API key Email, name, role
1Password API key Email, name, role, active, MFA, created at
DocuSign OAuth2 Email, name, role, job title, admin, active, created at
HubSpot OAuth2 Email, name, role (resolved via roles endpoint)
Notion OAuth2 Email, name, role, bot support (ServiceAccount)
Figma OAuth2 Email, name
OpenAI API key Email, name, role, MFA
Sentry API key Email, name, role, MFA, active
GitHub OAuth2 Email, name, role, admin, MFA, 2FA status
Supabase API key Email, name, role, MFA, active
Intercom OAuth2 Email, name, role
Resend API key Email, name, role
Probo memberships Internal Email, name, role, admin, active, created at
CSV File upload Email, name, role, job title, admin, active, external ID

API surface (GraphQL + MCP + CLI)

  • Campaign CRUD, start, close, cancel
  • Access source CRUD with OAuth2 and API key connector support
  • Campaign scope source management (add/remove)
  • Access entry listing, flagging, and decision recording (approve/revoke)
  • Bulk decide-all for batch processing

Console UI

  • Campaign list with pagination and status badges
  • Campaign detail page with entry table (flag, decision columns)
  • Access source creation page with OAuth2 flow and API key dialog
  • CSV upload source creation
  • Vendor icon components for all supported providers

Bug fixes included

  • Fix source name worker retry on resolution failure
  • Fix React hooks ordering in access source page (lint)
  • Fix connector dropdown showing non-unique labels
  • Fix OAuth callback retry when mutation fails
  • Handle DocuSign pagination parse errors
  • Return error when driver pagination limit is reached
  • Accept variable-length rows in CSV driver
  • Fix scope sources query returning all org sources after removal

Test plan

  • make test passes
  • make lint passes (0 errors)
  • make test-e2e — access review e2e tests pass
  • Create a campaign via CLI, add a source, start it, verify source fetch completes
  • Verify campaign lifecycle transitions (draft → in_progress → pending_actions → completed)
  • Test cancel and delete flows

gearnode and others added 30 commits March 25, 2026 23:39
Add SQL migration for access reviews, campaigns, sources, and
entries. Implement coredata types for access entries, reviews,
campaigns, source fetches, and supporting enums (decision, flag,
MFA status, auth method, connector provider/protocol).

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Introduce an API key-based connector protocol alongside the
existing OAuth flow. This allows access source drivers to
authenticate with third-party services using static API keys.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Implement core domain logic for access reviews: entry service
for CRUD and decisions, source service for managing access
sources, review service for campaign orchestration, and a review
engine that flags anomalies (inactive accounts, missing MFA,
admin privileges, unknown entries).

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Define the Driver interface that all access source integrations
must implement: Name, Snapshot, and IsAdmin methods.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Implement campaign lifecycle management (create, initialize,
cancel, complete) with optimistic locking for state transitions.
Add a poll-based worker that fetches access snapshots from
configured sources with bounded concurrency, then finalizes
campaigns when all fetches complete.

Wire the access review service into probod.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Add GraphQL types, queries, mutations, and resolvers for access
reviews, campaigns, sources, and entries. Includes connection-based
pagination for sources and entries, campaign lifecycle mutations
(initialize, cancel, record decision), and access source CRUD.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Add pages for viewing the access review, managing access sources,
creating sources (OAuth and CSV), and a sidebar navigation link.
Includes Relay fragments, mutations, and pagination for the
access source list.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Expose access review operations through the MCP interface:
list/get access sources, list/get campaigns, list/get entries,
record entry decisions, and initialize/cancel campaigns.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Test the full access review lifecycle through the GraphQL API:
source CRUD, campaign initialization, entry listing with
pagination, decision recording, and campaign completion.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Remove identity_source_id from campaigns (no longer needed), collapse
migrations into a single file, remove dead code functions, and add a
campaign detail page with collapsible source entries.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Replace ref.Ref() with new() pointer syntax, update access source
driver interface to use typed source names, and clean up GraphQL
schema and MCP specification for access review campaigns.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Every return from WithTx/WithConn now wraps the error with
context. Also removes the redundant lockCampaignForUpdate
method, fixes duplicate source loading in Start, and drops
useless doc comments.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Convert ProviderDisplayName to map, remove dead resolvers
(Figma, Notion, 1Password) that always returned empty, and
drop unused HubSpot CompanyName field with wrong json tag.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add ConnectorProvider constants and settings structs for
Sentry, Supabase, GitHub, Intercom, and Resend. Register
driver constructors in the review engine switch and add
name resolvers for the source name worker.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Set FieldsPerRecord = -1 so that rows with fewer columns than the
header are not rejected. The existing idx < len(row) guards already
handle missing columns gracefully.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Return an error instead of silently defaulting to 0 when
strconv.Atoi fails on TotalSetSize or EndPosition. This prevents
silent truncation of user enumeration.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
All paginated drivers now return ErrPaginationLimitReached instead
of silently returning partial data when the 500-page safety cap is
exhausted.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Move NameSyncedAt out of the initial claim transaction so that
transient resolution failures leave the row eligible for retry.
The field is now only set after name resolution succeeds or is
permanently skipped.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Wrap layout in Suspense boundary and replace alert() with toast
notification in source row delete handler.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Use extracted status/badge helpers, add load-more pagination to
campaigns list, and use dateFormat for consistent date display.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Replace useMutationWithToasts with useMutation and explicit
onCompleted/onError handlers. Show creation date in connector
dropdown to distinguish same-provider connectors.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Replace useMutationWithToasts with useMutation and explicit
onCompleted/onError handlers. Use navigate() instead of
window.location.href for client-side routing.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Move hooks before early return to fix rules-of-hooks lint error.
Clear processedConnectorIdRef on mutation error so OAuth callback
can be retried. Replace useMutationWithToasts with explicit error
handling.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
@aureliensibiril aureliensibiril force-pushed the aureliensibiril/eng-136-access-review branch from 489fede to c621d0e Compare March 26, 2026 15:26
Remove the UNION fallback that returned all organization sources
when no explicit scope rows existed. After removing the last scope
source from a campaign, the query now correctly returns an empty
list instead of falling back to all org sources.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Extend the connector package to support three token exchange
styles (post-form, basic-form, basic-json) and the OAuth2
client credentials grant type. DocuSign and Notion require
HTTP Basic auth for token exchange while 1Password uses
client_credentials grant for machine-to-machine auth.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add environment variable blocks for HubSpot, DocuSign,
Notion, GitHub, Sentry, and Intercom following the Slack
bootstrap pattern. Each provider is opt-in via its
CLIENT_ID env var with validation for required secrets.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Remove unused Figma provider, add ConnectorProviders() slice
for iteration, and add OnePasswordUsersAPISettings for the
new Users API driver. Remove Figma from display name map.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Expand ConnectorProvider enum to 16 providers, add the
connectorProviderInfos query on Organization for dynamic
frontend rendering, and add createClientCredentialsConnector
mutation for 1Password OAuth2 client credentials flow.
Inject ConnectorRegistry into the GraphQL resolver.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Inject ConnectorRegistry into the service layer so the
review engine can use RefreshableClient for OAuth2
connections with short-lived tokens. Add provider-specific
settings fields to CreateConnectorRequest and branch on
grant type for 1Password driver selection.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Allow Sentry driver to auto-discover the organization slug
from the OAuth token when not explicitly configured. Add a
new OnePasswordUsersAPIDriver that fetches users from the
1Password Users API v1beta1 with paginated requests.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Use connectorProviderInfos from GraphQL to dynamically
render provider cards with OAuth, API key, and client
credentials options. Add client credentials mutation
support for 1Password and dynamic extra settings fields.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add unit tests for the three token exchange styles and
client credentials token caching. Add E2E tests covering
API key connector creation, client credentials connector
creation, deletion, provider info query, and RBAC.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Single component that renders a vendor SVG by name with an
optional tint mode that renders monochrome logos adapting to
the current theme via grayscale filter and dark:invert.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add a search input to filter providers by name, render
tinted vendor logos next to provider names using VendorLogo,
and replace the plain text Region field with a select
dropdown for 1Password client credentials.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Use grayscale and brightness-0 Tailwind utilities instead
of inline style filter so that dark:invert can properly
compose and render white logos on dark backgrounds.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
@aureliensibiril aureliensibiril force-pushed the aureliensibiril/eng-136-access-review branch from 13a33a5 to 62b2960 Compare March 26, 2026 17:11
Add connector entries for Linear, HubSpot, DocuSign, Notion,
GitHub, Sentry, and Intercom with placeholder credentials
and correct auth/token URLs from each provider's docs.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add Brex OAuth bootstrap with Authorization Code Grant
using accounts-api.brex.com endpoints. Add Linear and Brex
entries to dev config with placeholder credentials.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Move the provider list into a modal dialog triggered from
the sources tab, matching the established pattern used by
Documents and Assets. Handle OAuth callback connector_id
directly in the sources tab instead of a dedicated page.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Delete the dedicated access source page and its route now
that the dialog handles provider selection and the sources
tab handles OAuth callbacks. Give the CSV page its own
query and fix its back link to point to the sources tab.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants