Commit Graph

2570 Commits

Author SHA1 Message Date
Aurélien Sibiril
94552dbf11 Make Grafana/Metabase/Tailscale name errors terminal
The source-name worker keeps a generic name and marks the source
synced only when a resolver reports ErrTerminalNameResolution. The
Grafana and Metabase name resolvers, plus Tailscale via its shared
fetchUsers, returned a plain error on any non-2xx, so a revoked
credential (401/403) never reached the terminal path and the worker
re-claimed the row every drain cycle with no delay.

Route their non-2xx through nameStatusError so permanent 4xx wrap
ErrTerminalNameResolution while 5xx stay retryable. The sentinel is
inert on the ListAccounts sync path, which does not inspect it.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
aeb7a1c8e6 Default first workspace for picker access sources
Picker providers (GitHub, Sentry, GitLab, Bitbucket, Heroku, Asana,
Netlify, ClickUp, DocuSign) require the user to pick an org/workspace in
a follow-up step after connecting. When that step is skipped, the source
stays connected but unconfigured, and its first campaign silently
resolves no users because the driver needs an org — the same "connects
fine, campaign fetches nobody" symptom seen on Sentry.

When a connector is linked to a source, auto-select the first workspace
the connection can list, so the source is usable immediately. An org the
user already chose is never overridden, and the picker stays visible
(the frontend shows the selector whenever an org is selected) so they
can switch when several are listed.

Best-effort: a provider that is unreachable or lists nothing leaves the
source in its existing needs-configuration state rather than failing the
create/update mutation.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
c50d2657f4 Request Brex companies.readonly scope for name resolution
The Brex name resolver calls GET /v2/company to build the source
display name, but the connector only requested openid, offline_access,
and users.readonly -- and users.readonly covers /v2/users (the account
fetch), not /v2/company. So the company endpoint 403'd for every Brex
source, which the source-name worker retried forever (2.9M errors in 7
days) until the terminal-error handling stopped the loop.

Add companies.readonly so the endpoint resolves. Verified as the exact
scope string against Brex's OAuth authorize flow (the "Companies: Read
only" developer scope maps to companies.readonly). Existing Brex
connectors must reconnect to re-consent to the added scope; until they
do, /v2/company still 403s but is now handled cleanly (terminal ->
generic name, no loop) rather than silently retried.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
c837701099 Stop source-name worker looping on permanent failures
The access-review source-name worker never marked a source synced when
name resolution errored, so it re-claimed the source on every poll and
retried at vendor-latency cadence. Two permanently-failing sources
generated millions of error logs (Brex /v2/company 403 and Cloudflare
/accounts 400) and hammered vendor APIs (8.6M 403s to Brex in 30 days) --
a ban risk, all for best-effort display metadata.

Generalize the Google-403 special case: name resolvers now classify a
non-2xx response through nameStatusError, which wraps
ErrTerminalNameResolution for permanent client errors (400, 401, 403,
404) and returns a plain, retryable error for everything else (5xx,
network). The worker treats a terminal error as done -- it keeps the
generic name and marks the source synced -- while transient failures
keep retrying as before.

Also fix the Cloudflare name resolver's own bug: it requested
per_page=1, but Cloudflare's List Accounts endpoint requires per_page in
5..50 and 400s otherwise (the driver already uses 50). That 400 was the
sole cause of the Cloudflare retry storm; bump it to 50.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
60d373e5c4 Disable Clerk access-review connector
Clerk's Backend API secret key only exposes the customer's application
end-users (GET /v1/users) -- the people who sign into the customer's
product -- not the Clerk workspace/dashboard team who administer the
platform. Those admins have no Backend API and are managed only in the
Clerk Dashboard UI. So an access-review campaign against Clerk reviews
the wrong population entirely (verified in prod: one source paginating
16K+ end-users), which is a compliance defect, not just the pagination
timeouts it also caused.

Unregister Clerk from the builtin provider registry and drop its driver,
provider registration, tests, and cassette so it can no longer be added
or fetched. Keep the ConnectorProviderClerk enum value, its IsValid
membership, the GraphQL enum value, and the frontend logo so existing
stored CLERK connectors still validate, serialize, and display (users
can still see and remove them). Drop it from ConnectorProviders so the
registry stays in sync with the registered set.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
0da9e3c5c4 Read Vercel team from the teamId callback param
Vercel appends the customer's team to the OAuth completion callback as
teamId (camelCase), but the handler read query.Get("team_id"). The
lookup always missed, so every team install fell through to the
/v2/user personal-account fallback -- which returns 404 for a
team-scoped integration token -- leaving TeamID empty. The Vercel
driver then refused to build ("team_id is required") and every
access-review campaign targeting a Vercel team fetched zero accounts.

