Commit Graph

1493 Commits

Author SHA1 Message Date
Sacha Al Himdani
e8e5e9bf9d Show missing OAuth scopes after connector reconnect
Partial grants completed without feedback, leaving Reconnect
required unexplained. Keep the token and toast the backend
missing-scopes error after the OAuth callback redirect.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-31 15:32:45 +02:00
Ludovic Vielle
2f81b69ad1 Fix manual agent install download instructions
Repo-wide /releases/latest pointed at the wrong
assets when other tracks published more recently.
Point users at probo-agent/v* releases and split
download vs enroll into separate copy blocks with
placeholder URLs they fill in.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-31 11:08:23 +02:00
Ludovic Vielle
7731566c68 Add soft delete for revoked devices
Admins could only revoke devices, so never-enrolled and revoked
inventory rows piled up with no way to remove them. Soft-delete
is limited to REVOKED devices (revoke first), and ITAM GC now
hard-deletes PENDING/REVOKED orphans with no API key, postures,
or valid enrollment token—including user tombstones without
history.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-30 16:34:16 +02:00
Sacha Al Himdani
573549fcec Show reconnect when access-review connectors need new OAuth scopes
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-30 15:43:47 +02:00
Cursor Agent
5cd407bd86 Emit audit and SCIM log exports as CSV
JSONL was awkward in spreadsheets and SIEM imports. Write
tab-separated-friendly CSV with organization name on every row,
resolve audit actors to email or API key name, and enrich SCIM rows
with profile email and display name when available.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-30 10:12:47 +00:00
Émile Ré
b3cf6d909b Improve people list filters and invite state
Replace the cramped status checkboxes with a multi-select
dropdown (new DropdownCheckboxItem), and update the Relay
store to PENDING when an activation email is sent so the
row reflects the server-side state change immediately.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Émile Ré
bb16be8d17 Cancel pending people search on filter change
A debounced search could fire after status, role, kind, or sort
changed and refetch with the previous filter values, briefly
reverting the list. Cancel the pending callback before those
immediate refetches so the latest filters stick.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Émile Ré
eecf8a1d97 Make profile state filters multi-value
Address PR review feedback on the profile-state split: SAML sign-in now
activates a pending profile, deactivation counts owners against the
profile's own organization to close a last-owner bypass, the migration
leaves historical activated_at/deactivated_at NULL rather than
fabricating timestamps, and pending members are no longer rendered with
the deactivated (faded) styling.

Drop the single-value state filter in favor of the multi-value states
across the profile and signatory surfaces. Remove ProfileFilter.state
(only states[] remains) and convert the signatures profileState filter
to profileStates. Turn the console people filter, the CLI
"user list --state" flag, and the n8n listUsers and getAllSignatures
state inputs into multi-select controls, where an empty selection means
all states.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Émile Ré
b10fc55b7f Rename user archive action to deactivate
"Archive" was misleading for users: the action sets a profile to
DEACTIVATED while keeping the person in the organization. Rename it to
"deactivate" across the API, CLI, MCP, n8n, and console UI.

Consolidate the two overlapping operations into a single deactivateUser
backed by the fuller, guarded logic (SCIM guard, last-active-owner
guard, invitation expiry, signature cancellation, membership update,
webhook) and authorized via iam:membership-profile:deactivate. Remove
the archiveUser surface and the thin state-only deactivate path.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Émile Ré
99c3235b46 Exclude deactivated owners from asset table picker
The inline owner picker on the assets table queried people without a
state filter, so deactivated profiles remained selectable as owners.
Restrict it to ACTIVE and PENDING to match the other owner pickers,
keeping pending members assignable while dropping deactivated ones.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Émile Ré
4a276e3ef7 Split inactive profile state
Replace the binary profile ACTIVE/INACTIVE model with PENDING, ACTIVE,
and DEACTIVATED so invited-but-not-yet-activated members remain
assignable to assets, data, and risks instead of being treated like
deactivated users.

Add activated_at/deactivated_at timestamps and Mark* lifecycle helpers,
and update every transition (create, invite/re-invite, activation,
archive, SCIM, SAML, sessions, compliance-portal grant) to the new
states. Expose a multi-state states[] filter across coredata, GraphQL,
MCP, and the console owner pickers, which now request ACTIVE and
PENDING members.

