@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.
- package/README.md +48 -7
- package/dist/api/contracts.d.ts +0 -2
- package/dist/api/migrations/1739500000000-create-security-identity.d.ts +1 -1
- package/dist/api/migrations/1739500000000-create-security-identity.js +9 -35
- package/dist/api/migrations/1739510000000-create-security-roles.d.ts +1 -1
- package/dist/api/migrations/1739510000000-create-security-roles.js +1 -67
- package/dist/api/migrations/1739515000000-create-security-user-roles.d.ts +9 -0
- package/dist/api/migrations/1739515000000-create-security-user-roles.js +39 -0
- package/dist/api/migrations/1739520000000-create-password-reset-tokens.js +1 -1
- package/dist/api/migrations/1739530000000-create-security-user.d.ts +9 -0
- package/dist/api/migrations/1739530000000-create-security-user.js +41 -0
- package/dist/api/migrations/index.d.ts +3 -2
- package/dist/api/migrations/index.js +7 -4
- package/dist/api/migrations/migrations.test.js +37 -83
- package/dist/api/notification-workflows.d.ts +0 -4
- package/dist/api/notification-workflows.js +0 -1
- package/dist/api/notification-workflows.test.js +1 -4
- package/dist/app/client.d.ts +0 -2
- package/dist/app/client.test.js +0 -2
- package/dist/nest/contracts.d.ts +0 -3
- package/dist/nest/dto/auth.dto.d.ts +0 -2
- package/dist/nest/dto/auth.dto.js +0 -10
- package/dist/nest/entities/app-user.entity.d.ts +0 -7
- package/dist/nest/entities/app-user.entity.js +0 -35
- package/dist/nest/entities/security-user.entity.d.ts +9 -0
- package/dist/nest/entities/security-user.entity.js +54 -0
- package/dist/nest/index.d.ts +1 -0
- package/dist/nest/index.js +1 -0
- package/dist/nest/security-auth.controller.d.ts +0 -2
- package/dist/nest/security-auth.controller.js +0 -2
- package/dist/nest/security-auth.controller.test.js +0 -4
- package/dist/nest/security-auth.module.js +2 -0
- package/dist/nest/security-auth.service.d.ts +5 -4
- package/dist/nest/security-auth.service.js +81 -51
- package/dist/nest/security-auth.service.test.js +45 -41
- package/dist/nest/security-workflows.module.js +2 -0
- package/dist/nest/security-workflows.service.d.ts +4 -2
- package/dist/nest/security-workflows.service.js +19 -16
- package/dist/nest/security-workflows.service.test.js +29 -24
- package/package.json +3 -3
- package/src/api/contracts.ts +0 -2
- package/src/api/migrations/1739500000000-create-security-identity.ts +11 -50
- package/src/api/migrations/1739510000000-create-security-roles.ts +2 -89
- package/src/api/migrations/1739515000000-create-security-user-roles.ts +49 -0
- package/src/api/migrations/1739520000000-create-password-reset-tokens.ts +1 -1
- package/src/api/migrations/1739530000000-create-security-user.ts +51 -0
- package/src/api/migrations/index.ts +6 -3
- package/src/api/migrations/migrations.test.ts +48 -111
- package/src/api/notification-workflows.test.ts +1 -4
- package/src/api/notification-workflows.ts +1 -8
- package/src/app/client.test.ts +0 -2
- package/src/app/client.ts +1 -6
- package/src/nest/contracts.ts +1 -6
- package/src/nest/dto/auth.dto.ts +0 -6
- package/src/nest/entities/app-user.entity.ts +0 -21
- package/src/nest/entities/security-user.entity.ts +25 -0
- package/src/nest/index.ts +1 -0
- package/src/nest/security-auth.controller.test.ts +0 -4
- package/src/nest/security-auth.controller.ts +0 -4
- package/src/nest/security-auth.module.ts +2 -0
- package/src/nest/security-auth.service.test.ts +74 -43
- package/src/nest/security-auth.service.ts +88 -51
- package/src/nest/security-workflows.module.ts +2 -0
- package/src/nest/security-workflows.service.test.ts +31 -25
- package/src/nest/security-workflows.service.ts +18 -13
- package/dist/api/migrations/1739490000000-add-google-subject-to-user.d.ts +0 -5
- package/dist/api/migrations/1739490000000-add-google-subject-to-user.js +0 -14
- 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
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
51
|
-
|
|
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,
|
|
77
|
-
|
|
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,
|
|
95
|
-
|
|
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,
|
|
104
|
-
|
|
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,
|
|
122
|
-
|
|
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,
|
|
170
|
-
|
|
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,
|
|
193
|
-
|
|
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,
|
|
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(
|
|
216
|
-
{
|
|
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,
|
|
223
|
-
|
|
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
|
|
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.
|
|
27
|
-
{
|
|
29
|
+
await this.securityUsersRepo.update(
|
|
30
|
+
{ userId },
|
|
28
31
|
{ emailVerifiedAt: new Date(), emailVerificationToken: null },
|
|
29
32
|
);
|
|
30
33
|
|
|
31
|
-
const user = await this.
|
|
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.
|
|
56
|
-
{
|
|
56
|
+
await this.securityUsersRepo.update(
|
|
57
|
+
{ userId },
|
|
57
58
|
{ adminApprovedAt: approved ? new Date() : null },
|
|
58
59
|
);
|
|
59
60
|
|
|
60
|
-
const user = await this.
|
|
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("
|
|
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.
|
|
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.
|
|
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,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
|
-
}
|