@scryan7371/sdr-security 0.1.2 → 0.1.3

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 (68) hide show
  1. package/README.md +48 -7
  2. package/dist/api/contracts.d.ts +0 -2
  3. package/dist/api/migrations/1739500000000-create-security-identity.d.ts +1 -1
  4. package/dist/api/migrations/1739500000000-create-security-identity.js +9 -35
  5. package/dist/api/migrations/1739510000000-create-security-roles.d.ts +1 -1
  6. package/dist/api/migrations/1739510000000-create-security-roles.js +1 -67
  7. package/dist/api/migrations/1739515000000-create-security-user-roles.d.ts +9 -0
  8. package/dist/api/migrations/1739515000000-create-security-user-roles.js +39 -0
  9. package/dist/api/migrations/1739520000000-create-password-reset-tokens.js +1 -1
  10. package/dist/api/migrations/1739530000000-create-security-user.d.ts +9 -0
  11. package/dist/api/migrations/1739530000000-create-security-user.js +41 -0
  12. package/dist/api/migrations/index.d.ts +3 -2
  13. package/dist/api/migrations/index.js +7 -4
  14. package/dist/api/migrations/migrations.test.js +37 -83
  15. package/dist/api/notification-workflows.d.ts +0 -4
  16. package/dist/api/notification-workflows.js +0 -1
  17. package/dist/api/notification-workflows.test.js +1 -4
  18. package/dist/app/client.d.ts +0 -2
  19. package/dist/app/client.test.js +0 -2
  20. package/dist/nest/contracts.d.ts +0 -3
  21. package/dist/nest/dto/auth.dto.d.ts +0 -2
  22. package/dist/nest/dto/auth.dto.js +0 -10
  23. package/dist/nest/entities/app-user.entity.d.ts +0 -7
  24. package/dist/nest/entities/app-user.entity.js +0 -35
  25. package/dist/nest/entities/security-user.entity.d.ts +9 -0
  26. package/dist/nest/entities/security-user.entity.js +54 -0
  27. package/dist/nest/index.d.ts +1 -0
  28. package/dist/nest/index.js +1 -0
  29. package/dist/nest/security-auth.controller.d.ts +0 -2
  30. package/dist/nest/security-auth.controller.js +0 -2
  31. package/dist/nest/security-auth.controller.test.js +0 -4
  32. package/dist/nest/security-auth.module.js +2 -0
  33. package/dist/nest/security-auth.service.d.ts +5 -4
  34. package/dist/nest/security-auth.service.js +81 -51
  35. package/dist/nest/security-auth.service.test.js +45 -41
  36. package/dist/nest/security-workflows.module.js +2 -0
  37. package/dist/nest/security-workflows.service.d.ts +4 -2
  38. package/dist/nest/security-workflows.service.js +19 -16
  39. package/dist/nest/security-workflows.service.test.js +29 -24
  40. package/package.json +3 -3
  41. package/src/api/contracts.ts +0 -2
  42. package/src/api/migrations/1739500000000-create-security-identity.ts +11 -50
  43. package/src/api/migrations/1739510000000-create-security-roles.ts +2 -89
  44. package/src/api/migrations/1739515000000-create-security-user-roles.ts +49 -0
  45. package/src/api/migrations/1739520000000-create-password-reset-tokens.ts +1 -1
  46. package/src/api/migrations/1739530000000-create-security-user.ts +51 -0
  47. package/src/api/migrations/index.ts +6 -3
  48. package/src/api/migrations/migrations.test.ts +48 -111
  49. package/src/api/notification-workflows.test.ts +1 -4
  50. package/src/api/notification-workflows.ts +1 -8
  51. package/src/app/client.test.ts +0 -2
  52. package/src/app/client.ts +1 -6
  53. package/src/nest/contracts.ts +1 -6
  54. package/src/nest/dto/auth.dto.ts +0 -6
  55. package/src/nest/entities/app-user.entity.ts +0 -21
  56. package/src/nest/entities/security-user.entity.ts +25 -0
  57. package/src/nest/index.ts +1 -0
  58. package/src/nest/security-auth.controller.test.ts +0 -4
  59. package/src/nest/security-auth.controller.ts +0 -4
  60. package/src/nest/security-auth.module.ts +2 -0
  61. package/src/nest/security-auth.service.test.ts +74 -43
  62. package/src/nest/security-auth.service.ts +88 -51
  63. package/src/nest/security-workflows.module.ts +2 -0
  64. package/src/nest/security-workflows.service.test.ts +31 -25
  65. package/src/nest/security-workflows.service.ts +18 -13
  66. package/dist/api/migrations/1739490000000-add-google-subject-to-user.d.ts +0 -5
  67. package/dist/api/migrations/1739490000000-add-google-subject-to-user.js +0 -14
  68. 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,
