Commit Graph

84 Commits

Author SHA1 Message Date
Aurélien Sibiril
29b72ebc3b Add Better Stack access review connector
Better Stack exposes team members and pending invitations through its
Uptime API. Wire it as an access-review connector so a Better Stack
team can be reviewed in access-review campaigns.

Better Stack has no third-party OAuth app for listing members (its
OAuth is an end-user MCP sign-in), so the connector authenticates with
a Bearer API token plus the team name that scopes the team-members
listing. The driver paginates /api/v2/team-members, maps roles and
invitation records into account records, and the source name is
resolved from the configured team.

This wires the full surface: the provider enum and migration, the
connector settings, the registry registration with the team-name extra
setting, the GraphQL input and resolver marshaling, the frontend field
mapping and connector logo, and cassette-backed driver tests.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-09 17:07:08 +02:00
Sacha Al Himdani
9ac71f948f Update contact email to hello@probo.com
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-09 16:45:23 +02:00
Aurélien Sibiril
4df0e52810 Add SigNoz access review driver
Add a SigNoz connector so its organization members can be pulled into
access-review campaign snapshots. SigNoz authenticates with a
SIGNOZ-API-KEY admin service-account key and a customer-supplied base
URL (a SigNoz Cloud region/tenant host or a self-hosted instance).

The driver lists users via GET /api/v1/user, which returns the role
(ADMIN/EDITOR/VIEWER) inline so admin detection works in a single call,
and maps the SigNoz user status (active / pending_invite / deleted) to
the account active flag. The name resolver reads the organization
display name from GET /api/v2/orgs/me to title the access source.

Wire the provider through the coredata enum and settings, the
connector-provider registry (driver and name-resolver factories), the
console API-key input schema and validation, the access-review source
label, and the SigNoz brand logo.

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-09 15:22:05 +02:00
Aurélien Sibiril
b92f7511b6 Simplify Zendesk driver and share time parsing
Drop the zendeskRole helper and use the raw API role, matching the other
drivers. Factor the duplicated RFC3339 timestamp parsing (parseZendeskTime
and parseDatadogTime were identical) into a shared parseRFC3339Ptr in
driver.go.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:58:11 +02:00
Aurélien Sibiril
dbd920dc24 Add Zendesk access-review connector
Zendesk is a multi-tenant OAuth connector keyed by the customer
subdomain. The customer enters it at connect time; it rides the signed
state to the callback, is re-validated, and is stored on the connector
settings to build the API host.

List staff (agents and admins) via GET /api/v2/users.json with cursor
pagination, mapping role, active/suspended, and 2FA status; end-users
are excluded. The subdomain is validated as a single DNS label at every
trust boundary to close the SSRF vector, and the data client keeps the
SSRF-protected transport.

Zendesk OAuth across customer subdomains requires a Zendesk-approved
global OAuth client; the connector goes live once those credentials are
supplied via bootstrap.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:55:48 +02:00
Aurélien Sibiril
13bc57b591 Drop unused OktaProvider const, wrap parse error
OktaProvider was dead: the provider-string const is only matched by
OAuth providers (SlackProvider in oauth2.go / connector.go), and an
API-key connector never matches on it.

Wrap the url.Parse error in nextPageURL with %w, like every sibling
driver, instead of dropping it.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:27 +02:00
Aurélien Sibiril
d3e8845fac Pin Okta pagination to the same https origin
The rel="next" link is taken from the provider response, so the host
check alone let a response downgrade the next request to http or move
it to another port on the same host. Reject a non-https scheme and any
explicit port in addition to a host mismatch, so the crawl stays on
the same https origin.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:27 +02:00
Aurélien Sibiril
44c3d3fd9d Re-record Okta cassette against a real Okta org
The driver test cassette was regenerated by running the driver in
record mode against a live Okta org, which validated the field
mapping (id, status, created/activated/lastLogin, profile.*) and the
rel="self" Link header format against the real GET /api/v1/users
response.

