Skip to main content
CYPEX Documentation
Support
v2.0.0 Latest stable release View changelog ->

Pending and rejected users

6 min read

Every SSO identity CYPEX has ever seen sits in sso_gateway.t_sso_user_map in exactly one of three states (sso_gateway.user_map_status): pending, active, or rejected. This page documents the full lifecycle — what moves a row between states, what each transition writes to the audit trail, and where to find evidence of a governance decision. For adding a provider and the first successful sign-in, see the OIDC setup guide.

States

StateMeaningWhat a repeat login does
PendingIdP authentication succeeded, but no t_sso_role_mapping rule matched and (if Auto-link by email is off, the default) no admin has reviewed it yet. cypex_user_id and cypex_role are both NULL.Refreshes the captured profile in place, stays pending. The exchange returns 403 pending_approval — “Your account is pending administrator approval.”
ActiveAn admin approved or linked the identity, or a role mapping matched automatically. cypex_user_id and cypex_role are set.Signs in normally; mints an access/refresh token pair like any other login.
RejectedAn admin explicitly rejected the identity. The row is kept (not deleted) — rejected_at, rejected_by, and an optional rejection_reason (≤200 characters) are recorded.Blocked. The exchange returns 403 user_rejected — a deliberately generic “This SSO identity has been blocked. Contact your administrator.” The real reason and who rejected it never reach the browser; they’re in the audit trail only.

Users → Pending SSO and Users → Rejected SSO are the two admin-facing queues for the states above:

Users — Pending SSO tab

Email, external ID, provider, organization, a suggested role (when one can be derived), and how long ago the request came in.

Email, external ID, provider, organization, a suggested role (when one can be derived), and how long ago the request came in.

Users — Rejected SSO tab

"SSO identities an administrator has blocked. The original sign-in is refused on retry. Use Un-reject to send the identity back to the pending queue for a fresh review." Email, Provider, Rejected (when), Rejected by, Reason, and an Un-reject action per row.

"SSO identities an administrator has blocked. The original sign-in is refused on retry. Use Un-reject to send the identity back to the pending queue for a fresh review." Email, Provider, Rejected (when), Rejected by, Reason, and an Un-reject action per row.

State diagram

stateDiagram-v2
    [*] --> Pending: First IdP login,<br/>no role-mapping match
    Pending --> Pending: Repeat login<br/>(403 pending_approval)
    Pending --> Active: Admin Approve<br/>(new CYPEX user)
    Pending --> Active: Admin Link<br/>(existing CYPEX user)
    Pending --> Rejected: Admin Reject<br/>(+ optional reason)
    Rejected --> Rejected: Repeat login<br/>(403 user_rejected, blocked)
    Rejected --> Pending: Admin Un-reject<br/>("re-invite")
    Active --> Active: Normal login

A role-mapping match (t_sso_role_mapping) or an email match with Auto-link by email on skips Pending entirely and goes straight to Active on first login — see the OIDC setup guide for that path. Everything on this page covers the identities that don’t take that shortcut.

Admin actions

All four actions require the caller to hold isSuperAdmin or isOrganizationAdmin — enforced server-side regardless of which PostgreSQL role the caller’s JWT carries.

ActionEndpointEffect
ApprovePATCH /sso/user-mappings/:id/approve
{ "cypexRole": "<role>" }
Provisions a brand-new CYPEX user from the captured external profile, sets status='active'. Role defaults to a suggested value (matching mapping rule, or the provider’s default_role as a fallback hint) but is always admin-editable before confirming.
LinkPATCH /sso/user-mappings/:id/link
{ "linkToCypexUserId": "<id>" }
Binds the identity to an existing CYPEX user instead of creating a new one, sets status='active'. The target user must already have a role/org grant in this organization; the mapping adopts that user’s existing role — approve and link never let an admin pick a role for a user with a pre-existing one.
RejectDELETE /sso/user-mappings/:id
{ "reason": "<optional, ≤200 chars>" }
Sets status='rejected'. Despite the HTTP verb, this is a soft state change, not a row delete — kept for audit and to block the identity from silently re-queuing.
Un-reject (“re-invite”)PATCH /sso/user-mappings/:id/unrejectSets status='pending' and clears the rejection fields. No invitation email is sent — nothing notifies the external user. This only re-opens the door: the identity re-enters the admin queue on its next real sign-in attempt through the IdP.

Approve and Reject/Un-reject both return 404 for a mapping that isn’t in the expected starting state (already resolved, out of the caller’s organization scope, or non-existent) — the 404 deliberately doesn’t distinguish those cases, to avoid leaking row existence across organizations.

Org assignment on approval

A pending row’s organization_id is fixed at first login, from the SSO provider the identity signed in through — it is never chosen ad hoc during approval. Approving or linking assigns a role (which in turn drives the organizations the resulting user can access, per Roles & Capabilities); it does not reassign which organization the identity belongs to. A single external identity that needs access to more than one organization needs a separate SSO provider (and a separate pending row) per organization.

Audit trail

Every transition writes a row to cypex_log.t_sso_audit_log (organization-scoped, RLS-protected — an organization admin sees only their own org’s rows, a system admin sees all):

Transitionevent_typeWritten by
Approve (new user)user_provisionSSO user-mapping admin
Link (existing user)user_provisionSSO user-mapping admin
Rejectuser_deprovisionSSO user-mapping admin
Un-rejectuser_provisionSSO user-mapping admin
Blocked login while pendingauthentication_failed (errorCode: pending_approval)SSO sign-in
Blocked login while rejectedauthentication_failed (errorCode: user_rejected)SSO sign-in

Each row carries organization_id, provider_id, user_map_id, event_type/event_subtype, event_description, ip_address, user_agent, event_data (JSONB), error_code/error_message, success, and a level (info / warning / error / critical) — the full column set is defined with the other sso_gateway.* tables in the SSO Gateway migrations.

Warning
Known gap — no dedicated read endpoint yet. Unlike Connector Audit, the SSO gateway does not currently expose a GET endpoint over t_sso_audit_log — the only queries against this table today are the INSERT write path and one narrow internal read used for provider failure-rate tracking, not general browsing. Pulling evidence today means querying cypex_log.t_sso_audit_log directly, filtered on organization_id and user_map_id. A future ticket should add an admin-facing list endpoint, mirroring the connector audit pattern.

Governance evidence

This is the SSO-specific instance of the evidence model documented in Organizations — Governance evidence: a non-trivial access decision must leave a retrievable record behind.

Applying that page’s three layers to an SSO approve/reject decision:

  1. Live audit endpoint — currently a gap for SSO specifically (see the warning above); the closest live evidence today is the Users → Pending SSO / Rejected SSO tabs, which show current state but not the full historical trail.
  2. Backend write path — the audit table above. This is the layer that carries the evidence: every approve, reject, link, and un-reject is captured with who, when, and (for rejections) why.
  3. Doc-side evidence — this page, plus the state diagram above, is itself the doc-side record of how the workflow is intended to behave. A reviewer auditing SSO governance should read this page, then pull the t_sso_audit_log rows for the organization and time range in question, and confirm the two agree.

See also