rl-core-api 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/core/auth/actor.interface.d.ts +4 -0
  2. package/dist/core/auth/actor.interface.js +2 -0
  3. package/dist/core/auth/permissionScope.util.d.ts +3 -0
  4. package/dist/core/auth/permissionScope.util.js +10 -0
  5. package/dist/core/config/env.schema.d.ts +1 -0
  6. package/dist/core/config/env.schema.js +1 -0
  7. package/dist/core/context/auditContext.d.ts +1 -0
  8. package/dist/core/context/auditContext.js +8 -1
  9. package/dist/core/database/migrations/1787270400000-GroupManager.d.ts +6 -0
  10. package/dist/core/database/migrations/1787270400000-GroupManager.js +26 -0
  11. package/dist/core/database/migrations/index.js +5 -1
  12. package/dist/core/events/appEvent.enum.d.ts +1 -0
  13. package/dist/core/filters/appErrorCode.enum.d.ts +3 -1
  14. package/dist/core/filters/appErrorCode.enum.js +2 -0
  15. package/dist/core/websocket/baseGateway.d.ts +1 -0
  16. package/dist/core/websocket/baseGateway.js +3 -0
  17. package/dist/features/audit/audit.module.js +1 -0
  18. package/dist/features/audit/domain/audit.service.d.ts +8 -1
  19. package/dist/features/audit/domain/audit.service.js +18 -1
  20. package/dist/features/audit/infra/auditData.subscriber.js +4 -3
  21. package/dist/features/audit/presentation/audit.interceptor.js +8 -2
  22. package/dist/features/audit/presentation/decorators/auditable.decorator.d.ts +8 -1
  23. package/dist/features/audit/presentation/decorators/auditable.decorator.js +4 -1
  24. package/dist/features/audit/presentation/index.d.ts +1 -0
  25. package/dist/features/audit/presentation/index.js +1 -0
  26. package/dist/features/audit/presentation/userRolesChanged.listener.d.ts +7 -0
  27. package/dist/features/audit/presentation/userRolesChanged.listener.js +43 -0
  28. package/dist/features/auth/domain/auth.service.d.ts +7 -4
  29. package/dist/features/auth/domain/auth.service.js +22 -4
  30. package/dist/features/auth/domain/twoFactor.service.d.ts +2 -0
  31. package/dist/features/auth/domain/twoFactor.service.js +7 -2
  32. package/dist/features/auth/presentation/auth.controller.d.ts +2 -5
  33. package/dist/features/auth/presentation/auth.controller.js +2 -1
  34. package/dist/features/notifications/presentation/domainEvents.listener.d.ts +5 -2
  35. package/dist/features/notifications/presentation/domainEvents.listener.js +24 -2
  36. package/dist/features/rbac/domain/rbac.catalog.js +31 -2
  37. package/dist/features/rbac/domain/rbac.service.d.ts +1 -0
  38. package/dist/features/rbac/domain/rbac.service.js +14 -0
  39. package/dist/features/rbac/domain/teamScope.service.d.ts +15 -0
  40. package/dist/features/rbac/domain/teamScope.service.js +74 -0
  41. package/dist/features/rbac/infra/repositories/group.repository.d.ts +2 -0
  42. package/dist/features/rbac/infra/repositories/group.repository.js +26 -0
  43. package/dist/features/rbac/infra/schema/group.schema.d.ts +1 -0
  44. package/dist/features/rbac/infra/schema/group.schema.js +5 -0
  45. package/dist/features/rbac/presentation/commands/createGroup.command.d.ts +1 -0
  46. package/dist/features/rbac/presentation/commands/createGroup.command.js +7 -0
  47. package/dist/features/rbac/presentation/rbac.controller.d.ts +1 -0
  48. package/dist/features/rbac/presentation/rbac.controller.js +11 -0
  49. package/dist/features/rbac/presentation/responses/rbac.response.d.ts +1 -0
  50. package/dist/features/rbac/presentation/responses/rbac.response.js +4 -0
  51. package/dist/features/rbac/rbac.module.js +3 -0
  52. package/dist/features/users/domain/users.service.d.ts +16 -8
  53. package/dist/features/users/domain/users.service.js +72 -14
  54. package/dist/features/users/infra/repositories/user.repository.d.ts +1 -1
  55. package/dist/features/users/infra/repositories/user.repository.js +9 -1
  56. package/dist/features/users/presentation/commands/createUser.command.d.ts +1 -0
  57. package/dist/features/users/presentation/commands/createUser.command.js +8 -0
  58. package/dist/features/users/presentation/filters/users.filters.js +8 -0
  59. package/dist/features/users/presentation/users.controller.d.ts +5 -5
  60. package/dist/features/users/presentation/users.controller.js +33 -27
  61. package/dist/features/users/users.module.js +2 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +6 -2
  64. package/package.json +1 -1