A migration renames the membership_state enum, classifies existing
inactive profiles as PENDING from recent invitation activity, and
backfills the new timestamp columns.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-30 09:19:42 +02:00
Sacha Al Himdani
cd6c46212a Add log export for audit logs and SCIM events
Route audit-log and SCIM-event exports through export_jobs with typed
arguments, an iam BuildAndUploadExport/SendExportEmail implementation,
and a concurrent export-job worker with stale recovery. Stream JSONL via
page.WalkAll into S3, and expose the request flow on console, connect,
MCP, and CLI.

Co-authored-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-29 18:57:31 +02:00
Cursor Agent
6cbfe7e755 Sync console document lang with i18next locale
Static lang=\"en\" in index.html left French UI declared as English
for assistive tech. Mirror the compliance portal by updating
document.documentElement.lang on init and when the locale changes.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-29 16:39:58 +00:00
Cursor Agent
b229da0115 Block delete for in-progress access review campaigns
Reject deletion while a campaign is fetching sources so workers are
not racing a removed record. All other statuses remain deletable.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-29 16:02:26 +00:00
Cursor Agent
b156b0333a Allow deleting access review campaigns in any status
Drop the backend status gate on campaign delete and show delete in
the console whenever the user has delete permission, regardless of
whether the campaign is draft, in progress, or completed.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-29 16:02:26 +00:00
Cursor Agent
9c1cecfc1e Allow deleting completed access review campaigns
Completed campaigns were immutable because delete only accepted draft
or cancelled status. Extend deletion to completed records and surface
delete in the console for that status.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-29 16:02:26 +00:00
Ludovic Vielle
ad615a47a0 Show posture values and report history
Pass/fail was the main device UI signal, but operators need
the agent's observed value. Expose a formatted value per
check, show current postures on the device page, and replace
the Postures tab with paginated report history grouped by
agent push time. Status stays in the model for later rulesets.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-29 16:27:23 +02:00
Cursor Agent
fe215efbd1 Add audit start and end dates to audits
ISO audits often span a window distinct from certificate validity.
Store optional audit_start_date and audit_end_date on the audit
record and expose them through GraphQL, MCP, CLI, n8n, and console.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-28 17:27:33 +02:00
Émile Ré
82a62005f8 Harden email verification resend against abuse
Add a per-address confirmation-email cooldown and disable the
resend/forgot-password submit buttons while the mutation is in
flight so callers cannot flood the mail queue or double-submit.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-28 15:11:06 +02:00
Émile Ré
5d0882778f Gate password sign-in on email verification
Unverified password identities were able to open sessions after
signing out. Reject sign-in with EMAIL_NOT_VERIFIED and add a
resend-confirmation flow so users can complete verification.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-28 15:11:05 +02:00
Sacha Al Himdani
2ebf3c180f Fix crash on third-party details when country is GLOBAL
Intl.DisplayNames rejects the GLOBAL pseudo-region, so label
resolution now handles it explicitly before rendering the picker.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-27 18:56:24 +02:00
Sacha Al Himdani
d61ec8dd65 Add search and status/role/type filters to People
Makes it practical to find people in larger orgs across GraphQL, MCP, CLI, and n8n, with page size raised to 100.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-27 17:20:18 +02:00
Sacha Al Himdani
edb67743ae Limit third party selectors to first-level parties
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-27 17:18:01 +02:00
Sacha Al Himdani
5c9c9e3888 Fix npm audit findings in postcss and react-router
Bump postcss to 8.5.23 and react-router to 8.3.0 to clear their high-severity advisories. Leave nested brace-expansion issues for a follow-up that won't break older minimatch consumers.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-27 17:07:59 +02:00
Cursor Agent
51144d25fc Add Risk Accepted status to finding forms
Findings create and update views filtered out RISK_ACCEPTED
even though the API requires a linked risk for that status.
Restore the option, collect riskId via a searchable paginated
picker, and clear stale links when status changes.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-27 14:14:43 +02:00
Cursor Agent
9abea50507 Preserve continue URL on auth error re-login
Failed OIDC, magic-link, and SAML sign-ins sent users to /auth/error
without the post-login destination, so Sign in dropped OAuth flows
and deep links. Propagate a validated continue query through auth
error redirects, recover it from OIDC state when the IdP denies or
cancels login, and forward it from AuthErrorPage to /auth/login.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-27 14:14:02 +02:00
Bryan Frimin
428d28fade Add french translation keys
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-27 13:59:51 +02:00
Jonathan
a913b0e3b6 Fix eslint & checks
Signed-off-by: Jonathan <contact@grafikart.fr>
2026-07-27 11:52:32 +02:00
Jonathan
7d2b9b73b7 Remove new useTranslate usage
Signed-off-by: Jonathan <contact@grafikart.fr>
2026-07-27 11:52:31 +02:00
Jonathan
13ba251aba Fix eslint errors
Signed-off-by: Jonathan <contact@grafikart.fr>
2026-07-27 11:52:31 +02:00
Jonathan
a7ff5f07bc Add react i18next to console
Signed-off-by: Jonathan <contact@grafikart.fr>
2026-07-27 11:52:31 +02:00
Aurélien Sibiril
fb68e98941 Split connector extra settings per credential path
Registration.ExtraSettings was a single flat list, but the API-key and
client-credentials connect dialogs need different fields whenever a
provider offers both paths, because a different create resolver and a
different driver sits behind each. Replace it with
APIKeyExtraSettings and ClientCredentialsExtraSettings, and split the
GraphQL surface to match so a client cannot render one path's settings
on the other.