@@ -5,6 +5,7 @@ import { ADMIN_ROLE } from "../api/contracts";
5
5
  import { normalizeRoleName } from "../api/roles";
6
6
  import { AppUserEntity } from "./entities/app-user.entity";
7
7
  import { SecurityRoleEntity } from "./entities/security-role.entity";
8
+ import { SecurityUserEntity } from "./entities/security-user.entity";
8
9
  import { SecurityUserRoleEntity } from "./entities/security-user-role.entity";
9
10
  import { SecurityWorkflowNotifier } from "./contracts";
10
11
  import { SECURITY_WORKFLOW_NOTIFIER } from "./tokens";
@@ -13,7 +14,9 @@ import { SECURITY_WORKFLOW_NOTIFIER } from "./tokens";
13
14
  export class SecurityWorkflowsService {
14
15
  constructor(
15
16
  @InjectRepository(AppUserEntity)
16
- private readonly usersRepo: Repository<AppUserEntity>,
17
+ private readonly appUsersRepo: Repository<AppUserEntity>,
18
+ @InjectRepository(SecurityUserEntity)
19
+ private readonly securityUsersRepo: Repository<SecurityUserEntity>,
17
20
  @InjectRepository(SecurityRoleEntity)
18
21
  private readonly rolesRepo: Repository<SecurityRoleEntity>,
19
22
  @InjectRepository(SecurityUserRoleEntity)
@@ -23,12 +26,12 @@ export class SecurityWorkflowsService {
23
26
  ) {}
24
27
 
25
28
  async markEmailVerifiedAndNotifyAdmins(userId: string) {
26
- await this.usersRepo.update(
27
- { id: userId },
29
+ await this.securityUsersRepo.update(
30
+ { userId },
28
31
  { emailVerifiedAt: new Date(), emailVerificationToken: null },
29
32
  );
30
33
 
31
- const user = await this.usersRepo.findOne({ where: { id: userId } });
34
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
32
35
  if (!user) {
33
36
  throw new NotFoundException("User not found");
34
37
  }
@@ -43,8 +46,6 @@ export class SecurityWorkflowsService {
43
46
  user: {
44
47
  id: user.id,
45
48
  email: user.email,
46
- firstName: user.firstName,
47
- lastName: user.lastName,
48
49
  },
49
50
  });
50
51
 
@@ -52,12 +53,12 @@ export class SecurityWorkflowsService {
52
53
  }
53
54
 
54
55
  async setAdminApprovalAndNotifyUser(userId: string, approved: boolean) {
55
- await this.usersRepo.update(
56
- { id: userId },
56
+ await this.securityUsersRepo.update(
57
+ { userId },
57
58
  { adminApprovedAt: approved ? new Date() : null },
58
59
  );
59
60
 
60
- const user = await this.usersRepo.findOne({ where: { id: userId } });
61
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
61
62
  if (!user) {
62
63
  throw new NotFoundException("User not found");
63
64
  }
@@ -68,7 +69,6 @@ export class SecurityWorkflowsService {
68
69
 
69
70
  await this.notifier.sendUserAccountApproved({
70
71
  email: user.email,
71
- firstName: user.firstName,
72
72
  });
73
73
 
74
74
  return { success: true as const, notified: true as const };
@@ -79,8 +79,13 @@ export class SecurityWorkflowsService {
79
79
  .createQueryBuilder("userRole")
80
80
  .innerJoin("security_role", "role", "role.id = userRole.role_id")
81
81
  .innerJoin("app_user", "user", "user.id = userRole.user_id")
82
+ .innerJoin(
83
+ "security_user",
84
+ "securityUser",
85
+ "securityUser.user_id = userRole.user_id",
86
+ )
82
87
  .where("role.role_key = :roleKey", { roleKey: ADMIN_ROLE })
83
- .andWhere("user.is_active = :isActive", { isActive: true })
88
+ .andWhere("securityUser.is_active = :isActive", { isActive: true })
84
89
  .select("DISTINCT user.email", "email")
85
90
  .getRawMany<{ email: string }>();
86
91
 
@@ -178,12 +183,12 @@ export class SecurityWorkflowsService {
178
183
  }
179
184
 
180
185
  async setUserActive(userId: string, active: boolean) {
181
- await this.usersRepo.update({ id: userId }, { isActive: active });
186
+ await this.securityUsersRepo.update({ userId }, { isActive: active });
182
187
  return { success: true as const, userId, active };
183
188
  }
184
189
 
185
190
  private async assertUserExists(userId: string) {
186
- const user = await this.usersRepo.findOne({ where: { id: userId } });
191
+ const user = await this.appUsersRepo.findOne({ where: { id: userId } });
187
192
  if (!user) {
188
193
  throw new NotFoundException("User not found");
189
194
  }
@@ -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
- }