The Vercel connector reads the team from the camelCase teamId callback
parameter, unlike the snake_case params most providers use. Nothing
guarded the name, so a regression back to team_id would silently drop
the team on every Vercel connect and leave the source resolving no
users.
Extract the read into vercelCallbackTeamID and pin the exact parameter
name with a test, since the surrounding handler needs a live connector
registry and database to exercise directly.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Org-defaulting for picker providers only ran in the GraphQL resolver,
so a picker-provider source created or updated through the MCP API
connected fine but resolved no users until the org was picked. Move
the defaulting into the accessreview service as
AutoSelectDefaultOrganization and call it from both surfaces, moving
the providerOrgConfigs picker dispatch alongside it (the three console
picker resolvers now dispatch through service accessors, behavior
unchanged).
Also harden the moved path: resolve the provider from cheap connector
metadata before building the authenticated HTTP client, so the ~50
non-picker providers no longer pay a decrypt/refresh/DB-write on every
create/update; bound the outbound ListOrgs call with a 10s timeout so
a hung provider cannot stall the mutation; and re-check inside the
ConfigureAccessReviewSource tx (OnlyIfUnset) so an org the user picks
while ListOrgs is in flight is not overwritten by the first listed
org.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Once the source-name worker sets name_synced_at, nothing cleared it,
so a source that hit a terminal failure (Brex 403, wrong Sentry org
slug) kept its generic name forever — even after the user reconnected
with the right scope or picked the correct org, contradicting the
Brex guidance to reconnect.
Clear name_synced_at whenever the connection changes: on UpdateSource
when a connector is (re)set, inside ConfigureAccessReviewSource when
the org is (re)selected, and on the OAuth reconnect path via the new
ResetSourceNameSyncForConnector service method. The worker then
re-claims the row and re-resolves the display name.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Drop the portal-login redirect, accept continue URLs for magic links,
brand emails from the OAuth client when present, and update e2e coverage.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Move range, ETag, and cache handling out of the files API handler
so brand logos and other public assets can reuse the same code path.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The compliance portal OAuth callback accepted ID tokens after only
parsing claims, without checking the signature, issuer, audience, or
expiry. Add RS256 verification helpers to the JOSE package, enforce
those checks in ParseIDTokenIdentity, and thread JWKS, issuer, and
client ID through the token response and callback handler.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Compliance portal OAuth must call the configured Probo base URL
during connect flows; in local dev that endpoint is loopback and
was blocked by default SSRF protection on the initiate HTTP client.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Mount the compliance portal mux on trust center hosts, drop the
legacy trust web server, and share response header helpers.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Relocate the public trust center GraphQL surface, OAuth handlers,
and SPA serving into the compliance portal API package and remove
the legacy trust v1 server tree.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Drop the one-time session handoff flow now that trust center
visitors authenticate through OAuth against the compliance portal.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Store website, email, and headquarters on the trust center so
public and admin surfaces read branding from one place. Drop the
trust API organization type and wire console, MCP, CLI, and apps
through the updated schema.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Rewire the console and visitor resolvers onto the management and visitor
services with compliance-portal authorization. Rename the GraphQL and MCP
ComplianceExternalURL type to ComplianceCustomLink, expose trust center
profile fields, default and custom domains, public URL, and the managed
flag, and drop the profile fields from the organization surface.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Rename the compliancepage HTTP package to complianceportal and drop the
slug-based id middleware. Compliance pages are now served exclusively
over SNI-based custom-domain HTTPS, with the SNI middleware resolving the
page by TLS server name and redirecting secondary domains to the
canonical host.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Wire the certificate manager and trust center base domain into IAM so
organization creation provisions a managed default domain and certificate
atomically. Email presenters in IAM and mailman resolve public URLs
through the compliance portal resolver and read profile fields from the
trust center. probod initializes the certmanager service and injects the
new management and visitor services.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Emit lifecycle events for rights requests created through either the
console or compliance portal. Keep webhook subscription choices in sync
across every API and client surface.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Sacha Al Himdani <SachaProbo@users.noreply.github.com>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Sync GraphQL commitment group and item operations
to the remaining API surfaces so automation can
manage compliance portal commitments end to end.
Signed-off-by: Émile Ré <emile@probo.com>
Embed and build @probo/compliance-portal for the
/trust path and custom-domain SPA so production
ships the v2 portal. Keep apps/trust in the repo
for local use on port 5175; portal takes 5174.
Signed-off-by: Émile Ré <emile@probo.com>
Address PR review: avoid reintroducing cleared URL markers,
treat already-closed sessions as successful logout, and stop
stale subscribe/sign-out completions from racing the UI.
Signed-off-by: Émile Ré <emile@probo.com>
Visitors can subscribe after sign-in via the Updates
CTA or user menu. Also add trust signOut so Log out
works from the menu.
Signed-off-by: Émile Ré <emile@probo.com>
Require a verified viewer email before creating a rights request and
validate the free-text fields with the same SafeText bounds the console
uses, so this public portal mutation stays safe and bounded.
Move myRightsRequests onto the base Query, drop the now-dead count
loaders, and order the RECTIFICATION enum value before PORTABILITY so
the Postgres sort order matches RightsRequestTypes().
Harden the v2 kit primitives: SegmentedControl keeps equal-width cards
(auto-fill), preserves its selection when the active card is toggled,
and forwards an accessible name; Field associates its label and error
by id/aria instead of wrapping the control in a label. Give the type
group an accessible name, require the name field for non-complaint
types, use a timezone-stable reference year, drop the underreporting
header count, and neutralize the response-deadline copy.
Signed-off-by: Émile Ré <emile@probo.com>