Commit Graph

4540 Commits

Author SHA1 Message Date
Ludovic Vielle
0e4d73bb0f Migrate audit reports to the files table
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-05 16:16:50 +02:00
Ludovic Vielle
b0a0f0efc9 Add report_file_id to audits and trust_center_document_accesses
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-05 16:16:49 +02:00
Sacha Al Himdani
6f2e5737d4 Drop dead users_organizations table and unused columns
Remove schema objects that are no longer referenced by any query code,
GraphQL resolver, or frontend and only survive in old migration files:

- users_organizations: superseded by iam_memberships (data was migrated in
  20251006T220024Z).
- organizations.logo_object_key: pre-files-table logo storage; replaced by
  logo_file_id / horizontal_logo_file_id (data migrated in 20251009T140000Z).
- trust_center_accesses.has_accepted_non_disclosure_agreement,
  has_accepted_non_disclosure_agreement_metadata, nda_file_id and
  last_token_expires_at: the NDA-acceptance flow now uses
  electronic_signature_id together with the state column.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-05 16:16:16 +02:00
Bryan Frimin
44da0e251a Add new supported language
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 15:44:05 +02:00
Sacha Al Himdani
21abcc619e Override uuid to ^14.0.0 to resolve @n8n/node-cli conflicts
@n8n/node-cli pulls LangChain and n8n-workflow packages that require
incompatible uuid versions (^10, ^11, ^13). Force uuid 14.0.0 via npm
overrides and refresh the lockfile so audit can apply the security fix.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-05 15:06:05 +02:00
Bryan Frimin
1d36123ead Add dedicated expired magic link page
Instead of a toast, expired magic link tokens now return a TOKEN_EXPIRED
GraphQL error code and redirect users to a dedicated /magic-link-expired
page with a clear CTA to request a new link.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 14:33:11 +02:00
Bryan Frimin
11d6f8606c Make magic link expiry warning more visible
Users were missing the expiry notice and hitting link errors. Replace the
small gray footer text with a prominent yellow callout box in the HTML
email, and prefix the plain-text version with IMPORTANT.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 14:33:10 +02:00
Bryan Frimin
1998052a0d Fall back to os.Hostname() when SMTP_HELLO_NAME is not set
Using localhost as the EHLO identity is the Go net/smtp default but is
rejected by strict relays such as Google Workspace. os.Hostname() is
the conventional SMTP client fallback and returns the actual machine or
pod name in production environments.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 14:28:44 +02:00
Bryan Frimin
552246b5d3 Add SMTP_HELLO_NAME to let operators set the EHLO/HELO identity
Google Workspace SMTP relay (smtp-relay.gmail.com) rejects generic
EHLO identifiers such as "localhost", which is Go net/smtp's default.
Operators can now set SMTP_HELLO_NAME to a valid hostname so that
c.Hello() is called before StartTLS, satisfying strict relay policies.

Fixes https://github.com/getprobo/probo/issues/1284

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 14:28:41 +02:00
Sacha Al Himdani
8231aecaba Clarify trust center access rejection emails
Rejecting one audit report via Slack could look like a blanket denial
when HIPAA and SOC 2 reports shared a filename. Use framework and
audit name in rejection emails.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-05 14:13:48 +02:00
Aurélien Sibiril
8253796743 Use Field onValueChange in the Zendesk dialog
Field exposes onValueChange(value) and wires the input's onChange itself,
so the explicit event-typed onChange handler was redundant.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:58:12 +02:00
Aurélien Sibiril
8ca724fdb6 Document why OAuth uses two token-URL closures
BuildTokenURLForDomain and BuildTokenURLForSite cannot be merged: a
provider's signed-state Site (e.g. Datadog's region key) is not always the
string its token host needs (the API domain). Spell that out at the switch
so the split is not mistaken for accidental duplication.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:58:12 +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
bc53f603eb Normalize pasted Zendesk subdomain URLs
Pasting a full Zendesk URL with a path or query (e.g.
acme.zendesk.com/agent?x=1) left the trailing segments in place because
the .zendesk.com suffix no longer matched at the end, producing an
invalid site value rejected by the backend. Drop everything from the
first path/query/fragment separator before stripping the suffix.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:58:11 +02:00
Aurélien Sibiril
bb6a961b12 Reject setting both OAuth token-URL closures
A Registration that sets both BuildTokenURLForDomain and
BuildTokenURLForSite would pass startup but silently use only the first
at the OAuth callback (CompleteWithState checks them in order). Reject
the combination in Register so the misconfiguration fails loud at
process start, matching the existing APIKeyBasicAuth/APIKeyHeader guard.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:58:11 +02:00
Aurélien Sibiril
09cfbce6e5 Add Zendesk connector logo and connect dialog
Add the Zendesk brand mark under ThirdParties and wire it into the
logo map. Because the customer subdomain is free-form (not a fixed
region list), the connect flow opens a dialog with a subdomain text
input that is sanitized and passed as ?site= to the OAuth initiate
endpoint, mirroring the Datadog multi-site dialog.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:55:48 +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
1c40121591 Add BuildTokenURLForSite for subdomain OAuth flows
Subdomain-based OAuth providers (e.g. Zendesk) authenticate against a
per-customer host, <subdomain>.zendesk.com, for both the authorize and
the token endpoint. The subdomain is known at initiate but, unlike
Datadog's region, the provider does not echo it back on the callback,
so it must survive the round-trip another way.

