Skip to content

User Management v1.0.0

Source: src/services/user.service.ts

Data Model

PolicyDefinition Usage

PurposevariantsubjectTypetargetType
Assign role to userassign_roleUserRole
Map user to organizerjoin_domainUserOrganizer
Map user to merchantjoin_domainUserMerchant
Grant permission to rolegrantRolePermission
Grant permission to usergrantUserPermission

PolicyDefinition replaces the legacy UserRole and UserMapping tables with a single Casbin-style linking model.

UserService Dependencies

DependencyPurpose
JWKSIssuerTokenServiceJWT token operations
UserConfigurationServicePost-create default configs
UserRepositoryUser CRUD, transactions
UserCredentialRepositoryPassword storage
UserIdentifierRepositoryIdentifier uniqueness + CRUD
UserProfileRepositoryProfile CRUD
PolicyDefinitionRepositoryRole/org/merchant assignments

User Creation Flow

Identifier Verification on Creation

SchemeverifiedCan Sign-In Immediately
USERNAMEtrueYes
EMAILfalseNo - requires verification
PHONE_NUMBERfalseNo - requires verification

Credential Handling

  • If credential is provided → hashed with Bun.password.hash() (Argon2id), stored as BASIC scheme
  • If credential is omitted → step skipped entirely (e.g., customers without login)

User Update Flow

Updates are transactional with parallel sub-operations:

Identifier Update Logic

ActionDetail
AddNew identifiers not in DB → create with verified: false
RemoveDB identifiers not in request → soft-delete
UsernameNot updatable via this method

Role Update Logic

Diff-based against PolicyDefinition records:

  1. Fetch current assign_role (USER→ROLE) policies
  2. Add: roleIds in request but not in DB → create new policies
  3. Remove: policies in DB but roleId not in request → soft-delete

Employee Management

Source: src/services/employee.service.ts

Create Employee

ValidationCheckError
Organizer accessCurrentUser has PolicyDef mapping to organizerId403
Merchant ownershipAll merchantIds belong to the organizer403

Update Employee

  • Delegates user fields to UserService.updateById()
  • Merchant mappings: delete-all-then-recreate strategy via PolicyDefinitionRepository

Request Schemas

CreateEmployeeRequest = CreateUserRequest + { organizerId, merchantIds[] }

UpdateByIdEmployeeRequest = UpdateByIdUserRequest + { merchantIds[]? } (all partial)

Employee Controller Authorization

All operations are scoped via UserRepository.findIdsByPoliciesForCurrentUser():

MethodScoping
find / count / findOneFilter by policy-scoped employee IDs + optional organizerId/merchantIds query params
findById / deleteByIdVerify ID is in policy-scoped set
createValidates organizer ownership in EmployeeService
updateByIdValidates organizer ownership in EmployeeService

User Controller

RouteMethodDescription
/usersGETList users (paginated)
/users/:idGETGet by ID
/usersPOSTCreate → UserService.create()
/users/:idPATCHUpdate → UserService.updateById()
/users/:idDELETESoft-delete
/users/countGETCount
/users/profileGETCurrent user's profile
/users/profilePATCHUpdate current user's profile

Request Schemas

CreateUserRequest

FieldTypeRequiredValidation
usernamestringNo4-80 chars
credentialstringNo4-80 chars, PASSWORD_PATTERN
emailsstring[]Yesmin 1, valid email
phonesstring[]Yesmin 1, E.164
statusenumYesACTIVATED · DEACTIVATED · BLOCKED · UNKNOWN · ARCHIVED
profileobjectYesfirstName, lastName, birthday?, locale?
roleIdsstring[]Yesmin 1

UpdateByIdUserRequest (all fields .partial())

FieldTypeValidation
emailsstring[]min 1 if provided
phonesstring[]min 1 if provided
statusenumoptional
profileobjectpartial update
roleIdsstring[]min 1 if provided

UpdateUserProfileRequest (for PATCH /profile)

FieldType
emailsstring[]?
phonesstring[]?
statusenum?
profile{ firstName?, lastName?, birthday?, locale?, metadata? }

UserConfiguration Service

Source: src/services/user-configuration.service.ts

Called during _postCreate() after user creation:

UserConfiguration Controller

MethodPathDescription
CRUD/user-configurationsStandard operations
POST/user-configurations/viewsCreate config from template (validates code/name uniqueness per user)

Repositories

UserRepository

typescript
findIdsByPoliciesForCurrentUser(opts: {
  currentUserId: string;
  organizerId?: string;
  merchantIds?: string[];
  roleIdentifiers?: string[];
  transaction?: ITransaction;
}): Promise<string[]>

SQL logic: PolicyDefinition (currentUser → Organizer) → JOIN PolicyDefinition (other users → same Organizer), optional merchant/role filters → distinct User IDs.

UserIdentifierRepository

typescript
validateIdentifierUniqueness(opts: {
  identifiers: string[];
  scheme?: string;
  userId?: string;  // exclude own identifiers during update
  transaction?: ITransaction;
}): Promise<void>  // throws if duplicates found

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.