@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
@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
17
17
  const crypto_1 = require("crypto");
18
18
  const bcryptjs_1 = require("bcryptjs");
19
19
  const jsonwebtoken_1 = require("jsonwebtoken");
20
+ const uuid_1 = require("uuid");
20
21
  const typeorm_1 = require("@nestjs/typeorm");
21
22
  const typeorm_2 = require("typeorm");
22
23
  const roles_1 = require("../api/roles");
@@ -26,21 +27,24 @@ const app_user_entity_1 = require("./entities/app-user.entity");
26
27
  const password_reset_token_entity_1 = require("./entities/password-reset-token.entity");
27
28
  const refresh_token_entity_1 = require("./entities/refresh-token.entity");
28
29
  const security_role_entity_1 = require("./entities/security-role.entity");
30
+ const security_user_entity_1 = require("./entities/security-user.entity");
29
31
  const security_user_role_entity_1 = require("./entities/security-user-role.entity");
30
32
  const tokens_1 = require("./tokens");
31
33
  const EMAIL_TOKEN_BYTES = 24;
32
34
  const REFRESH_TOKEN_BYTES = 32;
33
35
  const PASSWORD_ROUNDS = 12;
34
36
  let SecurityAuthService = class SecurityAuthService {
35
- usersRepo;
37
+ appUsersRepo;
38
+ securityUsersRepo;
36
39
  refreshTokenRepo;
37
40
  passwordResetRepo;
38
41
  rolesRepo;
39
42
  userRolesRepo;
40
43
  options;
41
44
  notifier;
42
- constructor(usersRepo, refreshTokenRepo, passwordResetRepo, rolesRepo, userRolesRepo, options, notifier) {
43
- this.usersRepo = usersRepo;
45
+ constructor(appUsersRepo, securityUsersRepo, refreshTokenRepo, passwordResetRepo, rolesRepo, userRolesRepo, options, notifier) {
46
+ this.appUsersRepo = appUsersRepo;
47
+ this.securityUsersRepo = securityUsersRepo;
44
48
  this.refreshTokenRepo = refreshTokenRepo;
45
49
  this.passwordResetRepo = passwordResetRepo;
46
50
  this.rolesRepo = rolesRepo;
@@ -50,45 +54,53 @@ let SecurityAuthService = class SecurityAuthService {
50
54
  }
51
55
  async register(params) {
52
56
  const email = (0, validation_1.sanitizeEmail)(params.email);
53
- const existing = await this.usersRepo.findOne({ where: { email } });
57
+ const existing = await this.appUsersRepo.findOne({ where: { email } });
54
58
  if (existing) {
55
59
  throw new common_1.BadRequestException("Email already in use");
56
60
  }
57
- const user = await this.usersRepo.save(this.usersRepo.create({
61
+ const appUser = await this.appUsersRepo.save(this.appUsersRepo.create({
62
+ id: (0, uuid_1.v7)(),
58
63
  email,
64
+ }));
65
+ const securityUser = await this.securityUsersRepo.save(this.securityUsersRepo.create({
66
+ userId: appUser.id,
59
67
  passwordHash: await (0, bcryptjs_1.hash)(params.password, PASSWORD_ROUNDS),
60
- firstName: params.firstName ?? null,
61
- lastName: params.lastName ?? null,
62
68
  emailVerifiedAt: null,
63
69
  emailVerificationToken: null,
64
70
  adminApprovedAt: null,
65
71
  isActive: true,
66
72
  }));
67
- const verificationToken = await this.createEmailVerificationToken(user.id);
73
+ const verificationToken = await this.createEmailVerificationToken(appUser.id);
68
74
  if (this.notifier.sendEmailVerification) {
69
75
  await this.notifier.sendEmailVerification({
70
- email: user.email,
76
+ email: appUser.email,
71
77
  token: verificationToken,
72
78
  });
73
79
  }
74
80
  return {
75
81
  success: true,
76
- user: await this.toSafeUser(user),
82
+ user: await this.toSafeUser(appUser, securityUser),
77
83
  debugToken: verificationToken,
78
84
  };
79
85
  }
80
86
  async login(params) {
81
87
  const email = (0, validation_1.sanitizeEmail)(params.email);
82
- const user = await this.usersRepo.findOne({ where: { email } });
83
- if (!user) {
88
+ const appUser = await this.appUsersRepo.findOne({ where: { email } });
89
+ if (!appUser) {
90
+ throw new common_1.UnauthorizedException("Invalid credentials");
91
+ }
92
+ const securityUser = await this.securityUsersRepo.findOne({
93
+ where: { userId: appUser.id },
94
+ });
95
+ if (!securityUser) {
84
96
  throw new common_1.UnauthorizedException("Invalid credentials");
85
97
  }
86
- const ok = await (0, bcryptjs_1.compare)(params.password, user.passwordHash);
98
+ const ok = await (0, bcryptjs_1.compare)(params.password, securityUser.passwordHash);
87
99
  if (!ok) {
88
100
  throw new common_1.UnauthorizedException("Invalid credentials");
89
101
  }
90
- this.assertCanAuthenticate(user);
91
- return this.issueTokens(user);
102
+ this.assertCanAuthenticate(securityUser);
103
+ return this.issueTokens(appUser, securityUser);
92
104
  }
93
105
  async refresh(refreshToken) {
94
106
  const record = await this.findValidRefreshToken(refreshToken);
@@ -96,14 +108,20 @@ let SecurityAuthService = class SecurityAuthService {
96
108
  throw new common_1.UnauthorizedException("Invalid refresh token");
97
109
  }
98
110
  await this.refreshTokenRepo.update({ id: record.id }, { revokedAt: new Date() });
99
- const user = await this.usersRepo.findOne({
111
+ const appUser = await this.appUsersRepo.findOne({
100
112
  where: { id: record.userId ?? "" },
101
113
  });
102
- if (!user) {
114
+ if (!appUser) {
103
115
  throw new common_1.UnauthorizedException("User not found");
104
116
  }
105
- this.assertCanAuthenticate(user);
106
- return this.issueTokens(user);
117
+ const securityUser = await this.securityUsersRepo.findOne({
118
+ where: { userId: appUser.id },
119
+ });
120
+ if (!securityUser) {
121
+ throw new common_1.UnauthorizedException("User not found");
122
+ }
123
+ this.assertCanAuthenticate(securityUser);
124
+ return this.issueTokens(appUser, securityUser);
107
125
  }
108
126
  async logout(refreshToken) {
109
127
  if (!refreshToken) {
@@ -116,34 +134,43 @@ let SecurityAuthService = class SecurityAuthService {
116
134
  return { success: true };
117
135
  }
118
136
  async changePassword(params) {
119
- const user = await this.usersRepo.findOne({ where: { id: params.userId } });
120
- if (!user) {
137
+ const securityUser = await this.securityUsersRepo.findOne({
138
+ where: { userId: params.userId },
139
+ });
140
+ if (!securityUser) {
121
141
  throw new common_1.BadRequestException("User not found");
122
142
  }
123
- const ok = await (0, bcryptjs_1.compare)(params.currentPassword, user.passwordHash);
143
+ const ok = await (0, bcryptjs_1.compare)(params.currentPassword, securityUser.passwordHash);
124
144
  if (!ok) {
125
145
  throw new common_1.UnauthorizedException("Current password is incorrect");
126
146
  }
127
- await this.usersRepo.update({ id: user.id }, { passwordHash: await (0, bcryptjs_1.hash)(params.newPassword, PASSWORD_ROUNDS) });
147
+ await this.securityUsersRepo.update({ userId: securityUser.userId }, { passwordHash: await (0, bcryptjs_1.hash)(params.newPassword, PASSWORD_ROUNDS) });
128
148
  return { success: true };
129
149
  }
130
150
  async requestForgotPassword(emailInput) {
131
151
  const email = (0, validation_1.sanitizeEmail)(emailInput);
132
- const user = await this.usersRepo.findOne({ where: { email } });
133
- if (!user) {
152
+ const appUser = await this.appUsersRepo.findOne({ where: { email } });
153
+ if (!appUser) {
154
+ return { success: true };
155
+ }
156
+ const securityUser = await this.securityUsersRepo.findOne({
157
+ where: { userId: appUser.id },
158
+ });
159
+ if (!securityUser) {
134
160
  return { success: true };
135
161
  }
136
162
  const token = (0, crypto_1.randomBytes)(EMAIL_TOKEN_BYTES).toString("hex");
137
163
  const expiresAt = new Date(Date.now() +
138
164
  (this.options.passwordResetTokenExpiresInMinutes ?? 30) * 60_000);
139
165
  await this.passwordResetRepo.save(this.passwordResetRepo.create({
140
- userId: user.id,
166
+ id: (0, uuid_1.v7)(),
167
+ userId: appUser.id,
141
168
  token,
142
169
  expiresAt,
143
170
  usedAt: null,
144
171
  }));
145
172
  if (this.notifier.sendPasswordReset) {
146
- await this.notifier.sendPasswordReset({ email: user.email, token });
173
+ await this.notifier.sendPasswordReset({ email: appUser.email, token });
147
174
  }
148
175
  return { success: true };
149
176
  }
@@ -152,23 +179,29 @@ let SecurityAuthService = class SecurityAuthService {
152
179
  if (!reset || reset.usedAt || reset.expiresAt.getTime() <= Date.now()) {
153
180
  throw new common_1.BadRequestException("Invalid password reset token");
154
181
  }
155
- await this.usersRepo.update({ id: reset.userId }, { passwordHash: await (0, bcryptjs_1.hash)(newPassword, PASSWORD_ROUNDS) });
182
+ await this.securityUsersRepo.update({ userId: reset.userId }, { passwordHash: await (0, bcryptjs_1.hash)(newPassword, PASSWORD_ROUNDS) });
156
183
  await this.passwordResetRepo.update({ id: reset.id }, { usedAt: new Date() });
157
184
  return { success: true };
158
185
  }
159
186
  async verifyEmailByToken(token) {
160
- const user = await this.usersRepo.findOne({
187
+ const user = await this.securityUsersRepo.findOne({
161
188
  where: { emailVerificationToken: token },
162
189
  });
163
190
  if (!user) {
164
191
  throw new common_1.BadRequestException("Invalid verification token");
165
192
  }
166
- await this.usersRepo.update({ id: user.id }, { emailVerifiedAt: new Date(), emailVerificationToken: null });
193
+ await this.securityUsersRepo.update({ userId: user.userId }, { emailVerifiedAt: new Date(), emailVerificationToken: null });
167
194
  return { success: true };
168
195
  }
169
196
  async getMyRoles(userId) {
170
197
  return { userId, roles: await this.getUserRoleKeys(userId) };
171
198
  }
199
+ async getUserIdByVerificationToken(token) {
200
+ const user = await this.securityUsersRepo.findOne({
201
+ where: { emailVerificationToken: token },
202
+ });
203
+ return user?.userId ?? null;
204
+ }
172
205
  assertCanAuthenticate(user) {
173
206
  if (!user.isActive) {
174
207
  throw new common_1.UnauthorizedException("Account is inactive");
@@ -181,17 +214,17 @@ let SecurityAuthService = class SecurityAuthService {
181
214
  throw new common_1.UnauthorizedException("Admin approval required");
182
215
  }
183
216
  }
184
- async issueTokens(user) {
185
- const roles = await this.getUserRoleKeys(user.id);
217
+ async issueTokens(appUser, securityUser) {
218
+ const roles = await this.getUserRoleKeys(appUser.id);
186
219
  const accessTokenExpiresIn = this.options.accessTokenExpiresIn ?? "15m";
187
- const accessToken = (0, jsonwebtoken_1.sign)({ sub: user.id, email: user.email, roles }, this.options.jwtSecret, { expiresIn: accessTokenExpiresIn });
220
+ const accessToken = (0, jsonwebtoken_1.sign)({ sub: appUser.id, email: appUser.email, roles }, this.options.jwtSecret, { expiresIn: accessTokenExpiresIn });
188
221
  const refreshToken = (0, crypto_1.randomBytes)(REFRESH_TOKEN_BYTES).toString("hex");
189
222
  const refreshTokenHash = await (0, bcryptjs_1.hash)(refreshToken, PASSWORD_ROUNDS);
190
223
  const refreshTokenExpiresAt = new Date(Date.now() +
191
224
  (this.options.refreshTokenExpiresInDays ?? 30) * 24 * 60 * 60 * 1000);
192
225
  await this.refreshTokenRepo.save(this.refreshTokenRepo.create({
193
- id: (0, crypto_1.randomUUID)(),
194
- userId: user.id,
226
+ id: (0, uuid_1.v7)(),
227
+ userId: appUser.id,
195
228
  tokenHash: refreshTokenHash,
196
229
  expiresAt: refreshTokenExpiresAt,
197
230
  revokedAt: null,
@@ -201,12 +234,12 @@ let SecurityAuthService = class SecurityAuthService {
201
234
  accessTokenExpiresIn,
202
235
  refreshToken,
203
236
  refreshTokenExpiresAt,
204
- user: await this.toSafeUser(user),
237
+ user: await this.toSafeUser(appUser, securityUser),
205
238
  };
206
239
  }
207
240
  async createEmailVerificationToken(userId) {
208
241
  const token = (0, crypto_1.randomBytes)(EMAIL_TOKEN_BYTES).toString("hex");
209
- await this.usersRepo.update({ id: userId }, { emailVerificationToken: token });
242
+ await this.securityUsersRepo.update({ userId }, { emailVerificationToken: token });
210
243
  return token;
211
244
  }
212
245
  async findValidRefreshToken(token, onlyUnexpired = true) {
@@ -236,18 +269,16 @@ let SecurityAuthService = class SecurityAuthService {
236
269
  const roles = await this.rolesRepo.find({ where: { id: (0, typeorm_2.In)(roleIds) } });
237
270
  return roles.map((role) => (0, roles_1.normalizeRoleName)(role.roleKey)).sort();
238
271
  }
239
- async toSafeUser(user) {
272
+ async toSafeUser(appUser, securityUser) {
240
273
  return {
241
- id: user.id,
242
- email: user.email,
243
- firstName: user.firstName,
244
- lastName: user.lastName,
274
+ id: appUser.id,
275
+ email: appUser.email,
245
276
  phone: null,
246
- roles: await this.getUserRoleKeys(user.id),
247
- emailVerifiedAt: user.emailVerifiedAt,
277
+ roles: await this.getUserRoleKeys(appUser.id),
278
+ emailVerifiedAt: securityUser.emailVerifiedAt,
248
279
  phoneVerifiedAt: null,
249
- adminApprovedAt: user.adminApprovedAt,
250
- isActive: user.isActive,
280
+ adminApprovedAt: securityUser.adminApprovedAt,
281
+ isActive: securityUser.isActive,
251
282
  };
252
283
  }
253
284
  };
@@ -255,15 +286,17 @@ exports.SecurityAuthService = SecurityAuthService;
255
286
  exports.SecurityAuthService = SecurityAuthService = __decorate([
256
287
  (0, common_1.Injectable)(),
257
288
  __param(0, (0, typeorm_1.InjectRepository)(app_user_entity_1.AppUserEntity)),
258
- __param(1, (0, typeorm_1.InjectRepository)(refresh_token_entity_1.RefreshTokenEntity)),
259
- __param(2, (0, typeorm_1.InjectRepository)(password_reset_token_entity_1.PasswordResetTokenEntity)),
260
- __param(3, (0, typeorm_1.InjectRepository)(security_role_entity_1.SecurityRoleEntity)),
261
- __param(4, (0, typeorm_1.InjectRepository)(security_user_role_entity_1.SecurityUserRoleEntity)),
262
- __param(5, (0, common_1.Inject)(security_auth_constants_1.SECURITY_AUTH_OPTIONS)),
263
- __param(6, (0, common_1.Inject)(tokens_1.SECURITY_WORKFLOW_NOTIFIER)),
289
+ __param(1, (0, typeorm_1.InjectRepository)(security_user_entity_1.SecurityUserEntity)),
290
+ __param(2, (0, typeorm_1.InjectRepository)(refresh_token_entity_1.RefreshTokenEntity)),
291
+ __param(3, (0, typeorm_1.InjectRepository)(password_reset_token_entity_1.PasswordResetTokenEntity)),
292
+ __param(4, (0, typeorm_1.InjectRepository)(security_role_entity_1.SecurityRoleEntity)),
293
+ __param(5, (0, typeorm_1.InjectRepository)(security_user_role_entity_1.SecurityUserRoleEntity)),
294
+ __param(6, (0, common_1.Inject)(security_auth_constants_1.SECURITY_AUTH_OPTIONS)),
295
+ __param(7, (0, common_1.Inject)(tokens_1.SECURITY_WORKFLOW_NOTIFIER)),
264
296
  __metadata("design:paramtypes", [typeorm_2.Repository,
265
297
  typeorm_2.Repository,
266
298
  typeorm_2.Repository,
267
299
  typeorm_2.Repository,
300
+ typeorm_2.Repository,
268
301
  typeorm_2.Repository, Object, Object])
269
302
  ], SecurityAuthService);
@@ -8,11 +8,13 @@ vitest_1.vi.mock("bcryptjs", () => ({
8
8
  }));
9
9
  vitest_1.vi.mock("crypto", () => ({
10
10
  randomBytes: vitest_1.vi.fn(() => ({ toString: () => "token-bytes" })),
11
- randomUUID: vitest_1.vi.fn(() => "uuid-1"),
12
11
  }));
13
12
  vitest_1.vi.mock("jsonwebtoken", () => ({
14
13
  sign: vitest_1.vi.fn(() => "signed-access-token"),
15
14
  }));
15
+ vitest_1.vi.mock("uuid", () => ({
16
+ v7: vitest_1.vi.fn(() => "uuid-1"),
17
+ }));
16
18
  const bcryptjs_1 = require("bcryptjs");
17
19
  const jsonwebtoken_1 = require("jsonwebtoken");
18
20
  const security_auth_service_1 = require("./security-auth.service");
@@ -32,22 +34,24 @@ const makeNotifier = () => ({
32
34
  const makeUser = () => ({
33
35
  id: "user-1",
34
36
  email: "user@example.com",
37
+ });
38
+ const makeSecurityUser = () => ({
39
+ userId: "user-1",
35
40
  passwordHash: "hashed:Secret123",
36
- firstName: "A",
37
- lastName: "B",
38
41
  emailVerifiedAt: new Date("2026-01-01T00:00:00.000Z"),
39
42
  emailVerificationToken: null,
40
43
  adminApprovedAt: new Date("2026-01-01T00:00:00.000Z"),
41
44
  isActive: true,
42
45
  });
43
46
  const setup = () => {
44
- const usersRepo = makeRepo();
47
+ const appUsersRepo = makeRepo();
48
+ const securityUsersRepo = makeRepo();
45
49
  const refreshTokenRepo = makeRepo();
46
50
  const passwordResetRepo = makeRepo();
47
51
  const rolesRepo = makeRepo();
48
52
  const userRolesRepo = makeRepo();
49
53
  const notifier = makeNotifier();
50
- const service = new security_auth_service_1.SecurityAuthService(usersRepo, refreshTokenRepo, passwordResetRepo, rolesRepo, userRolesRepo, {
54
+ const service = new security_auth_service_1.SecurityAuthService(appUsersRepo, securityUsersRepo, refreshTokenRepo, passwordResetRepo, rolesRepo, userRolesRepo, {
51
55
  jwtSecret: "secret",
52
56
  accessTokenExpiresIn: "15m",
53
57
  refreshTokenExpiresInDays: 30,
@@ -57,7 +61,8 @@ const setup = () => {
57
61
  }, notifier);
58
62
  return {
59
63
  service,
60
- usersRepo,
64
+ appUsersRepo,
65
+ securityUsersRepo,
61
66
  refreshTokenRepo,
62
67
  passwordResetRepo,
63
68
  rolesRepo,
@@ -70,30 +75,30 @@ const setup = () => {
70
75
  vitest_1.vi.clearAllMocks();
71
76
  });
72
77
  (0, vitest_1.it)("registers user and sends verification", async () => {
73
- const { service, usersRepo, userRolesRepo, rolesRepo, notifier } = setup();
74
- usersRepo.findOne.mockResolvedValue(null);
75
- usersRepo.save.mockResolvedValue(makeUser());
78
+ const { service, appUsersRepo, securityUsersRepo, userRolesRepo, rolesRepo, notifier, } = setup();
79
+ appUsersRepo.findOne.mockResolvedValue(null);
80
+ appUsersRepo.save.mockResolvedValue(makeUser());
81
+ securityUsersRepo.save.mockResolvedValue(makeSecurityUser());
76
82
  userRolesRepo.find.mockResolvedValue([]);
77
83
  rolesRepo.find.mockResolvedValue([]);
78
84
  const result = await service.register({
79
85
  email: "USER@example.com",
80
86
  password: "Secret123",
81
- firstName: "A",
82
- lastName: "B",
83
87
  });
84
88
  (0, vitest_1.expect)(result.success).toBe(true);
85
- (0, vitest_1.expect)(usersRepo.create).toHaveBeenCalledWith(vitest_1.expect.objectContaining({ email: "user@example.com" }));
89
+ (0, vitest_1.expect)(appUsersRepo.create).toHaveBeenCalledWith(vitest_1.expect.objectContaining({ email: "user@example.com" }));
86
90
  (0, vitest_1.expect)(notifier.sendEmailVerification).toHaveBeenCalled();
87
91
  });
88
92
  (0, vitest_1.it)("rejects duplicate email on register", async () => {
89
- const { service, usersRepo } = setup();
90
- usersRepo.findOne.mockResolvedValue(makeUser());
93
+ const { service, appUsersRepo } = setup();
94
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
91
95
  await (0, vitest_1.expect)(service.register({ email: "user@example.com", password: "Secret123" })).rejects.toBeInstanceOf(common_1.BadRequestException);
92
96
  });
93
97
  (0, vitest_1.it)("handles login success and auth failures", async () => {
94
- const { service, usersRepo, userRolesRepo, rolesRepo, refreshTokenRepo } = setup();
98
+ const { service, appUsersRepo, securityUsersRepo, userRolesRepo, rolesRepo, refreshTokenRepo, } = setup();
95
99
  const user = makeUser();
96
- usersRepo.findOne.mockResolvedValue(user);
100
+ appUsersRepo.findOne.mockResolvedValue(user);
101
+ securityUsersRepo.findOne.mockResolvedValue(makeSecurityUser());
97
102
  userRolesRepo.find.mockResolvedValue([{ roleId: "r1" }]);
98
103
  rolesRepo.find.mockResolvedValue([{ id: "r1", roleKey: "admin" }]);
99
104
  const auth = await service.login({
@@ -103,27 +108,28 @@ const setup = () => {
103
108
  (0, vitest_1.expect)(auth.accessToken).toBe("signed-access-token");
104
109
  (0, vitest_1.expect)(jsonwebtoken_1.sign).toHaveBeenCalled();
105
110
  (0, vitest_1.expect)(refreshTokenRepo.save).toHaveBeenCalled();
106
- usersRepo.findOne.mockResolvedValue(null);
111
+ appUsersRepo.findOne.mockResolvedValue(null);
107
112
  await (0, vitest_1.expect)(service.login({ email: "none@example.com", password: "Secret123" })).rejects.toBeInstanceOf(common_1.UnauthorizedException);
108
113
  });
109
114
  (0, vitest_1.it)("blocks login when account is inactive or missing approvals", async () => {
110
- const { service, usersRepo } = setup();
111
- const inactive = { ...makeUser(), isActive: false };
112
- usersRepo.findOne.mockResolvedValue(inactive);
113
- await (0, vitest_1.expect)(service.login({ email: inactive.email, password: "Secret123" })).rejects.toThrow("Account is inactive");
114
- usersRepo.findOne.mockResolvedValue({
115
- ...makeUser(),
115
+ const { service, appUsersRepo, securityUsersRepo } = setup();
116
+ const inactive = { ...makeSecurityUser(), isActive: false };
117
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
118
+ securityUsersRepo.findOne.mockResolvedValue(inactive);
119
+ await (0, vitest_1.expect)(service.login({ email: "x@example.com", password: "Secret123" })).rejects.toThrow("Account is inactive");
120
+ securityUsersRepo.findOne.mockResolvedValue({
121
+ ...makeSecurityUser(),
116
122
  emailVerifiedAt: null,
117
123
  });
118
124
  await (0, vitest_1.expect)(service.login({ email: "x@example.com", password: "Secret123" })).rejects.toThrow("Email verification required");
119
- usersRepo.findOne.mockResolvedValue({
120
- ...makeUser(),
125
+ securityUsersRepo.findOne.mockResolvedValue({
126
+ ...makeSecurityUser(),
121
127
  adminApprovedAt: null,
122
128
  });
123
129
  await (0, vitest_1.expect)(service.login({ email: "x@example.com", password: "Secret123" })).rejects.toThrow("Admin approval required");
124
130
  });
125
131
  (0, vitest_1.it)("refreshes and revokes tokens", async () => {
126
- const { service, usersRepo, refreshTokenRepo, userRolesRepo, rolesRepo } = setup();
132
+ const { service, appUsersRepo, securityUsersRepo, refreshTokenRepo, userRolesRepo, rolesRepo, } = setup();
127
133
  refreshTokenRepo.find.mockResolvedValue([
128
134
  {
129
135
  id: "rt1",
@@ -132,7 +138,8 @@ const setup = () => {
132
138
  expiresAt: new Date(Date.now() + 60_000),
133
139
  },
134
140
  ]);
135
- usersRepo.findOne.mockResolvedValue(makeUser());
141
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
142
+ securityUsersRepo.findOne.mockResolvedValue(makeSecurityUser());
136
143
  userRolesRepo.find.mockResolvedValue([]);
137
144
  rolesRepo.find.mockResolvedValue([]);
138
145
  const result = await service.refresh("token-bytes");
@@ -144,7 +151,7 @@ const setup = () => {
144
151
  });
145
152
  });
146
153
  (0, vitest_1.it)("rejects invalid refresh token and missing refresh user", async () => {
147
- const { service, refreshTokenRepo, usersRepo } = setup();
154
+ const { service, refreshTokenRepo, appUsersRepo } = setup();
148
155
  refreshTokenRepo.find.mockResolvedValue([]);
149
156
  await (0, vitest_1.expect)(service.refresh("bad-token")).rejects.toThrow("Invalid refresh token");
150
157
  refreshTokenRepo.find.mockResolvedValue([
@@ -155,18 +162,18 @@ const setup = () => {
155
162
  expiresAt: new Date(Date.now() + 60_000),
156
163
  },
157
164
  ]);
158
- usersRepo.findOne.mockResolvedValue(null);
165
+ appUsersRepo.findOne.mockResolvedValue(null);
159
166
  await (0, vitest_1.expect)(service.refresh("token-bytes")).rejects.toThrow("User not found");
160
167
  });
161
168
  (0, vitest_1.it)("changes password", async () => {
162
- const { service, usersRepo } = setup();
163
- usersRepo.findOne.mockResolvedValue(makeUser());
169
+ const { service, securityUsersRepo } = setup();
170
+ securityUsersRepo.findOne.mockResolvedValue(makeSecurityUser());
164
171
  await (0, vitest_1.expect)(service.changePassword({
165
172
  userId: "user-1",
166
173
  currentPassword: "Secret123",
167
174
  newPassword: "NewPass1",
168
175
  })).resolves.toEqual({ success: true });
169
- usersRepo.findOne.mockResolvedValue(null);
176
+ securityUsersRepo.findOne.mockResolvedValue(null);
170
177
  await (0, vitest_1.expect)(service.changePassword({
171
178
  userId: "missing",
172
179
  currentPassword: "Secret123",
@@ -174,8 +181,9 @@ const setup = () => {
174
181
  })).rejects.toThrow("User not found");
175
182
  });
176
183
  (0, vitest_1.it)("handles forgot/reset password flow", async () => {
177
- const { service, usersRepo, passwordResetRepo, notifier } = setup();
178
- usersRepo.findOne.mockResolvedValue(makeUser());
184
+ const { service, appUsersRepo, securityUsersRepo, passwordResetRepo, notifier, } = setup();
185
+ appUsersRepo.findOne.mockResolvedValue(makeUser());
186
+ securityUsersRepo.findOne.mockResolvedValue(makeSecurityUser());
179
187
  passwordResetRepo.findOne.mockResolvedValue({
180
188
  id: "pr1",
181
189
  userId: "user-1",
@@ -193,8 +201,8 @@ const setup = () => {
193
201
  });
194
202
  });
195
203
  (0, vitest_1.it)("returns success for unknown forgot email and rejects bad reset token", async () => {
196
- const { service, usersRepo, passwordResetRepo } = setup();
197
- usersRepo.findOne.mockResolvedValue(null);
204
+ const { service, appUsersRepo, passwordResetRepo } = setup();
205
+ appUsersRepo.findOne.mockResolvedValue(null);
198
206
  await (0, vitest_1.expect)(service.requestForgotPassword("none@example.com")).resolves.toEqual({
199
207
  success: true,
200
208
  });
@@ -210,10 +218,8 @@ const setup = () => {
210
218
  await (0, vitest_1.expect)(service.resetPassword("bad", "x")).rejects.toThrow("Invalid password reset token");
211
219
  });
212
220
  (0, vitest_1.it)("verifies email token and reads user roles", async () => {
213
- const { service, usersRepo, userRolesRepo, rolesRepo } = setup();
214
- usersRepo.findOne
215
- .mockResolvedValueOnce(makeUser())
216
- .mockResolvedValueOnce(makeUser());
221
+ const { service, securityUsersRepo, userRolesRepo, rolesRepo } = setup();
222
+ securityUsersRepo.findOne.mockResolvedValue(makeSecurityUser());
217
223
  userRolesRepo.find.mockResolvedValue([{ roleId: "r1" }]);
218
224
  rolesRepo.find.mockResolvedValue([{ id: "r1", roleKey: "coach" }]);
219
225
  await (0, vitest_1.expect)(service.verifyEmailByToken("token-bytes")).resolves.toEqual({
@@ -225,8 +231,8 @@ const setup = () => {
225
231
  });
226
232
  });
227
233
  (0, vitest_1.it)("rejects invalid email verification token", async () => {
228
- const { service, usersRepo } = setup();
229
- usersRepo.findOne.mockResolvedValue(null);
234
+ const { service, securityUsersRepo } = setup();
235
+ securityUsersRepo.findOne.mockResolvedValue(null);
230
236
  await (0, vitest_1.expect)(service.verifyEmailByToken("missing")).rejects.toThrow("Invalid verification token");
231
237
  });
232
238
  (0, vitest_1.it)("handles refresh with expired matching token", async () => {
@@ -15,6 +15,7 @@ const security_admin_guard_1 = require("./security-admin.guard");
15
15
  const security_jwt_guard_1 = require("./security-jwt.guard");
16
16
  const app_user_entity_1 = require("./entities/app-user.entity");
17
17
  const security_role_entity_1 = require("./entities/security-role.entity");
18
+ const security_user_entity_1 = require("./entities/security-user.entity");
18
19
  const security_user_role_entity_1 = require("./entities/security-user-role.entity");
19
20
  const security_workflows_controller_1 = require("./security-workflows.controller");
20
21
  const security_workflows_service_1 = require("./security-workflows.service");
@@ -34,6 +35,7 @@ let SecurityWorkflowsModule = SecurityWorkflowsModule_1 = class SecurityWorkflow
34
35
  imports: [
35
36
  typeorm_1.TypeOrmModule.forFeature([
36
37
  app_user_entity_1.AppUserEntity,
38
+ security_user_entity_1.SecurityUserEntity,
37
39
  security_role_entity_1.SecurityRoleEntity,
38
40
  security_user_role_entity_1.SecurityUserRoleEntity,
39
41
  ]),
@@ -1,14 +1,16 @@
1
1
  import { Repository } from "typeorm";
2
2
  import { AppUserEntity } from "./entities/app-user.entity";
3
3
  import { SecurityRoleEntity } from "./entities/security-role.entity";
4
+ import { SecurityUserEntity } from "./entities/security-user.entity";
4
5
  import { SecurityUserRoleEntity } from "./entities/security-user-role.entity";
5
6
  import { SecurityWorkflowNotifier } from "./contracts";
6
7
  export declare class SecurityWorkflowsService {
7
- private readonly usersRepo;
8
+ private readonly appUsersRepo;
9
+ private readonly securityUsersRepo;
8
10
  private readonly rolesRepo;
9
11
  private readonly userRolesRepo;
10
12
  private readonly notifier;
11
- constructor(usersRepo: Repository<AppUserEntity>, rolesRepo: Repository<SecurityRoleEntity>, userRolesRepo: Repository<SecurityUserRoleEntity>, notifier: SecurityWorkflowNotifier);
13
+ constructor(appUsersRepo: Repository<AppUserEntity>, securityUsersRepo: Repository<SecurityUserEntity>, rolesRepo: Repository<SecurityRoleEntity>, userRolesRepo: Repository<SecurityUserRoleEntity>, notifier: SecurityWorkflowNotifier);
12
14
  markEmailVerifiedAndNotifyAdmins(userId: string): Promise<{
13
15
  success: true;
14
16
  notified: false;