rl-core-api 0.4.0 → 0.6.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 (66) 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 +7 -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/notifications.module.js +1 -1
  35. package/dist/features/notifications/presentation/domainEvents.listener.d.ts +5 -2
  36. package/dist/features/notifications/presentation/domainEvents.listener.js +24 -2
  37. package/dist/features/rbac/domain/rbac.catalog.js +31 -2
  38. package/dist/features/rbac/domain/rbac.service.d.ts +1 -0
  39. package/dist/features/rbac/domain/rbac.service.js +14 -0
  40. package/dist/features/rbac/domain/teamScope.service.d.ts +15 -0
  41. package/dist/features/rbac/domain/teamScope.service.js +74 -0
  42. package/dist/features/rbac/infra/repositories/group.repository.d.ts +2 -0
  43. package/dist/features/rbac/infra/repositories/group.repository.js +26 -0
  44. package/dist/features/rbac/infra/schema/group.schema.d.ts +1 -0
  45. package/dist/features/rbac/infra/schema/group.schema.js +5 -0
  46. package/dist/features/rbac/presentation/commands/createGroup.command.d.ts +1 -0
  47. package/dist/features/rbac/presentation/commands/createGroup.command.js +7 -0
  48. package/dist/features/rbac/presentation/rbac.controller.d.ts +1 -0
  49. package/dist/features/rbac/presentation/rbac.controller.js +11 -0
  50. package/dist/features/rbac/presentation/responses/rbac.response.d.ts +1 -0
  51. package/dist/features/rbac/presentation/responses/rbac.response.js +4 -0
  52. package/dist/features/rbac/rbac.module.js +3 -0
  53. package/dist/features/users/domain/users.service.d.ts +16 -8
  54. package/dist/features/users/domain/users.service.js +72 -14
  55. package/dist/features/users/infra/repositories/user.repository.d.ts +1 -1
  56. package/dist/features/users/infra/repositories/user.repository.js +9 -1
  57. package/dist/features/users/presentation/commands/createUser.command.d.ts +1 -0
  58. package/dist/features/users/presentation/commands/createUser.command.js +8 -0
  59. package/dist/features/users/presentation/filters/users.filters.js +8 -0
  60. package/dist/features/users/presentation/users.controller.d.ts +5 -5
  61. package/dist/features/users/presentation/users.controller.js +33 -27
  62. package/dist/features/users/users.module.js +2 -0
  63. package/dist/index.d.ts +6 -0
  64. package/dist/index.js +14 -2
  65. package/dist/rlCore.module.js +1 -0
  66. package/package.json +1 -1
