Skip to content

Users & Roles

Breeze provides a multi-tenant user management system with role-based access control (RBAC), multi-factor authentication (MFA), single sign-on (SSO), and granular permission assignment. Users are scoped to either a Partner (MSP) or an Organisation, and every action is governed by an assigned role.


Overview

The user and role system is built around Breeze’s multi-tenant hierarchy:

Partner (MSP) --> Organisation (Customer) --> Site --> Device Group --> Device
  • Partner-scoped users are linked via the partner_users table and can be granted access to all, selected, or no organisations.
  • Organisation-scoped users are linked via the organization_users table and can optionally be restricted to specific sites or device groups.
  • Roles are scoped to either partner or organization level and carry a set of permissions.
  • Permissions follow a resource:action model (e.g. devices:read, scripts:execute).

User Management

User Status

Every user account has one of three statuses:

StatusDescription
activeThe user can log in and access the platform.
invitedThe user has been invited but has not yet completed setup.
disabledThe user account is suspended and cannot log in.

Inviting Users

Administrators invite users by providing an email address, display name, and role assignment. The invitation flow differs by scope.

When inviting into an organisation, you can optionally restrict the user to specific sites and device groups:

{
"email": "[email protected]",
"name": "Jane Doe",
"roleId": "<role-uuid>",
"siteIds": ["<site-uuid>"],
"deviceGroupIds": ["<group-uuid>"]
}

Omit siteIds and deviceGroupIds to grant access to all sites and device groups within the organisation.

If an email service is configured, the invitee receives an invitation email with a link to accept. You can resend the invitation for users who are still in invited status.

Updating Users

Administrators with users:write permission can update a user’s name or status. For example, disabling a user:

{
"status": "disabled"
}

Removing Users

Removing a user from a scope (DELETE /users/:id) deletes the association record (partner_users or organization_users) but does not delete the underlying user account. This means the user can be re-invited later.

Self-Service Profile

Authenticated users can view and update their own profile at GET /users/me and PATCH /users/me without any special permissions. Updatable fields are name and avatarUrl.


Authentication

Login

Users authenticate with email and password via POST /auth/login. The endpoint is rate-limited per IP + email combination using a Redis-backed sliding window. On success, the response includes a JWT access token and a refresh token set as an HTTP-only cookie.

Terminal window
curl -X POST https://breeze.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "..."}'

If the user has MFA enabled, the response includes mfaRequired: true and a tempToken instead of tokens. See the MFA section below.

Token Refresh

Access tokens are short-lived. Use POST /auth/refresh to rotate the token pair. The refresh token is read from the breeze_refresh_token HTTP-only cookie. A CSRF header (x-breeze-csrf) is required when the token is supplied via cookie.

Old refresh tokens are revoked on each rotation to prevent replay.

Logout

POST /auth/logout revokes all active tokens for the user and clears the refresh cookie.

Registration

When enabled (ENABLE_REGISTRATION=true), two registration endpoints are available:

EndpointPurpose
POST /auth/registerCreate a standalone user account.
POST /auth/register-partnerSelf-service MSP signup: creates a partner, admin role, and user in a single transaction.

Registration is rate-limited to 5 attempts per IP per hour (3 for partner registration). Password strength requirements are enforced.


Multi-Factor Authentication (MFA)

MFA is enabled by default (controlled by the ENABLE_2FA environment variable). Two methods are supported:

MethodDescription
TOTPTime-based one-time passwords via authenticator apps (Google Authenticator, Authy, etc.).
SMSOne-time codes sent to a verified phone number via Twilio.

TOTP Setup Flow

  1. Call POST /auth/mfa/setup (authenticated). The response includes a secret, otpAuthUrl, QR code data URL, and one-time recovery codes.
  2. Scan the QR code with an authenticator app.
  3. Confirm setup by calling POST /auth/mfa/verify with the 6-digit code from the app. MFA is now active.

Alternatively, use POST /auth/mfa/enable with { "code": "123456" } after the setup step — this endpoint is designed for the frontend settings flow and also returns recovery codes.

SMS MFA Setup Flow

  1. Verify your phone number by calling POST /auth/phone/verify with { "phoneNumber": "+14155551234" }.
  2. Confirm the verification code with POST /auth/phone/confirm.
  3. Enable SMS MFA with POST /auth/mfa/sms/enable. Recovery codes are returned.