Personal-account installs were unaffected because they never send a
teamId and legitimately use the /v2/user path, which is why the
breakage only showed up for team installs.

Read the parameter under its real name so the team is captured and
persisted in the connector settings.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Aurélien Sibiril
9c0f95bab1 Restore trailing slash on Sentry API requests
Commit f5703d390 replaced the fmt.Sprintf URL construction with
url.JoinPath, which calls path.Join and therefore strips a trailing
slash unless the final element carries one. Sentry's API only routes
slashed paths and answers 404 without redirecting, so every
ListAccounts call failed on its first request and no access-review
campaign targeting Sentry could fetch a single account.

The failure was invisible for two reasons. queryMembers maps 404 to
errSentryOrgNotAccessible, so a routing bug surfaced to users as
"reconnect the connector with the correct organization" -- advice that
could never help, because the slug was never wrong. And commit
74ce2bc5d edited the recorded request URL in testdata/sentry.yaml to
match the new construction instead of re-recording the cassette, which
kept CI green; that cassette still carries Sentry's own Link header
with the trailing slash, contradicting its own request line.

Pass the slash on the final JoinPath element in both the members
endpoint and the organization name resolver, revert the cassette to the
URL Sentry actually served, and add a regression test that drives the
driver against a server which 404s unslashed paths, so the URL shape is
pinned independently of the cassette matcher.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-22 17:27:55 +02:00
Ludovic Vielle
cbd0387490 Expose ACME cooldown end time and error details
Operators could see that a rate-limit cooldown was active, but not
when it ends, and failure logs omitted most of the CA problem
document. Add a until-timestamp gauge and log the full acme.Error
surface so cooldowns and ACME responses are diagnosable.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-22 16:38:11 +02:00
Bryan Frimin
9f6a0c1d40 Fixes cubic review
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:57:56 +02:00
Bryan Frimin
3f202002d9 Fix parsing int error
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:20:53 +02:00
Bryan Frimin
81b7ee5fad Close cert provisioning correctness gaps
Several race and validity gaps could leave certificate provisioning
stuck, unusable, or noisy:

- Accept the HTTP-01 challenge only after the key authorization is
  committed, so the CA cannot hit the token before this instance can
  serve it and invalidate the order.
- Persist challenge metadata under a blocking write-back lock; a row
  merely locked by a competing transaction no longer silently drops the
  accepted order.
- Abandon a recovered VALID order and restart instead of issuing it
  with a freshly generated key that cannot match the existing cert.
- Exclude rate-limited rows from the ten-minute stale reset so the
  resumable order survives the ACME cooldown.
- Size the provisioning poll lease to exceed the max processing window
  so a released claim lock cannot let another worker process the same
  row concurrently.
- Parse Retry-After as unsigned seconds and clamp overflow so malformed
  values fall back to the default cooldown instead of disabling it.
- Normalize the acme_errors problem_type label to the RFC 8555 set to
  bound Prometheus cardinality.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:20:53 +02:00
Bryan Frimin
45c45ac5a0 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:20:52 +02:00
Bryan Frimin
121d4dcf93 Fix certmanager provisioning retry and metrics
Address several provisioning defects that either stalled the retry
budget or crashed the process:

- Classify CAA resolver/transport failures apart from a real CAA policy
  denial. Both shared the "caa records" wording, so a transient resolver
  error was persisted as customer misconfiguration and retried forever
  without consuming the retry budget. A new ErrCAANotPermitted sentinel
  now marks the genuine misconfiguration; other CAA errors are treated
  as ordinary transient failures.

- Honor an explicit Retry-After: 0 (or a past date) as permission for an
  immediate retry instead of promoting it to the one-hour default
  cooldown. acme.RateLimit collapses zero, invalid, and absent headers
  to a zero duration, so the header is now parsed directly to tell an
  explicit zero apart from a missing one.

- Reuse already-registered Prometheus collectors when a second
  ACMEService shares a registerer. The fixed-name collectors were
  MustRegistered, so a duplicate registration panicked the process.

- Persist provisioning failures on a context detached from the process
  tick deadline. A timed-out attempt reached persistFailure with an
  expired context, so the write-back failed and the retry budget never
  advanced, leaving the certificate indefinitely retriable.

- Use pgx.StrictNamedArgs in the certificate FOR UPDATE loaders to match
  the coredata SQL contract.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:20:52 +02:00
