Registration & Onboarding Flow
This document describes how user registration and tenant onboarding work in the Ever Gauzy platform.
Overviewβ
Registration and onboarding are two separate processes:
- Registration creates a bare user account (no role, no tenant)
- Onboarding creates a tenant, assigns roles, and sets up the organization
The public registration endpoint (POST /api/auth/register) is shared across multiple scenarios β from public self-signup to admin-initiated user creation.
Registration Scenariosβ
Scenario 1: Public Self-Registrationβ
A new user visits the platform for the first time and registers via the UI.
Frontend flow:
- User navigates to
/auth/register - Fills in: full name, email, password, confirm password, accepts terms
NgxRegisterComponent(extends NebularNbRegisterComponent) callsAuthStrategy.register()
API call:
POST /api/auth/register
{
"password": "...",
"confirmPassword": "...",
"user": {
"firstName": "...",
"lastName": "...",
"email": "...",
"preferredLanguage": "en"
}
}
Backend processing:
AuthController.register()dispatchesAuthRegisterCommandAuthRegisterHandler.execute()checks for SUPER_ADMIN role (not present here, so skipped)AuthService.register()creates the user withroleId=NULL,tenantId=NULL- Returns the created user
After registration:
AuthStrategy.register()auto-logs in the user viathis.login()- Frontend redirects to
/which redirects to/pages PagesComponent/UserResolverdetectsuser.tenantId === null- Redirects to
/onboarding/tenant(see Onboarding section below)
Key files:
- UI:
packages/ui-auth/src/lib/components/register/register.component.ts - Auth strategy:
packages/ui-core/core/src/lib/services/auth/auth-strategy.service.ts(lines 114-163) - Controller:
packages/core/src/lib/auth/auth.controller.ts(lines 123-140) - Handler:
packages/core/src/lib/auth/commands/handlers/auth.register.handler.ts - Service:
packages/core/src/lib/auth/auth.service.ts(lines 664-756)
Scenario 2: Workspace Creation Flowβ
A newer self-contained registration flow used by the public layout UI plugin.
Frontend flow:
- User goes through the workspace creation wizard
- First verifies email via magic code
- Then fills in name + password + organization details
WorkspaceAuthService.onboardUser()orchestrates the entire process
API calls (sequential):
POST /api/auth/registerβ creates user (no role, no tenant)POST /api/auth/loginβ logs in, gets JWTPOST /api/tenantβ creates tenant + assigns SUPER_ADMIN roleGET /api/user/meβ fetches updated userPOST /api/auth/refresh-tokenβ refreshes JWT with tenant/role claimsPOST /api/organizationβ creates the organization
Key files:
- Workspace service:
packages/ui-core/core/src/lib/services/workspace/workspace-auth.service.ts(lines 59-169) - Workspace UI:
packages/plugins/public-layout-ui/src/lib/components/workspace-actions/workspace-create/workspace-create.component.ts
Scenario 3: Admin Creates User in Dashboardβ
An authenticated admin creates a user from the dashboard user management dialog.
Frontend flow:
- Admin opens user management dialog (
UserMutationComponent) - Fills in: name, email, password, role (dropdown), tags, avatar
BasicInfoFormComponent.createUser()callsauthService.register()with full details
API call:
POST /api/auth/register
Authorization: Bearer <admin-jwt>
{
"password": "...",
"confirmPassword": "...",
"organizationId": "<uuid>",
"createdByUserId": "<admin-user-uuid>",
"featureAsEmployee": true,
"user": {
"firstName": "...",
"lastName": "...",
"email": "...",
"role": { "id": "<role-uuid>", "name": "VIEWER", ... },
"tenant": { "id": "<tenant-uuid>", ... },
"tags": [...]
}
}
Backend processing:
AuthRegisterHandlerchecks if role is SUPER_ADMIN β if so, verifiescreatedByUserIdbelongs to an existing SUPER_ADMINAuthService.register()creates user with the specified role and tenant- If
featureAsEmployeeis true, also creates an Employee record - If
organizationIdis provided, adds user to that organization
Key files:
- Form component:
packages/ui-core/shared/src/lib/user/forms/basic-info/basic-info-form.component.ts(lines 180-249) - User mutation:
packages/ui-core/shared/src/lib/user/user-mutation/user-mutation.component.ts
Scenario 4: Desktop Appsβ
Desktop apps (timer, agent) do NOT support registration. The register method in desktop AuthStrategy is a stub that returns failure. Users are redirected to the web app to register.
Key file: packages/desktop-ui-lib/src/lib/auth/services/auth-strategy.service.ts (lines 79-85)
Scenario 5: Invite Acceptanceβ
When a user accepts an invite, the invite handler calls authService.register() directly β bypassing the controller and command handler entirely.
Flow:
- Invite is created by admin with a specific role and organization
- User clicks invite link, fills in password
- Invite acceptance handler creates the user via
authService.register()with role, tenant, and organization from the invite record
Key files:
packages/core/src/lib/invite/commands/handlers/invite.accept-user.handler.tspackages/core/src/lib/invite/commands/handlers/invite.accept-employee.handler.tspackages/core/src/lib/invite/commands/handlers/invite.accept-candidate.handler.tspackages/core/src/lib/invite/commands/handlers/invite.accept-organization-contact.handler.ts
Scenario 6: Cloud Migrationβ
The GauzyCloudService can migrate users to a remote Gauzy Cloud instance by calling POST /api/auth/register on the remote server.
Key file: packages/core/src/lib/gauzy-cloud/gauzy-cloud.service.ts (lines 27-33)
Tenant Onboardingβ
After public registration (Scenarios 1 & 2), the user has no tenant or role. The onboarding process creates these.
Legacy Onboarding Flow (Scenario 1)β
Frontend:
PagesComponentdetectsuser.tenantId === nullβ redirects to/onboarding/tenantOnboardingResolverconfirms user has no tenant β loadsTenantOnboardingComponent- User fills in organization details
TenantOnboardingComponent.onboardUser()executes:
Steps:
POST /api/tenantβ callsTenantService.onboardTenant()GET /api/user/meβ fetch updated userPOST /api/organizationβ create organization- Refresh JWT token
- Optionally register as employee
- Redirect to
/onboarding/complete
Key files:
- Pages redirect:
apps/gauzy/src/app/pages/pages.component.ts(lines 66-70, 393-397) - User resolver:
packages/ui-core/core/src/lib/resolvers/user.resolver.ts(lines 26-28) - Onboarding resolver:
packages/ui-core/core/src/lib/resolvers/onboarding.resolver.ts - Onboarding component:
packages/plugins/onboarding-ui/src/lib/components/tenant-onboarding/tenant-onboarding.component.ts(lines 56-85)
What TenantService.onboardTenant() Doesβ
This is where the SUPER_ADMIN role gets assigned. Located at packages/core/src/lib/tenant/tenant.service.ts (lines 41-73):
- Creates the tenant in the database
- Creates ALL roles for the new tenant (SUPER_ADMIN, ADMIN, DATA_ENTRY, EMPLOYEE, CANDIDATE, MANAGER, VIEWER, INTERVIEWER) via
TenantRoleBulkCreateCommand - Initializes tenant defaults (features, task statuses, task sizes, task priorities, issue types, settings)
- Finds the SUPER_ADMIN role for the new tenant
- Updates the user with the new tenant AND the SUPER_ADMIN role
Guard: The POST /api/tenant endpoint (TenantController.create()) checks:
if (user.tenantId || user.roleId) {
throw new BadRequestException("Tenant already exists");
}
This ensures only a fresh user (no existing tenant or role) can create a new tenant.
Workspace Onboarding Flow (Scenario 2)β
The WorkspaceAuthService.onboardUser() performs the same steps but in a single orchestrated flow β register, login, create tenant, refresh token, create organization β all sequentially from the frontend.
The AuthService.register() Methodβ
Located at packages/core/src/lib/auth/auth.service.ts (lines 664-756). This is the shared backend method used by all registration scenarios.
Steps:
- Determine tenant from
input.user.tenant(or fromcreatedByUserIdif provided) - Create user entity with
...input.userspread + tenant + hashed password - If
featureAsEmployeeβ create Employee record - If
inviteIdβ auto-verify email - Fetch user with role relation
- If
organizationIdβ add user to organization - If
isImportingβ create import record - If email not verified β send verification email
- Publish
AccountRegistrationEvent - Send welcome email
Role Hierarchyβ
Defined in packages/contracts/src/lib/role.model.ts:
| Role | Description |
|---|---|
SUPER_ADMIN | Bypasses all tenant and organization permission guards. Full system access. |
ADMIN | Tenant-level admin. Can manage users, roles, organizations within their tenant. |
MANAGER | Organization-level management capabilities. |
DATA_ENTRY | Data entry permissions. |
EMPLOYEE | Standard employee access. |
CANDIDATE | Limited access for job candidates. |
VIEWER | Read-only access. |
INTERVIEWER | Interview-related access. |
Each tenant gets its own set of roles with unique auto-generated UUIDs. Roles are created during tenant onboarding via RoleService.createBulk().
DTO Structureβ
Registration DTOsβ
RegisterUserDTO
βββ password: string (required)
βββ confirmPassword: string (required, must match password)
βββ user: CreateUserDTO (required, validated nested)
βββ email: string (required, from UserEmailDTO)
βββ firstName?: string (optional)
βββ lastName?: string (optional)
βββ imageUrl?: string (optional)
βββ preferredLanguage?: LanguagesEnum (optional)
βββ roleId?: string (optional, from PartialType(RoleFeatureDTO))
βββ role?: IRole (optional, from PartialType(RoleFeatureDTO))
The IUserRegistrationInput interface also includes top-level fields that are not declared on the DTO class but may be passed via ...input spread:
organizationId?: stringcreatedByUserId?: stringfeatureAsEmployee?: booleaninviteId?: stringisImporting?: booleansourceId?: string
Validationβ
IsRoleShouldExist Validatorβ
Located at packages/core/src/lib/shared/validators/constraints/role-should-exist.constraint.ts.
Validates that a roleId or role object references an existing role within the current tenant. Uses RequestContext.currentTenantId() to scope the query.
Used by:
RoleFeatureDTOβ inherited byCreateUserDTO(viaPartialType)CreateRolePermissionDTOCreateInviteDTO(viaIntersectionTypewithRoleFeatureDTO)
Security Considerationsβ
The @Public() Decoratorβ
The registration endpoint uses @Public() which bypasses the global AuthGuard. This means:
- No JWT verification occurs automatically
RequestContext.currentTenantId()returnsundefinedRequestContext.currentUserId()returnsundefined- But
RequestContext.currentToken()still extracts the Bearer token if one is provided in the Authorization header
Privileged Fieldsβ
When calling the register endpoint, certain fields carry security implications:
user.roleId/user.roleβ determines the user's access leveluser.tenant/user.tenantIdβ determines which tenant the user belongs tocreatedByUserIdβ influences tenant assignment logicorganizationIdβ adds user to a specific organization
These fields should only be accepted from authenticated, authorized callers (ADMIN or SUPER_ADMIN).
SUPER_ADMIN Guardβ
The AuthRegisterHandler includes a check: if the target role is SUPER_ADMIN, the createdByUserId must reference an existing SUPER_ADMIN user. This prevents unauthorized SUPER_ADMIN creation even from authenticated admin callers.