Files
pos-system/services/iam-service/src/modules/rbac/rbac.service.ts
Ho Ngoc Hai 8cc2f66df6 Update IAM Service with various enhancements and fixes
- Added `xmlchars` dependency to `pnpm-lock.yaml` for improved XML character handling.
- Updated IAM Service audit plan to streamline post-deployment monitoring tasks.
- Enhanced Dockerfile to prune development dependencies after build for a leaner production image.
- Introduced a new encryption key configuration in the environment example for better security practices.
- Refactored multiple service files to improve import organization and maintainability.
- Improved error handling in seed scripts to provide more detailed logging on failures.
- Updated various controllers and services to ensure consistent import statements and enhance readability.

These changes aim to improve the overall functionality, security, and maintainability of the IAM Service.
2026-01-02 16:13:36 +07:00

319 lines
8.2 KiB
TypeScript

import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';
import { getPrismaClient } from '../../config/database.config';
import { cacheService } from '../../core/cache/cache.service';
import { RoleRepository, PermissionRepository } from '../../repositories/role.repository';
import { UserRepository } from '../../repositories/user.repository';
/**
* EN: RBAC Service for role-based access control
* VI: Service RBAC cho kiểm soát truy cập dựa trên vai trò
*/
export class RBACService {
private prisma: PrismaClient;
private userRepo: UserRepository;
private roleRepo: RoleRepository;
private permissionRepo: PermissionRepository;
constructor() {
this.prisma = getPrismaClient();
this.userRepo = new UserRepository(this.prisma);
this.roleRepo = new RoleRepository(this.prisma);
this.permissionRepo = new PermissionRepository(this.prisma);
}
/**
* EN: Check if user has specific permission
* VI: Kiểm tra người dùng có quyền cụ thể
*/
async hasPermission(
userId: string,
resource: string,
action: string,
scope?: string
): Promise<boolean> {
const cacheKey = cacheService.keys.userPermissions(userId);
// Try cache first
const cached = await cacheService.get<Record<string, boolean>>(cacheKey);
const permissionKey = `${resource}:${action}:${scope || 'all'}`;
if (cached && cached[permissionKey] !== undefined) {
return cached[permissionKey];
}
// Get user with permissions
const user = await this.userRepo.findWithPermissions(userId);
if (!user || !('userPermissions' in user)) {
return false;
}
// Check direct user permissions (highest priority)
const userPermission = (user as any).userPermissions.find(
(up: any) =>
up.permission.resource === resource &&
up.permission.action === action &&
(up.permission.scope === scope || (!up.permission.scope && !scope))
);
if (userPermission) {
const result = userPermission.granted; // Can be false to deny
await this.updatePermissionCache(userId, permissionKey, result);
return result;
}
// Check role permissions
const hasRolePermission = (user as any).userRoles.some((userRole: any) => {
if (userRole.expiresAt && userRole.expiresAt < new Date()) {
return false; // Role expired
}
return userRole.role.permissions.some(
(rp: any) =>
rp.permission.resource === resource &&
rp.permission.action === action &&
(rp.permission.scope === scope || (!rp.permission.scope && !scope))
);
});
await this.updatePermissionCache(userId, permissionKey, hasRolePermission);
return hasRolePermission;
}
/**
* EN: Get all permissions for user
* VI: Lấy tất cả quyền của người dùng
*/
async getUserPermissions(userId: string): Promise<string[]> {
const cacheKey = cacheService.keys.userPermissions(userId);
// Try cache
const cached = await cacheService.get<string[]>(cacheKey);
if (cached) {
return cached;
}
const user = await this.userRepo.findWithPermissions(userId);
if (!user) {
return [];
}
const permissions = new Set<string>();
// Add role permissions
(user as any).userRoles.forEach((userRole: any) => {
if (userRole.expiresAt && userRole.expiresAt < new Date()) {
return; // Skip expired roles
}
userRole.role.permissions.forEach((rp: any) => {
const key = `${rp.permission.resource}:${rp.permission.action}:${rp.permission.scope || 'all'}`;
permissions.add(key);
});
});
// Add/override with direct user permissions
(user as any).userPermissions.forEach((up: any) => {
if (up.expiresAt && up.expiresAt < new Date()) {
return; // Skip expired permissions
}
const key = `${up.permission.resource}:${up.permission.action}:${up.permission.scope || 'all'}`;
if (up.granted) {
permissions.add(key);
} else {
permissions.delete(key); // Deny permission
}
});
const permissionArray = Array.from(permissions);
// Cache for 5 minutes
await cacheService.set(cacheKey, permissionArray, 300);
return permissionArray;
}
/**
* EN: Get all roles for user
* VI: Lấy tất cả roles của người dùng
*/
async getUserRoles(userId: string): Promise<string[]> {
const cacheKey = cacheService.keys.userRoles(userId);
// Try cache
const cached = await cacheService.get<string[]>(cacheKey);
if (cached) {
return cached;
}
const user = await this.userRepo.findWithPermissions(userId);
if (!user) {
return [];
}
const roles = (user as any).userRoles
.filter((ur: any) => !ur.expiresAt || ur.expiresAt >= new Date())
.map((ur: any) => ur.role.name);
// Cache for 5 minutes
await cacheService.set(cacheKey, roles, 300);
return roles;
}
/**
* EN: Assign role to user
* VI: Gán role cho người dùng
*/
async assignRole(
userId: string,
roleId: string,
options?: {
grantedBy?: string;
expiresAt?: Date;
}
): Promise<void> {
await this.prisma.userRole.upsert({
where: {
userId_roleId: {
userId,
roleId,
},
},
create: {
userId,
roleId,
grantedBy: options?.grantedBy,
expiresAt: options?.expiresAt,
},
update: {
expiresAt: options?.expiresAt,
},
});
// Invalidate cache
await cacheService.delMany([
cacheService.keys.userPermissions(userId),
cacheService.keys.userRoles(userId),
]);
logger.info('Role assigned', { userId, roleId });
}
/**
* EN: Revoke role from user
* VI: Thu hồi role từ người dùng
*/
async revokeRole(userId: string, roleId: string): Promise<void> {
await this.prisma.userRole.deleteMany({
where: {
userId,
roleId,
},
});
// Invalidate cache
await cacheService.delMany([
cacheService.keys.userPermissions(userId),
cacheService.keys.userRoles(userId),
]);
logger.info('Role revoked', { userId, roleId });
}
/**
* EN: Grant permission to user
* VI: Cấp quyền cho người dùng
*/
async grantPermission(
userId: string,
permissionId: string,
options?: {
grantedBy?: string;
expiresAt?: Date;
}
): Promise<void> {
await this.prisma.userPermission.upsert({
where: {
userId_permissionId: {
userId,
permissionId,
},
},
create: {
userId,
permissionId,
granted: true,
grantedBy: options?.grantedBy,
expiresAt: options?.expiresAt,
},
update: {
granted: true,
expiresAt: options?.expiresAt,
},
});
// Invalidate cache
await cacheService.del(cacheService.keys.userPermissions(userId));
logger.info('Permission granted', { userId, permissionId });
}
/**
* EN: Deny permission to user
* VI: Từ chối quyền cho người dùng
*/
async denyPermission(
userId: string,
permissionId: string,
options?: {
grantedBy?: string;
}
): Promise<void> {
await this.prisma.userPermission.upsert({
where: {
userId_permissionId: {
userId,
permissionId,
},
},
create: {
userId,
permissionId,
granted: false,
grantedBy: options?.grantedBy,
},
update: {
granted: false,
},
});
// Invalidate cache
await cacheService.del(cacheService.keys.userPermissions(userId));
logger.info('Permission denied', { userId, permissionId });
}
/**
* EN: Update permission cache
* VI: Cập nhật cache permission
*/
private async updatePermissionCache(
userId: string,
permissionKey: string,
granted: boolean
): Promise<void> {
const cacheKey = cacheService.keys.userPermissions(userId);
const cached = await cacheService.get<Record<string, boolean>>(cacheKey);
if (cached) {
cached[permissionKey] = granted;
await cacheService.set(cacheKey, cached, 300);
}
}
}
export const rbacService = new RBACService();