@@ -16,11 +16,13 @@ const event_emitter_1 = require("@nestjs/event-emitter");
16
16
  const appEvent_enum_1 = require("../../../core/events/appEvent.enum");
17
17
  const notificationType_enum_1 = require("../domain/enums/notificationType.enum");
18
18
  const scopeType_enum_1 = require("../domain/enums/scopeType.enum");
19
+ const notifications_gateway_1 = require("./notifications.gateway");
19
20
  const notifications_publisher_1 = require("./notifications.publisher");
20
21
  const ADMIN_ROLE = "Super Admin";
21
22
  let DomainEventsListener = DomainEventsListener_1 = class DomainEventsListener {
22
- constructor(publisher) {
23
+ constructor(publisher, gateway) {
23
24
  this.publisher = publisher;
25
+ this.gateway = gateway;
24
26
  this.logger = new common_1.Logger(DomainEventsListener_1.name);
25
27
  }
26
28
  async onUserLocked(event) {
@@ -48,6 +50,19 @@ let DomainEventsListener = DomainEventsListener_1 = class DomainEventsListener {
48
50
  targets: [{ type: scopeType_enum_1.ScopeType.USER, key: event.userId }],
49
51
  }));
50
52
  }
53
+ async onRolesChanged(event) {
54
+ await this.safePublish(appEvent_enum_1.AppEvent.USER_ROLES_CHANGED, async () => {
55
+ await this.publisher.publishAndPush({
56
+ title: "Suas permissões foram atualizadas",
57
+ body: event.roleNames.length
58
+ ? `Um administrador alterou seus papéis de acesso. Agora você é: ${event.roleNames.join(", ")}.`
59
+ : "Um administrador removeu seus papéis de acesso. Parte do sistema deixa de aparecer para você.",
60
+ type: notificationType_enum_1.NotificationType.INFO,
61
+ targets: [{ type: scopeType_enum_1.ScopeType.USER, key: event.userId }],
62
+ });
63
+ this.gateway.requestSessionRefresh(event.userId);
64
+ });
65
+ }
51
66
  async onPasswordResetRequested(event) {
52
67
  await this.safePublish(appEvent_enum_1.AppEvent.USER_PASSWORD_RESET_REQUESTED, () => this.publisher.publishAndPush({
53
68
  title: "Redefinição de senha solicitada",
@@ -97,6 +112,12 @@ __decorate([
97
112
  __metadata("design:paramtypes", [Object]),
98
113
  __metadata("design:returntype", Promise)
99
114
  ], DomainEventsListener.prototype, "onTwoFactorReset", null);
115
+ __decorate([
116
+ (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_ROLES_CHANGED),
117
+ __metadata("design:type", Function),
118
+ __metadata("design:paramtypes", [Object]),
119
+ __metadata("design:returntype", Promise)
120
+ ], DomainEventsListener.prototype, "onRolesChanged", null);
100
121
  __decorate([
101
122
  (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_PASSWORD_RESET_REQUESTED),
102
123
  __metadata("design:type", Function),
@@ -117,5 +138,6 @@ __decorate([
117
138
  ], DomainEventsListener.prototype, "onCleanupFailed", null);
118
139
  exports.DomainEventsListener = DomainEventsListener = DomainEventsListener_1 = __decorate([
119
140
  (0, common_1.Injectable)(),
120
- __metadata("design:paramtypes", [notifications_publisher_1.NotificationsPublisher])
141
+ __metadata("design:paramtypes", [notifications_publisher_1.NotificationsPublisher,
142
+ notifications_gateway_1.NotificationsGateway])
121
143
  ], DomainEventsListener);
@@ -62,6 +62,30 @@ exports.PERMISSIONS = [
62
62
  code: "users:invite:any",
63
63
  description: "Enviar link de primeiro acesso a qualquer usuário",
64
64
  },
65
+ {
66
+ code: "users:read:team",
67
+ description: "Ler usuários da própria equipe",
68
+ },
69
+ {
70
+ code: "users:create:team",
71
+ description: "Criar usuário dentro da própria equipe",
72
+ },
73
+ {
74
+ code: "users:update:team",
75
+ description: "Atualizar usuários da própria equipe",
76
+ },
77
+ {
78
+ code: "users:invite:team",
79
+ description: "Enviar link de primeiro acesso a usuários da própria equipe",
80
+ },
81
+ {
82
+ code: "users:reset-password:team",
83
+ description: "Resetar senha e 2FA de usuários da própria equipe",
84
+ },
85
+ {
86
+ code: "users:deactivate:team",
87
+ description: "Ativar/desativar usuários da própria equipe",
88
+ },
65
89
  { code: "roles:create:any", description: "Criar papéis" },
66
90
  { code: "roles:read:any", description: "Ler papéis" },
67
91
  { code: "roles:update:any", description: "Atualizar papéis" },
@@ -132,9 +156,14 @@ exports.ROLES = [
132
156
  },
133
157
  {
134
158
  name: "Manager",
135
- description: "Gestor de departamento",
159
+ description: "Gestor de equipe — administra quem está na árvore dele",
136
160
  permissions: [
137
- "users:read:any",
161
+ "users:read:team",
162
+ "users:create:team",
163
+ "users:update:team",
164
+ "users:invite:team",
165
+ "users:reset-password:team",
166
+ "users:deactivate:team",
138
167
  "reports:read:department",
139
168
  "reports:read:any",
140
169
  "audit:read:any",
@@ -59,6 +59,7 @@ export declare class RbacService {
59
59
  addPermissionToRole(roleId: string, permissionId: string, actorPermissions?: string[]): Promise<Role>;
60
60
  removePermissionFromRole(roleId: string, permissionId: string): Promise<Role>;
61
61
  listGroups(): Promise<Group[]>;
62
+ listManagedGroups(managerId: string): Promise<Group[]>;
62
63
  getGroup(id: string): Promise<Group>;
63
64
  createGroup(dto: CreateGroupCommand): Promise<Group>;
64
65
  updateGroup(id: string, dto: UpdateGroupCommand): Promise<Group>;
@@ -348,6 +348,16 @@ let RbacService = class RbacService {
348
348
  order: { name: "ASC" },
349
349
  });
350
350
  }
351
+ async listManagedGroups(managerId) {
352
+ const ids = await this.groupRepo.findManagedGroupIds(managerId);
353
+ if (ids.length === 0) {
354
+ return [];
355
+ }
356
+ return this.groupRepo.find({
357
+ where: { id: (0, typeorm_1.In)(ids) },
358
+ order: { name: "ASC" },
359
+ });
360
+ }
351
361
  async getGroup(id) {
352
362
  const group = await this.groupRepo.findOne({
353
363
  where: { id },
@@ -367,6 +377,7 @@ let RbacService = class RbacService {
367
377
  description: dto.description,
368
378
  type: dto.type ?? "team",
369
379
  parentId: dto.parentId ?? null,
380
+ managerId: dto.managerId ?? null,
370
381
  roles,
371
382
  });
372
383
  return this.groupRepo.save(group);
@@ -385,6 +396,9 @@ let RbacService = class RbacService {
385
396
  if (dto.parentId !== undefined) {
386
397
  group.parentId = dto.parentId;
387
398
  }
399
+ if (dto.managerId !== undefined) {
400
+ group.managerId = dto.managerId;
401
+ }
388
402
  if (dto.isActive !== undefined) {
389
403
  group.isActive = dto.isActive;
390
404
  }
@@ -0,0 +1,15 @@
1
+ import { Actor } from "../../../core/auth/actor.interface";
2
+ import { RbacService } from "./rbac.service";
3
+ import { GroupRepository } from "../infra/repositories/group.repository";
4
+ export declare class TeamScopeService {
5
+ private readonly groupRepo;
6
+ private readonly rbac;
7
+ constructor(groupRepo: GroupRepository, rbac: RbacService);
8
+ reachableUserIds(actor: Actor, action: string): Promise<string[] | null>;
9
+ managedGroupIds(managerId: string): Promise<string[]>;
10
+ private reachOf;
11
+ assertCanAct(actor: Actor, targetUserId: string, action: string): Promise<void>;
12
+ private assertActorOutranks;
13
+ assertOwnsGroups(actor: Actor, groupIds: string[]): Promise<void>;
14
+ private has;
15
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TeamScopeService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const permissionScope_util_1 = require("../../../core/auth/permissionScope.util");
15
+ const rbac_service_1 = require("./rbac.service");
16
+ const group_repository_1 = require("../infra/repositories/group.repository");
17
+ let TeamScopeService = class TeamScopeService {
18
+ constructor(groupRepo, rbac) {
19
+ this.groupRepo = groupRepo;
20
+ this.rbac = rbac;
21
+ }
22
+ async reachableUserIds(actor, action) {
23
+ if (this.has(actor, action, permissionScope_util_1.SCOPE_ANY)) {
24
+ return null;
25
+ }
26
+ if (!this.has(actor, action, permissionScope_util_1.SCOPE_TEAM)) {
27
+ return [];
28
+ }
29
+ const reach = await this.reachOf(actor.id);
30
+ return reach.userIds;
31
+ }
32
+ async managedGroupIds(managerId) {
33
+ return this.groupRepo.findManagedGroupIds(managerId);
34
+ }
35
+ async reachOf(managerId) {
36
+ const groupIds = await this.groupRepo.findManagedGroupIds(managerId);
37
+ const userIds = await this.groupRepo.findMemberIds(groupIds);
38
+ return { groupIds, userIds };
39
+ }
40
+ async assertCanAct(actor, targetUserId, action) {
41
+ const reach = await this.reachableUserIds(actor, action);
42
+ if (reach === null) {
43
+ return;
44
+ }
45
+ if (!reach.includes(targetUserId)) {
46
+ throw new common_1.ForbiddenException("Este usuário não faz parte da sua equipe");
47
+ }
48
+ await this.assertActorOutranks(actor, targetUserId);
49
+ }
50
+ async assertActorOutranks(actor, targetUserId) {
51
+ const targetPermissions = await this.rbac.getUserPermissionCodes(targetUserId);
52
+ const granted = new Set(actor.permissions);
53
+ const excess = targetPermissions.filter((code) => !granted.has(code));
54
+ if (excess.length > 0) {
55
+ throw new common_1.ForbiddenException("Este usuário tem permissões que você não possui — só um administrador pode administrá-lo");
56
+ }
57
+ }
58
+ async assertOwnsGroups(actor, groupIds) {
59
+ const managed = new Set(await this.managedGroupIds(actor.id));
60
+ const foreign = groupIds.filter((id) => !managed.has(id));
61
+ if (foreign.length > 0) {
62
+ throw new common_1.ForbiddenException("Você só pode incluir usuários em grupos que gerencia");
63
+ }
64
+ }
65
+ has(actor, action, scope) {
66
+ return actor.permissions.includes(`${action}:${scope}`);
67
+ }
68
+ };
69
+ exports.TeamScopeService = TeamScopeService;
70
+ exports.TeamScopeService = TeamScopeService = __decorate([
71
+ (0, common_1.Injectable)(),
72
+ __metadata("design:paramtypes", [group_repository_1.GroupRepository,
73
+ rbac_service_1.RbacService])
74
+ ], TeamScopeService);
@@ -2,4 +2,6 @@ import { DataSource, Repository } from "typeorm";
2
2
  import { Group } from "../schema/group.schema";
3
3
  export declare class GroupRepository extends Repository<Group> {
4
4
  constructor(dataSource: DataSource);
5
+ findManagedGroupIds(managerId: string): Promise<string[]>;
6
+ findMemberIds(groupIds: string[]): Promise<string[]>;
5
7
  }
@@ -17,6 +17,32 @@ let GroupRepository = class GroupRepository extends typeorm_1.Repository {
17
17
  constructor(dataSource) {
18
18
  super(group_schema_1.Group, dataSource.createEntityManager());
19
19
  }
20
+ async findManagedGroupIds(managerId) {
21
+ const rows = await this.query(`
22
+ WITH RECURSIVE team AS (
23
+ SELECT id FROM \`groups\`
24
+ WHERE manager_id = ? AND is_active = 1 AND deleted_at IS NULL
25
+ UNION ALL
26
+ SELECT child.id
27
+ FROM \`groups\` child
28
+ INNER JOIN team ON child.parent_id = team.id
29
+ WHERE child.is_active = 1 AND child.deleted_at IS NULL
30
+ )
31
+ SELECT id FROM team
32
+ `, [managerId]);
33
+ return rows.map((row) => row.id);
34
+ }
35
+ async findMemberIds(groupIds) {
36
+ if (groupIds.length === 0) {
37
+ return [];
38
+ }
39
+ const rows = await this.query(`
40
+ SELECT DISTINCT ug.user_id AS id
41
+ FROM user_groups ug
42
+ WHERE ug.group_id IN (?)
43
+ `, [groupIds]);
44
+ return rows.map((row) => row.id);
45
+ }
20
46
  };
21
47
  exports.GroupRepository = GroupRepository;
22
48
  exports.GroupRepository = GroupRepository = __decorate([
@@ -7,6 +7,7 @@ export declare class Group extends BaseEntity {
7
7
  type: GroupType;
8
8
  parentId?: string | null;
9
9
  parent?: Group | null;
10
+ managerId?: string | null;
10
11
  isActive: boolean;
11
12
  roles: Role[];
12
13
  }
@@ -41,6 +41,11 @@ __decorate([
41
41
  (0, typeorm_1.JoinColumn)({ name: "parent_id" }),
42
42
  __metadata("design:type", Object)
43
43
  ], Group.prototype, "parent", void 0);
44
+ __decorate([
45
+ (0, swagger_1.ApiProperty)({ required: false, nullable: true }),
46
+ (0, typeorm_1.Column)({ name: "manager_id", type: "char", length: 36, nullable: true }),
47
+ __metadata("design:type", Object)
48
+ ], Group.prototype, "managerId", void 0);
44
49
  __decorate([
45
50
  (0, swagger_1.ApiProperty)(),
46
51
  (0, typeorm_1.Column)({ name: "is_active", type: "boolean", default: true }),
@@ -3,5 +3,6 @@ export declare class CreateGroupCommand {
3
3
  description?: string;
4
4
  type?: "company" | "department" | "team";
5
5
  parentId?: string;
6
+ managerId?: string | null;
6
7
  roleIds?: string[];
7
8
  }
@@ -44,6 +44,13 @@ __decorate([
44
44
  (0, class_validator_1.IsUUID)(),
45
45
  __metadata("design:type", String)
46
46
  ], CreateGroupCommand.prototype, "parentId", void 0);
47
+ __decorate([
48
+ (0, swagger_1.ApiProperty)({ type: String, required: false, format: "uuid", nullable: true }),
49
+ (0, class_validator_1.IsOptional)(),
50
+ (0, class_validator_1.ValidateIf)((_, value) => value !== null),
51
+ (0, class_validator_1.IsUUID)(),
52
+ __metadata("design:type", Object)
53
+ ], CreateGroupCommand.prototype, "managerId", void 0);
47
54
  __decorate([
48
55
  (0, swagger_1.ApiProperty)({ required: false, type: [String], format: "uuid" }),
49
56
  (0, class_validator_1.IsOptional)(),
@@ -38,6 +38,7 @@ export declare class RbacController {
38
38
  updatePermission(id: string, dto: UpdatePermissionCommand): Promise<Permission>;
39
39
  deletePermission(id: string): Promise<void>;
40
40
  groups(): Promise<Group[]>;
41
+ managedGroups(actor: AuthenticatedUser): Promise<Group[]>;
41
42
  group(id: string): Promise<Group>;
42
43
  createGroup(dto: CreateGroupCommand): Promise<Group>;
43
44
  updateGroup(id: string, dto: UpdateGroupCommand): Promise<Group>;
@@ -88,6 +88,9 @@ let RbacController = class RbacController {
88
88
  groups() {
89
89
  return this.rbac.listGroups();
90
90
  }
91
+ managedGroups(actor) {
92
+ return this.rbac.listManagedGroups(actor.id);
93
+ }
91
94
  group(id) {
92
95
  return this.rbac.getGroup(id);
93
96
  }
@@ -281,6 +284,14 @@ __decorate([
281
284
  __metadata("design:paramtypes", []),
282
285
  __metadata("design:returntype", Promise)
283
286
  ], RbacController.prototype, "groups", null);
287
+ __decorate([
288
+ (0, common_1.Get)("groups/managed"),
289
+ (0, swagger_1.ApiOkResponse)({ type: rbac_response_1.GroupResponse, isArray: true }),
290
+ __param(0, (0, currentUser_decorator_1.CurrentUser)()),
291
+ __metadata("design:type", Function),
292
+ __metadata("design:paramtypes", [Object]),
293
+ __metadata("design:returntype", Promise)
294
+ ], RbacController.prototype, "managedGroups", null);
284
295
  __decorate([
285
296
  (0, common_1.Get)("groups/:id"),
286
297
  (0, requirePermission_decorator_1.RequirePermission)("groups:read:any"),
@@ -41,6 +41,7 @@ export declare class GroupResponse extends RbacTimestampsResponse {
41
41
  description?: string | null;
42
42
  type: string;
43
43
  parentId?: string | null;
44
+ managerId?: string | null;
44
45
  isActive: boolean;
45
46
  roles?: RoleResponse[];
46
47
  }
@@ -151,6 +151,10 @@ __decorate([
151
151
  (0, swagger_1.ApiProperty)({ type: String, required: false, nullable: true, format: "uuid" }),
152
152
  __metadata("design:type", Object)
153
153
  ], GroupResponse.prototype, "parentId", void 0);
154
+ __decorate([
155
+ (0, swagger_1.ApiProperty)({ type: String, required: false, nullable: true, format: "uuid" }),
156
+ __metadata("design:type", Object)
157
+ ], GroupResponse.prototype, "managerId", void 0);
154
158
  __decorate([
155
159
  (0, swagger_1.ApiProperty)(),
156
160
  __metadata("design:type", Boolean)
@@ -10,6 +10,7 @@ exports.RbacModule = void 0;
10
10
  const common_1 = require("@nestjs/common");
11
11
  const typeorm_1 = require("@nestjs/typeorm");
12
12
  const domain_1 = require("./domain");
13
+ const teamScope_service_1 = require("./domain/teamScope.service");
13
14
  const infra_1 = require("./infra");
14
15
  const presentation_1 = require("./presentation");
15
16
  const user_repository_1 = require("../users/infra/repositories/user.repository");
@@ -40,6 +41,7 @@ exports.RbacModule = RbacModule = __decorate([
40
41
  infra_1.GroupRepository,
41
42
  user_repository_1.UserRepository,
42
43
  domain_1.RbacService,
44
+ teamScope_service_1.TeamScopeService,
43
45
  ],
44
46
  exports: [
45
47
  infra_1.ResourceRepository,
@@ -50,6 +52,7 @@ exports.RbacModule = RbacModule = __decorate([
50
52
  infra_1.GroupRepository,
51
53
  user_repository_1.UserRepository,
52
54
  domain_1.RbacService,
55
+ teamScope_service_1.TeamScopeService,
53
56
  ],
54
57
  })
55
58
  ], RbacModule);
@@ -1,8 +1,11 @@
1
1
  import { EventEmitter2 } from "@nestjs/event-emitter";
2
+ import { Actor } from "../../../core/auth/actor.interface";
2
3
  import { EnvService } from "../../../core/config/env.service";
3
4
  import { MailerService } from "../../../core/mailer/mailer.service";
4
5
  import { Paginated, PaginationQuery } from "../../../core/utils/pagination.util";
5
6
  import { TokenService } from "../../auth/domain/token.service";
7
+ import { TeamScopeService } from "../../rbac/domain/teamScope.service";
8
+ import { GroupRepository } from "../../rbac/infra/repositories/group.repository";
6
9
  import { RoleRepository } from "../../rbac/infra/repositories/role.repository";
7
10
  import { PasswordHistoryRepository } from "../infra/repositories/passwordHistory.repository";
8
11
  import { UserRepository } from "../infra/repositories/user.repository";
@@ -14,28 +17,33 @@ export declare class UsersService {
14
17
  private readonly userRepo;
15
18
  private readonly historyRepo;
16
19
  private readonly roleRepo;
20
+ private readonly groupRepo;
21
+ private readonly teamScope;
17
22
  private readonly env;
18
23
  private readonly mailer;
19
24
  private readonly tokens;
20
25
  private readonly events;
21
- constructor(userRepo: UserRepository, historyRepo: PasswordHistoryRepository, roleRepo: RoleRepository, env: EnvService, mailer: MailerService, tokens: TokenService, events: EventEmitter2);
26
+ constructor(userRepo: UserRepository, historyRepo: PasswordHistoryRepository, roleRepo: RoleRepository, groupRepo: GroupRepository, teamScope: TeamScopeService, env: EnvService, mailer: MailerService, tokens: TokenService, events: EventEmitter2);
22
27
  findByEmail(email: string): Promise<User | null>;
23
28
  findById(id: string): Promise<User>;
24
- list(query: PaginationQuery, actorPermissions?: string[]): Promise<Paginated<User>>;
25
- create(dto: CreateUserCommand, actorPermissions?: string[]): Promise<User>;
26
- sendFirstAccessLink(id: string): Promise<void>;
27
- sendPasswordResetLink(id: string): Promise<void>;
29
+ findByIdForActor(id: string, actor: Actor): Promise<User>;
30
+ list(query: PaginationQuery, actor: Actor): Promise<Paginated<User>>;
31
+ create(dto: CreateUserCommand, actor: Actor): Promise<User>;
32
+ private resolveGroupsForCreate;
33
+ sendFirstAccessLink(id: string, actor: Actor): Promise<void>;
34
+ sendPasswordResetLink(id: string, actor: Actor): Promise<void>;
28
35
  private invalidateCurrentPassword;
29
36
  private issueAndSendFirstAccess;
30
37
  private findActiveForLink;
31
38
  private requirePasswordChange;
32
- update(id: string, dto: UpdateUserCommand, actorPermissions?: string[]): Promise<User>;
39
+ update(id: string, dto: UpdateUserCommand, actor: Actor): Promise<User>;
40
+ private emitIfRolesChanged;
33
41
  remove(id: string): Promise<void>;
34
- setActive(id: string, isActive: boolean): Promise<User>;
42
+ setActive(id: string, isActive: boolean, actor: Actor): Promise<User>;
35
43
  updateProfile(userId: string, dto: UpdateProfileCommand): Promise<User>;
36
44
  setAvatar(userId: string, avatarPath: string): Promise<User>;
37
45
  removeAvatar(userId: string): Promise<User>;
38
- adminResetTwoFactor(id: string): Promise<void>;
46
+ adminResetTwoFactor(id: string, actor: Actor): Promise<void>;
39
47
  changePassword(userId: string, currentPassword: string, newPassword: string): Promise<void>;
40
48
  assertPasswordNotReused(userId: string, newPassword: string): Promise<void>;
41
49
  savePasswordToHistory(userId: string, passwordHash: string): Promise<void>;
@@ -13,6 +13,7 @@ exports.UsersService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
14
  const event_emitter_1 = require("@nestjs/event-emitter");
15
15
  const typeorm_1 = require("typeorm");
16
+ const permissionScope_util_1 = require("../../../core/auth/permissionScope.util");
16
17
  const env_service_1 = require("../../../core/config/env.service");
17
18
  const appEvent_enum_1 = require("../../../core/events/appEvent.enum");
18
19
  const appErrorCode_enum_1 = require("../../../core/filters/appErrorCode.enum");
@@ -21,16 +22,20 @@ const codedError_util_1 = require("../../../core/utils/codedError.util");
21
22
  const hash_util_1 = require("../../../core/utils/hash.util");
22
23
  const pagination_util_1 = require("../../../core/utils/pagination.util");
23
24
  const token_service_1 = require("../../auth/domain/token.service");
25
+ const teamScope_service_1 = require("../../rbac/domain/teamScope.service");
26
+ const group_repository_1 = require("../../rbac/infra/repositories/group.repository");
24
27
  const role_repository_1 = require("../../rbac/infra/repositories/role.repository");
25
28
  const passwordHistory_repository_1 = require("../infra/repositories/passwordHistory.repository");
26
29
  const user_repository_1 = require("../infra/repositories/user.repository");
27
30
  const PASSWORD_HISTORY_LIMIT = 3;
28
31
  const PASSWORD_REUSED_MESSAGE = "Esta senha já foi utilizada. Escolha uma senha que você ainda não usou neste sistema";
29
32
  let UsersService = class UsersService {
30
- constructor(userRepo, historyRepo, roleRepo, env, mailer, tokens, events) {
33
+ constructor(userRepo, historyRepo, roleRepo, groupRepo, teamScope, env, mailer, tokens, events) {
31
34
  this.userRepo = userRepo;
32
35
  this.historyRepo = historyRepo;
33
36
  this.roleRepo = roleRepo;
37
+ this.groupRepo = groupRepo;
38
+ this.teamScope = teamScope;
34
39
  this.env = env;
35
40
  this.mailer = mailer;
36
41
  this.tokens = tokens;
@@ -49,11 +54,16 @@ let UsersService = class UsersService {
49
54
  }
50
55
  return user;
51
56
  }
52
- async list(query, actorPermissions) {
53
- const [items, total] = await this.userRepo.findPaginated(query, actorPermissions);
57
+ async findByIdForActor(id, actor) {
58
+ await this.teamScope.assertCanAct(actor, id, "users:read");
59
+ return this.findById(id);
60
+ }
61
+ async list(query, actor) {
62
+ const reachable = await this.teamScope.reachableUserIds(actor, "users:read");
63
+ const [items, total] = await this.userRepo.findPaginated(query, actor.permissions, reachable);
54
64
  return new pagination_util_1.Paginated(items, total, query.page, query.limit);
55
65
  }
56
- async create(dto, actorPermissions) {
66
+ async create(dto, actor) {
57
67
  const exists = await this.userRepo.findOne({
58
68
  where: { email: dto.email },
59
69
  withDeleted: true,
@@ -61,12 +71,13 @@ let UsersService = class UsersService {
61
71
  if (exists) {
62
72
  throw new common_1.ConflictException("Email já cadastrado");
63
73
  }
64
- if (dto.roleIds?.length && !actorPermissions?.includes("users:promote:any")) {
74
+ if (dto.roleIds?.length && !actor.permissions.includes("users:promote:any")) {
65
75
  throw new common_1.ForbiddenException("Definir papéis na criação de um usuário é restrito a administradores");
66
76
  }
67
77
  const roles = dto.roleIds?.length
68
78
  ? await this.roleRepo.findBy({ id: (0, typeorm_1.In)(dto.roleIds) })
69
79
  : [];
80
+ const groups = await this.resolveGroupsForCreate(dto, actor);
70
81
  const hashed = await (0, hash_util_1.hashPassword)((0, hash_util_1.randomToken)(48));
71
82
  const user = this.userRepo.create({
72
83
  name: dto.name,
@@ -78,18 +89,41 @@ let UsersService = class UsersService {
78
89
  mustChangePassword: true,
79
90
  isActive: dto.isActive ?? true,
80
91
  roles,
92
+ groups,
81
93
  });
82
94
  const saved = await this.userRepo.save(user);
83
95
  await this.savePasswordToHistory(saved.id, hashed);
84
96
  void this.issueAndSendFirstAccess(saved).catch(() => undefined);
85
97
  return this.findById(saved.id);
86
98
  }
87
- async sendFirstAccessLink(id) {
99
+ async resolveGroupsForCreate(dto, actor) {
100
+ const isAdmin = actor.permissions.includes("users:create:any");
101
+ if (dto.groupIds?.length) {
102
+ if (!isAdmin) {
103
+ await this.teamScope.assertOwnsGroups(actor, dto.groupIds);
104
+ }
105
+ return this.groupRepo.findBy({ id: (0, typeorm_1.In)(dto.groupIds) });
106
+ }
107
+ if (isAdmin) {
108
+ return [];
109
+ }
110
+ const managed = await this.teamScope.managedGroupIds(actor.id);
111
+ if (managed.length === 0) {
112
+ throw new common_1.ForbiddenException("Você não gerencia nenhum grupo — peça a um administrador para criar o usuário");
113
+ }
114
+ if (managed.length > 1) {
115
+ throw (0, codedError_util_1.codedBadRequest)("Informe em qual dos seus grupos o usuário entra", appErrorCode_enum_1.AppErrorCode.GROUP_REQUIRED);
116
+ }
117
+ return this.groupRepo.findBy({ id: (0, typeorm_1.In)(managed) });
118
+ }
119
+ async sendFirstAccessLink(id, actor) {
120
+ await this.teamScope.assertCanAct(actor, id, "users:invite");
88
121
  const user = await this.findActiveForLink(id);
89
122
  await this.issueAndSendFirstAccess(user);
90
123
  await this.requirePasswordChange(user);
91
124
  }
92
- async sendPasswordResetLink(id) {
125
+ async sendPasswordResetLink(id, actor) {
126
+ await this.teamScope.assertCanAct(actor, id, "users:reset-password");
93
127
  const user = await this.findActiveForLink(id);
94
128
  const token = await this.tokens.issuePasswordReset(user.id);
95
129
  await this.mailer.sendPasswordReset(user.email, token, user.name);
@@ -123,7 +157,9 @@ let UsersService = class UsersService {
123
157
  user.failedLoginAttempts = 0;
124
158
  await this.userRepo.save(user);
125
159
  }
126
- async update(id, dto, actorPermissions) {
160
+ async update(id, dto, actor) {
161
+ await this.teamScope.assertCanAct(actor, id, "users:update");
162
+ const actorPermissions = actor.permissions;
127
163
  const user = await this.findById(id);
128
164
  if (dto.email && dto.email !== user.email) {
129
165
  if (!actorPermissions?.includes("users:update-email:any")) {
@@ -137,12 +173,13 @@ let UsersService = class UsersService {
137
173
  throw new common_1.ConflictException("Email já cadastrado");
138
174
  }
139
175
  }
176
+ const canDeactivate = (0, permissionScope_util_1.anyOrTeam)("users:deactivate").some((code) => actorPermissions.includes(code));
140
177
  if (dto.isActive !== undefined &&
141
178
  dto.isActive !== user.isActive &&
142
- !actorPermissions?.includes("users:deactivate:any")) {
143
- throw new common_1.ForbiddenException("Ativar/desativar um usuário é restrito a administradores");
179
+ !canDeactivate) {
180
+ throw new common_1.ForbiddenException("Ativar/desativar um usuário exige permissão específica");
144
181
  }
145
- if (dto.roleIds && !actorPermissions?.includes("users:promote:any")) {
182
+ if (dto.roleIds && !actorPermissions.includes("users:promote:any")) {
146
183
  throw new common_1.ForbiddenException("Alterar papéis de um usuário é restrito a administradores");
147
184
  }
148
185
  Object.assign(user, {
@@ -152,17 +189,35 @@ let UsersService = class UsersService {
152
189
  email: dto.email ?? user.email,
153
190
  isActive: dto.isActive ?? user.isActive,
154
191
  });
192
+ const previousRoles = [...(user.roles ?? [])];
155
193
  if (dto.roleIds) {
156
194
  user.roles = await this.roleRepo.findBy({ id: (0, typeorm_1.In)(dto.roleIds) });
157
195
  }
158
196
  await this.userRepo.save(user);
159
- return this.findById(id);
197
+ const updated = await this.findById(id);
198
+ this.emitIfRolesChanged(previousRoles, updated);
199
+ return updated;
200
+ }
201
+ emitIfRolesChanged(previousRoles, user) {
202
+ const roles = user.roles ?? [];
203
+ const previousIds = new Set(previousRoles.map((role) => role.id));
204
+ const unchanged = roles.length === previousIds.size &&
205
+ roles.every((role) => previousIds.has(role.id));
206
+ if (unchanged) {
207
+ return;
208
+ }
209
+ this.events.emit(appEvent_enum_1.AppEvent.USER_ROLES_CHANGED, {
210
+ userId: user.id,
211
+ roleNames: roles.map((role) => role.name),
212
+ previousRoleNames: previousRoles.map((role) => role.name),
213
+ });
160
214
  }
161
215
  async remove(id) {
162
216
  const user = await this.findById(id);
163
217
  await this.userRepo.softRemove(user);
164
218
  }
165
- async setActive(id, isActive) {
219
+ async setActive(id, isActive, actor) {
220
+ await this.teamScope.assertCanAct(actor, id, "users:deactivate");
166
221
  const user = await this.findById(id);
167
222
  user.isActive = isActive;
168
223
  await this.userRepo.save(user);
@@ -190,7 +245,8 @@ let UsersService = class UsersService {
190
245
  await this.userRepo.save(user);
191
246
  return user;
192
247
  }
193
- async adminResetTwoFactor(id) {
248
+ async adminResetTwoFactor(id, actor) {
249
+ await this.teamScope.assertCanAct(actor, id, "users:reset-password");
194
250
  const user = await this.findById(id);
195
251
  user.twoFactorEnabled = false;
196
252
  user.twoFactorSecret = null;
@@ -284,6 +340,8 @@ exports.UsersService = UsersService = __decorate([
284
340
  __metadata("design:paramtypes", [user_repository_1.UserRepository,
285
341
  passwordHistory_repository_1.PasswordHistoryRepository,
286
342
  role_repository_1.RoleRepository,
343
+ group_repository_1.GroupRepository,
344
+ teamScope_service_1.TeamScopeService,
287
345
  env_service_1.EnvService,
288
346
  mailer_service_1.MailerService,
289
347
  token_service_1.TokenService,