Carry the chosen site on the HMAC-signed OAuth state (OAuth2State.Site)
and add a BuildTokenURLForSite closure, symmetric with the existing
BuildAuthURLForSite, that builds the token URL from it at callback time.
The state is signed, so a tampered site is rejected before use; the
closure still re-validates the format. Datadog's domain-echo path is
unchanged and a provider sets at most one of the two closures.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 20:51:57 +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
06f3f8aecf Add Okta logo and API-key dialog mapping
Add the Okta brand mark to the ThirdParties atoms (export, logo map,
and ThirdPartyLogo wiring) and map the generic "domain" extra setting
to the oktaDomain field in the add-access-source dialog so the
API-key connect flow submits the org domain.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:26 +02:00
Aurélien Sibiril
03827704ec Register Okta connector and wire API-key settings
Register the Okta provider (SupportsAPIKey, APIKeyAuthScheme SSWS, a
required "domain" extra setting, and the driver/name-resolver
factories) and add it to the builtin registry.

The create-API-key resolver normalizes and validates oktaDomain into
OktaConnectorSettings, returning a static INVALID error that never
echoes operator input, and stamps the SSWS scheme onto the
connection. No picker, OAuth metadata, or probe URL: the token plus
domain identify exactly one org and the host is per-connection.

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
511472aca3 Add SSWS API-key Authorization scheme
Okta API tokens authenticate as "Authorization: SSWS <token>", a
scheme none of the existing API-key modes (Bearer, custom header,
Basic) can express. Add Registration.APIKeyAuthScheme, plumb it onto
APIKeyConnection.Scheme, and send it via a new schemeAuthTransport.

The three API-key presentations (BasicAuth, Header, Scheme) are
mutually exclusive; Register rejects setting more than one so a
misconfiguration fails at process start rather than silently.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-04 19:23:26 +02:00
Aurélien Sibiril
5dd8769d19 Add OKTA connector provider enum and settings
Introduce the OKTA value to the connector_provider enum (Go const,
ConnectorProviders slice, IsValid switch, GraphQL @goEnum, and the
Postgres ALTER TYPE migration) and an OktaConnectorSettings struct
holding the customer's Okta org domain.

Okta is a per-tenant IdP with no central API gateway, so the
connector is keyed on the org domain rather than a shared host. The
oktaDomain field on CreateAPIKeyConnectorInput lets the API-key flow
carry it.

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
Bryan Frimin
fca7f2a1e6 Fix login redirect password only
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-04 14:37:37 +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
f764d42431 Use official Datadog logo mark
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:44:43 +02:00
Aurélien Sibiril
dd0fb9a108 Validate RawSettings JSON on connector reconnect
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:41:17 +02:00
Aurélien Sibiril
fedebd66ad Persist Datadog domain settings on reconnect
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +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
01933928a8 Validate Datadog API domain in driver factory
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
929bf81161 Add Datadog region selector to connect dialog
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
e7b6990ea6 Add Datadog third-party logo
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
3aafdad7f5 Wire Datadog connector bootstrap config
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
950b87f426 Capture Datadog API domain on OAuth callback
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
1df8c8d2f2 Register Datadog connector provider
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
f10d29a76c Add Datadog connector enum value, migration, and settings
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:16:26 +02:00
Aurélien Sibiril
082772465d Add per-site authorize URL and per-domain token URL OAuth2 plumbing
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:14:44 +02:00
Aurélien Sibiril
5effc4bbf9 Add Datadog site table and OAuth URL helpers
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-03 17:14:44 +02:00