- 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.
319 lines
8.2 KiB
TypeScript
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();
|