The recording is then fully anonymized: the real domain, member
names, emails, user IDs, session cookie, and request-id are replaced
with synthetic values, and a second page with a rel="next" Link
header is added back to keep pagination coverage (the source org had
a single user). TestCassettesUseSyntheticEmails guards the result.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:26 +02:00
Aurélien Sibiril
06603372d7 Add Okta users driver and name resolver
The driver lists GET /api/v1/users (limit=200) on the customer's org
host and follows the RFC 5988 Link header, pinning pagination to the
configured host so a response cannot redirect the crawl off-tenant.
User status maps to the three-valued Active flag (SUSPENDED and
DEPROVISIONED are inactive); ExternalID is the stable Okta user id.

The name resolver reads /api/v1/org and returns ("", nil) on any
non-2xx so a read-only token lacking org-settings read does not loop
the source-name worker.

The org domain is operator-supplied and feeds the URL host, so it is
the one SSRF-sensitive input: NormalizeOktaDomain validates and
strips it on the write path and IsValidOktaDomain re-checks it at
driver construction, on top of the transport's SSRF protection.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:26 +02:00
Aurélien Sibiril
900b0608c1 Fix Clerk user list decoding and add logo
GET /v1/users returns a bare JSON array, not a {"data":[...]} envelope
(total_count is a separate endpoint), so decode directly into a slice.
The previous envelope-first decode errored on the array and never
reached the fallback, so ListAccounts failed against the real API.

Treat deprovisioned users as inactive. Add the missing Clerk
third-party logo and its wiring, and document why the registration
sets no probe URL or name resolver.

Record the driver-test cassette against a live Clerk development
instance, scrubbed of PII and instance identifiers (emails to
example.com, image_url payloads and CF transport headers dropped).
A locked account verifies the inactive path.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:01:44 +02:00
Cursor Agent
e3cf1c7e48 Use cassette replay for Clerk driver test
Align the Clerk driver tests with the rest of the access-review\ndriver suite by exercising VCR cassette replay instead of a\ncustom transport stub.\n\nThis keeps test coverage consistent with existing providers and\nensures request matching behavior is validated against a checked-in\nfixture.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-04 18:50:57 +02:00
Cursor Agent
432a5bf82e Add Clerk access review driver
Wire Clerk in as a supported connector provider for access\nreviews and expose it through the console GraphQL provider enum.\n\nAdd a dedicated Clerk driver that lists users from the Clerk\nBackend API, maps account state and authentication signals into\nAccountRecord fields, and covers the behavior with focused driver\nand provider tests.\n\nInclude a migration that appends CLERK to the connector_provider\nenum so environments can persist Clerk connectors safely.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-06-04 18:50:57 +02:00
Aurélien Sibiril
2e86d0ebf5 Fix SendGrid teammate-detail decode and harden MFA/auth coverage
The teammate detail endpoint returns a bare object, not a {"result":...}
envelope, so the N+1 scope fetch silently discarded scopes and MFA was
always Unknown for every teammate. Decode the bare object.

Found by re-recording the driver cassette against the live API; the
hand-written cassette had hidden the mismatch. Also from that review:
- Map is_sso/is_partner_sso to AuthMethod (SSO vs Password) instead of
  hardcoding Unknown
- Log the swallowed teammate-detail fetch error (wire the driver logger,
  matching github.go)
- Add a restricted teammate + a missing-email row to the cassette so the
  N+1 decode, the empty-email skip, and the SSO path are load-bearing
- Drop the stale Content-Length response header; document ExternalID

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 18:29:18 +02:00
Aurélien Sibiril
d7ec442d61 Address SendGrid connector review feedback
- Add SendGrid third-party logo and wire it into ThirdPartyLogo
- Add SendGrid name resolver (account company name, graceful fallback)
- Fix MFA detection: full-access teammates carry both 2fa_exempt and
  2fa_required, so report Unknown unless exactly one is present
