Add four API-key access-review connectors

Add Pylon, OpenRouter, incident.io and Brevo as access-review connectors.
All are API-key, single-tenant providers (Pattern 3): the key identifies
one tenant, so there is no OAuth flow, picker UI, or bootstrap/helm
configuration.

- Pylon: Bearer token, GET /users; resolves each user's opaque role_id to
  a role name via GET /user-roles, with cursor pagination.
- OpenRouter: Bearer management key, GET /api/v1/organization/members. The
  endpoint requires an organization account -- a personal key authenticates
  but returns 404 -- so the connection probe rejects 404 on top of 401/403
  (doProbeRequest gained an opt-in extra-reject set) to surface a non-org
  key at connect time instead of mid-campaign.
- incident.io: Bearer token, GET /v2/users. Its OAuth is outbound-only, so
  the API key is the inbound path; live base_role/custom_roles take
  precedence over the deprecated role enum.
- Brevo: API key in the api-key header (Registration.APIKeyHeader), GET
  /v3/organization/invited/users. A live recording corrected the documented
  schema: is_owner is a JSON boolean (not a string) and an id field is
  present, so it is used as the stable ExternalID.

The OpenRouter and Brevo cassettes are anonymized live recordings; Pylon
and incident.io use hand-authored fixtures (no self-serve test tenant). The
shared three-valued active-status mapping is consolidated into
activeFromStatus in driver.go.

Each adds the enum value, migration, GraphQL binding, provider
Registration, a driver with a cassette-driven test, and a brand logo.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-06-24 14:29:54 +02:00
parent f5f9842df5
commit a0d3806c21
34 changed files with 1922 additions and 15 deletions

View File

@@ -0,0 +1,12 @@
import type { ComponentProps } from "react";
export function Brevo(props: ComponentProps<"svg">) {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fill="#0B996E"
d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zM7.2 4.8h5.747c2.34 0 3.895 1.406 3.895 3.516 0 1.022-.348 1.862-1.09 2.588C17.189 11.812 18 13.22 18 14.785c0 2.86-2.64 5.016-6.164 5.016H7.199v-15zm2.085 1.952v5.537h.07c.233-.432.858-.796 2.249-1.226 2.039-.659 3.037-1.52 3.037-2.655 0-.998-.766-1.656-1.924-1.656H9.285zm4.87 5.266c-.766.385-1.67.748-2.76 1.11-1.229.387-2.11 1.386-2.11 2.407v2.315h2.365c2.387 0 4.149-1.34 4.149-3.155 0-1.067-.625-2.087-1.645-2.677z"
/>
</svg>
);
}

View File

@@ -0,0 +1,17 @@
import type { ComponentProps } from "react";
export function IncidentIO(props: ComponentProps<"svg">) {
return (
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" {...props}>
<rect
x="42"
y="19.5"
width="9"
height="36"
transform="rotate(90 42 19.5)"
fill="#F25533"
/>
<rect x="19.5" y="6" width="9" height="36" fill="#F25533" />
</svg>
);
}

View File

@@ -0,0 +1,12 @@
import type { ComponentProps } from "react";
export function OpenRouter(props: ComponentProps<"svg">) {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fill="#94A3B8"
d="M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"
/>
</svg>
);
}

View File

@@ -0,0 +1,23 @@
import type { ComponentProps } from "react";
export function Pylon(props: ComponentProps<"svg">) {
return (
<svg
viewBox="0 0 614 614"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<circle cx="306" cy="306" r="277" fill="none" stroke="#5B0EFF" strokeWidth="60" />
<clipPath id="pylon-logo-clip">
<circle cx="306" cy="306" r="247" />
</clipPath>
<g clipPath="url(#pylon-logo-clip)" fill="#5B0EFF">
<rect x="279" y="0" width="56" height="614" />
<rect x="0" y="155" width="335" height="60" />
<rect x="0" y="277" width="335" height="60" />
<rect x="0" y="399" width="335" height="60" />
</g>
</svg>
);
}

View File

@@ -19,6 +19,7 @@ import { Apollo } from "./Apollo";
import { Asana } from "./Asana";
import { BetterStack } from "./BetterStack";
import { Bitbucket } from "./Bitbucket";
import { Brevo } from "./Brevo";
import { Brex } from "./Brex";
import { Clerk } from "./Clerk";
import { ClickHouse } from "./ClickHouse";
@@ -35,6 +36,7 @@ import { Google } from "./Google";
import { Grafana } from "./Grafana";
import { Heroku } from "./Heroku";
import { HubSpot } from "./HubSpot";
import { IncidentIO } from "./IncidentIO";
import { Intercom } from "./Intercom";
import { Langfuse } from "./Langfuse";
import { Linear } from "./Linear";
@@ -48,8 +50,10 @@ import { Notion } from "./Notion";
import { Okta } from "./Okta";
import { OnePassword } from "./OnePassword";
import { OpenAI } from "./OpenAI";
import { OpenRouter } from "./OpenRouter";
import { PagerDuty } from "./PagerDuty";
import { PostHog } from "./PostHog";
import { Pylon } from "./Pylon";
import { Qovery } from "./Qovery";
import { Render } from "./Render";
import { Resend } from "./Resend";
@@ -69,6 +73,7 @@ const thirdParties: Record<string, FC<ComponentProps<"svg">>> = {
ASANA: Asana,
BETTER_STACK: BetterStack,
BITBUCKET: Bitbucket,
BREVO: Brevo,
BREX: Brex,
CLERK: Clerk,
CLICKHOUSE: ClickHouse,
@@ -86,6 +91,7 @@ const thirdParties: Record<string, FC<ComponentProps<"svg">>> = {
GRAFANA: Grafana,
HEROKU: Heroku,
HUBSPOT: HubSpot,
INCIDENT_IO: IncidentIO,
INTERCOM: Intercom,
LANGFUSE: Langfuse,
LINEAR: Linear,
@@ -101,8 +107,10 @@ const thirdParties: Record<string, FC<ComponentProps<"svg">>> = {
ONE_PASSWORD: OnePassword,
ONEPASSWORD: OnePassword,
OPENAI: OpenAI,
OPENROUTER: OpenRouter,
PAGERDUTY: PagerDuty,
POSTHOG: PostHog,
PYLON: Pylon,
QOVERY: Qovery,
RENDER: Render,
RESEND: Resend,

View File

@@ -3,6 +3,7 @@ export { Apollo } from "./Apollo";
export { Asana } from "./Asana";
export { BetterStack } from "./BetterStack";
export { Bitbucket } from "./Bitbucket";
export { Brevo } from "./Brevo";
export { Brex } from "./Brex";
export { Clerk } from "./Clerk";
export { ClickHouse } from "./ClickHouse";
@@ -19,6 +20,7 @@ export { Google } from "./Google";
export { Grafana } from "./Grafana";
export { Heroku } from "./Heroku";
export { HubSpot } from "./HubSpot";
export { IncidentIO } from "./IncidentIO";
export { Intercom } from "./Intercom";
export { Langfuse } from "./Langfuse";
export { Linear } from "./Linear";
@@ -32,8 +34,10 @@ export { Notion } from "./Notion";
export { Okta } from "./Okta";
export { OnePassword } from "./OnePassword";
export { OpenAI } from "./OpenAI";
export { OpenRouter } from "./OpenRouter";
export { PagerDuty } from "./PagerDuty";
export { PostHog } from "./PostHog";
export { Pylon } from "./Pylon";
export { Qovery } from "./Qovery";
export { Render } from "./Render";
export { Resend } from "./Resend";