Commit Graph

27 Commits

Author SHA1 Message Date
É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é
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
É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
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
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
d447fa295f Add OAuth client branding for trust centers
Serve CIMD logos from dedicated endpoints, expose client metadata
branding through GraphQL, and resolve branding from OAuth clients.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:17 +02:00
Bryan Frimin
ce1b64b529 Update GraphQL, MCP, and CLI for the portal
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>
2026-07-21 15:44:08 +02:00
Sacha Al Himdani
86c45875a4 Whitelist ownership grants via allow policies
Replace the deny-based restriction on granting OWNER with role-scoped
allow policies so authorization fails closed: admins may create and
update memberships only when the assigned role is not OWNER, and the
absence of a target role no longer implies permission.

To keep console UI gating accurate without loosening the base grants,
the permission field gains an optional typed options argument
(PermissionOptionsInput) that forwards target_role into the dry-run
authorization. Only the two role-related console calls (create user,
update membership) pass it; the OWNER option stays hidden for admins via
the existing assignable-roles helper.

Add a non-regression test that an admin cannot promote a member to OWNER
while still being able to change members between non-owner roles.
2026-07-08 18:41:13 +02:00
Ludovic Vielle
c93932f026 Introduce oauth2scope registry with freeze lifecycle
Replace pkg/iam/scopeset with pkg/iam/oauth2scope.Registry, a shared
OAuth2 scope→action registry used by the authorizer, OAuth2 service,
and Connect API. Registration stays open until probod calls Freeze();
read paths (RegisteredScopes, Allows, ValidateScopes) panic before
that.

Drop the leaky APIScopes surface and AllowedAPIScopes on manual
access-token creation in favor of registry.ValidateScopes. Metadata,
protected-resource metadata, and CIMD scope lists are built from
RegisteredScopes() via helpers in pkg/iam/oauth2/scopes.go. Expose
oauth2ScopesSupported as an OAuth2Scope GraphQL scalar.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-22 11:22:19 +02:00
Ludovic Vielle
26c5002932 Add identity-scoped OAuth token management
Let users create, list, and revoke manual bearer tokens from
/me/oauth-tokens, scoped to their identity rather than an
organization. Manual tokens store a null client_id and are
authorized with a self-manage IAM policy.

Wire Connect GraphQL on Identity (list, create, revoke), add
console UI with scoped create flow and credentials dialog, and
cover the flow in e2e tests. Fix list pagination ordering and
keep the Relay connection in sync after create.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-18 20:08:49 +02:00
Ludovic Vielle
e06f3e0520 Migrate Connect org logos to File type
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>
2026-06-11 13:55:44 +02:00
Bryan Frimin
383ea5a2d4 Retrict some query and mutation to only session
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-02 17:43:21 -07:00
Cursor Agent
8b6e9e420a Allow ordering profiles by email address
GraphQL profile ordering rejected EMAIL_ADDRESS because ProfileOrderField
did not expose this enum value in connect and console schemas.

Add EMAIL_ADDRESS to MembershipProfileOrderField and its validation list
so order input coercion accepts the value consistently. Extend
MembershipProfile cursor key encoding to support email ordering and avoid
runtime panics during pagination.

Update the MCP profile order enum to keep API surface definitions aligned
with the same ordering capability.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-28 12:23:17 -07:00
Sacha Al Himdani
e6b40957ee Add invitingOrganizations field on viewer
Expose viewer.invitingOrganizations: [Organization!]! returning the
organizations that have a live pending invitation directed at the
current identity (accepted_at IS NULL AND expires_at > NOW()). The
list is rendered under a "Pending invitations" section on the
memberships page and in the organization selector dropdown, so a user
already signed in with an existing identity can see which
organizations have invited them without having to dig through their
inbox.

The new field is gated by iam:invitation:list against the viewer's
own identity, so it does not loosen authorization on Organization
elsewhere. E2E coverage validates the live-pending case, the
no-invitation and post-accept cases, and a multi-org scenario.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-28 13:42:29 +02:00
Bryan Frimin
b717e71d1a Fix bad state serialization
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-27 19:23:43 -07:00
Cursor Agent
6f4ac54a27 Expose bridge sync errors in SCIM API
Add a dedicated SCIMBridge syncError field to the Connect GraphQL API
and populate it from the core bridge model.