@@ -0,0 +1,4 @@
1
+ export interface Actor {
2
+ id: string;
3
+ permissions: string[];
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export declare const SCOPE_ANY = "any";
2
+ export declare const SCOPE_TEAM = "team";
3
+ export declare const anyOrTeam: (action: string) => [string, string];
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.anyOrTeam = exports.SCOPE_TEAM = exports.SCOPE_ANY = void 0;
4
+ exports.SCOPE_ANY = "any";
5
+ exports.SCOPE_TEAM = "team";
6
+ const anyOrTeam = (action) => [
7
+ `${action}:${exports.SCOPE_ANY}`,
8
+ `${action}:${exports.SCOPE_TEAM}`,
9
+ ];
10
+ exports.anyOrTeam = anyOrTeam;
@@ -28,6 +28,7 @@ export declare const envSchema: z.ZodObject<{
28
28
  JWT_ACCESS_SECRET: z.ZodString;
29
29
  JWT_PENDING_SECRET: z.ZodString;
30
30
  JWT_ACCESS_EXPIRES: z.ZodDefault<z.ZodString>;
31
+ JWT_PENDING_EXPIRES: z.ZodDefault<z.ZodString>;
31
32
  JWT_REFRESH_EXPIRES_MINUTES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
32
33
  TOTP_ENC_KEY: z.ZodString;
33
34
  COOKIE_SECURE: z.ZodDefault<z.ZodPipe<z.ZodEnum<{
@@ -31,6 +31,7 @@ exports.envSchema = zod_1.z
31
31
  JWT_ACCESS_SECRET: zod_1.z.string().min(32),
32
32
  JWT_PENDING_SECRET: zod_1.z.string().min(32),
33
33
  JWT_ACCESS_EXPIRES: zod_1.z.string().default("4h"),
34
+ JWT_PENDING_EXPIRES: zod_1.z.string().default("15m"),
34
35
  JWT_REFRESH_EXPIRES_MINUTES: zod_1.z.coerce
35
36
  .number()
36
37
  .int()
@@ -5,3 +5,4 @@ export interface AuditContextStore {
5
5
  requestId?: string | null;
6
6
  }
7
7
  export declare const auditContext: AsyncLocalStorage<AuditContextStore>;
8
+ export declare const setAuditUser: (userId: string) => void;
@@ -1,5 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.auditContext = void 0;
3
+ exports.setAuditUser = exports.auditContext = void 0;
4
4
  const node_async_hooks_1 = require("node:async_hooks");
5
5
  exports.auditContext = new node_async_hooks_1.AsyncLocalStorage();
6
+ const setAuditUser = (userId) => {
7
+ const store = exports.auditContext.getStore();
8
+ if (store) {
9
+ store.userId = userId;
10
+ }
11
+ };
12
+ exports.setAuditUser = setAuditUser;
@@ -0,0 +1,6 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+ export declare class GroupManager1787270400000 implements MigrationInterface {
3
+ name: string;
4
+ up(queryRunner: QueryRunner): Promise<void>;
5
+ down(queryRunner: QueryRunner): Promise<void>;
6
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GroupManager1787270400000 = void 0;
4
+ class GroupManager1787270400000 {
5
+ constructor() {
6
+ this.name = "GroupManager1787270400000";
7
+ }
8
+ async up(queryRunner) {
9
+ await queryRunner.query(`
10
+ ALTER TABLE \`groups\`
11
+ ADD COLUMN manager_id char(36) NULL AFTER parent_id,
12
+ ADD KEY IDX_groups_manager (manager_id),
13
+ ADD CONSTRAINT FK_groups_manager FOREIGN KEY (manager_id)
14
+ REFERENCES users (id) ON DELETE SET NULL;
15
+ `);
16
+ }
17
+ async down(queryRunner) {
18
+ await queryRunner.query(`
19
+ ALTER TABLE \`groups\`
20
+ DROP FOREIGN KEY FK_groups_manager,
21
+ DROP KEY IDX_groups_manager,
22
+ DROP COLUMN manager_id;
23
+ `);
24
+ }
25
+ }
26
+ exports.GroupManager1787270400000 = GroupManager1787270400000;
@@ -2,4 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.coreMigrations = void 0;
4
4
  const _1710000000000_InitialSchema_1 = require("./1710000000000-InitialSchema");
5
- exports.coreMigrations = [_1710000000000_InitialSchema_1.InitialSchema1710000000000];
5
+ const _1787270400000_GroupManager_1 = require("./1787270400000-GroupManager");
6
+ exports.coreMigrations = [
7
+ _1710000000000_InitialSchema_1.InitialSchema1710000000000,
8
+ _1787270400000_GroupManager_1.GroupManager1787270400000,
9
+ ];
@@ -18,6 +18,7 @@ export interface UserTwoFactorResetEvent {
18
18
  export interface UserRolesChangedEvent {
19
19
  userId: string;
20
20
  roleNames: string[];
21
+ previousRoleNames: string[];
21
22
  }
22
23
  export interface UserPasswordResetRequestedEvent {
23
24
  userId: string;
@@ -3,7 +3,9 @@ export declare enum AppErrorCode {
3
3
  ACCOUNT_INACTIVE = "ACCOUNT_INACTIVE",
4
4
  INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
5
5
  INVALID_TWO_FACTOR_CODE = "INVALID_TWO_FACTOR_CODE",
6
+ PENDING_TOKEN_EXPIRED = "PENDING_TOKEN_EXPIRED",
6
7
  PASSWORD_REUSED = "PASSWORD_REUSED",
7
8
  TOO_MANY_REQUESTS = "TOO_MANY_REQUESTS",
8
- INVALID_FILTER = "INVALID_FILTER"
9
+ INVALID_FILTER = "INVALID_FILTER",
10
+ GROUP_REQUIRED = "GROUP_REQUIRED"
9
11
  }
@@ -7,7 +7,9 @@ var AppErrorCode;
7
7
  AppErrorCode["ACCOUNT_INACTIVE"] = "ACCOUNT_INACTIVE";
8
8
  AppErrorCode["INVALID_CREDENTIALS"] = "INVALID_CREDENTIALS";
9
9
  AppErrorCode["INVALID_TWO_FACTOR_CODE"] = "INVALID_TWO_FACTOR_CODE";
10
+ AppErrorCode["PENDING_TOKEN_EXPIRED"] = "PENDING_TOKEN_EXPIRED";
10
11
  AppErrorCode["PASSWORD_REUSED"] = "PASSWORD_REUSED";
11
12
  AppErrorCode["TOO_MANY_REQUESTS"] = "TOO_MANY_REQUESTS";
12
13
  AppErrorCode["INVALID_FILTER"] = "INVALID_FILTER";
14
+ AppErrorCode["GROUP_REQUIRED"] = "GROUP_REQUIRED";
13
15
  })(AppErrorCode || (exports.AppErrorCode = AppErrorCode = {}));
@@ -16,6 +16,7 @@ export declare abstract class BaseGateway implements OnGatewayConnection, OnGate
16
16
  constructor(wsAuth: WsAuthService, env: EnvService);
17
17
  handleConnection(socket: Socket): Promise<void>;
18
18
  handleDisconnect(socket: Socket): void;
19
+ requestSessionRefresh(userId: string): void;
19
20
  protected emitTo<T>(rooms: string[], event: string, data: T): void;
20
21
  private scheduleExpiry;
21
22
  }
@@ -40,12 +40,19 @@ class BaseGateway {
40
40
  }
41
41
  socket.removeAllListeners();
42
42
  }
43
+ requestSessionRefresh(userId) {
44
+ this.emitTo([(0, socket_constants_1.userRoom)(userId)], socket_constants_1.SESSION_REFRESH_EVENT, { userId });
45
+ }
43
46
  emitTo(rooms, event, data) {
44
47
  const targets = rooms.filter((room) => room.length > 0);
45
48
  if (targets.length === 0) {
46
49
  this.logger.warn(`Evento ${event} descartado: sem sala de destino`);
47
50
  return;
48
51
  }
52
+ if (!this.server) {
53
+ this.logger.warn(`Evento ${event} descartado: servidor de socket não inicializado`);
54
+ return;
55
+ }
49
56
  this.server.to(targets).emit(event, (0, socketEvent_envelope_1.wrapSocketEvent)(event, data));
50
57
  }
51
58
  scheduleExpiry(socket, expiresAt) {
@@ -29,6 +29,7 @@ exports.AuditModule = AuditModule = __decorate([
29
29
  domain_1.AuditService,
30
30
  { provide: core_1.APP_INTERCEPTOR, useClass: presentation_1.AuditInterceptor },
31
31
  presentation_1.HttpErrorListener,
32
+ presentation_1.UserRolesChangedListener,
32
33
  infra_1.AuditDataSubscriber,
33
34
  ],
34
35
  exports: [
@@ -1,7 +1,7 @@
1
1
  import { Paginated, PaginationQuery } from "../../../core/utils/pagination.util";
2
2
  import { AuditDataChangeRepository } from "../infra/repositories/auditDataChange.repository";
3
3
  import { RequestLogRepository } from "../infra/repositories/requestLog.repository";
4
- import { AuditDataChange } from "../infra/schema/auditDataChange.schema";
4
+ import { AuditDataAction, AuditDataChange } from "../infra/schema/auditDataChange.schema";
5
5
  import { RequestLog } from "../infra/schema/requestLog.schema";
6
6
  import { UserRepository } from "../../users/infra/repositories/user.repository";
7
7
  export type WithUserRef<T> = T & {
@@ -19,6 +19,13 @@ export declare class AuditService {
19
19
  private readonly logger;
20
20
  constructor(requestLogRepo: RequestLogRepository, dataChangeRepo: AuditDataChangeRepository, userRepo: UserRepository);
21
21
  recordRequest(data: Partial<RequestLog>): Promise<void>;
22
+ recordChange(data: {
23
+ entity: string;
24
+ entityId: string | null;
25
+ action: AuditDataAction;
26
+ oldData: Record<string, unknown> | null;
27
+ newData: Record<string, unknown> | null;
28
+ }): Promise<void>;
22
29
  listRequests(query: PaginationQuery): Promise<Paginated<WithUserRef<RequestLog>>>;
23
30
  listDataChanges(query: PaginationQuery): Promise<Paginated<AuditDataChangeWithUser>>;
24
31
  private enrichWithUser;
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.AuditService = void 0;
14
14
  const common_1 = require("@nestjs/common");
15
15
  const typeorm_1 = require("typeorm");
16
+ const auditContext_1 = require("../../../core/context/auditContext");
16
17
  const pagination_util_1 = require("../../../core/utils/pagination.util");
17
18
  const auditDataChange_repository_1 = require("../infra/repositories/auditDataChange.repository");
18
19
  const requestLog_repository_1 = require("../infra/repositories/requestLog.repository");
@@ -32,6 +33,19 @@ let AuditService = AuditService_1 = class AuditService {
32
33
  this.logger.warn(`Falha ao gravar RequestLog: ${err.message}`);
33
34
  }
34
35
  }
36
+ async recordChange(data) {
37
+ try {
38
+ const ctx = auditContext_1.auditContext.getStore();
39
+ await this.dataChangeRepo.save(this.dataChangeRepo.create({
40
+ ...data,
41
+ userId: ctx?.userId ?? null,
42
+ ipAddress: ctx?.ipAddress ?? null,
43
+ }));
44
+ }
45
+ catch (err) {
46
+ this.logger.warn(`Falha ao gravar AuditDataChange (${data.entity}): ${err.message}`);
47
+ }
48
+ }
35
49
  async listRequests(query) {
36
50
  const [items, total] = await this.requestLogRepo.findPaginated(query);
37
51
  return new pagination_util_1.Paginated(await this.enrichWithUser(items), total, query.page, query.limit);
@@ -56,7 +70,10 @@ let AuditService = AuditService_1 = class AuditService {
56
70
  const user = item.userId ? usersById.get(item.userId) : undefined;
57
71
  return {
58
72
  ...item,
59
- user: user ? { id: user.id, name: user.name, email: user.email } : null,
73
+ user: user
74
+ ?
75
+ { id: user.id, name: user.fullName, email: user.email }
76
+ : null,
60
77
  };
61
78
  });
62
79
  }
@@ -26,6 +26,7 @@ function entityIdOf(source) {
26
26
  return null;
27
27
  }
28
28
  const EXCLUDED_ENTITIES = new Set([requestLog_schema_1.RequestLog, auditDataChange_schema_1.AuditDataChange, authToken_schema_1.AuthToken, passwordHistory_schema_1.PasswordHistory].map((entity) => entity.name));
29
+ const isJunction = (metadata) => metadata.isJunction;
29
30
  const BASE_FIELDS = new Set(["id", "createdAt", "updatedAt", "deletedAt"]);
30
31
  const SENSITIVE_FIELDS = {
31
32
  User: new Set(["password", "twoFactorSecret", "backupCodes"]),
@@ -47,7 +48,7 @@ let AuditDataSubscriber = AuditDataSubscriber_1 = class AuditDataSubscriber {
47
48
  dataSource.subscribers.push(this);
48
49
  }
49
50
  async afterInsert(event) {
50
- if (EXCLUDED_ENTITIES.has(event.metadata.name)) {
51
+ if (isJunction(event.metadata) || EXCLUDED_ENTITIES.has(event.metadata.name)) {
51
52
  return;
52
53
  }
53
54
  await this.persist(event.metadata, event.manager, {
@@ -58,7 +59,7 @@ let AuditDataSubscriber = AuditDataSubscriber_1 = class AuditDataSubscriber {
58
59
  });
59
60
  }
60
61
  async afterUpdate(event) {
61
- if (EXCLUDED_ENTITIES.has(event.metadata.name)) {
62
+ if (isJunction(event.metadata) || EXCLUDED_ENTITIES.has(event.metadata.name)) {
62
63
  return;
63
64
  }
64
65
  if (!event.updatedColumns.length) {
@@ -92,7 +93,7 @@ let AuditDataSubscriber = AuditDataSubscriber_1 = class AuditDataSubscriber {
92
93
  await this.recordRemoval(event);
93
94
  }
94
95
  async recordRemoval(event) {
95
- if (EXCLUDED_ENTITIES.has(event.metadata.name)) {
96
+ if (isJunction(event.metadata) || EXCLUDED_ENTITIES.has(event.metadata.name)) {
96
97
  return;
97
98
  }
98
99
  await this.persist(event.metadata, event.manager, {
@@ -39,7 +39,9 @@ let AuditInterceptor = class AuditInterceptor {
39
39
  res.setHeader("X-Request-Id", requestId);
40
40
  req.requestId = requestId;
41
41
  const auditable = this.reflector.getAllAndOverride(auditable_decorator_1.AUDITABLE_KEY, [context.getHandler(), context.getClass()]);
42
- const action = typeof auditable === "string" ? auditable : auditable ? `${method} ${req.path}` : null;
42
+ const action = auditable
43
+ ? (auditable.action ?? `${method} ${req.path}`)
44
+ : null;
43
45
  const logContext = { action, startedAt: start, userId: user?.id ?? null };
44
46
  (0, requestLog_context_1.setRequestLogContext)(req, logContext);
45
47
  res.once("finish", () => {
@@ -47,7 +49,11 @@ let AuditInterceptor = class AuditInterceptor {
47
49
  if (res.statusCode >= 400) {
48
50
  return;
49
51
  }
50
- if (!action && durationMs < this.env.get("AUDIT_SLOW_REQUEST_MS")) {
52
+ const slow = durationMs >= this.env.get("AUDIT_SLOW_REQUEST_MS");
53
+ if (!action && !slow) {
54
+ return;
55
+ }
56
+ if (action && !slow && !auditable.allowSystem && !logContext.userId) {
51
57
  return;
52
58
  }
53
59
  void this.audit.recordRequest({
@@ -1,3 +1,10 @@
1
1
  import { CustomDecorator } from "@nestjs/common";
2
2
  export declare const AUDITABLE_KEY = "auditable";
3
- export declare const Auditable: (action?: string) => CustomDecorator<string>;
3
+ export interface AuditableOptions {
4
+ allowSystem?: boolean;
5
+ }
6
+ export interface AuditableMetadata {
7
+ action: string | null;
8
+ allowSystem: boolean;
9
+ }
10
+ export declare const Auditable: (action?: string, options?: AuditableOptions) => CustomDecorator<string>;
@@ -3,5 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Auditable = exports.AUDITABLE_KEY = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
5
  exports.AUDITABLE_KEY = "auditable";
6
- const Auditable = (action) => (0, common_1.SetMetadata)(exports.AUDITABLE_KEY, action ?? true);
6
+ const Auditable = (action, options) => (0, common_1.SetMetadata)(exports.AUDITABLE_KEY, {
7
+ action: action ?? null,
8
+ allowSystem: options?.allowSystem ?? false,
9
+ });
7
10
  exports.Auditable = Auditable;
@@ -2,3 +2,4 @@ export * from "./audit.controller";
2
2
  export * from "./audit.interceptor";
3
3
  export * from "./decorators/auditable.decorator";
4
4
  export * from "./httpError.listener";
5
+ export * from "./userRolesChanged.listener";
@@ -18,3 +18,4 @@ __exportStar(require("./audit.controller"), exports);
18
18
  __exportStar(require("./audit.interceptor"), exports);
19
19
  __exportStar(require("./decorators/auditable.decorator"), exports);
20
20
  __exportStar(require("./httpError.listener"), exports);
21
+ __exportStar(require("./userRolesChanged.listener"), exports);
@@ -0,0 +1,7 @@
1
+ import { UserRolesChangedEvent } from "../../../core/events/appEvent.enum";
2
+ import { AuditService } from "../domain/audit.service";
3
+ export declare class UserRolesChangedListener {
4
+ private readonly audit;
5
+ constructor(audit: AuditService);
6
+ onRolesChanged(event: UserRolesChangedEvent): Promise<void>;
7
+ }
@@ -0,0 +1,43 @@
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.UserRolesChangedListener = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const event_emitter_1 = require("@nestjs/event-emitter");
15
+ const appEvent_enum_1 = require("../../../core/events/appEvent.enum");
16
+ const audit_service_1 = require("../domain/audit.service");
17
+ const auditDataChange_schema_1 = require("../infra/schema/auditDataChange.schema");
18
+ const asLabel = (names) => names.length ? [...names].sort().join(", ") : "—";
19
+ let UserRolesChangedListener = class UserRolesChangedListener {
20
+ constructor(audit) {
21
+ this.audit = audit;
22
+ }
23
+ async onRolesChanged(event) {
24
+ await this.audit.recordChange({
25
+ entity: "User",
26
+ entityId: event.userId,
27
+ action: auditDataChange_schema_1.AuditDataAction.UPDATE,
28
+ oldData: { roles: asLabel(event.previousRoleNames) },
29
+ newData: { roles: asLabel(event.roleNames) },
30
+ });
31
+ }
32
+ };
33
+ exports.UserRolesChangedListener = UserRolesChangedListener;
34
+ __decorate([
35
+ (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_ROLES_CHANGED),
36
+ __metadata("design:type", Function),
37
+ __metadata("design:paramtypes", [Object]),
38
+ __metadata("design:returntype", Promise)
39
+ ], UserRolesChangedListener.prototype, "onRolesChanged", null);
40
+ exports.UserRolesChangedListener = UserRolesChangedListener = __decorate([
41
+ (0, common_1.Injectable)(),
42
+ __metadata("design:paramtypes", [audit_service_1.AuditService])
43
+ ], UserRolesChangedListener);
@@ -14,6 +14,11 @@ interface RequestCtx {
14
14
  userAgent?: string;
15
15
  }
16
16
  export type LoginStatus = "SETUP_REQUIRED" | "TOTP_REQUIRED";
17
+ export interface LoginChallenge {
18
+ status: LoginStatus;
19
+ pendingToken: string;
20
+ expiresInSeconds: number;
21
+ }
17
22
  export type SessionUser = AuthenticatedUser;
18
23
  export interface LoginSession {
19
24
  accessToken: string;
@@ -42,10 +47,7 @@ export declare class AuthService {
42
47
  private readonly rbac;
43
48
  private dummyHash?;
44
49
  constructor(users: UsersService, tokens: TokenService, twoFactor: TwoFactorService, jwt: JwtService, env: EnvService, mailer: MailerService, rbac: RbacService);
45
- login(dto: LoginCommand): Promise<{
46
- status: LoginStatus;
47
- pendingToken: string;
48
- }>;
50
+ login(dto: LoginCommand): Promise<LoginChallenge>;
49
51
  setupTotp(pendingToken: string): Promise<TotpSetup>;
50
52
  requestEmailCode(pendingToken: string): Promise<{
51
53
  sent: boolean;
@@ -58,6 +60,7 @@ export declare class AuthService {
58
60
  inspectPasswordToken(token: string): Promise<PasswordTokenCheck>;
59
61
  resetPassword(token: string, newPassword: string): Promise<void>;
60
62
  private buildPendingToken;
63
+ private pendingExpiresIn;
61
64
  private decodePending;
62
65
  private signAccessToken;
63
66
  toSessionUser(user: User): Promise<SessionUser>;
@@ -13,6 +13,7 @@ exports.AuthService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
14
  const jwt_1 = require("@nestjs/jwt");
15
15
  const env_service_1 = require("../../../core/config/env.service");
16
+ const auditContext_1 = require("../../../core/context/auditContext");
16
17
  const appErrorCode_enum_1 = require("../../../core/filters/appErrorCode.enum");
17
18
  const mailer_service_1 = require("../../../core/mailer/mailer.service");
18
19
  const codedError_util_1 = require("../../../core/utils/codedError.util");
@@ -30,7 +31,6 @@ function attemptsWarning(prefix, remaining) {
30
31
  const attempts = remaining === 1 ? "Resta 1 tentativa" : `Restam ${remaining} tentativas`;
31
32
  return `${prefix}. ${attempts} antes do bloqueio da conta`;
32
33
  }
33
- const PENDING_EXPIRES = "10m";
34
34
  let AuthService = class AuthService {
35
35
  constructor(users, tokens, twoFactor, jwt, env, mailer, rbac) {
36
36
  this.users = users;
@@ -69,6 +69,7 @@ let AuthService = class AuthService {
69
69
  return {
70
70
  status: user.twoFactorEnabled ? "TOTP_REQUIRED" : "SETUP_REQUIRED",
71
71
  pendingToken,
72
+ expiresInSeconds: this.pendingExpiresIn(pendingToken),
72
73
  };
73
74
  }
74
75
  async setupTotp(pendingToken) {
@@ -77,6 +78,12 @@ let AuthService = class AuthService {
77
78
  throw new common_1.BadRequestException("2FA já está configurado para esta conta");
78
79
  }
79
80
  const user = await this.users.findById(payload.sub);
81
+ const pendingSecret = !user.twoFactorEnabled
82
+ ? user.twoFactorSecret
83
+ : null;
84
+ if (pendingSecret) {
85
+ return this.twoFactor.rebuildSetup(user.email, pendingSecret);
86
+ }
80
87
  const setup = await this.twoFactor.generateSetup(user.email);
81
88
  user.twoFactorSecret = this.twoFactor.encrypt(setup.secret);
82
89
  await this.users.save(user);
@@ -208,6 +215,7 @@ let AuthService = class AuthService {
208
215
  if (!consumed) {
209
216
  throw invalidToken();
210
217
  }
218
+ (0, auditContext_1.setAuditUser)(consumed.userId);
211
219
  await this.users.resetPassword(consumed.userId, newPassword);
212
220
  await this.tokens.revokeAllRefresh(consumed.userId);
213
221
  }
@@ -220,9 +228,16 @@ let AuthService = class AuthService {
220
228
  };
221
229
  return this.jwt.signAsync(payload, {
222
230
  secret: this.env.get("JWT_PENDING_SECRET"),
223
- expiresIn: PENDING_EXPIRES,
231
+ expiresIn: this.env.get("JWT_PENDING_EXPIRES"),
224
232
  });
225
233
  }
234
+ pendingExpiresIn(token) {
235
+ const decoded = this.jwt.decode(token);
236
+ if (!decoded?.exp) {
237
+ return 0;
238
+ }
239
+ return Math.max(0, decoded.exp - Math.floor(Date.now() / 1000));
240
+ }
226
241
  async decodePending(token) {
227
242
  try {
228
243
  const payload = await this.jwt.verifyAsync(token, {
@@ -233,8 +248,11 @@ let AuthService = class AuthService {
233
248
  }
234
249
  return payload;
235
250
  }
236
- catch {
237
- throw new common_1.UnauthorizedException("Token de verificação inválido ou expirado");
251
+ catch (err) {
252
+ if (err?.name === "TokenExpiredError") {
253
+ throw (0, codedError_util_1.codedUnauthorized)("A verificação expirou. Entre novamente para continuar", appErrorCode_enum_1.AppErrorCode.PENDING_TOKEN_EXPIRED);
254
+ }
255
+ throw new common_1.UnauthorizedException("Token de verificação inválido");
238
256
  }
239
257
  }
240
258
  signAccessToken(user) {
@@ -9,7 +9,9 @@ export declare class TwoFactorService {
9
9
  private readonly logger;
10
10
  constructor(env: EnvService);
11
11
  generateSetup(accountEmail: string): Promise<TotpSetup>;
12
+ private buildSetup;
12
13
  encrypt(secret: string): string;
14
+ rebuildSetup(accountEmail: string, storedSecret: string): Promise<TotpSetup>;
13
15
  verifyTotp(code: string, storedSecret: string): Promise<boolean>;
14
16
  generateBackupCodes(): Promise<{
15
17
  plain: string[];
@@ -55,8 +55,10 @@ let TwoFactorService = TwoFactorService_1 = class TwoFactorService {
55
55
  this.env = env;
56
56
  this.logger = new common_1.Logger(TwoFactorService_1.name);
57
57
  }
58
- async generateSetup(accountEmail) {
59
- const secret = (0, otplib_1.generateSecret)();
58
+ generateSetup(accountEmail) {
59
+ return this.buildSetup(accountEmail, (0, otplib_1.generateSecret)());
60
+ }
61
+ async buildSetup(accountEmail, secret) {
60
62
  const issuer = this.env.brand().totpIssuer;
61
63
  const otpauthUrl = (0, otplib_1.generateURI)({ issuer, label: accountEmail, secret });
62
64
  const qrDataUrl = await QRCode.toDataURL(otpauthUrl);
@@ -65,6 +67,9 @@ let TwoFactorService = TwoFactorService_1 = class TwoFactorService {
65
67
  encrypt(secret) {
66
68
  return (0, crypto_util_1.encryptSecret)(secret);
67
69
  }
70
+ rebuildSetup(accountEmail, storedSecret) {
71
+ return this.buildSetup(accountEmail, (0, crypto_util_1.decryptSecret)(storedSecret));
72
+ }
68
73
  async verifyTotp(code, storedSecret) {
69
74
  if (!storedSecret) {
70
75
  return false;
@@ -4,7 +4,7 @@ import { EnvService } from "../../../core/config/env.service";
4
4
  import { AuthService } from "../domain/auth.service";
5
5
  import { PasswordTokenCheck } from "../domain/auth.service";
6
6
  import { SessionUser } from "../domain/auth.service";
7
- import { LoginStatus } from "../domain/auth.service";
7
+ import { LoginChallenge } from "../domain/auth.service";
8
8
  import { TotpSetup } from "../domain/twoFactor.service";
9
9
  import { LoginCommand } from "./commands/login.command";
10
10
  import { ForgotPasswordCommand, ResetPasswordCommand } from "./commands/password.command";
@@ -25,10 +25,7 @@ export declare class AuthController {
25
25
  private readonly auth;
26
26
  private readonly env;
27
27
  constructor(auth: AuthService, env: EnvService);
28
- login(dto: LoginCommand): Promise<{
29
- status: LoginStatus;
30
- pendingToken: string;
31
- }>;
28
+ login(dto: LoginCommand): Promise<LoginChallenge>;
32
29
  setupTotp(dto: PendingTokenCommand): Promise<TotpSetup>;
33
30
  requestEmailCode(dto: PendingTokenCommand): Promise<{
34
31
  sent: boolean;
@@ -123,6 +123,7 @@ __decorate([
123
123
  __decorate([
124
124
  (0, public_decorator_1.Public)(),
125
125
  (0, throttler_1.Throttle)({ default: { limit: 5, ttl: 60_000 } }),
126
+ (0, auditable_decorator_1.Auditable)("E-mail: código de verificação", { allowSystem: true }),
126
127
  (0, common_1.Post)("2fa/email"),
127
128
  (0, swagger_1.ApiOperation)({ summary: "Fallback: envia código 2FA por email" }),
128
129
  __param(0, (0, common_1.Body)()),
@@ -212,7 +213,7 @@ __decorate([
212
213
  __decorate([
213
214
  (0, public_decorator_1.Public)(),
214
215
  (0, throttler_1.Throttle)({ default: { limit: 5, ttl: 60_000 } }),
215
- (0, auditable_decorator_1.Auditable)("Solicitação de redefinição de senha"),
216
+ (0, auditable_decorator_1.Auditable)("E-mail: redefinição de senha", { allowSystem: true }),
216
217
  (0, common_1.Post)("forgot-password"),
217
218
  (0, common_1.HttpCode)(200),
218
219
  (0, swagger_1.ApiOperation)({ summary: "Solicita email de redefinição de senha" }),
@@ -40,6 +40,6 @@ exports.NotificationsModule = NotificationsModule = __decorate([
40
40
  notifications_publisher_1.NotificationsPublisher,
41
41
  domainEvents_listener_1.DomainEventsListener,
42
42
  ],
43
- exports: [notifications_service_1.NotificationsService],
43
+ exports: [notifications_service_1.NotificationsService, notifications_publisher_1.NotificationsPublisher],
44
44
  })
45
45
  ], NotificationsModule);
@@ -1,11 +1,14 @@
1
- import { SystemCleanupFailedEvent, UserLockedEvent, UserPasswordExpiringEvent, UserPasswordResetRequestedEvent, UserTwoFactorResetEvent } from "../../../core/events/appEvent.enum";
1
+ import { SystemCleanupFailedEvent, UserLockedEvent, UserPasswordExpiringEvent, UserPasswordResetRequestedEvent, UserRolesChangedEvent, UserTwoFactorResetEvent } from "../../../core/events/appEvent.enum";
2
+ import { NotificationsGateway } from "./notifications.gateway";
2
3
  import { NotificationsPublisher } from "./notifications.publisher";
3
4
  export declare class DomainEventsListener {
4
5
  private readonly publisher;
6
+ private readonly gateway;
5
7
  private readonly logger;
6
- constructor(publisher: NotificationsPublisher);
8
+ constructor(publisher: NotificationsPublisher, gateway: NotificationsGateway);
7
9
  onUserLocked(event: UserLockedEvent): Promise<void>;
8
10
  onTwoFactorReset(event: UserTwoFactorResetEvent): Promise<void>;
11
+ onRolesChanged(event: UserRolesChangedEvent): Promise<void>;
9
12
  onPasswordResetRequested(event: UserPasswordResetRequestedEvent): Promise<void>;
10
13
  onPasswordExpiring(event: UserPasswordExpiringEvent): Promise<void>;
11
14
  onCleanupFailed(event: SystemCleanupFailedEvent): Promise<void>;