Bryan Frimin
9724a2ce50 Harden cert provisioning failure and write-back
Rate limits no longer inflate ssl_retry_count into an instant FAILED
path. Centralize outcomes in decideProvisioningOutcome, keep ACME
order state on transient and rate-limit errors, bound each Process
tick with a timeout, and block on FOR UPDATE when persisting a
freshly issued certificate.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-22 13:20:52 +02:00
Émile Ré
f1136cb748 Use entity name as CIMD client name
OAuth client_name should be the short brand
label, not the composed English hero heading.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 13:08:03 +02:00
Émile Ré
465242eb17 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 13:08:02 +02:00
Émile Ré
0d832508c9 Replace portal title with entity name
Store a short entity name instead of the full home
heading so orgs can brand portals for sub-entities.
Restore hero i18n composition and keep the English
document title composed from the entity name.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 13:08:02 +02:00
Émile Ré
0fe814cfe4 Fix deprecated use of proxyproto.ConnStrictWhiteListPolicy
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 13:07:52 +02:00
Émile Ré
9146a4ac1d Update gqlgen generated comments
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 12:53:45 +02:00
Émile Ré
52310dbf65 Read portal SEO origin from request context
SNI middleware already stores scheme://host; SEOFromRequest
no longer takes a base URL or re-strips the path. Expose
context helpers and drop the obsolete strip tests.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 12:19:25 +02:00
Émile Ré
9ee0df4d3f Document portal env and log SNI failures
Restore VITE_API_URL to :8080 in .env.example with notes
for local Vite (:5174 + proxy target). Log compliance
portal lookup errors in the SNI middleware with context.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 12:05:56 +02:00
Émile Ré
ee3136a0d2 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-22 10:13:49 +02:00
Émile Ré
6024a2e803 Omit SEO links for relative base URLs
portalOrigin fell back to the raw input when scheme or host
was missing, so paths like /fr/documents still produced
canonical and hreflang tags. Return an empty origin instead.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 19:08:55 +02:00
Émile Ré
59f053e8f9 Fix portal SEO, Korean copy, and favicon
Custom-domain base URLs included the request path, so
canonical/hreflang doubled routes; emit SEO links only with
an absolute origin. Rewrite Korean mismatch strings to avoid
literal particle parentheses, rename HtmlLang to HTMLLang, and
ship public favicons so Vite no longer 404s the fallback icon.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 18:50:34 +02:00
Émile Ré
e47091e5b5 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 18:43:50 +02:00
Émile Ré
418bb5a8f8 Address review feedback on portal i18n
Swallow locale mutation rejections after the toast, close the
mobile drawer on locale change, escape SEO paths, share the
IAM locale list with SEO, and finish dropping /trust leftovers.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 18:43:50 +02:00
Émile Ré
e4260d50d3 Drop leftover /trust path prefix handling
Portals are host-routed on slug subdomains, so SEO path
stripping and the frontend basename helper were dead code.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 18:43:49 +02:00
Émile Ré
c223873e96 Put locale in compliance portal URLs
Path-segment locales make each language crawlable with self
canonical and hreflang, while identity.locale persists an
explicit choice without client storage or cookie banners.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-21 18:43:48 +02:00
Bryan Frimin
63dffe801e Store full portal titles as home headings
The UI composed "Compliance at {{name}}." via i18n while the
DB only held the org name. Persist the full heading, backfill
existing rows, and drop the unused heroTitle locale keys.

