Skip to content

Users & Roles

Gatewyse organizes users into a deployment → organization → department hierarchy with role-based access control. This page covers user management, role assignments, the scope hierarchy, and single sign-on integration.

User Management

User List

The Users page displays a searchable, paginated table with filters for status and role:

ColumnDescription
NameThe user’s display name
EmailLogin email address
Statusactive, inactive, or suspended
RolesAssigned role tags
Last LoginMost recent login timestamp, or “Never”

Use the Status and Role dropdown filters to narrow the list. The search bar matches against name and email.

Creating a User

Click Create User to open the form with these fields:

  • Name — The user’s full name
  • Email — Must be unique across the system
  • Password — Initial password (users can change it after first login)
  • Role — One or more roles, chosen from the roles that actually exist on this deployment: the built-in roles listed below plus any custom roles defined on the Roles page. At least one is required — an account with no role reaches nothing, and the form says so rather than letting you create one by accident.

Editing a User

Click Edit to navigate to the user detail page where you can update the user’s name, email, password, role assignments, and status.

User Statuses

StatusEffect
activeUser can log in and make requests
inactiveUser account is disabled; login is blocked
suspendedUser account is temporarily suspended by an administrator

How authority actually works

A user does not have a list of roles. A user has a list of memberships, and each membership is a scope plus the roles held in that scope.

memberships: [
{ scopeKind: 'organization', scopeId: '…', roleIds: […], source: 'local' },
{ scopeKind: 'department', scopeId: '…', roleIds: […], source: 'sso' }
]

scopeKind is system, organization or department. source is local, sso or scim — which is how an administrator’s edit can replace the hand-managed grant without erasing one an identity provider wrote, and vice versa.

This is the model the rest of the page depends on, so three consequences are worth stating outright.

A session acts in one scope at a time. When you sign in, the gateway picks a default scope, collects the roles from every membership at that scope, and bakes the resulting permissions into the access token. Roles held elsewhere are not in play until you switch. See the scope switcher below.

The scope recorded on a role is descriptive. No enforcement path reads it. Containment comes from the scope the session is acting in, checked against the target of every request. A role written for the deployment, held by an account pinned to a department, confers department-level reach — it reads wide and acts narrow. See Organizations & Departments.

A role change takes effect at the next token issue. Permissions are resolved when a token is minted, not per request, so a revoked role stops applying at the user’s next sign-in, refresh or scope switch.

The flat role list you see on a user and in the console is a display of the locally-managed membership. Editing a user’s roles or its organization or department recomputes that membership from the merged result — writing one without the other is how a revoked role used to stay in force.

Roles

Roles control what actions a user can perform in the admin dashboard and through the API. The gateway ships with system roles and supports custom role definitions.

Built-in Roles

RoleScopeTypical Permissions
super-adminGlobalUnrestricted access to every area of the deployment, including system internals and queues
deployment-adminDeploymentManage providers, models, routing, guards, budgets and users across the deployment — everything except system internals and queues
org-adminOrganizationManage users and budgets within their organization
dept-adminDepartmentManage users and budgets within their department
userSelfMake API requests within assigned budget limits
read-onlyDeploymentView dashboards, usage, and configuration without making changes

Custom Roles

The Roles page (/roles) lets you create custom roles with fine-grained permissions. Each role defines:

  • Name and slug — Human-readable name and a URL-safe identifier
  • Description — What the role is intended for
  • Scopesystem, organization or department. Descriptive: it documents the level the role was written for and is not itself enforced.
  • Permissions — A list of resource/action pairs (e.g. providers: [read, write], budgets: [read])
  • System flag — System roles cannot be deleted or modified

The scope switcher

The header of every admin page carries a readout of the scope the session is currently acting in — Deployment, or the name of an organization or department. It is always visible, even when there is only one option, because “which scope am I looking at” is a question every page’s contents depend on.

Where more than one scope is available it is also a menu. Choosing one re-issues the session and reloads the console: every page on screen holds data resolved for the scope being left, so a targeted refresh would leave some of it stale.

List the scopes available to the session

GET /api/admin/auth/me/scopes
{
"data": {
"active": { "kind": "organization", "id": "665f..." },
"authority": { "kind": "local" },
"available": [
{ "kind": "system", "label": "Whole deployment" },
{ "kind": "organization", "id": "665f...", "label": "Acme Corp" }
]
}
}

Any authenticated session may call it; there is no permission attached, because it only ever reports on the caller.

Switch scope

POST /api/admin/auth/me/scope
Content-Type: application/json
{ "kind": "organization", "id": "665f..." }

kind is system, organization or department; id is omitted for system. On success both the access and refresh tokens are re-issued into the session cookies, carrying the new scope and a permission set re-aggregated from the roles held in that scope alone. Re-issue is the point: the permissions in the old token were aggregated for the old scope, so keeping it would carry the old reach into the new context.

Requesting a scope the session cannot reach is refused with 403“That scope is not available to this session. Sign in again at the level you need.”

Authority bounds what is reachable

Which scopes appear in available depends on how the session authenticated, reported as authority:

AuthorityReaches
localEvery scope the account holds a membership in
system-idpThe same — the deployment’s own identity provider is trusted deployment-wide
org-idpOnly that organization and its departments. Never system.

