st
staging
Support guide Account setup tool

Domain join & license access

How users with a corporate email domain discover and join an existing license, and how SSO can link them directly. Logic lives in the PHP api and surfaces in web-presentations.

Account owner (app definition)

The license owner is the user on the account with role = admin and createdByUserId IS NULL (User::findAccountOwner). This may differ from accounts.accountOwner and from your support contact.

How accounts become join-eligible

Accounts::getJoinEligibleAccounts() returns targets when all of the following hold:

  • User domain is not marked generic in domains.
  • Account has allowsJoinRequests = 1.
  • Match via owner email domain (plan with REQUEST_LICENSE_ACCESS or legacy paid: users > 1 and audienceSize > maxFreeAudienceSize from the checking user’s branding) or account_domains row.
  • Account owner has a non-expired session (owner-match path only).

isAutomaticallyJoinable does not affect discovery — only which email/path runs after the user is eligible.

UserLicenseFlow cases

Returned to web-presentations as userLicenseFlow from GET users/flow.

CaseMeaning
NOT_ELIGIBLENo matching accounts or generic email domain
MULTIPLE_ELIGIBLETwo or more joinable accounts — user picks in web-presentations
LICENSE_ELIGIBLEOne account; plan has LICENSE_AUTO_APPROVAL
LICENSE_REQUEST_ACCESS_ELIGIBLEOne account; plan lacks auto-approval feature
LICENSE_PENDINGUser has unused account_activation_tokens row

Join flows

Signup → domain join funnel

New email/password users with a matching domain may receive a join email at signup. Redirect params: welcome, license-eligible, or multi-license.

api/common/components/signup/SignupComponent.php

Self-join via verification email

When LICENSE_AUTO_APPROVAL + isAutomaticallyJoinable + seats available, user gets join-license link. Click adds them via addFreeUserToExistingAccount.

api/api/controllers/LicenseController.php → actionJoinLicense

Admin approval path

When auto-join conditions fail or isAutomaticallyJoinable is off, user confirms request-license-access link; admin accepts in team UI.

api/common/models/Accounts.php → acceptJoinRequest

SSO auto-join

identity_provider_identifiers.accountId in OAuth state → addExistingUser on callback. Bypasses domain eligibility checks.

api/common/modules/identityprovider/components/IdentityProviderCognito.php

Database flags & settings

SettingDefaultSet byEffect
accounts.allowsJoinRequests1Account admin (if plan allows)Master switch for domain join discovery
accounts.isAutomaticallyJoinable0Support (st-admin)Self-verify email vs admin-approval email
accounts.domainCheck0Account admin + planRestrict add-user emails (with TOGGLE_DOMAIN_CHECK feature)
account_domains.domainSupport (st-admin)Extra join-eligible domains + domain-check exceptions
domains.isGeneric0Support (st-admin)1 = public provider — blocks join discovery
Plan: REQUEST_LICENSE_ACCESSper planPlans & featuresRequired for join via plan; enables admin toggle
Plan: LICENSE_AUTO_APPROVALper planPlans & featuresLICENSE_ELIGIBLE case; part of auto-verify path
Plan: TOGGLE_DOMAIN_CHECKper planPlans & featuresMakes domainCheck effective
identity_provider_identifiers.accountIdnullSupport (st-admin)SSO users auto-join this account

Auto-join decision (email path)

LicenseController::attemptToRequestLicenseAccess sends a self-join verification email when all are true:

canAutoAcceptUsers()          // plan feature LICENSE_AUTO_APPROVAL
isAutomaticallyJoinable       // accounts column
!getReachedMaximumUsers()     // seats available

Otherwise the user gets a request-access link; the admin must accept via acceptJoinRequest.

SSO (separate fast path)

When Cognito OAuth state contains accountId (from identity_provider_identifiers), the authorize callback calls addExistingUser immediately — no domain eligibility, no verification email.

  • fullEmailDomain — matches user email domain to IdP
  • accountId — target account for auto-join
  • exclusions — comma-separated emails blocked from SSO URL
  • location — label shown on login picker

Domain check (add-user restriction)

Separate from self-join. When domainCheck = 1 and plan has TOGGLE_DOMAIN_CHECK, admins can only add users whose email domain matches the owner or appears in account_domains (User::compareEmails).

web-presentations routes

/license-eligible     → verification email sent overlay
/multi-license        → pick from multiple accounts
/join-license         → welcome after successful email join
/requested-access     → admin request confirmed
/request-access-failed

st-admin API (support tooling)

GET  /api/account-join/accounts/{id}
PUT  /api/account-join/accounts/{id}
POST /api/account-join/accounts/{id}/domains
DELETE /api/account-join/accounts/{id}/domains/{domain}
GET  /api/account-join/domains/{id}
PUT  /api/account-join/domains/{id}
PUT  /api/account-join/identity-provider-identifiers/{id}
GET  /api/account-join/preview?email=user@company.com