This fixes two connectors that could not be connected at all.

1Password declared accountId and region only, which are the
client-credentials shape. The API-key dialog therefore rendered those
two fields, mapAPIKeyExtraSettingToField returned nil for both so
buildExtraFields discarded them, and the SCIM-bridge driver failed on an
empty SCIMBridgeURL. The console already mapped scimBridgeUrl, but no
registration declared that key, so the branch was dead. It now declares
scimBridgeUrl on the API-key path and accountId + region on client
credentials.

Langfuse declared baseUrl as required, but mapAPIKeyExtraSettingToField
had no LANGFUSE case, so buildExtraFields dropped the value the customer
typed and the mutation failed with "langfuseBaseUrl is required". Every
other extra-settings provider had a case. The GraphQL input field, the
settings struct, the probe builder and the driver were all already
correct; only the console mapping was missing.

buildExtraFields now takes the settings list explicitly instead of
reading it off the provider, so each dialog passes its own path's list
and cannot silently iterate the other one.

Register rejects a settings list for a path the provider does not offer,
and an empty or duplicate setting key within one list. A key repeated
across the two lists is allowed: that is how a dual-path provider
declares a setting both dialogs need.

The new resolver tests walk the whole chain the console walks, from the
key a Registration declares through the mutation input field to the
persisted settings struct, so a key renamed on one side and not the
other fails in CI instead of at connect time.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-26 15:56:10 +02:00
Aurélien Sibiril
4cbc35e79f Fix the Segment region input and invite duplicates
The region is a two-value allow-list the server resolves to an API host,
but it rendered as a free-text field: only PostHog is special-cased in
the API-key dialog, everything else falls through to a generic Field.
Typing "EU1" — the region Segment's own UI shows for the EU workspace —
passed the non-empty check, then failed the mutation, and the dialog's
generic error blamed the API key. It is a select now, so the label no
longer has to spell the accepted values out.

An invite that has already been accepted can still be listed, and the
member and the invite were keyed differently (user ID vs email), so the
same person surfaced as two rows — one active with roles, one inactive
without. Invites for an email already seen among members are dropped.

Per-user permission errors now name the user, and the probe URL builds
its query with url.Values rather than a hand-written string.

The region-to-host mapping is the only API-key setting that derives a
value instead of storing input verbatim, and it had no test; a typo in
either host would only have surfaced as a live 404.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-26 09:22:05 +02:00
Aurélien Sibiril
60628645ae Add Google Analytics, Dotfile, Segment and Square access-review connectors
Two OAuth2 and two API-key connectors:

- Google Analytics (GA4): OAuth2 with both analytics.readonly and
  analytics.manage.users.readonly (readonly alone 403s on the accounts
  list); v1alpha accessBindings enumerated at account and property level
  and merged by email; manual account picker (Pattern 1) with a
  per-connection probe and name resolver; distinct from Google Workspace.
- Dotfile: API key in the X-DOTFILE-API-KEY header (Pattern 3); GET
  /v1/users (owner/admin, suspended_at) with a static probe.
- Segment (Twilio): Public API token as Bearer with a required Region
  setting (US or EU) mapped to the regional host; GET /users plus per-user
  GET /users/{id} for roles and /invites for pending members; per-connection
  BuildProbeURL.
- Square: OAuth2 (EMPLOYEES_READ) or a personal access token (Pattern 3);
  POST /v2/team-members/search returns email/status/is_owner directly, so no
  role resolution; custom probe and name resolver.

Google Analytics and Square are confidential OAuth clients, wired into the
bootstrap OAuth provider list and .env.example. Segment carries a required
extra setting, so the console add-source dialog maps region onto its
segmentRegion API-key input; without that mapping the value is silently
dropped and the create is rejected.

