Long role strings in the access review table broke row layout when
drivers joined many roles into one comma-separated value. Expose
roles as a string array in GraphQL by splitting the stored role at
the API layer, and render the first three roles as badges with a
"+X more" popover for the rest.
Closes ENG-459.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Employee document signatures recorded an acknowledgment with no
cryptographic proof, unlike document approvals which already create
and accept an electronic signature on every decision.
Mirror the approval flow on the sign path: generate the signed
document PDF, create-and-accept an esign record, and persist its id
on the document_version_signatures row through a new
electronic_signature_id column. Capture the signer IP and user agent
in the resolver, and re-check the published/archived preconditions
inside the transaction so the seal cannot race document state.
Make the consent wording a single backend source of truth shared by
the text that is sealed and the text shown in the UI. Define
DocumentSignatureConsentText and DocumentApprovalConsentText in the
probo service package and the NDA copy in the trust service, each
owned by the flow that uses it, and stop esign from appending the
generic clause to caller-provided consent text so approvals no
longer seal a duplicated sentence.
Expose the resolved consent text through GraphQL on
EmployeeDocumentVersion and DocumentVersionApprovalDecision, and have
the signing, approval, and NDA pages render it from the API instead
of hard-coded strings, mirroring how the NDA page already worked.
Align the wording with the actual interaction: the buttons read
"Review and sign" and "Review and approve", the clauses reference
those actions, and the inaccurate "typing my full name" phrasing is
removed everywhere.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Expose campaign sources as first-class nodes, paginate fetch attempts
instead of denormalized status fields, and bind entries to their
campaign snapshot. Update GraphQL, MCP, CLI, console, and e2e coverage
to match.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Bad API keys and expired OAuth tokens showed Connected because
probes ran only for OAuth2 and many providers had no ProbeURL.
Add a registry ProbeConnection dispatcher with static, dynamic,
and custom probes so all 41 providers are checked on demand.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Decouple each campaign from the live access-review sources it was started
with by introducing a per-campaign source snapshot table
(access_review_campaign_sources). The snapshot captures the source name,
category, and connector at start time, so a review remains coherent even
after the underlying source is edited or deleted. Fetch tracking becomes
an append-only log (access_review_campaign_source_fetch_attempts) that
preserves every attempt with its own status and error rather than
overwriting a single row.
Rename the shared access-review tables and enums to use a consistent
access_review_ prefix throughout:
access_entries → access_review_entries
access_sources → access_review_sources
access_source_category → access_review_source_category
access_entry_* → access_review_entry_*
The same rename propagates to every coredata type, service, GraphQL
schema, MCP specification, CLI command, frontend component, and e2e test.
The accessreview package gains dedicated actions.go and policies.go files
for its own IAM policy set, mirroring the agentrun package pattern.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Trust GraphQL and MCP still exposed presigned URL strings for
trust-center logos while console and connect already serve stable
File.downloadUrl paths. Phase 1 migrates the seven public logo
fields on trust GraphQL and the trust-center file references on MCP
to the shared File type; trust GraphQL NDA stays on fileUrl for a
follow-up.
Trust resolvers load public files through filemanager and map them
with types.NewFile. The trust app Relay queries and components now
read logo.downloadUrl. MCP specification, resolvers, and helpers
are updated in sync, including NDA on MCP where callers already
have file access.
filemanager is split into focused files and its URL surface is
narrowed to GenerateFileURL(file) for stable app URLs and
GeneratePresignedURL for S3 redirects. GetPublicFile remains the
DB entry point when only a file ID is known.
Add trust and MCP e2e coverage for public logo download URLs.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Replace Organization.logoUrl and horizontalLogoUrl with nested File
objects whose downloadUrl points at /api/files/v1/public/{id}, matching
the Console migration.
Org logos are FileVisibilityPublic and served without HTTP auth, so
Connect File.downloadUrl is built eagerly in NewFile with no field-level
authorize. Logo loading moves to iam.OrganizationService.LogoFile and
HorizontalLogoFile; the old URL generators are removed.
Sync IAM Relay components and n8n organization operations. Add an e2e
test for Connect multipart logo upload and ExecuteConnectWithFile.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Replace the bare `getprobo.com` domain and the `www.getprobo.com`
marketing host with `probo.com` / `www.probo.com` across the codebase.
Functional subdomains (app, console, notification, custom, test,
cookie-banner, compliance) keep their existing `getprobo.com` hosts,
and changelog entries are left untouched.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Stack the tracker type badge above the pattern name and drop the
separate Type column so the now-wider row reads more compactly; the
edit row spans the reduced column count. Tint script-sourced rows light
blue (excluded styling still wins when both apply).
Make the move-to-category select stand out when the tracker sits in a
real category by switching it to the highlighted pill variant, and align
its ghost-variant padding so the label does not shift horizontally
between categorised and uncategorised rows.
Signed-off-by: Émile Ré <emile@probo.com>
Moving a tracker to a category used to auto-create an org third party in
the mapping worker, so the move was gated behind a confirmation that
warned about it. The worker now only links to an existing vendor and
never creates one, making the prompt inaccurate.
Remove the confirm dialog and move directly on selection, folding the
same-category no-op guard into handleMove.
Signed-off-by: Émile Ré <emile@probo.com>
A tracker pattern resolved only to a catalog vendor now shows an "Import
to third parties" action in its row menu. It calls
importThirdPartyFromCommon for the pattern's common third party and, on
success, links the imported org vendor onto the row so it stops showing
the "Common catalog" badge.
Sibling patterns of the same vendor are backfilled server-side and pick
up the link on the next fetch of the list, so the store update only
needs to reflect the clicked row.
Signed-off-by: Émile Ré <emile@probo.com>
The publish flow ignored a document's stored default approvers and only
requested approval when approver_ids were passed in the call, so a major
publish with no approver_ids silently published directly without routing
through the approval flow — there was no way to tell "caller forgot
approvers" (null) from "caller wants no approval" (empty).
Make approver_ids an explicit choice, enforced once in the service so it
covers every caller (console, MCP, n8n):
- major publish: approver_ids must be set; an empty list publishes
directly, a non-empty list requests approval.
- minor publish: approver_ids must be omitted (approvers are ignored).
Validate this in PublishDocumentRequest.Validate(), update the console
publish dialog and the n8n publish node to honour the contract, document
it in the MCP tool spec, and cover it with e2e tests.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Replace presigned URL string fields (logoUrl, fileUrl, ndaFileName,
etc.) with nested File references resolved through /api/files/v1/.
Update console Relay queries and e2e coverage accordingly.
Route NDA upload through filemanager.PutFile and return stable IAM
org logo URLs for consistency with the files API.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Track whether an account is active (enabled) or disabled at the
source system. The field is nullable so existing entries without
this data remain valid.
- DB migration adds active BOOLEAN column to access_entries
- Coredata read/write/upsert/filter wiring for the new column
- Review engine propagates Active from source accounts
- GraphQL schema exposes active on AccessEntry and AccessEntryFilter
- MCP spec, types, and resolvers expose active and fix missing
account_type filter that was wired in GraphQL but not MCP
- CLI list command adds --active filter flag and ACTIVE output column
- Console campaign detail table shows Active/Disabled status badge
- E2e and unit tests updated to cover the new field
Signed-off-by: Bryan Frimin <bryan@probo.com>
When IP geolocation does not resolve a country, or resolves one with no
known cookie-consent regulation (common on localhost and unmapped
regions), the banner previously fell back to OPT_OUT with no recorded
regulation. Apply GDPR (OPT_IN) as the safe default in that case so the
strictest consent model wins when origin is unknown.
To keep consent records auditable, stamp each one with a regulation
source of DETECTED (resolved from geolocation) or DEFAULT (fell back to
GDPR). The shared cookiebanner.ResolveRegulation helper centralizes the
decision for both the config and consent endpoints, and the new value is
exposed through GraphQL, MCP, the CLI, the n8n node, and the console
consent-records views.
Signed-off-by: Émile Ré <emile@probo.com>
The "Detected Count" label was ambiguous about whether it counted
distinct trackers or report occurrences. The report endpoint upserts
each detected tracker by identifier, so the value is the number of
distinct trackers matched by the pattern. Rename the label to
"Distinct Trackers Detected" to reflect this.
Signed-off-by: Émile Ré <emile@probo.com>
Replace the many-to-many junction table with a direct
parent_third_party_id foreign key on third_parties. Each
sub-third-party now belongs to exactly one parent, making
duplicates across parents independent entities.
Replace the firstLevel boolean with an integer level field
(1 = direct, 2+ = parent level + 1) to support arbitrary
nesting depth.
Remove the createThirdPartyThirdPartyMapping and
deleteThirdPartyThirdPartyMapping mutations, the CLI
link/unlink commands, and the corresponding MCP tools.
Creating a child third party now just requires passing
parentThirdPartyId on the existing createThirdParty mutation.
The frontend walks the parentThirdParty chain to build
display names like "Name (Ancestor1/Ancestor2)" and shows
clickable ancestor links on the detail page.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Register Neon as a connector provider and add a new access review
driver that fetches organization members from the Neon API with
cursor-based pagination.
Neon's OAuth is partner-gated, so the connector is API-key only
(Bearer, the default scheme). A personal or organization API key can
belong to several organizations; the operator supplies the ID of the
one to review. The members endpoint exposes per-user MFA state
(has_mfa) and deactivation, which map to the access entry MFA status
and active flag; the stable account UUID (user_id) is used as the
external ID over the membership ID.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Register Render as an API-key connector provider and add an access
review driver that fetches workspace members from the Render API
(GET /v1/owners/{ownerId}/members).
Render exposes no partner OAuth program, so the connector authenticates
with a read-scoped API key (Authorization: Bearer) plus the customer's
Workspace ID. The flat members endpoint reports an explicit account
status and MFA flag, surfaced as the Active and MFAStatus fields; the
stable "usr-" id becomes ExternalID. There is no picker -- the
workspace is captured up front via ExtraSettings -- so
SetOrganizationSettings is omitted.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Register Qovery as a connector provider and add a new access review
driver that fetches organization members from the Qovery API.
Extend API key connection handling with a configurable Authorization
token scheme so Qovery can use "Token" while existing providers
continue to default to Bearer.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Move connectorProviderInfos from Organization to a new root query field
accessReviewDrivers, backed by a deployment-scoped policy so any
authenticated identity can list it without an org-scoped permission check.
Delete the now-unused helper file and update the frontend and e2e tests.
Signed-off-by: Bryan Frimin <bryan@probo.com>
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>
The common tracker pattern show command swallowed every error from
loading the linked common third party, hiding real database failures
behind silent partial output. Only tolerate ErrResourceNotFound now and
wrap any other error so the command fails loudly.
In the console, the copy-to-clipboard button reported success
unconditionally. Await the clipboard write and surface an error toast
when it rejects, guarding against a missing common tracker id.
Signed-off-by: Émile Ré <emile@probo.com>
Expose the existing tracker_patterns.common_tracker_pattern_id foreign
key on the TrackerPattern type so it is possible to tell whether a
pattern is linked to the global common-tracker catalog (its description
likely came from the seed or the mapping/enrichment agents) or has no
link (added manually or inherited). This is a read-only debugging aid
for agent-generated descriptions; no migration or write path changes.
The field is added in sync across all four API surfaces (GraphQL, MCP,
CLI, n8n) plus the console UI, and covered by e2e assertions for both
the linked and unlinked cases.
Signed-off-by: Émile Ré <emile@probo.com>
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>
Introduce RiskAssessmentBoundary as a first-class, self-nesting entity that
groups nodes within a risk assessment scope, and thread it through every
surface.
- coredata: new risk_assessment_boundaries table + migration, boundary_id on
nodes, self-referential parent_boundary_id, entity type registration
- riskmanagement: boundary CRUD service methods, boundary_id wiring on node
create/update, scope-membership and self-parent validation, nested-subgraph
Mermaid rendering
- IAM: core:risk-assessment-boundary:{get,list,create,update,delete} actions
and viewer/auditor read policies
- console GraphQL: RiskAssessmentBoundary type, connection, order enum, CRUD
mutations, boundaries field on scope, boundaryId on nodes
- CLI: risk-assessment boundary command group and --boundary-id on nodes
- MCP: boundary tools and boundary_id on node tools
- n8n: boundary operations and boundary fields on node operations
- console UI: boundary list/create/edit, boundary selector on nodes, diagram
refetch on boundary changes
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
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>
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>
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>
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>
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>
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>
Replace the nested ternary in the extra-settings map with an
early-return body and a shared setter, so the region select and the
free-text field each read cleanly.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Show a US/EU region dropdown for the PostHog Cloud API-key form and
an instance-URL field for Self-Hosted, map both to the connector inputs,
and register the PostHog logo for the self-hosted provider.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Expose the generated cookie policy Document on the CookieBanner
GraphQL type through a nullable policyDocument field and resolver,
and surface a link to it from the banner configuration header next
to the origin and ID. The link is hidden until a banner version is
published and the document exists.
Rework the tracker table rows: drop the Source column in favour of
a tracker Type badge, and move each tracker's description inline
beneath its name (and into the add/edit row inputs) instead of a
separate Description column.
Signed-off-by: Émile Ré <emile@probo.com>