MFA During Login

When MFA is enabled, login returns mfaRequired: true and a tempToken (valid for 5 minutes). The flow continues:

  1. (If SMS) Call POST /auth/mfa/sms/send with the tempToken to trigger an SMS code.
  2. Call POST /auth/mfa/verify with { "code": "123456", "tempToken": "..." }.
  3. On success, full JWT tokens are issued with the mfa: true claim.

MFA verification is rate-limited separately from login.

Disabling MFA

Call POST /auth/mfa/disable with the current 6-digit code (TOTP or SMS depending on active method). This clears the MFA secret, phone number, and recovery codes. If the organisation enforces MFA, the endpoint returns 403.

Recovery Codes

Recovery codes are generated during MFA setup and stored as hashed values. To regenerate codes for an active MFA configuration, call POST /auth/mfa/recovery-codes (authenticated). This replaces all existing codes.


Password Management

Forgot Password

POST /auth/forgot-password accepts an email address and sends a password reset link (valid for 1 hour) if the account exists. The response is always 200 regardless of whether the email was found, to prevent account enumeration.

Reset Password

POST /auth/reset-password accepts the reset token and a new password (minimum 8 characters, strength-checked). On success, all existing sessions and tokens are invalidated.

Change Password

Authenticated users can change their password via POST /auth/change-password by providing currentPassword and newPassword. All sessions and tokens are invalidated on success.


Single Sign-On (SSO)

Breeze supports organisation-level SSO via OpenID Connect (OIDC). SAML schema support is defined but not yet implemented.

Provider Configuration

SSO providers are configured per organisation. Built-in presets are available for common identity providers (retrieved via GET /sso/presets).

Key provider settings:

SettingDescription
issuerOIDC issuer URL. Endpoint discovery is attempted automatically.
clientId / clientSecretOAuth 2.0 credentials. The secret is stored encrypted.
autoProvisionWhen true, users who authenticate via SSO but do not yet have a Breeze account are created automatically.
defaultRoleIdThe role assigned to auto-provisioned users. Required when autoProvision is enabled.
allowedDomainsComma-separated list of email domains permitted to log in via this provider.
enforceSSOWhen true, password-based login is disabled for the organisation.

Provider status can be active, inactive, or testing. Only active providers are available for user login.

SSO Login Flow

  1. The browser navigates to GET /sso/login/:orgId. Breeze generates a PKCE challenge, state, and nonce, stores them in an sso_sessions record, and redirects to the identity provider’s authorization URL.
  2. The user authenticates with the identity provider.
  3. The identity provider redirects back to GET /sso/callback with an authorization code.
  4. Breeze exchanges the code for tokens, verifies the ID token claims, retrieves user info, and maps attributes.
  5. If the user does not exist and autoProvision is enabled, a new user and organisation membership are created.
  6. A short-lived token exchange code is generated and the user is redirected to the application with #ssoCode=....
  7. The frontend calls POST /sso/exchange with the code to receive the access and refresh tokens.

SSO Check

The frontend can check whether an organisation has SSO enabled by calling the public endpoint GET /sso/check/:orgId. This returns the provider name, type, and whether SSO is enforced.


Role-Based Access Control

How Roles Work

Every user-to-scope association includes a roleId. The role determines what the user can do. Roles are scoped:

  • Partner roles — govern what MSP-level users can do across the partner and its organisations.
  • Organisation roles — govern what organisation-level users can do within their organisation.

Roles can be system (built-in, immutable) or custom (created by administrators).

Permission Model

Permissions are stored as resource:action pairs in the permissions table. Roles are linked to permissions through the role_permissions join table.

Available resources:

ResourceDescription
devicesManage and view endpoints
scriptsScript library and execution
alertsAlert rules and acknowledgement
automationsAutomation workflows
reportsReporting and analytics
usersUser and role management
settingsOrganisation and system settings
organizationsOrganisation CRUD
sitesSite management
remoteRemote access sessions

Available actions:

ActionDescription
viewRead-only access
createCreate new resources
updateModify existing resources
deleteRemove resources
executeRun scripts, commands, remote sessions

Additionally, the wildcard permission *:* grants full administrative access.

