Skip to content

User Management v1.0.0

Source: src/services/user.service.ts

Data Model

PolicyDefinition Usage

PurposevariantsubjectTypetargetType
Assign role to usergroupUserRole
Map user to organizergroupUserOrganizer
Map user to merchantgroupUserMerchant
Grant permission to rolepolicyRolePermission
Grant permission to userpolicyUserPermission

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 GROUP (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.