616 lines
22 KiB
TypeScript
616 lines
22 KiB
TypeScript
import { Injectable, Logger, BadRequestException, ForbiddenException, NotFoundException, InternalServerErrorException } from '@nestjs/common';
|
|
import { KeycloakApiService } from '../../auth/services/keycloak-api.service';
|
|
import { CreateUserData as KeycloakCreateUserData, UserRole, KeycloakUser, LoginDto, TokenResponse, KeycloakRole } from '../../auth/services/keycloak-user.model';
|
|
import { CreateUserData, User, UserType } from '../models/hub-user.model';
|
|
|
|
// Configuration Centralisée
|
|
const SECURITY_CONFIG = {
|
|
ROLES: {
|
|
HUB: [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER],
|
|
MERCHANT: [
|
|
UserRole.DCB_PARTNER_ADMIN,
|
|
UserRole.DCB_PARTNER_MANAGER,
|
|
UserRole.DCB_PARTNER_SUPPORT,
|
|
]
|
|
},
|
|
VALIDATION: {
|
|
MIN_PASSWORD_LENGTH: 8,
|
|
MAX_USERNAME_LENGTH: 50,
|
|
MIN_USERNAME_LENGTH: 3
|
|
}
|
|
};
|
|
|
|
@Injectable()
|
|
export class HubUsersService {
|
|
private readonly logger = new Logger(HubUsersService.name);
|
|
|
|
constructor(private readonly keycloakApi: KeycloakApiService) {}
|
|
|
|
// === PUBLIC INTERFACE ===
|
|
|
|
async authenticateUser(loginDto: LoginDto): Promise<TokenResponse> {
|
|
return this.keycloakApi.authenticateUser(loginDto.username, loginDto.password);
|
|
}
|
|
|
|
async getCompleteUserProfile(userId: string, tokenUser: any) {
|
|
try {
|
|
const [userDetails, userRoles] = await Promise.all([
|
|
this.keycloakApi.getUserById(userId, userId),
|
|
this.keycloakApi.getUserClientRoles(userId)
|
|
]);
|
|
|
|
return this.buildUserProfile(userId, tokenUser, userDetails, userRoles);
|
|
} catch (error) {
|
|
throw new InternalServerErrorException('Could not retrieve user profile');
|
|
}
|
|
}
|
|
|
|
// === HUB USERS MANAGEMENT ===
|
|
|
|
async getAllHubUsers(requesterId: string): Promise<User[]> {
|
|
await this.validateHubUserAccess(requesterId);
|
|
return this.processUsersByType(await this.keycloakApi.getAllUsers(), UserType.HUB);
|
|
}
|
|
|
|
/**
|
|
* Récupère uniquement les utilisateurs DCB_PARTNER
|
|
*/
|
|
async getAllDcbPartners(requesterId: string): Promise<User[]> {
|
|
await this.validateHubUserAccess(requesterId);
|
|
|
|
const allUsers = await this.keycloakApi.getAllUsers();
|
|
const dcbPartners: User[] = [];
|
|
|
|
for (const user of allUsers) {
|
|
if (!user.id) continue;
|
|
|
|
try {
|
|
const userRoles = await this.keycloakApi.getUserClientRoles(user.id);
|
|
|
|
// Vérifier si l'utilisateur est un DCB_PARTNER
|
|
const isDcbPartner = userRoles.some(role =>
|
|
role.name === UserRole.DCB_PARTNER
|
|
);
|
|
|
|
if (isDcbPartner) {
|
|
const mappedUser = this.mapToUser(user, userRoles);
|
|
dcbPartners.push(mappedUser);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(`Could not process user ${user.id} for DCB_PARTNER filter: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
this.logger.log(`Retrieved ${dcbPartners.length} DCB_PARTNER users`);
|
|
|
|
return dcbPartners;
|
|
}
|
|
|
|
async getHubUserById(userId: string, requesterId: string): Promise<User> {
|
|
await this.validateHubUserAccess(requesterId);
|
|
return this.getValidatedUser(userId, requesterId, UserType.HUB);
|
|
}
|
|
|
|
async createHubUser(creatorId: string, userData: CreateUserData): Promise<User> {
|
|
this.validateUserCreationData(userData, UserType.HUB);
|
|
await this.validateHubUserAccess(creatorId);
|
|
await this.validateUserUniqueness(userData.username, userData.email);
|
|
|
|
const keycloakUserData = this.buildKeycloakUserData(userData);
|
|
const userId = await this.keycloakApi.createUser(creatorId, keycloakUserData);
|
|
|
|
this.logger.log(`Hub user created: ${userData.username}`);
|
|
return this.getHubUserById(userId, creatorId);
|
|
}
|
|
|
|
async updateHubUser(
|
|
userId: string,
|
|
updates: Partial<Pick<User, 'firstName' | 'lastName' | 'email' | 'enabled'>>,
|
|
requesterId: string
|
|
): Promise<User> {
|
|
await this.executeWithValidation(userId, requesterId, UserType.HUB, async () => {
|
|
await this.keycloakApi.updateUser(userId, updates, requesterId);
|
|
});
|
|
return this.getHubUserById(userId, requesterId);
|
|
}
|
|
|
|
async deleteHubUser(userId: string, requesterId: string): Promise<void> {
|
|
await this.executeWithValidation(userId, requesterId, UserType.HUB, async () => {
|
|
await this.keycloakApi.deleteUser(userId, requesterId);
|
|
});
|
|
this.logger.log(`Hub user deleted: ${userId} by ${requesterId}`);
|
|
}
|
|
|
|
async getHubUsersByRole(role: UserRole, requesterId: string): Promise<User[]> {
|
|
await this.validateHubUserAccess(requesterId);
|
|
const allHubUsers = await this.getAllHubUsers(requesterId);
|
|
return allHubUsers.filter(user => user.role === role);
|
|
}
|
|
|
|
async updateHubUserRole(userId: string, newRole: UserRole, requesterId: string): Promise<User> {
|
|
await this.validateRoleChangePermission(requesterId);
|
|
await this.executeWithValidation(userId, requesterId, UserType.HUB, async () => {
|
|
await this.keycloakApi.setClientRoles(userId, [newRole]);
|
|
});
|
|
return this.getHubUserById(userId, requesterId);
|
|
}
|
|
|
|
// === MERCHANT USERS MANAGEMENT ===
|
|
|
|
/**
|
|
* Vérifie si un utilisateur est Hub Admin ou Support
|
|
*/
|
|
async isUserHubAdminOrSupport(userId: string): Promise<boolean> {
|
|
try {
|
|
const userRoles = await this.keycloakApi.getUserClientRoles(userId);
|
|
const hubAdminSupportRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT];
|
|
|
|
return userRoles.some(role =>
|
|
hubAdminSupportRoles.includes(role.name as UserRole)
|
|
);
|
|
} catch (error) {
|
|
this.logger.error(`Error checking Hub Admin/Support status for user ${userId}:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Récupère les utilisateurs marchands selon les permissions de l'utilisateur
|
|
* - Hub Admin/Support: tous les utilisateurs marchands de tous les merchants
|
|
* - Autres: seulement les utilisateurs de leur propre merchant
|
|
*/
|
|
async getMyMerchantUsers(userId: string): Promise<User[]> {
|
|
try {
|
|
// Vérifier si l'utilisateur est un admin ou support Hub
|
|
const isHubAdminOrSupport = await this.isUserHubAdminOrSupport(userId)
|
|
|
|
if (isHubAdminOrSupport) {
|
|
// Hub Admin/Support peuvent voir TOUS les utilisateurs marchands
|
|
return await this.getAllMerchantUsersForHubAdmin(userId);
|
|
}
|
|
|
|
// Pour les autres utilisateurs (DCB_PARTNER, DCB_PARTNER_ADMIN, etc.)
|
|
return await this.getMerchantUsersForRegularUser(userId);
|
|
|
|
} catch (error) {
|
|
this.logger.error(`Error in getMyMerchantUsers for user ${userId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Récupère TOUS les utilisateurs marchands pour les Hub Admin/Support
|
|
*/
|
|
private async getAllMerchantUsersForHubAdmin(adminUserId: string): Promise<User[]> {
|
|
this.logger.log(`Hub Admin/Support ${adminUserId} accessing ALL merchant users`);
|
|
|
|
// Valider que l'utilisateur a bien les droits Hub
|
|
await this.validateHubUserAccess(adminUserId);
|
|
|
|
// Récupérer tous les utilisateurs du système
|
|
const allUsers = await this.keycloakApi.getAllUsers();
|
|
const merchantUsers: User[] = [];
|
|
|
|
// Filtrer pour ne garder que les utilisateurs marchands
|
|
for (const user of allUsers) {
|
|
if (!user.id) continue;
|
|
|
|
try {
|
|
const userRoles = await this.keycloakApi.getUserClientRoles(user.id);
|
|
|
|
// Vérifier si l'utilisateur a un rôle marchand
|
|
const hasMerchantRole = userRoles.some(role =>
|
|
[UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]
|
|
.includes(role.name as UserRole)
|
|
);
|
|
|
|
if (hasMerchantRole) {
|
|
const mappedUser = this.mapToUser(user, userRoles);
|
|
merchantUsers.push(mappedUser);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(`Could not process user ${user.id} for hub admin view: ${error.message}`);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
this.logger.log(`Hub Admin/Support retrieved ${merchantUsers.length} merchant users from all merchants`);
|
|
return merchantUsers;
|
|
}
|
|
|
|
/**
|
|
* Récupère les utilisateurs marchands pour les utilisateurs réguliers (non Hub Admin/Support)
|
|
*/
|
|
private async getMerchantUsersForRegularUser(userId: string): Promise<User[]> {
|
|
// Récupérer le merchantPartnerId de l'utilisateur
|
|
const userMerchantId = await this.getUserMerchantPartnerId(userId);
|
|
|
|
if (!userMerchantId) {
|
|
throw new BadRequestException('Current user is not associated with a merchant partner');
|
|
}
|
|
|
|
this.logger.log(`User ${userId} accessing merchant users for partner ${userMerchantId}`);
|
|
|
|
// Utiliser la méthode existante pour récupérer les utilisateurs du merchant spécifique
|
|
const users = await this.getMerchantUsersByPartner(userMerchantId, userId);
|
|
|
|
this.logger.log(`User ${userId} retrieved ${users.length} merchant users for partner ${userMerchantId}`);
|
|
return users;
|
|
}
|
|
|
|
async getMerchantUsersByPartner(merchantPartnerId: string, requesterId: string): Promise<User[]> {
|
|
await this.keycloakApi.validateUserAccess(requesterId, merchantPartnerId);
|
|
const allUsers = await this.keycloakApi.getAllUsers();
|
|
|
|
const merchantUsers = allUsers.filter(user =>
|
|
user.attributes?.merchantPartnerId?.[0] === merchantPartnerId
|
|
);
|
|
|
|
return this.processUsersByType(merchantUsers, UserType.MERCHANT_PARTNER);
|
|
}
|
|
|
|
async getMerchantUserById(userId: string, requesterId: string): Promise<User> {
|
|
return this.getValidatedUser(userId, requesterId, UserType.MERCHANT_PARTNER);
|
|
}
|
|
|
|
async createMerchantUser(creatorId: string, userData: CreateUserData): Promise<User> {
|
|
this.validateUserCreationData(userData, UserType.MERCHANT_PARTNER);
|
|
await this.validateMerchantUserCreation(creatorId, userData);
|
|
await this.validateUserUniqueness(userData.username, userData.email);
|
|
|
|
const keycloakUserData = this.buildKeycloakUserData(userData, userData.merchantPartnerId!);
|
|
const userId = await this.keycloakApi.createUser(creatorId, keycloakUserData);
|
|
|
|
this.logger.log(`Merchant user created: ${userData.username}`);
|
|
return this.getMerchantUserById(userId, creatorId);
|
|
}
|
|
|
|
async updateMerchantUser(
|
|
userId: string,
|
|
updates: Partial<Pick<User, 'firstName' | 'lastName' | 'email' | 'enabled'>>,
|
|
requesterId: string
|
|
): Promise<User> {
|
|
await this.executeWithValidation(userId, requesterId, UserType.MERCHANT_PARTNER, async () => {
|
|
await this.keycloakApi.updateUser(userId, updates, requesterId);
|
|
});
|
|
return this.getMerchantUserById(userId, requesterId);
|
|
}
|
|
|
|
async updateMerchantUserRole(userId: string, newRole: UserRole, requesterId: string): Promise<User> {
|
|
await this.validateRoleChangePermission(requesterId);
|
|
await this.executeWithValidation(userId, requesterId, UserType.MERCHANT_PARTNER, async () => {
|
|
await this.keycloakApi.setClientRoles(userId, [newRole]);
|
|
});
|
|
return this.getMerchantUserById(userId, requesterId);
|
|
}
|
|
|
|
async deleteMerchantUser(userId: string, requesterId: string): Promise<void> {
|
|
await this.validateSelfDeletion(userId, requesterId);
|
|
await this.executeWithValidation(userId, requesterId, UserType.MERCHANT_PARTNER, async () => {
|
|
await this.keycloakApi.deleteUser(userId, requesterId);
|
|
});
|
|
this.logger.log(`Merchant user deleted: ${userId} by ${requesterId}`);
|
|
}
|
|
|
|
// === COMMON OPERATIONS ===
|
|
|
|
async resetUserPassword(
|
|
userId: string,
|
|
newPassword: string,
|
|
temporary: boolean = true,
|
|
requesterId: string
|
|
): Promise<void> {
|
|
await this.ensureUserExists(userId, requesterId);
|
|
await this.keycloakApi.resetUserPassword(userId, newPassword, temporary);
|
|
this.logger.log(`Password reset for user: ${userId}`);
|
|
}
|
|
|
|
async getUserMerchantPartnerId(userId: string): Promise<string | null> {
|
|
return this.keycloakApi.getUserMerchantPartnerId(userId);
|
|
}
|
|
|
|
// === PRIVATE CORE METHODS ===
|
|
|
|
private async getValidatedUser(
|
|
userId: string,
|
|
requesterId: string,
|
|
userType: UserType.HUB | UserType.MERCHANT_PARTNER
|
|
): Promise<User> {
|
|
const [user, userRoles] = await Promise.all([
|
|
this.keycloakApi.getUserById(userId, requesterId),
|
|
this.keycloakApi.getUserClientRoles(userId)
|
|
]);
|
|
|
|
this.validateUserType(userRoles, userType, userId);
|
|
return this.mapToUser(user, userRoles);
|
|
}
|
|
|
|
private async processUsersByType(users: KeycloakUser[], userType: UserType.HUB | UserType.MERCHANT_PARTNER): Promise<User[]> {
|
|
const result: User[] = [];
|
|
|
|
for (const user of users) {
|
|
if (!user.id) continue;
|
|
|
|
try {
|
|
const userRoles = await this.keycloakApi.getUserClientRoles(user.id);
|
|
if (this.isUserType(userRoles, userType)) {
|
|
result.push(this.mapToUser(user, userRoles));
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(`Could not process user ${user.id}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async executeWithValidation(
|
|
userId: string,
|
|
requesterId: string,
|
|
userType: UserType.HUB | UserType.MERCHANT_PARTNER,
|
|
operation: () => Promise<void>
|
|
): Promise<void> {
|
|
await this.getValidatedUser(userId, requesterId, userType);
|
|
await operation();
|
|
}
|
|
|
|
// === USER MAPPING AND VALIDATION ===
|
|
|
|
private mapToUser(user: KeycloakUser, roles: KeycloakRole[]): User {
|
|
if (!user.id || !user.email) {
|
|
throw new Error('User ID and email are required');
|
|
}
|
|
|
|
const role = this.determineUserRole(roles);
|
|
const userType = this.determineUserType(roles);
|
|
|
|
return {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
firstName: user.firstName || '',
|
|
lastName: user.lastName || '',
|
|
role,
|
|
enabled: user.enabled,
|
|
emailVerified: user.emailVerified,
|
|
merchantPartnerId: user.attributes?.merchantPartnerId?.[0],
|
|
createdBy: user.attributes?.createdBy?.[0] || 'unknown',
|
|
createdByUsername: user.attributes?.createdByUsername?.[0] || 'unknown',
|
|
createdTimestamp: user.createdTimestamp || Date.now(),
|
|
lastLogin: this.parseTimestamp(user.attributes?.lastLogin),
|
|
userType,
|
|
};
|
|
}
|
|
|
|
private determineUserRole(roles: KeycloakRole[]): UserRole {
|
|
const allRoles = [...SECURITY_CONFIG.ROLES.HUB, ...SECURITY_CONFIG.ROLES.MERCHANT];
|
|
const userRole = roles.find(role => allRoles.includes(role.name as UserRole));
|
|
|
|
if (!userRole) {
|
|
throw new Error('No valid role found for user');
|
|
}
|
|
|
|
return userRole.name as UserRole;
|
|
}
|
|
|
|
private determineUserType(roles: KeycloakRole[]): UserType {
|
|
return roles.some(role => SECURITY_CONFIG.ROLES.HUB.includes(role.name as UserRole))
|
|
? UserType.HUB
|
|
: UserType.MERCHANT_PARTNER;
|
|
}
|
|
|
|
private isUserType(roles: KeycloakRole[], userType: UserType.HUB | UserType.MERCHANT_PARTNER): boolean {
|
|
const targetRoles = userType === UserType.HUB
|
|
? SECURITY_CONFIG.ROLES.HUB
|
|
: SECURITY_CONFIG.ROLES.MERCHANT;
|
|
|
|
return roles.some(role => targetRoles.includes(role.name as UserRole));
|
|
}
|
|
|
|
private validateUserType(roles: KeycloakRole[], expectedType: UserType.HUB | UserType.MERCHANT_PARTNER, userId: string): void {
|
|
if (!this.isUserType(roles, expectedType)) {
|
|
throw new BadRequestException(`User ${userId} is not a ${expectedType.toLowerCase()} user`);
|
|
}
|
|
}
|
|
|
|
// === VALIDATION METHODS ===
|
|
|
|
private async validateHubUserAccess(requesterId: string): Promise<void> {
|
|
const requesterRoles = await this.keycloakApi.getUserClientRoles(requesterId);
|
|
const hasHubAccess = requesterRoles.some(role =>
|
|
SECURITY_CONFIG.ROLES.HUB.includes(role.name as UserRole)
|
|
);
|
|
|
|
if (!hasHubAccess) {
|
|
throw new ForbiddenException('Only hub administrators can manage hub users');
|
|
}
|
|
}
|
|
|
|
|
|
private validateUserCreationData(userData: CreateUserData, userType: UserType.HUB | UserType.MERCHANT_PARTNER): void {
|
|
// 🔍 DEBUG COMPLET
|
|
this.logger.debug('🔍 === VALIDATION USER CREATION DATA ===');
|
|
this.logger.debug('UserType:', userType);
|
|
this.logger.debug('UserData complet:', JSON.stringify(userData, null, 2));
|
|
this.logger.debug('userData.role:', userData.role);
|
|
this.logger.debug('Type de role:', typeof userData.role);
|
|
this.logger.debug('Est un tableau?:', Array.isArray(userData.role));
|
|
this.logger.debug('Valeur brute role:', userData.role);
|
|
|
|
// Afficher les rôles valides configurés
|
|
const validRoles = userType === UserType.HUB
|
|
? SECURITY_CONFIG.ROLES.HUB
|
|
: SECURITY_CONFIG.ROLES.MERCHANT;
|
|
|
|
this.logger.debug('Rôles valides pour', userType, ':', validRoles);
|
|
this.logger.debug('merchantPartnerId:', userData.merchantPartnerId);
|
|
this.logger.debug('====================================');
|
|
|
|
// Validation des rôles
|
|
if (!validRoles.includes(userData.role)) {
|
|
console.error(`❌ Rôle invalide: ${userData.role} pour le type ${userType}`);
|
|
console.error(`Rôles autorisés: ${validRoles.join(', ')}`);
|
|
throw new BadRequestException(`Invalid ${userType.toLowerCase()} role: ${userData.role}`);
|
|
}
|
|
|
|
// Validation merchantPartnerId pour HUB
|
|
if (userType === UserType.HUB && userData.merchantPartnerId) {
|
|
console.error('❌ merchantPartnerId fourni pour un utilisateur HUB');
|
|
throw new BadRequestException('merchantPartnerId should not be provided for hub users');
|
|
}
|
|
|
|
// Validation merchantPartnerId pour MERCHANT
|
|
// Vérifier d'abord si role est un tableau ou une valeur simple
|
|
const isDCBPartner = Array.isArray(userData.role)
|
|
? userData.role.includes(UserRole.DCB_PARTNER)
|
|
: userData.role === UserRole.DCB_PARTNER;
|
|
|
|
this.logger.debug('Est DCB_PARTNER?:', isDCBPartner);
|
|
|
|
if (userType === UserType.MERCHANT_PARTNER && !userData.merchantPartnerId && !isDCBPartner) {
|
|
console.error('❌ merchantPartnerId manquant pour un utilisateur MERCHANT');
|
|
throw new BadRequestException('merchantPartnerId is required for merchant users');
|
|
}
|
|
|
|
this.logger.debug('✅ Validation réussie');
|
|
}
|
|
|
|
private async validateUserUniqueness(username: string, email: string): Promise<void> {
|
|
const [existingUsers, existingEmails] = await Promise.all([
|
|
this.keycloakApi.findUserByUsername(username),
|
|
this.keycloakApi.findUserByEmail(email)
|
|
]);
|
|
|
|
if (existingUsers.length > 0) {
|
|
throw new BadRequestException(`User with username ${username} already exists`);
|
|
}
|
|
if (existingEmails.length > 0) {
|
|
throw new BadRequestException(`User with email ${email} already exists`);
|
|
}
|
|
}
|
|
|
|
private async validateRoleChangePermission(requesterId: string): Promise<void> {
|
|
const requesterRoles = await this.keycloakApi.getUserClientRoles(requesterId);
|
|
const isRequesterAdmin = requesterRoles.some(role => role.name === UserRole.DCB_ADMIN || UserRole.DCB_PARTNER || UserRole.DCB_PARTNER_ADMIN);
|
|
|
|
if (!isRequesterAdmin) {
|
|
throw new ForbiddenException('Only DCB_ADMIN can change user roles');
|
|
}
|
|
}
|
|
|
|
private async validateSelfDeletion(userId: string, requesterId: string): Promise<void> {
|
|
if (userId === requesterId) {
|
|
throw new BadRequestException('Cannot delete your own account');
|
|
}
|
|
}
|
|
|
|
private async ensureUserExists(userId: string, requesterId: string): Promise<void> {
|
|
try {
|
|
await this.getHubUserById(userId, requesterId);
|
|
} catch {
|
|
try {
|
|
await this.getMerchantUserById(userId, requesterId);
|
|
} catch {
|
|
throw new NotFoundException(`User ${userId} not found`);
|
|
}
|
|
}
|
|
}
|
|
|
|
private buildUserProfile(
|
|
userId: string,
|
|
tokenUser: any,
|
|
userDetails: KeycloakUser,
|
|
userRoles: KeycloakRole[]
|
|
) {
|
|
return {
|
|
id: userId,
|
|
username: tokenUser.preferred_username,
|
|
email: tokenUser.email,
|
|
firstName: tokenUser.given_name,
|
|
lastName: tokenUser.family_name,
|
|
emailVerified: tokenUser.email_verified,
|
|
enabled: userDetails.enabled,
|
|
clientRoles: userRoles.map(role => role.name),
|
|
merchantPartnerId: userDetails.attributes?.merchantPartnerId?.[0],
|
|
createdBy: userDetails.attributes?.createdBy?.[0],
|
|
createdByUsername: userDetails.attributes?.createdByUsername?.[0]
|
|
};
|
|
}
|
|
|
|
private buildKeycloakUserData(
|
|
userData: CreateUserData,
|
|
merchantPartnerId?: string
|
|
): KeycloakCreateUserData {
|
|
return {
|
|
username: userData.username,
|
|
email: userData.email,
|
|
firstName: userData.firstName,
|
|
lastName: userData.lastName,
|
|
password: userData.password,
|
|
enabled: userData.enabled ?? true,
|
|
emailVerified: userData.emailVerified ?? false,
|
|
merchantPartnerId,
|
|
clientRoles: [userData.role]
|
|
};
|
|
}
|
|
|
|
private parseTimestamp(value: string[] | undefined): number | undefined {
|
|
const strValue = value?.[0];
|
|
if (!strValue) return undefined;
|
|
|
|
const timestamp = parseInt(strValue);
|
|
if (!isNaN(timestamp)) return timestamp;
|
|
|
|
const date = new Date(strValue);
|
|
return isNaN(date.getTime()) ? undefined : date.getTime();
|
|
}
|
|
|
|
private async validateMerchantUserCreation(creatorId: string, userData: CreateUserData): Promise<void> {
|
|
const creatorRoles = await this.keycloakApi.getUserClientRoles(creatorId);
|
|
const creationRules = this.getMerchantCreationRules();
|
|
|
|
for (const rule of creationRules) {
|
|
if (creatorRoles.some(role => role.name === rule.role)) {
|
|
await rule.validator(creatorId, userData);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Vérifier les permissions des administrateurs Hub
|
|
if (creatorRoles.some(role => [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT].includes(role.name as UserRole))) {
|
|
return;
|
|
}
|
|
|
|
throw new ForbiddenException('Insufficient permissions to create merchant users');
|
|
}
|
|
|
|
private getMerchantCreationRules() {
|
|
return [
|
|
{
|
|
role: UserRole.DCB_PARTNER,
|
|
validator: async (creatorId: string, userData: CreateUserData) => {
|
|
if (creatorId !== userData.merchantPartnerId) {
|
|
throw new ForbiddenException('DCB_PARTNER can only create users for their own merchant');
|
|
}
|
|
const allowedRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
|
if (!allowedRoles.includes(userData.role)) {
|
|
throw new ForbiddenException('DCB_PARTNER can only create MANAGER and SUPPORT roles');
|
|
}
|
|
}
|
|
},
|
|
{
|
|
role: UserRole.DCB_PARTNER_ADMIN,
|
|
validator: async (creatorId: string, userData: CreateUserData) => {
|
|
const creatorMerchantId = await this.keycloakApi.getUserMerchantPartnerId(creatorId);
|
|
if (creatorMerchantId !== userData.merchantPartnerId) {
|
|
throw new ForbiddenException('DCB_PARTNER_ADMIN can only create users for their own merchant partner');
|
|
}
|
|
const allowedRoles = [UserRole.DCB_PARTNER_SUPPORT];
|
|
if (!allowedRoles.includes(userData.role)) {
|
|
throw new ForbiddenException('DCB_PARTNER_ADMIN can only create SUPPORT roles');
|
|
}
|
|
}
|
|
}
|
|
];
|
|
}
|
|
} |