Built-in Permissions

The following named permissions are referenced by route middleware:

ConstantResourceAction
DEVICES_READdevicesread
DEVICES_WRITEdeviceswrite
DEVICES_DELETEdevicesdelete
DEVICES_EXECUTEdevicesexecute
SCRIPTS_READscriptsread
SCRIPTS_WRITEscriptswrite
SCRIPTS_DELETEscriptsdelete
SCRIPTS_EXECUTEscriptsexecute
ALERTS_READalertsread
ALERTS_WRITEalertswrite
ALERTS_ACKNOWLEDGEalertsacknowledge
USERS_READusersread
USERS_WRITEuserswrite
USERS_DELETEusersdelete
USERS_INVITEusersinvite
ORGS_READorganizationsread
ORGS_WRITEorganizationswrite
ORGS_DELETEorganizationsdelete
SITES_READsitesread
SITES_WRITEsiteswrite
SITES_DELETEsitesdelete
AUTOMATIONS_READautomationsread
AUTOMATIONS_WRITEautomationswrite
AUTOMATIONS_DELETEautomationsdelete
REMOTE_ACCESSremoteaccess
AUDIT_READauditread
AUDIT_EXPORTauditexport
ADMIN_ALL**

Role Inheritance

Roles support single-parent inheritance via the parentRoleId field. A child role inherits all permissions from its parent chain and can add additional permissions of its own. Circular inheritance is detected and rejected.

Use GET /roles/:id/effective-permissions to view the full permission set including inherited permissions. The response distinguishes direct permissions from inherited ones and identifies the source role for each.

Creating Custom Roles

Custom roles are created with POST /roles and must include a name. Permissions and an optional parent role can be specified:

{
"name": "Helpdesk Tier 1",
"description": "View devices and acknowledge alerts",
"permissions": [
{ "resource": "devices", "action": "view" },
{ "resource": "alerts", "action": "view" },
{ "resource": "alerts", "action": "acknowledge" }
],
"parentRoleId": null
}

Custom roles are automatically scoped to the current user’s context (partner or organisation). System roles cannot be modified or deleted.

Cloning Roles

Use POST /roles/:id/clone to duplicate an existing role (including system roles) as a starting point for a custom role. Provide a new name in the request body:

{
"name": "Helpdesk Tier 2"
}

The cloned role copies all permissions from the source and is created as a non-system role owned by your partner or organisation.

Deleting Roles

Roles can only be deleted when:

  • The role is not a system role.
  • No users are currently assigned to the role.
  • No child roles inherit from the role.

Attempting to delete a role with assigned users or child roles returns 400 with the count of blocking resources.


API Reference

User Endpoints

All user endpoints are mounted under /api/v1/users and require JWT authentication.

MethodPathPermissionDescription
GET/users/me(authenticated)Get the current user’s profile.
PATCH/users/me(authenticated)Update the current user’s name or avatar.
GET/usersusers:readList all users in the current scope.
GET/users/:idusers:readGet a specific user’s details.
POST/users/inviteusers:inviteInvite a new user to the current scope.
POST/users/resend-inviteusers:inviteResend an invitation email.
PATCH/users/:idusers:writeUpdate a user’s name or status.
DELETE/users/:idusers:deleteRemove a user from the current scope.
POST/users/:id/roleusers:writeAssign a different role to a user.
GET/users/rolesusers:readList available roles for the current scope.

Role Endpoints

All role endpoints are mounted under /api/v1/roles and require JWT authentication.

MethodPathPermissionDescription
GET/rolesusers:readList all roles (system + custom) for the current scope. Includes user counts.
POST/rolesusers:writeCreate a custom role with permissions.
GET/roles/permissions/availableusers:readGet the list of available resources and actions.
GET/roles/:idusers:readGet a role with its direct permissions and user count.
PATCH/roles/:idusers:writeUpdate a custom role’s name, description, permissions, or parent.
DELETE/roles/:idusers:deleteDelete a custom role (must have no users or child roles).
POST/roles/:id/cloneusers:writeClone an existing role as a new custom role.
GET/roles/:id/usersusers:readList users assigned to a specific role.
GET/roles/:id/effective-permissionsusers:readGet all permissions including inherited ones.

Auth Endpoints

