@scryan7371/sdr-security 0.1.2 → 0.1.4

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 (83) hide show
  1. package/README.md +48 -7
  2. package/dist/api/contracts.d.ts +0 -2
  3. package/dist/api/migrations/1700000000001-add-refresh-tokens.js +4 -2
  4. package/dist/api/migrations/1739500000000-create-security-identity.d.ts +1 -1
  5. package/dist/api/migrations/1739500000000-create-security-identity.js +12 -36
  6. package/dist/api/migrations/1739510000000-create-security-roles.d.ts +1 -1
  7. package/dist/api/migrations/1739510000000-create-security-roles.js +3 -68
  8. package/dist/api/migrations/1739515000000-create-security-user-roles.d.ts +9 -0
  9. package/dist/api/migrations/1739515000000-create-security-user-roles.js +42 -0
  10. package/dist/api/migrations/1739520000000-create-password-reset-tokens.js +4 -2
  11. package/dist/api/migrations/1739530000000-create-security-user.d.ts +9 -0
  12. package/dist/api/migrations/1739530000000-create-security-user.js +42 -0
  13. package/dist/api/migrations/index.d.ts +3 -2
  14. package/dist/api/migrations/index.js +7 -4
  15. package/dist/api/migrations/migrations.test.js +37 -83
  16. package/dist/api/notification-workflows.d.ts +0 -4
  17. package/dist/api/notification-workflows.js +0 -1
  18. package/dist/api/notification-workflows.test.js +1 -4
  19. package/dist/app/client.d.ts +0 -2
  20. package/dist/app/client.test.js +0 -2
  21. package/dist/integration/database.integration.test.js +1 -1
  22. package/dist/nest/contracts.d.ts +0 -3
  23. package/dist/nest/dto/auth.dto.d.ts +0 -2
  24. package/dist/nest/dto/auth.dto.js +0 -10
  25. package/dist/nest/entities/app-user.entity.d.ts +0 -7
  26. package/dist/nest/entities/app-user.entity.js +1 -36
  27. package/dist/nest/entities/password-reset-token.entity.d.ts +1 -0
  28. package/dist/nest/entities/password-reset-token.entity.js +14 -2
  29. package/dist/nest/entities/refresh-token.entity.js +2 -2
  30. package/dist/nest/entities/security-role.entity.d.ts +1 -0
  31. package/dist/nest/entities/security-role.entity.js +13 -1
  32. package/dist/nest/entities/security-user-role.entity.d.ts +1 -0
  33. package/dist/nest/entities/security-user-role.entity.js +14 -2
  34. package/dist/nest/entities/security-user.entity.d.ts +9 -0
  35. package/dist/nest/entities/security-user.entity.js +54 -0
  36. package/dist/nest/index.d.ts +1 -0
  37. package/dist/nest/index.js +1 -0
  38. package/dist/nest/security-auth.controller.d.ts +0 -2
  39. package/dist/nest/security-auth.controller.js +0 -2
  40. package/dist/nest/security-auth.controller.test.js +0 -4
  41. package/dist/nest/security-auth.module.js +2 -0
  42. package/dist/nest/security-auth.service.d.ts +5 -4
  43. package/dist/nest/security-auth.service.js +85 -52
  44. package/dist/nest/security-auth.service.test.js +48 -42
  45. package/dist/nest/security-workflows.module.js +2 -0
  46. package/dist/nest/security-workflows.service.d.ts +4 -2
  47. package/dist/nest/security-workflows.service.js +23 -16
  48. package/dist/nest/security-workflows.service.test.js +29 -24
  49. package/package.json +5 -4
  50. package/src/api/contracts.ts +0 -2
  51. package/src/api/migrations/1700000000001-add-refresh-tokens.ts +4 -2
  52. package/src/api/migrations/1739500000000-create-security-identity.ts +14 -51
  53. package/src/api/migrations/1739510000000-create-security-roles.ts +4 -90
  54. package/src/api/migrations/1739515000000-create-security-user-roles.ts +52 -0
  55. package/src/api/migrations/1739520000000-create-password-reset-tokens.ts +4 -2
  56. package/src/api/migrations/1739530000000-create-security-user.ts +52 -0
  57. package/src/api/migrations/index.ts +6 -3
  58. package/src/api/migrations/migrations.test.ts +48 -111
  59. package/src/api/notification-workflows.test.ts +1 -4
  60. package/src/api/notification-workflows.ts +1 -8
  61. package/src/app/client.test.ts +0 -2
  62. package/src/app/client.ts +1 -6
  63. package/src/integration/database.integration.test.ts +1 -1
  64. package/src/nest/contracts.ts +1 -6
  65. package/src/nest/dto/auth.dto.ts +0 -6
  66. package/src/nest/entities/app-user.entity.ts +2 -23
  67. package/src/nest/entities/password-reset-token.entity.ts +12 -3
  68. package/src/nest/entities/refresh-token.entity.ts +2 -2
  69. package/src/nest/entities/security-role.entity.ts +10 -2
  70. package/src/nest/entities/security-user-role.entity.ts +11 -3
  71. package/src/nest/entities/security-user.entity.ts +25 -0
  72. package/src/nest/index.ts +1 -0
  73. package/src/nest/security-auth.controller.test.ts +0 -4
  74. package/src/nest/security-auth.controller.ts +0 -4
  75. package/src/nest/security-auth.module.ts +2 -0
  76. package/src/nest/security-auth.service.test.ts +78 -44
  77. package/src/nest/security-auth.service.ts +93 -53
  78. package/src/nest/security-workflows.module.ts +2 -0
  79. package/src/nest/security-workflows.service.test.ts +31 -25
  80. package/src/nest/security-workflows.service.ts +22 -13
  81. package/dist/api/migrations/1739490000000-add-google-subject-to-user.d.ts +0 -5
  82. package/dist/api/migrations/1739490000000-add-google-subject-to-user.js +0 -14
  83. package/src/api/migrations/1739490000000-add-google-subject-to-user.ts +0 -12
