User Management — Practitioners, Staff and Accounts
Audiences: developer, internal
Dudoxx HMS manages all clinical and administrative identities — doctors, nurses, receptionists, patients, and platform administrators — through a unified
user-mgmtdomain that handles creation, role assignment, FHIR Practitioner synchronization, invitation flows, and lifecycle events within strict tenant boundaries.
Business Purpose
A multi-tenant clinical platform must answer: who can log in, what can they see, and in which clinic? User management solves all three. The user-mgmt domain handles: (1) CRUD for every account type in the system — SUPER_ADMIN to PATIENT; (2) FHIR Practitioner resource creation when a doctor or nurse joins a clinic (so the FHIR layer has the practitioner reference for appointments, observations, and prescriptions); (3) invitation-based onboarding so staff can join clinics without super-admin involvement; (4) bulk operations and statistics for ops dashboards. Patient and doctor registration are not separate subsystems — they converge here into a unified identity model gated by role assignment.
Audiences
- Investor: A single identity model across all eight roles eliminates the fragmented user silos typical of legacy EMR systems. Combined with RBAC, it supports the granular access control that hospital procurement requires.
- Clinical buyer (doctor/nurse/receptionist): Doctors exist simultaneously as a Prisma
User(with login credentials) and a FHIRPractitionerresource (with clinical reference IDs). Admins manage all staff from one panel without touching the FHIR server directly. - Developer/partner:
UserManagementServiceis a facade orchestrating six domain services. All admin user operations go throughPOST/GET/PATCH /admin/userswithX-Clinic-IDheader scoping. Patient registration goes throughPOST /patients(clinical module), not this module directly. - Internal (ops/support): Role hierarchy:
SUPER_ADMIN > ORG_ADMIN > CLINIC_ADMIN > DOCTOR/NURSE > RECEPTIONIST > ACCOUNTANT > STAFF > PATIENT. Password resets, status changes, and bulk deactivations all flow throughUserStatusService.
Architecture
user-mgmt/ ├── users/ User profile CRUD + avatar upload (MinIO) │ ├── users.service.ts Top-level user queries (profile, search) │ ├── user-avatar.service.ts MinIO avatar upload + presigned URL │ └── controllers/ │ ├── users-crud.controller.ts GET/PATCH /users (self-profile) │ └── users-ddxllm.controller.ts AI-context user descriptors │ ├── user-management/ Admin user operations (org-scoped) │ ├── user-management.service.ts Orchestrator facade (6 domain services) │ ├── practitioner.descriptor.ts FHIR Practitioner resource shape │ └── services/ │ ├── user-crud.service.ts Admin CRUD, pagination │ ├── user-role.service.ts Role assignment + FHIR Practitioner create │ ├── user-status.service.ts Status changes, password reset │ ├── user-bulk-operations.service.ts Bulk deactivate/reassign │ ├── user-invitation.service.ts Invite flow (email token) │ └── user-statistics.service.ts Activity log, stats │ ├── roles/ Role definitions (platform-scoped) ├── user-onboarding/ First-login wizard (profile completion) ├── user-settings/ TTS preferences, UI preferences ├── user-preferences/ Per-user notification + locale prefs └── demo-accounts/ Demo/seed account helpers (dev tenants)
Identity unification: patients and doctors are both User rows in ddx_api_main. What differentiates them is the UserRole assignment (via user_role_assignments join table) and the presence of a FHIR Practitioner or FHIR Patient resource. Patient registration (POST /patients) creates both the User row AND the FHIR Patient resource. Doctor registration creates both the User row AND the FHIR Practitioner resource via UserRoleService.
Tech Stack & Choices
| Concern | Choice | Rationale |
|---|---|---|
| DB storage | prisma-main User model | Unified table for all roles; organizationId scoping enforced by TenantIsolationInterceptor |
| Role management | user_role_assignments join table | Many-to-many so a user can hold roles in multiple clinics (e.g. DOCTOR at clinic A, ORG_ADMIN at clinic B) |
| FHIR sync | FhirClientService (via FHIR_CLIENT token) | Canonical FHIR client; UserRoleService calls it when assigning DOCTOR/NURSE role |
| Avatar upload | StorageService → MinIO {slug}-attachments bucket | Presigned upload URLs; same storage pattern as all other attachments |
| Invitation | Email token via MailModule | Time-limited invite link; UserInvitationService validates token on acceptance |
| Bulk ops | UserBulkOperationsService | Deactivate/reassign N users in one call; used by admin dashboards |
| Onboarding wizard | user-onboarding/ module | First-login profile completion flow; separate module, not part of the core user-management service |
Data Flow
Admin creates a doctor:
- Admin POSTs
AdminCreateUserDtotoPOST /admin/userswithX-Clinic-ID. TenantIsolationInterceptorvalidatesX-Clinic-ID→ injectsrequest.clinicId.UserManagementService.createUser()delegates toUserCrudService.createUser().UserCrudServicecreates theUserrow inddx_api_mainwith hashed password.UserRoleService.assignRole(DOCTOR)createsuser_role_assignmentrow.UserRoleServicecallsFHIR_CLIENT.create('Practitioner', ...)→ HAPI FHIR storesPractitionerresource in the clinic's partition.fhirPractitionerIdstored on theUserrow for downstream FHIR references.AuditService.log()records the creation event.
Invitation flow:
- Admin calls
POST /admin/users/invitewith email + desired role. UserInvitationServicegenerates a time-limited token and sends email viaMailModule.- Invitee clicks link →
POST /admin/users/invite/acceptwith token + password. UserInvitationServicevalidates token, callsUserCrudService.createUser()+UserRoleService.assignRole().
Implicated Code
ddx-api/src/user-mgmt/user-management/user-management.service.ts:41—UserManagementServiceorchestrator; 6 injected domain services at:44–51; all admin user operations delegated from hereddx-api/src/user-mgmt/user-management/services/user-role.service.ts:52—UserRoleService; FHIR Practitioner creation on DOCTOR/NURSE role assignmentddx-api/src/user-mgmt/user-management/services/user-crud.service.ts:44—UserCrudService;AdminCreateUserDto, pagination query withQueryUsersDtoddx-api/src/user-mgmt/user-management/services/user-status.service.ts:35—UserStatusService;ChangeUserStatusDto, password reset, deactivationddx-api/src/user-mgmt/user-management/services/user-invitation.service.ts:44—UserInvitationService;InviteUserDto, token generation and email dispatchddx-api/src/user-mgmt/users/users.service.ts—UsersService; self-profile queries (non-admin path)ddx-api/src/user-mgmt/roles/roles.service.ts—RolesService; platform-scoped role definitions (decorated@PlatformScoped()— noX-Clinic-IDrequired)ddx-api/src/user-mgmt/user-onboarding/user-onboarding.service.ts—UserOnboardingService; first-login wizard completion flow
Operational Notes
- RBAC bootstrap order: The
initseeder phase must run before user creation — it seedspermissionsandrole_permissionstables viaPOST /super-admin/permissions/seed. Without this,DynamicPermissionGuardhas no data to authorize against. - Patient registration drift note: Patients are NOT registered through this module's admin endpoint. They are created via
POST /patientsin theclinical/patientsmodule, which handles both theUserrow and the FHIRPatientresource. This module covers practitioners, staff, and administrative accounts. - Multi-clinic users: A user can belong to multiple clinics via
user_role_assignments.X-Clinic-IDheader on each request determines which clinic's data context applies. - Password storage: Passwords are bcrypt-hashed. Plain-text passwords never leave
UserStatusService. Default seed password isDudoxx123!(dev only — not for production). - Avatar upload:
UserAvatarServicegenerates a presigned MinIO URL; the client uploads directly to MinIO. The URL path is stored on theUserrow, not the binary. - Roles are
@PlatformScoped():RolesControllerbypassesX-Clinic-IDenforcement because role definitions are global, not per-clinic. The@PlatformScoped()decorator is applied at class level.
Related Topics
- RBAC / Roles — role hierarchy and permission grants;
initseeder seeds the permission catalog before user creation - Multi-Portal Architecture — each portal (doctor, nurse, receptionist, patient, admin) maps to a user role
- Organization Management — users are always scoped to an organization; org must exist before users are created
- Tenant Provisioner — tenant must be provisioned (FHIR partition + MinIO) before FHIR Practitioner resources can be created for new users