- Re-record the driver cassette against the live API
- Use a random time suffix for the migration filename

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 14:44:00 +02:00
Cursor Agent
035ff36b71 Check SendGrid 2FA enforcement scope
Add a best-effort MFA status check to the SendGrid access-review
fetch pipeline by querying teammate details and inspecting 2FA scopes.

When teammate scopes include 2fa_required or 2fa_exempt, map those
to ENABLED or DISABLED MFA status values; otherwise keep UNKNOWN.

Extend the SendGrid cassette and tests to cover the detail lookups and
MFA scope mapping behavior.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-04 14:44:00 +02:00
Cursor Agent
6556116601 Add SendGrid access review driver
Implement a SendGrid access-review driver that fetches teammates
from the SendGrid API and maps them into AccountRecord values.

Register SendGrid as a connector provider, expose it through the
connector provider enum, and add a migration that extends the
connector_provider type with SENDGRID.

Cover the new driver with a VCR-backed fixture test and helper
tests for role and response-shape handling to keep parsing robust.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-04 14:44:00 +02:00
Aurélien Sibiril
fba3174739 Map Datadog account type, MFA, and job title
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 18:02:44 +02:00
Aurélien Sibiril
424c75c8aa Strengthen Datadog connector tests
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
08539bffe7 Style
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
245ad1f434 Add Datadog access-review driver and name resolver
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
b4e6f73b78 Merge PostHog self-hosted into a single PostHog provider
Fold POSTHOG_SELF_HOSTED into POSTHOG: one provider now covers Cloud (OAuth + region-pinned API key) and self-hosted (API key + instance URL), since both already share the driver, name resolver, and PostHogConnectorSettings{BaseURL}. The API-key form picks a deployment (Cloud US/EU or self-hosted URL); the resolver requires exactly one of region/instanceUrl.

Drop the POSTHOG_SELF_HOSTED enum value, registration, migration, and logo mapping. Extract the deployment selector into a dedicated PostHogDeploymentField component. Point the driver tests at us.posthog.com instead of the legacy app.posthog.com host.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:00:07 +02:00
Aurélien Sibiril
e4f247a20d Share PostHog region mapping and tidy pagination
The us/eu host strings lived in the driver and were duplicated in the
connector-settings resolver. Expose drivers.PostHogRegionBaseURL as the
single source and call it from the resolver.

Also parse the base URL only inside the absolute-next branch of
resolveNextURL, where it is actually used for the host check.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:00:07 +02:00
Aurélien Sibiril
3888ff95cd Add PostHog Cloud OAuth and self-hosted support
PostHog Cloud authenticates via CIMD OAuth (public client, PKCE)
through the region-agnostic oauth.posthog.com gateway, with an API-key
fallback. PostHog Self-Hosted is a separate provider using an API key
and an instance URL.

The shared driver discovers the data region by probing us/eu for OAuth
connections, since the gateway does not serve the data API, and pins
pagination to the resolved host.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:00:07 +02:00
Émile Ré
e55569848e Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-02 11:59:21 +02:00
Aurélien Sibiril
e9c4e39ed9 Log instance name resolution failures as warning
Provider 4xx on an instance-name lookup is rarely actionable -- it
usually reflects a configuration state (deleted org, revoked token)
rather than a system error worth paging on. Downgrade Probo's own
log entry to Warn. The kit/worker entry for the same failure is
still Error pending the retry-limit work, which will mark sources
synced after N attempts and stop the duplicate log line.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-02 11:57:32 +02:00
Aurélien Sibiril
79bc5aa79e Guard github name resolver against empty organization
When a GitHub access-source connector has no organization configured
yet (user finished OAuth but abandoned the picker), the source-name
worker called https://api.github.com/orgs/ and got a 404 every 10
seconds, flooding logs. All other picker resolvers (sentry, gitlab,
bitbucket, heroku, asana, netlify, clickup, vercel) short-circuit to
("", nil) for empty settings -- this aligns github with them so the
worker falls into its existing "empty instance name -> mark synced
with generic name" branch instead of retrying forever.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-02 11:57:30 +02:00
Cursor Agent
e732f7e706 Always tolerate source fetch failures
Allow campaigns to continue when a source fetch fails by keeping
that failure on the source fetch record only.