Auth endpoints are mounted under /api/v1/auth. Public endpoints do not require a token.

MethodPathAuthDescription
POST/auth/registerPublicRegister a new user account.
POST/auth/register-partnerPublicSelf-service MSP/partner signup.
POST/auth/loginPublicLog in with email and password.
POST/auth/logoutAuthenticatedLog out and revoke all tokens.
POST/auth/refreshCookieRotate the access/refresh token pair.
GET/auth/meAuthenticatedGet current user details (including MFA and phone status).
POST/auth/forgot-passwordPublicRequest a password reset email.
POST/auth/reset-passwordPublicReset password with a token.
POST/auth/change-passwordAuthenticatedChange password (requires current password).
POST/auth/mfa/setupAuthenticatedBegin TOTP MFA setup; returns QR code and recovery codes.
POST/auth/mfa/verifyPublic/AuthVerify an MFA code during login (with tempToken) or setup confirmation.
POST/auth/mfa/enableAuthenticatedConfirm MFA setup (frontend settings flow).
POST/auth/mfa/disableAuthenticatedDisable MFA (requires current code).
POST/auth/mfa/recovery-codesAuthenticatedRegenerate MFA recovery codes.
POST/auth/phone/verifyAuthenticatedSend a phone verification SMS.
POST/auth/phone/confirmAuthenticatedConfirm phone verification code.
POST/auth/mfa/sms/enableAuthenticatedEnable SMS MFA (requires verified phone).
POST/auth/mfa/sms/sendPublicSend SMS code during MFA login (requires tempToken).

SSO Endpoints

SSO endpoints are mounted under /api/v1/sso.

MethodPathAuthDescription
GET/sso/presetsAuthenticatedList available SSO provider presets.
GET/sso/providersAuthenticatedList SSO providers for an organisation.
GET/sso/providers/:idAuthenticatedGet SSO provider details.
POST/sso/providersAuthenticatedCreate an SSO provider.
PATCH/sso/providers/:idAuthenticatedUpdate an SSO provider.
DELETE/sso/providers/:idAuthenticatedDelete an SSO provider and all linked identities.
POST/sso/providers/:id/statusAuthenticatedSet provider status (active, inactive, testing).
POST/sso/providers/:id/testAuthenticatedTest OIDC provider discovery.
GET/sso/login/:orgIdPublicInitiate SSO login (redirects to identity provider).
GET/sso/callbackPublicOIDC callback handler.
POST/sso/exchangePublicExchange SSO code for JWT tokens.
GET/sso/check/:orgIdPublicCheck if an organisation has SSO enabled.

Database Schema

Users Table

ColumnTypeDescription
iduuidPrimary key.
emailvarchar(255)Unique email address.
namevarchar(255)Display name.
password_hashtextArgon2 password hash (nullable for SSO users).
mfa_secrettextEncrypted TOTP secret.
mfa_enabledbooleanWhether MFA is active.
mfa_recovery_codesjsonbHashed recovery codes.
phone_numbertextE.164 phone number for SMS MFA.
phone_verifiedbooleanWhether the phone number has been verified.
mfa_methodenumtotp or sms.
statusenumactive, invited, or disabled.
avatar_urltextProfile image URL.
last_login_attimestampLast successful login time.
password_changed_attimestampLast password change time.
created_attimestampAccount creation time.
updated_attimestampLast modification time.

Roles Table

ColumnTypeDescription
iduuidPrimary key.
partner_iduuidOwning partner (nullable).
org_iduuidOwning organisation (nullable).
parent_role_iduuidParent role for inheritance (nullable).
scopeenumsystem, partner, or organization.
namevarchar(100)Role display name.
descriptiontextOptional description.
is_systembooleanWhether the role is a built-in system role.

Permissions Table

ColumnTypeDescription
iduuidPrimary key.
resourcevarchar(100)Resource name (e.g. devices).
actionvarchar(50)Action name (e.g. read).
descriptiontextHuman-readable description.

Role Permissions Table

ColumnTypeDescription
role_iduuidFK to roles.
permission_iduuidFK to permissions.
constraintsjsonbOptional constraints (reserved for future use).

Partner Users Table