Cassette-backed driver tests plus unit tests for the Segment probe URL and
the bootstrap OAuth provider list.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-26 09:22:04 +02:00
Bryan Frimin
00ed11e30b Keep SAML auth failures generic for users
Specific SAML refusal reasons leak organization and account state.
Still redirect to the shared auth error page, but always use
authentication_failed while logging the real cause server-side.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-24 21:45:31 +00:00
Bryan Frimin
b8e5d279a0 Surface invalid OIDC state on auth error page
Missing, expired, or reused OIDC state left users on a JSON error
or a generic failure. Map those cases to invalid_state so they get
a clear prompt to restart sign-in.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-24 21:37:31 +00:00
Bryan Frimin
318789ec38 Cover magic-link and SAML failures on auth error page
Browser auth callbacks still returned JSON or used one-off pages
for several refusal reasons. Route OIDC email verification,
magic-link, and SAML ACS failures through /auth/error with stable
error codes so users always see an explanation.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-24 21:32:56 +00:00
Bryan Frimin
6c4a67d3f7 Route OIDC failures through shared auth error page
A dedicated personal-account page does not scale as more refusal
reasons appear. Redirect the OIDC callback to /auth/error with an
error code query parameter so the frontend can map codes to clear
user-facing messages.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-24 21:19:33 +00:00
Bryan Frimin
d1814d7051 Show why personal OIDC logins are refused
Personal Google and Microsoft accounts were rejected with a raw
JSON unauthorized response after the OIDC callback. Redirect to a
dedicated auth page that explains the enterprise-account
requirement, and check enterprise eligibility before xms_edov so
Microsoft consumer accounts get the same clear error.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-24 21:16:11 +00:00
Sacha Al Himdani
55a1bd03eb Rename My Signatures menu label to Employee Portal
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-24 18:53:41 +02:00
Ludovic Vielle
238c19d509 Add pre-assume enrolled device status query
The /enroll wait UI polled device state via node(), which
requires an assumed org session, so confirmation never
succeeded for unassumed viewers. Expose viewer.enrolledDevice
behind itam:employee-device:get (own-device, skip assumption)
and point the poller at it.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-24 15:08:34 +02:00
Ludovic Vielle
6dbbd70229 Add console devices UI
Add org admin device management, employee self-service enrollment,
posture views, and owner assignment across console routes.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-24 15:08:27 +02:00
Aurélien Sibiril
fa3b7dc2b3 Add docs button to the API-key connect dialog
The Add Source card links to a connector's docs, but the API-key
connect dialog did not, so a user filling in an API key had no path
to the setup instructions. Add a "Documentation" button to the dialog
footer, styled like Cancel and on the left of the Cancel/Connect row,
shown only when the provider has a docs page.

Extract the rendering into a shared ConnectorDocumentationLink
component with a link/button variant (the card keeps the quiet link,
the dialog uses the button) and give DialogFooter an optional start
slot for left-aligned footer content.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-24 14:50:18 +02:00
Aurélien Sibiril
b1a67aba00 Link connector docs in the Add Source dialog
The access review Add Source dialog listed each connector with no
path to its setup documentation. Connectors that have a published
docs page on probo.com now surface a "Documentation" link on the
card, opening the page in a new tab; connectors without a page show
nothing extra.

The link is data-driven from the connector registry: a new
DocumentationURL on the provider Registration, populated for the 12
documented providers via a single accessReviewDocsURL helper, is
surfaced as a nullable documentationUrl on ConnectorProviderInfo and
rendered by the console only when present. This keeps the registry
the single source of truth and adds no client-side provider map.

The links resolve once the probo.com access-review docs pages are
deployed; until then they 404, so deploy the docs alongside this
change.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-07-24 14:50:18 +02:00
Sacha Al Himdani
5c5f60d5e1 Render longer vetting notes as markdown
Keep more of the orchestrator assessment text,
skipping profile fields already on the third party,
and render the notes as markdown in the console.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-23 08:50:22 +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é
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
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
d0090e8c65 Use portal visibility fields in console lists
Stop aliasing compliancePortalVisibility to compliancePageVisibility in
document, file, and audit list items so the console matches the renamed
GraphQL field.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:28 +02:00
Bryan Frimin
53cc6c487e Update console for compliance portal
Point compliance-page screens and Relay graphs
at the renamed Compliance Portal API fields and
mutations.

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