The source fetch worker now logs the failure after persisting it and
returns success so campaign execution is not interrupted by source-level
fetch errors.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-30 11:17:25 -07:00
Cursor Agent
2a5ccbc122 Allow one source fetch failure
Treat a single source fetch failure as tolerated so campaigns can
continue fetching and transition normally.

The worker now records failed fetches and only propagates a process
error once the failed source count exceeds one. This keeps the first
failed source visible on the source fetch while preventing the
campaign-level run from being marked failed too early.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-30 11:17:23 -07:00
Bryan Frimin
36bd377d47 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 23:42:35 -07:00
Bryan Frimin
0e40938158 Fix metabase list user
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 20:12:24 -07:00
Bryan Frimin
1853e5af39 Fix posthig resolver name
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 19:42:05 -07:00
Bryan Frimin
1f9ce02f10 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 18:44:40 -07:00
Cursor Agent
88eb340aba Use VCR cassette for PostHog driver test
Replace the PostHog driver unit test's local HTTP server with the same\nrecorder-backed test style used by the other access-review drivers.\n\nAdd a committed PostHog cassette under testdata so replay mode works\nwithout network access while keeping fixture coverage for role, MFA,\nand timestamp mapping expectations.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 18:37:18 -07:00
Cursor Agent
4d1417b512 Add PostHog access review connector support
Introduce a PostHog access-review driver that lists organization\nmembers and maps role, MFA, and timestamp fields into account\nrecords.\n\nRegister PostHog as a builtin API-key connector provider and expose\nit through the connector provider enum so access-review source\ncreation can discover it.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 18:37:18 -07:00
Cursor Agent
b493534545 Use k7 Metabase cassette fixture
Align the Metabase driver VCR test with the existing driver fixture
pattern by using a k7 Metabase host in the cassette and default test
instance URL.

This keeps cassette replay deterministic and consistent with the
expected environment naming used by other driver tests.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 18:36:36 -07:00
Cursor Agent
0920785bdf Add Metabase access review source
Implement Metabase as a first-class access review connector backed by
GET /api/user, including account mapping and error handling in the
driver. Register the provider with API-key auth metadata and required
instance URL settings so connectors can be created and resolved
consistently.

Expose Metabase through the console GraphQL and UI flows by adding the
provider enum value, API-key extra setting field wiring, and source
label mapping. Add migration support for the connector_provider enum and
cover driver/provider behavior with focused tests.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-28 18:36:36 -07:00
Cursor Agent
5ca1e369c0 Adopt VCR cassette style for Grafana tests
Refactor the Grafana driver test to use the shared recorder and VCR
helpers used by other access-review drivers.

This aligns the test with the existing cassette workflow and adds a
committed cassette fixture for deterministic replay.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 18:35:02 -07:00
Cursor Agent
f5a632ffac Add Grafana access review connector support
Add Grafana as an access review connector-backed source.

This introduces a Grafana access-review driver, provider registration,
and connector settings for the Grafana base URL. It also wires the
new provider through GraphQL and access-review UI input mapping so
API-key connectors can be created from the product.

A connector_provider enum migration is included so Grafana can be
persisted in existing databases.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 18:35:01 -07:00
Aurélien Sibiril
427af88f3a Add Cursor access-review driver
Fetch team members from the Cursor Admin API (GET /teams/members)
and map them to access records. The endpoint is not paginated, so
a single request returns the whole team; removed members are
returned as inactive rather than dropped, and team owners are
flagged as admins.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-29 00:05:48 +02:00
Bryan Frimin
122bc02a23 Fix too much account on MS365 access review
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 14:56:54 -07:00
Aurélien Sibiril
50093c9d19 Add Anthropic access-review driver and resolver
The driver lists organization members through the Anthropic Admin API
(GET /v1/organizations/users) with cursor pagination, mapping the role
and the RFC 3339 added_at timestamp. The name resolver reads the
organization name from /v1/organizations/me; a non-2xx response (for
example a revoked key) yields no name rather than making the
source-name worker retry forever. Both send the required
anthropic-version header.