ColumnTypeDescription
iduuidPrimary key.
partner_iduuidFK to partners.
user_iduuidFK to users.
role_iduuidFK to roles.
org_accessenumall, selected, or none.
org_idsuuid[]Array of accessible organisation IDs (when org_access is selected).

Organisation Users Table

ColumnTypeDescription
iduuidPrimary key.
org_iduuidFK to organizations.
user_iduuidFK to users.
role_iduuidFK to roles.
site_idsuuid[]Optional site restrictions.
device_group_idsuuid[]Optional device group restrictions.

Sessions Table

ColumnTypeDescription
iduuidPrimary key.
user_iduuidFK to users.
token_hashtextSHA-256 hash of the session token.
ip_addressvarchar(45)Client IP at session creation.
user_agenttextBrowser user agent string.
expires_attimestampSession expiry.

Audit Logging

All user and role management actions are recorded in the audit log. Key audit events include:

ActionTrigger
user.inviteA user is invited to a scope.
user.invite.resendAn invitation email is resent.
user.updateA user’s name or status is changed.
user.removeA user is removed from a scope.
user.role.assignA user’s role is changed.
user.profile.updateA user updates their own profile.
user.loginSuccessful login.
user.logoutSuccessful logout.
role.createA custom role is created.
role.updateA custom role is updated.
role.deleteA custom role is deleted.
role.cloneA role is cloned.
auth.mfa.setupMFA is enabled for an account.
auth.mfa.disableMFA is disabled.
auth.mfa.recovery_codes.rotateRecovery codes are regenerated.
sso.provider.createAn SSO provider is created.
sso.provider.updateAn SSO provider is updated.
sso.provider.deleteAn SSO provider is deleted.

Troubleshooting

”Partner or organization context required”

This error means the authenticated user is not associated with any partner or organisation. Ensure the user has been added to a partner (via partner_users) or an organisation (via organization_users).

”Full partner organization access required”

Partner-scoped users with orgAccess: selected must have access to all organisations under the partner to manage users and roles. Users with limited organisation access cannot administer other users.

”Cannot modify system roles” / “Cannot delete system roles”

System roles (created with isSystem: true) are immutable. To customise permissions, clone the system role with POST /roles/:id/clone and modify the clone.

”Cannot delete role with assigned users”

Re-assign all users from the role before deleting it. Use GET /roles/:id/users to find users assigned to the role, then POST /users/:id/role to move them.

”Cannot set parent role: would create circular inheritance”

A role’s parent cannot be one of its own descendants. Review the inheritance chain with GET /roles/:id/effective-permissions and choose a different parent.

”User already exists in this scope” (409)

The user (by email) is already linked to this partner or organisation. Use PATCH /users/:id or POST /users/:id/role to modify their existing membership.

MFA setup expires

The MFA setup data (TOTP secret and recovery codes) is stored in Redis with a 10-minute TTL. If the user does not confirm the setup within 10 minutes, they must restart with POST /auth/mfa/setup.

”Your organization requires MFA”

The organisation has settings.security.requireMfa enabled. MFA cannot be disabled by individual users while this policy is active. Contact an organisation administrator to change the policy.

”Your organization does not allow SMS MFA”

The organisation has restricted allowed MFA methods via settings.security.allowedMfaMethods.sms. Use TOTP instead, or ask an administrator to enable SMS MFA.

SSO login redirects to /login?error=...

Common SSO callback errors:

ErrorCause
session_expiredThe SSO session (state) expired. Login sessions are valid for 10 minutes.
provider_not_foundThe SSO provider was deleted or deactivated during the flow.
domain_not_allowedThe user’s email domain is not in the provider’s allowedDomains list.
user_not_foundThe user does not exist and autoProvision is disabled on the provider.
default_role_requiredAuto-provisioning is enabled but no defaultRoleId is configured.
no_org_accessThe user exists but is not a member of the SSO provider’s organisation.

Rate Limiting

EndpointLimit
Login (/auth/login)Per IP + email combination (configurable via loginLimiter).
MFA verificationPer user ID (configurable via mfaLimiter).
Forgot passwordPer IP (configurable via forgotPasswordLimiter).
Registration5 per IP per hour (3 for partner registration).
Phone verificationPer phone number and per user.
SMS send during loginPer tempToken and per phone number globally.

If you receive a 429 Too Many Requests response, wait for the retryAfter period specified in the response body.