Update the connector cards to read bridge state and syncError directly
from the bridge object instead of inferring failures from event
history.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 18:10:22 -07:00
Cursor Agent
1e08a23ddc Split user remove and archive actions
Restore RemoveUser as a hard delete operation and surface dependency\nconflicts with a dedicated IAM error.\n\nAdd a new ArchiveUser flow that deactivates profiles while keeping the\nmember in the organization, then expose both actions across Connect, MCP,\nCLI, n8n, console UI, and e2e coverage.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:17 -07:00
Bryan Frimin
5e55c888c4 Add Microsoft 365 SCIM bridge and access review driver
Microsoft 365's native SCIM endpoint is unreliable, so mirror the
Google Workspace bridge over Microsoft Graph: a new MICROSOFT_365
OAuth2 connector, a SCIM bridge provider listing /v1.0/users with
$select pagination, and an access review driver that derives admin
status from /directoryRoles members. Refactor the bridge runner to
share OAuth2 plumbing across providers and surface the new bridge
type, scopes, UI card, and bootstrap env wiring.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-05-06 15:41:30 +02:00
Émile Ré
272f63828f Add state to MCP profile and refactor contract filter
Add the profile state attribute (ACTIVE/INACTIVE) to the MCP
Profile schema so listUsers and getUser tools expose it, and
add a state filter to listUsers.

Rename excludeContractEnded to contractEnded across the entire
stack (MCP, GraphQL, CLI, frontend). The new boolean is two-way:
true returns only users with ended contracts, false returns only
users with active or no contract, and null returns all.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:06:44 +04:00
Bryan Frimin
0e701ec6e9 Style
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-19 12:00:53 +02:00
Bryan Frimin
11770b4058 Add OAuth2/OpenID Connect authorization server
Implement a full OAuth2 2.0 and OpenID Connect 1.0 authorization
server with support for authorization code flow (with PKCE),
refresh token rotation, device authorization grant, dynamic
client registration, token introspection, and token revocation.

Includes database schema, coredata layer, service logic, HTTP
handlers, OIDC discovery endpoint, and JWKS publishing.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-19 12:00:53 +02:00
Émile Ré
0ce1d8039a Split connect profile schema into profile, membership, and invitation
Move OIDCProviderInfo type into base.graphql alongside its query field
in both connect and trust APIs, removing orphan oidc.graphql files.
Split connect profile.graphql into three domain files: profile (with
user mutations), membership, and invitation.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:03:50 +04:00
Émile Ré
925c53d689 Slim base.graphql to directives, scalars, Query, Node, and Mutation
Move Identity, Organization, Viewer, PageInfo, OrderDirection,
CountryCode, OIDCProviderInfo, File, and ReauthenticationReason out
of base.graphql into their own dedicated files across all three APIs.

base.graphql now only contains directives, scalars, Node interface,
Query type, and an empty Mutation type (required by Relay
schemaExtensions). Entity files use extend type Mutation for their
mutations.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 09:19:39 +04:00
Émile Ré
5f93071b20 Consolidate hub type definitions into their own files
Move Organization, Identity, TrustCenter, and Viewer definitions to
include all their connection fields directly, removing all extend type
blocks for these hub types from entity files.

This eliminates the Relay schemaExtensions constraint where extend type
could only target types defined in the main schema file. Entity files
now only define their own standalone types and extend type Mutation.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 09:19:39 +04:00
Émile Ré
31cca05ca4 Split GraphQL schemas into per-entity files
Split each API's monolithic schema.graphql into per-coredata-model
files under graphql/ subdirectories. gqlgen's follow-schema layout
with {name}.resolvers.go template generates one resolver file per
schema file. Relay uses schema + schemaExtensions to load the split
files.

Connect API: 8 files (base, session, organization, profile,
personal_api_key, saml, scim, audit_log)

Trust API: 5 files (base, trust_center, auth, nda, mailing_list)

Console API: 25 files covering all domain entities

Types extended across files (Organization, Mutation, Viewer,
TrustCenter, Identity) are defined in base.graphql as required by
Relay's schemaExtensions.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 09:19:38 +04:00