Add a VCR test helper that injects the key via x-api-key so the
cassette stays recordable, and strip x-api-key on save. The cassette
holds synthetic members covering the user, developer, and admin roles.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 22:33:29 +02:00
Aurélien Sibiril
e0c53e7a51 Name Heroku personal account without an API call
GET /teams/@personal 404s, which would loop the source-name worker the
same way a stale Sentry slug did. Short-circuit the personal-account
slug to a static name before any HTTP call.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 22:22:46 +02:00
Aurélien Sibiril
3b54a58d14 Offer Personal account in Heroku org picker
Heroku Teams are opt-in, so a solo account surfaces no options and the
picker used to dead-end at a free-text slug the user cannot fill. Always
append a synthetic Personal account entry so the picker offers personal
mode instead.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 22:22:46 +02:00
Aurélien Sibiril
05fc9508ec Review solo Heroku accounts via app collaborators
A personal Heroku account has no Team, so GET /teams returns an empty
list and the team-members path has nothing to fetch. Solo accounts
grant access per app, so enumerate the user's personal apps and collect
each app's owner plus its collaborators, deduped by Heroku user ID.

An empty teamID (or the reserved personal-account slug) now selects this
personal mode. Factor the Range/Next-Range pagination shared by the
members, apps, and collaborators endpoints into herokuListAll.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 22:22:46 +02:00
Bryan Frimin
bf4b3214f6 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 12:36:45 -07:00
Bryan Frimin
0161b21120 Best effort connector delete
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 12:30:36 -07:00
Bryan Frimin
d579879707 Add tailscale driver
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-28 12:30:34 -07:00
Aurélien Sibiril
93d8d20dbf Surface stale Sentry slug in ListAccounts error
ListAccounts on a connector whose stored slug is no longer accessible
to its OAuth token currently returns "cannot fetch sentry members:
unexpected status 404" -- opaque, and indistinguishable from a real
Sentry outage. The campaign source-fetch worker records that string
verbatim as the customer-visible LastError, with no hint that the
connector itself needs reconnection.

queryMembers now returns a sentinel errSentryOrgNotAccessible on 404,
and ListAccounts wraps it with the slug and a directive to reconnect.
errors.Is preserves the chain so future callers can branch on the
permanent-config-failure case without string matching.

No auto-recovery: the only safe slug is one the customer explicitly
chose. Picking a different visible org would silently rebind the
source to the wrong tenant.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 19:22:45 +02:00
Aurélien Sibiril
1844797b39 Stop source-name worker from looping on stale Sentry slug
The source-name worker re-claims any AccessSource whose name resolver
returns an error. kit/worker drains tasks in a tight inner loop per
tick, so a permanently-failing resolver hammers Sentry as fast as the
HTTP RTT allows -- in prod, ~5 errors/s for 12h+ on one stale slug.

A 404 from /api/0/organizations/{slug} means the stored slug is no
longer visible to the OAuth token (org renamed/deleted, membership
changed). Retrying cannot recover the name, so return ("", nil) like
the openai and intercom resolvers already do: the worker marks the
row synced, the flood stops, and the source keeps its generic name.

Other non-2xx (401/403/5xx) stay retryable so OAuth refresh and
transient outages still get another chance.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 19:22:45 +02:00
Aurélien Sibiril
036cd3306e Reuse ListSentryOrganizations in Sentry driver
SentryDriver.resolveOrgSlug duplicated the same /organizations/?member=true
call already implemented in ListSentryOrganizations, which is consumed by
the OAuth org picker. Delegating to the shared helper prevents the two
call sites from drifting (response shape, header set, pagination) and
keeps the driver focused on member listing.

Pure refactor: no behavior change. Add an httptest-backed smoke test
covering the empty-stored-slug path end-to-end through ListAccounts so
the auto-discovery flow stays exercised after the refactor.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-28 19:22:45 +02:00