Also use clientip.Extract for NDA signing events and default
the portal base domain to probopage.localhost for local runs.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:29 +02:00
Bryan Frimin
a3d6c120d7 Mint certificate GIDs with entity type 106
Certificate backfill migrations used entity type 104, which belongs to
commitment groups. Use 106 so migrated certificate IDs decode correctly.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:28 +02:00
Bryan Frimin
8402e0c31e Rename portal service locals to portal
Use portal and portalID in management portal_service helpers and align
error messages with the Compliance Portal product name.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:28 +02:00
Bryan Frimin
3f6795001a Finish compliance portal rename leftovers across portal, trust, and n8n.
Update mailing list queries to currentCompliancePortal, and include regenerated console resolvers plus sorted n8n/frontend imports from the rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:27 +02:00
Bryan Frimin
75075bcb15 Fix MCP commitment tools to use compliance portal management service.
Commitment group APIs moved out of pkg/probo, so the MCP resolvers were still referencing removed types and failing CI validation.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:27 +02:00
Bryan Frimin
263ea61aba Rename CLI trust-center commands
Replace the trust-center command tree with
compliance-portal so the CLI matches the
product and GraphQL rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:25 +02:00
Bryan Frimin
8773a54396 Rename GraphQL APIs to compliance portal
Update console, visitor, MCP, and Slack API
surfaces so schemas and resolvers use the
Compliance Portal naming consistently.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:24 +02:00
Bryan Frimin
0f0f6643ad Rename backend services for portal
Point domain services, bootstrap, and related
packages at Compliance Portal types so callers
stop depending on the old Trust Center names.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:24 +02:00
Bryan Frimin
4bf3d4df79 Rename trust center coredata types
Align entity types, order fields, and visibility
enums with the Compliance Portal product name so
the data layer matches the rest of the rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:23 +02:00
Bryan Frimin
69a7c5fca7 Port data-request feature after main rebase
Main's new compliance-portal data-request pages (rights request
submission and listing) still referenced the sign-in dialog and
package names the earlier rebase's OAuth /initiate migration and
compliancepage -> complianceportal rename had already replaced.
Gate the "New Request" button behind OAuth like the rest of the
portal and point resolvers at the flattened visitor.Service methods.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:23 +02:00
Bryan Frimin
4b4e7ea1f8 Fix OAuth code exchange and go lint
Authorization-code exchange used ExternalClientID, which
is empty for registered clients and broke e2e token
grants. Pass the authenticated client's GID instead.

Also satisfy wsl_v5 blank-line rules, wire the delete
reference dialog through its mutation hook, and name the
filemanager logger.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:23 +02:00
Bryan Frimin
43ce3a7c53 Harden compliance portal auth and TLS
Align console references and OAuth branding with the
compliance-page model, and fix certificate cache eviction,
portal OAuth handlers, and magic-link edge cases left after
the trust-center rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:22 +02:00
Bryan Frimin
b03acbd029 Fix step-ca root CA access on Linux CI
Host chmod cannot open 0700 dirs owned by UID 1000, so
`-f` never sees root_ca.crt. Chmod from inside the
container as root while waiting for the cert.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:22 +02:00
Bryan Frimin
caeb6d9225 Resolve custom domain SSL via certificate
Replace flattened SSL fields with a certificate
relation loaded through certmanager, and resolve
domain slots from IDs already on the trust center
instead of reloading the compliance page.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:21 +02:00
Bryan Frimin
bc128ec516 Port trust center features after main rebase
Move Emile's commitment CRUD into complianceportal management,
wire console and visitor GraphQL, and drop portal magic-link
sign-in in favor of OAuth /initiate while keeping documents,
NDA/full-name gates, and access-request resume markers.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:21 +02:00
Bryan Frimin
b221b17d1e Remove unused visitor portal helpers
Brand and public file serving replaced the old org/portal
logo and report URL generators, leaving dead methods and
unused Service fields behind.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:20 +02:00
Bryan Frimin
7e0d187dcf Flatten compliance portal package layout
Remove the root complianceportal package and the resolver
facade that existed only to break an IAM import cycle. Admin
policies, domain URL helpers, and actions live under
management; visitor OAuth metadata, brand URLs, and public
read paths live under visitor. Drop the duplicate trust API
magic-link mutations now that Connect handles portal auth, and
stop IAM from owning compliance page email branding.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:20 +02:00
Bryan Frimin
4cec74c1a1 Limit TLS cache warming to live domains
After the certificates split, WarmCache loaded every ACTIVE
certificate. Org deletes cascade-remove custom_domains but leave
certificates behind, so orphans could regain a usable SNI cache
entry on rebuild. Warm and serve only certs still referenced by a
domain, and purge unreferenced cache rows.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:20 +02:00
Bryan Frimin
e7df6f6b2a Restrict OAuth client branding URLs to http(s)
CIMD and registration accepted any URI scheme for client_uri and
logo_uri, so allowlisted metadata could surface javascript: links on
sign-in. Validate absolute http/https at ingest and only expose those
schemes in branding.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:19 +02:00
Bryan Frimin
6da00604ed Address remaining compliance portal review nits
Fill in certificate renewal processing, preserve OAuth and JWKS
edge cases, embed the compliance-portal app in production builds,
and close the smaller portal routing and n8n update gaps.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:19 +02:00
Bryan Frimin
bc78f08334 Harden compliance portal data migrations
Mint certificate and domain GIDs with generate_gid, preserve
org profile and domain rows that lack a trust center, and skip
managed default-domain backfill when no base domain is set.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:19 +02:00
Bryan Frimin
802a36bbf4 Rename visitor import and management wiring
Drop the misleading trust alias for complianceportal/visitor
and rename the CustomDomain config field to Management so
service wiring matches the package it points at.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:18 +02:00