@@ -20,25 +20,32 @@ const makeNotifier = () => ({
20
20
  const makeUser = () => ({
21
21
  id: "user-1",
22
22
  email: "user@example.com",
23
- firstName: "A",
24
- lastName: "B",
25
23
  isActive: true,
26
24
  });
27
25
 
28
26
  const setup = () => {
29
- const usersRepo = makeRepo();
27
+ const appUsersRepo = makeRepo();
28
+ const securityUsersRepo = makeRepo();
30
29
  const rolesRepo = makeRepo();
31
30
  const userRolesRepo = makeRepo();
32
31
  const notifier = makeNotifier();
33
32
 
34
33
  const service = new SecurityWorkflowsService(
35
- usersRepo as never,
34
+ appUsersRepo as never,
35
+ securityUsersRepo as never,
36
36
  rolesRepo as never,
37
37
  userRolesRepo as never,
38
38
  notifier as never,
39
39
  );
40
40
 
41
- return { service, usersRepo, rolesRepo, userRolesRepo, notifier };
41
+ return {
42
+ service,
43
+ appUsersRepo,
44
+ securityUsersRepo,
45
+ rolesRepo,
46
+ userRolesRepo,
47
+ notifier,
48
+ };
42
49
  };
43
50
 
44
51
  describe("SecurityWorkflowsService", () => {
@@ -47,8 +54,8 @@ describe("SecurityWorkflowsService", () => {
47
54
  });
48
55
 
49
56
  it("marks email verified and notifies admins", async () => {
50
- const { service, usersRepo, userRolesRepo, notifier } = setup();
51
- usersRepo.findOne.mockResolvedValue(makeUser());
57
+ const { service, appUsersRepo, userRolesRepo, notifier } = setup();
58
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
52
59
 
53
60
  const getRawMany = vi
54
61
  .fn()
@@ -73,8 +80,8 @@ describe("SecurityWorkflowsService", () => {
73
80
  });
74
81
 
75
82
  it("returns not-notified when no admins are present", async () => {
76
- const { service, usersRepo, userRolesRepo } = setup();
77
- usersRepo.findOne.mockResolvedValue(makeUser());
83
+ const { service, appUsersRepo, userRolesRepo } = setup();
84
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
78
85
 
79
86
  const qb = {
80
87
  innerJoin: vi.fn().mockReturnThis(),
@@ -91,8 +98,8 @@ describe("SecurityWorkflowsService", () => {
91
98
  });
92
99
 
93
100
  it("throws when user is missing during verification flow", async () => {
94
- const { service, usersRepo } = setup();
95
- usersRepo.findOne.mockResolvedValue(null);
101
+ const { service, appUsersRepo } = setup();
102
+ appUsersRepo.findOne.mockResolvedValue(null);
96
103
 
97
104
  await expect(
98
105
  service.markEmailVerifiedAndNotifyAdmins("missing"),
@@ -100,8 +107,8 @@ describe("SecurityWorkflowsService", () => {
100
107
  });
101
108
 
102
109
  it("handles admin approval notifications", async () => {
103
- const { service, usersRepo, notifier } = setup();
104
- usersRepo.findOne.mockResolvedValue(makeUser());
110
+ const { service, appUsersRepo, notifier } = setup();
111
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
105
112
 
106
113
  await expect(
107
114
  service.setAdminApprovalAndNotifyUser("user-1", false),
@@ -113,13 +120,12 @@ describe("SecurityWorkflowsService", () => {
113
120
 
114
121
  expect(notifier.sendUserAccountApproved).toHaveBeenCalledWith({
115
122
  email: "user@example.com",
116
- firstName: "A",
117
123
  });
118
124
  });
119
125
 
120
126
  it("throws when approval target user is missing", async () => {
121
- const { service, usersRepo } = setup();
122
- usersRepo.findOne.mockResolvedValue(null);
127
+ const { service, appUsersRepo } = setup();
128
+ appUsersRepo.findOne.mockResolvedValue(null);
123
129
 
124
130
  await expect(
125
131
  service.setAdminApprovalAndNotifyUser("missing", true),
@@ -166,8 +172,8 @@ describe("SecurityWorkflowsService", () => {
166
172
  });
167
173
 
168
174
  it("gets and sets user roles", async () => {
169
- const { service, usersRepo, userRolesRepo, rolesRepo } = setup();
170
- usersRepo.findOne.mockResolvedValue(makeUser());
175
+ const { service, appUsersRepo, userRolesRepo, rolesRepo } = setup();
176
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
171
177
  userRolesRepo.find.mockResolvedValue([{ roleId: "r1" }]);
172
178
  rolesRepo.find.mockResolvedValue([{ id: "r1", roleKey: "ADMIN" }]);
173
179
 
@@ -189,8 +195,8 @@ describe("SecurityWorkflowsService", () => {
189
195
  });
190
196
 
191
197
  it("assigns and removes role from user", async () => {
192
- const { service, usersRepo, userRolesRepo, rolesRepo } = setup();
193
- usersRepo.findOne.mockResolvedValue(makeUser());
198
+ const { service, appUsersRepo, userRolesRepo, rolesRepo } = setup();
199
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
194
200
 
195
201
  userRolesRepo.find.mockResolvedValue([]);
196
202
  rolesRepo.find.mockResolvedValue([]);
@@ -206,21 +212,21 @@ describe("SecurityWorkflowsService", () => {
206
212
  });
207
213
 
208
214
  it("sets user active state", async () => {
209
- const { service, usersRepo } = setup();
215
+ const { service, securityUsersRepo } = setup();
210
216
  await expect(service.setUserActive("user-1", false)).resolves.toEqual({
211
217
  success: true,
212
218
  userId: "user-1",
213
219
  active: false,
214
220
  });
215
- expect(usersRepo.update).toHaveBeenCalledWith(
216
- { id: "user-1" },
221
+ expect(securityUsersRepo.update).toHaveBeenCalledWith(
222
+ { userId: "user-1" },
217
223
  { isActive: false },
218
224
  );
219
225
  });
220
226
 
221
227
  it("throws when role operations target missing user", async () => {
222
- const { service, usersRepo } = setup();
223
- usersRepo.findOne.mockResolvedValue(null);
228
+ const { service, appUsersRepo } = setup();
229
+ appUsersRepo.findOne.mockResolvedValue(null);
224
230
 
225
231
  await expect(service.getUserRoles("missing")).rejects.toBeInstanceOf(
226
232
  NotFoundException,
@@ -1,10 +1,12 @@
1
1
  import { Inject, Injectable, NotFoundException } from "@nestjs/common";
2
2
  import { InjectRepository } from "@nestjs/typeorm";
3
3
  import { In, Repository } from "typeorm";
4
+ import { v7 as uuidv7 } from "uuid";
4
5
  import { ADMIN_ROLE } from "../api/contracts";
5
6
  import { normalizeRoleName } from "../api/roles";
6
7
  import { AppUserEntity } from "./entities/app-user.entity";
7
8
  import { SecurityRoleEntity } from "./entities/security-role.entity";
9
+ import { SecurityUserEntity } from "./entities/security-user.entity";
8
10
  import { SecurityUserRoleEntity } from "./entities/security-user-role.entity";
9
11
  import { SecurityWorkflowNotifier } from "./contracts";
10
12
  import { SECURITY_WORKFLOW_NOTIFIER } from "./tokens";
@@ -13,7 +15,9 @@ import { SECURITY_WORKFLOW_NOTIFIER } from "./tokens";
13
15
  export class SecurityWorkflowsService {
14
16
  constructor(
15
17
  @InjectRepository(AppUserEntity)
16
- private readonly usersRepo: Repository<AppUserEntity>,
18
+ private readonly appUsersRepo: Repository<AppUserEntity>,
19
+ @InjectRepository(SecurityUserEntity)
20
+ private readonly securityUsersRepo: Repository<SecurityUserEntity>,
17
21
  @InjectRepository(SecurityRoleEntity)
18
22
  private readonly rolesRepo: Repository<SecurityRoleEntity>,
19
23
  @InjectRepository(SecurityUserRoleEntity)
@@ -23,12 +27,12 @@ export class SecurityWorkflowsService {
23
27
  ) {}
24
28
 
25
29
  async markEmailVerifiedAndNotifyAdmins(userId: string) {
26
- await this.usersRepo.update(
27
- { id: userId },
30
+ await this.securityUsersRepo.update(
31
+ { userId },
28
32
  { emailVerifiedAt: new Date(), emailVerificationToken: null },
29
33
  );
30
34
 
31
- const user = await this.usersRepo.findOne({ where: { id: userId } });
35
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
32
36
  if (!user) {
33
37
  throw new NotFoundException("User not found");
34
38
  }
@@ -43,8 +47,6 @@ export class SecurityWorkflowsService {
43
47
  user: {
44
48
  id: user.id,
45
49
  email: user.email,
46
- firstName: user.firstName,
47
- lastName: user.lastName,
48
50
  },
49
51
  });
50
52
 
@@ -52,12 +54,12 @@ export class SecurityWorkflowsService {
52
54
  }
53
55
 
54
56
  async setAdminApprovalAndNotifyUser(userId: string, approved: boolean) {
55
- await this.usersRepo.update(
56
- { id: userId },
57
+ await this.securityUsersRepo.update(
58
+ { userId },
57
59
  { adminApprovedAt: approved ? new Date() : null },
58
60
  );
59
61
 
60
- const user = await this.usersRepo.findOne({ where: { id: userId } });
62
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
61
63
  if (!user) {
62
64
  throw new NotFoundException("User not found");
63
65
  }
@@ -68,7 +70,6 @@ export class SecurityWorkflowsService {
68
70
 
69
71
  await this.notifier.sendUserAccountApproved({
70
72
  email: user.email,
71
- firstName: user.firstName,
72
73
  });
73
74
 
74
75
  return { success: true as const, notified: true as const };
@@ -79,8 +80,13 @@ export class SecurityWorkflowsService {
79
80
  .createQueryBuilder("userRole")
80
81
  .innerJoin("security_role", "role", "role.id = userRole.role_id")
81
82
  .innerJoin("app_user", "user", "user.id = userRole.user_id")
83
+ .innerJoin(
84
+ "security_user",
85
+ "securityUser",
86
+ "securityUser.user_id = userRole.user_id",
87
+ )
82
88
  .where("role.role_key = :roleKey", { roleKey: ADMIN_ROLE })
83
- .andWhere("user.is_active = :isActive", { isActive: true })
89
+ .andWhere("securityUser.is_active = :isActive", { isActive: true })
84
90
  .select("DISTINCT user.email", "email")
85
91
  .getRawMany<{ email: string }>();
86
92
 
@@ -101,6 +107,7 @@ export class SecurityWorkflowsService {
101
107
  let role = await this.rolesRepo.findOne({ where: { roleKey } });
102
108
  if (!role) {
103
109
  role = this.rolesRepo.create({
110
+ id: uuidv7(),
104
111
  roleKey,
105
112
  description: description?.trim() || null,
106
113
  isSystem: roleKey === ADMIN_ROLE,
@@ -153,6 +160,7 @@ export class SecurityWorkflowsService {
153
160
  await this.userRolesRepo.save(
154
161
  roles.map((role) =>
155
162
  this.userRolesRepo.create({
163
+ id: uuidv7(),
156
164
  userId,
157
165
  roleId: role.id,
158
166
  }),
@@ -178,12 +186,12 @@ export class SecurityWorkflowsService {
178
186
  }
179
187
 
180
188
  async setUserActive(userId: string, active: boolean) {
181
- await this.usersRepo.update({ id: userId }, { isActive: active });
189
+ await this.securityUsersRepo.update({ userId }, { isActive: active });
182
190
  return { success: true as const, userId, active };
183
191
  }
184
192
 
185
193
  private async assertUserExists(userId: string) {
186
- const user = await this.usersRepo.findOne({ where: { id: userId } });
194
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
187
195
  if (!user) {
188
196
  throw new NotFoundException("User not found");
189
197
  }
@@ -205,6 +213,7 @@ export class SecurityWorkflowsService {
205
213
  await this.rolesRepo.save(
206
214
  missing.map((roleKey) =>
207
215
  this.rolesRepo.create({
216
+ id: uuidv7(),
208
217
  roleKey,
209
218
  description: null,
210
219
  isSystem: roleKey === ADMIN_ROLE,
@@ -1,5 +0,0 @@
1
- export declare class AddGoogleSubjectToUser1739490000000 {
2
- name: string;
3
- up(): Promise<void>;
4
- down(): Promise<void>;
5
- }
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AddGoogleSubjectToUser1739490000000 = void 0;
4
- class AddGoogleSubjectToUser1739490000000 {
5
- name = "AddGoogleSubjectToUser1739490000000";
6
- // Legacy migration retained for backward compatibility with existing migration history.
7
- async up() {
8
- return;
9
- }
10
- async down() {
11
- return;
12
- }
13
- }
14
- exports.AddGoogleSubjectToUser1739490000000 = AddGoogleSubjectToUser1739490000000;
@@ -1,12 +0,0 @@
1
- export class AddGoogleSubjectToUser1739490000000 {
2
- name = "AddGoogleSubjectToUser1739490000000";
3
-
4
- // Legacy migration retained for backward compatibility with existing migration history.
5
- async up(): Promise<void> {
6
- return;
7
- }
8
-
9
- async down(): Promise<void> {
10
- return;
11
- }
12
- }