So signing in through an organization’s identity provider cannot yield deployment authority, however wide the account’s memberships are. The console says as much in the menu when that applies. To reach the other scopes, sign in at deployment level instead.

Scope hierarchy

The gateway organizes users into a three-level hierarchy:

Deployment
+-- Organization
+-- Department
+-- User

The deployment

The root of the cascade, and implicit in every request — there is no object to manage and no page for it. Providers, models and the default policy are configured once for the deployment on the Settings page, and organizations and departments inherit them.

The organization is the first level that discriminates anything: two organizations can see different catalogues, route differently, and authenticate against different identity providers. A deployment cannot, because there is only one.

Organizations and Departments

Organizations group related departments, and departments group users. Each may narrow what it inherits — never widen it. These levels provide scoping for:

  • Budget enforcement (spend limits at each level)
  • Role-based access (org-admins and dept-admins)
  • Provider and model availability, and per-organization credentials
  • Usage reporting and audit trails

Managing them has its own page: see Organizations & Departments.

Authentication

Password Authentication

Users log in with email and password. The server issues HttpOnly cookies for session management with automatic token refresh. MFA (multi-factor authentication) is supported — when enabled, the login flow prompts for an MFA code after password verification.

Single Sign-On (SSO)

SSO is configured on the organization — open it and use the Sign-in and provisioning section, or PUT /api/admin/organizations/:id/sso. The deployment-wide configuration on the Settings page serves the deployment’s own sign-in, under the reserved slug system.

An organization with no SSO configuration of its own does not fall back to the deployment’s. Borrowing it would mean being authenticated by an authority entitled to deployment scope, so the sign-in is refused instead. See Organizations & Departments.

The gateway supports two protocols:

OpenID Connect (OIDC)

Configure with:

  • Issuer URL (e.g. https://accounts.google.com)
  • Client ID and Client Secret
  • Scopes (default: openid email profile)

SAML 2.0

Configure with:

  • IdP SSO URL (entry point)
  • IdP Entity ID (issuer)
  • IdP signing certificate (X.509 PEM format)

After SSO is enabled, the login page accepts an organization slug and sends the user to /api/auth/sso/<slug>/initiate. The callback is handled at /auth/sso-callback; tokens are set as HttpOnly cookies and never appear in the URL. User accounts are automatically provisioned on first SSO login, with roles taken from the organization’s groupRoleMappings where configured.

Multi-Factor Authentication (MFA)

Gatewyse supports TOTP-based multi-factor authentication (compatible with authenticator apps such as Google Authenticator, 1Password, or Authy). Enrollment is a two-step confirm-before-activate flow, so a pending secret never blocks login until the user proves possession of it. All MFA endpoints act on the authenticated user’s own account (/me).

Enrollment

1. Begin setup

POST /api/admin/auth/me/mfa/setup

Generates a pending TOTP secret and returns an otpauth:// URI (render it as a QR code for the authenticator app) plus the base32 secret for manual entry:

{
"data": {
"secret": "JBSWY3DPEHPK3PXP",
"otpauthUri": "otpauth://totp/AI%20Gateway:user@example.com?secret=..."
}
}

The secret stays pendingmfaEnabled remains false and login is unaffected — until the code is confirmed. Setup is rejected if MFA is already enabled; disable it first to re-enroll.

2. Confirm and enable

POST /api/admin/auth/me/mfa/enable
Content-Type: application/json
{ "code": "123456" }

Verifies the code against the pending secret, activates MFA, and returns a set of one-time recovery codes shown exactly once (store them securely; they are the fallback if the authenticator is lost):

{ "data": { "recoveryCodes": ["abcde-fghij", "klmno-pqrst", "..."] } }

Disabling MFA

POST /api/admin/auth/me/mfa/disable
Content-Type: application/json
{ "code": "123456" }

Disabling requires proof of possession — a current TOTP code or an unused recovery code — so a hijacked session cannot silently strip a user’s second factor.

Regenerating recovery codes

POST /api/admin/auth/me/mfa/recovery-codes
Content-Type: application/json
{ "code": "123456" }

Requires a current TOTP or recovery code, invalidates the previous set, and returns a fresh set of codes exactly once.

Once MFA is enabled, the login flow prompts for a code (mfaCode) after the password is verified.

Session Management

Each login establishes a session family backed by a rotating refresh token. Users can review and revoke their own sessions; administrators can force-logout any user within their own scope.

User self-service

List active sessions

GET /api/admin/auth/me/sessions

Returns the caller’s active sessions, each with its sid, createdAt, lastUsedAt, and (where captured) the originating ip and userAgent. Expired sessions are pruned as they are listed.

Revoke one session

DELETE /api/admin/auth/me/sessions/:sid

Revokes a single session by its sid — useful for signing out a lost or unrecognized device.

Admin force-logout

An administrator can revoke all of a user’s sessions at once (scope-bounded — only for users the caller may administer; an out-of-scope target is refused and the refusal is audited):

POST /api/admin/users/:id/revoke-sessions

Requires the users:update permission and returns the count of sessions revoked.

Refresh-token rotation and reuse detection

Refresh tokens are rotated on every use and tracked per session family. If a rotated-out (already-superseded) refresh token is ever presented again, that is treated as token theft: the entire session family is revoked, not just the replayed token. The replay is logged and the request is rejected.