@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.
- package/README.md +48 -7
- package/dist/api/contracts.d.ts +0 -2
- package/dist/api/migrations/1700000000001-add-refresh-tokens.js +4 -2
- package/dist/api/migrations/1739500000000-create-security-identity.d.ts +1 -1
- package/dist/api/migrations/1739500000000-create-security-identity.js +12 -36
- package/dist/api/migrations/1739510000000-create-security-roles.d.ts +1 -1
- package/dist/api/migrations/1739510000000-create-security-roles.js +3 -68
- package/dist/api/migrations/1739515000000-create-security-user-roles.d.ts +9 -0
- package/dist/api/migrations/1739515000000-create-security-user-roles.js +42 -0
- package/dist/api/migrations/1739520000000-create-password-reset-tokens.js +4 -2
- package/dist/api/migrations/1739530000000-create-security-user.d.ts +9 -0
- package/dist/api/migrations/1739530000000-create-security-user.js +42 -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/integration/database.integration.test.js +1 -1
- 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 +1 -36
- package/dist/nest/entities/password-reset-token.entity.d.ts +1 -0
- package/dist/nest/entities/password-reset-token.entity.js +14 -2
- package/dist/nest/entities/refresh-token.entity.js +2 -2
- package/dist/nest/entities/security-role.entity.d.ts +1 -0
- package/dist/nest/entities/security-role.entity.js +13 -1
- package/dist/nest/entities/security-user-role.entity.d.ts +1 -0
- package/dist/nest/entities/security-user-role.entity.js +14 -2
- 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 +85 -52
- package/dist/nest/security-auth.service.test.js +48 -42
- 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 +23 -16
- package/dist/nest/security-workflows.service.test.js +29 -24
- package/package.json +5 -4
- package/src/api/contracts.ts +0 -2
- package/src/api/migrations/1700000000001-add-refresh-tokens.ts +4 -2
- package/src/api/migrations/1739500000000-create-security-identity.ts +14 -51
- package/src/api/migrations/1739510000000-create-security-roles.ts +4 -90
- package/src/api/migrations/1739515000000-create-security-user-roles.ts +52 -0
- package/src/api/migrations/1739520000000-create-password-reset-tokens.ts +4 -2
- package/src/api/migrations/1739530000000-create-security-user.ts +52 -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/integration/database.integration.test.ts +1 -1
- package/src/nest/contracts.ts +1 -6
- package/src/nest/dto/auth.dto.ts +0 -6
- package/src/nest/entities/app-user.entity.ts +2 -23
- package/src/nest/entities/password-reset-token.entity.ts +12 -3
- package/src/nest/entities/refresh-token.entity.ts +2 -2
- package/src/nest/entities/security-role.entity.ts +10 -2
- package/src/nest/entities/security-user-role.entity.ts +11 -3
- 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 +78 -44
- package/src/nest/security-auth.service.ts +93 -53
- 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 +22 -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
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { AddRefreshTokens1700000000001 } from "./1700000000001-add-refresh-tokens";
|
|
2
|
-
import { AddGoogleSubjectToUser1739490000000 } from "./1739490000000-add-google-subject-to-user";
|
|
3
2
|
import { CreateSecurityIdentity1739500000000 } from "./1739500000000-create-security-identity";
|
|
4
3
|
import { CreateSecurityRoles1739510000000 } from "./1739510000000-create-security-roles";
|
|
4
|
+
import { CreateSecurityUserRoles1739515000000 } from "./1739515000000-create-security-user-roles";
|
|
5
5
|
import { CreatePasswordResetTokens1739520000000 } from "./1739520000000-create-password-reset-tokens";
|
|
6
|
+
import { CreateSecurityUser1739530000000 } from "./1739530000000-create-security-user";
|
|
6
7
|
|
|
7
8
|
export const securityMigrations = [
|
|
8
9
|
AddRefreshTokens1700000000001,
|
|
9
|
-
AddGoogleSubjectToUser1739490000000,
|
|
10
10
|
CreateSecurityIdentity1739500000000,
|
|
11
11
|
CreateSecurityRoles1739510000000,
|
|
12
|
+
CreateSecurityUserRoles1739515000000,
|
|
12
13
|
CreatePasswordResetTokens1739520000000,
|
|
14
|
+
CreateSecurityUser1739530000000,
|
|
13
15
|
];
|
|
14
16
|
|
|
15
17
|
export {
|
|
16
18
|
AddRefreshTokens1700000000001,
|
|
17
|
-
AddGoogleSubjectToUser1739490000000,
|
|
18
19
|
CreateSecurityIdentity1739500000000,
|
|
19
20
|
CreateSecurityRoles1739510000000,
|
|
21
|
+
CreateSecurityUserRoles1739515000000,
|
|
20
22
|
CreatePasswordResetTokens1739520000000,
|
|
23
|
+
CreateSecurityUser1739530000000,
|
|
21
24
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { AddRefreshTokens1700000000001 } from "./1700000000001-add-refresh-tokens";
|
|
3
|
-
import { AddGoogleSubjectToUser1739490000000 } from "./1739490000000-add-google-subject-to-user";
|
|
4
3
|
import { CreateSecurityIdentity1739500000000 } from "./1739500000000-create-security-identity";
|
|
5
4
|
import { CreateSecurityRoles1739510000000 } from "./1739510000000-create-security-roles";
|
|
5
|
+
import { CreateSecurityUserRoles1739515000000 } from "./1739515000000-create-security-user-roles";
|
|
6
6
|
import { CreatePasswordResetTokens1739520000000 } from "./1739520000000-create-password-reset-tokens";
|
|
7
|
+
import { CreateSecurityUser1739530000000 } from "./1739530000000-create-security-user";
|
|
7
8
|
import { securityMigrations } from "./index";
|
|
8
9
|
|
|
9
10
|
const originalEnv = { ...process.env };
|
|
@@ -15,11 +16,18 @@ afterEach(() => {
|
|
|
15
16
|
|
|
16
17
|
describe("security migrations", () => {
|
|
17
18
|
it("exports migration list", () => {
|
|
18
|
-
expect(securityMigrations.length).toBe(
|
|
19
|
-
expect(securityMigrations
|
|
19
|
+
expect(securityMigrations.length).toBe(6);
|
|
20
|
+
expect(securityMigrations).toEqual([
|
|
21
|
+
AddRefreshTokens1700000000001,
|
|
22
|
+
CreateSecurityIdentity1739500000000,
|
|
23
|
+
CreateSecurityRoles1739510000000,
|
|
24
|
+
CreateSecurityUserRoles1739515000000,
|
|
25
|
+
CreatePasswordResetTokens1739520000000,
|
|
26
|
+
CreateSecurityUser1739530000000,
|
|
27
|
+
]);
|
|
20
28
|
});
|
|
21
29
|
|
|
22
|
-
it("runs
|
|
30
|
+
it("runs refresh token migration up/down", async () => {
|
|
23
31
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
24
32
|
const migration = new AddRefreshTokens1700000000001();
|
|
25
33
|
|
|
@@ -34,46 +42,10 @@ describe("security migrations", () => {
|
|
|
34
42
|
);
|
|
35
43
|
});
|
|
36
44
|
|
|
37
|
-
it("
|
|
38
|
-
process.env.USER_TABLE = "users";
|
|
39
|
-
process.env.USER_TABLE_SCHEMA = "security";
|
|
45
|
+
it("runs security identity migration up/down", async () => {
|
|
40
46
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
41
|
-
|
|
42
|
-
await new AddRefreshTokens1700000000001().up({ query });
|
|
43
|
-
|
|
44
|
-
expect(query).toHaveBeenCalledWith(
|
|
45
|
-
expect.stringContaining('REFERENCES "security"."users" ("id")'),
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("throws for invalid identifiers in refresh token migration", async () => {
|
|
50
|
-
process.env.USER_TABLE = "bad-name;drop";
|
|
51
|
-
const query = vi.fn().mockResolvedValue(undefined);
|
|
52
|
-
|
|
53
|
-
await expect(
|
|
54
|
-
new AddRefreshTokens1700000000001().up({ query }),
|
|
55
|
-
).rejects.toThrow("Invalid SQL identifier");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("keeps legacy google subject migration as no-op", async () => {
|
|
59
|
-
const migration = new AddGoogleSubjectToUser1739490000000();
|
|
60
|
-
await expect(migration.up()).resolves.toBeUndefined();
|
|
61
|
-
await expect(migration.down()).resolves.toBeUndefined();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("runs security identity migration path with google_subject present", async () => {
|
|
65
|
-
const query = vi
|
|
66
|
-
.fn()
|
|
67
|
-
.mockResolvedValueOnce(undefined)
|
|
68
|
-
.mockResolvedValueOnce(undefined)
|
|
69
|
-
.mockResolvedValueOnce(undefined)
|
|
70
|
-
.mockResolvedValueOnce(undefined)
|
|
71
|
-
.mockResolvedValueOnce([{ "?column?": 1 }])
|
|
72
|
-
.mockResolvedValueOnce(undefined)
|
|
73
|
-
.mockResolvedValueOnce(undefined)
|
|
74
|
-
.mockResolvedValueOnce(undefined);
|
|
75
|
-
|
|
76
47
|
const migration = new CreateSecurityIdentity1739500000000();
|
|
48
|
+
|
|
77
49
|
await migration.up({ query });
|
|
78
50
|
await migration.down({ query });
|
|
79
51
|
|
|
@@ -85,52 +57,28 @@ describe("security migrations", () => {
|
|
|
85
57
|
);
|
|
86
58
|
});
|
|
87
59
|
|
|
88
|
-
it("
|
|
89
|
-
const query = vi
|
|
90
|
-
|
|
91
|
-
.mockResolvedValueOnce(undefined)
|
|
92
|
-
.mockResolvedValueOnce(undefined)
|
|
93
|
-
.mockResolvedValueOnce(undefined)
|
|
94
|
-
.mockResolvedValueOnce(undefined)
|
|
95
|
-
.mockResolvedValueOnce([]);
|
|
60
|
+
it("runs security role migration up/down", async () => {
|
|
61
|
+
const query = vi.fn().mockResolvedValue(undefined);
|
|
62
|
+
const migration = new CreateSecurityRoles1739510000000();
|
|
96
63
|
|
|
97
|
-
await
|
|
64
|
+
await migration.up({ query });
|
|
65
|
+
await migration.down({ query });
|
|
98
66
|
|
|
99
|
-
expect(query).
|
|
100
|
-
expect.stringContaining('
|
|
67
|
+
expect(query).toHaveBeenCalledWith(
|
|
68
|
+
expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_role"'),
|
|
69
|
+
);
|
|
70
|
+
expect(query).toHaveBeenCalledWith(
|
|
71
|
+
expect.stringContaining('DROP TABLE IF EXISTS "security_role"'),
|
|
101
72
|
);
|
|
102
73
|
});
|
|
103
74
|
|
|
104
|
-
it("
|
|
105
|
-
process.env.USER_TABLE_SCHEMA = "bad-schema!";
|
|
75
|
+
it("runs security user role migration up/down", async () => {
|
|
106
76
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
77
|
+
const migration = new CreateSecurityUserRoles1739515000000();
|
|
107
78
|
|
|
108
|
-
await expect(
|
|
109
|
-
new CreateSecurityIdentity1739500000000().up({ query }),
|
|
110
|
-
).rejects.toThrow("Invalid SQL identifier");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("runs security roles migration path with legacy role column", async () => {
|
|
114
|
-
const query = vi
|
|
115
|
-
.fn()
|
|
116
|
-
.mockResolvedValueOnce(undefined)
|
|
117
|
-
.mockResolvedValueOnce(undefined)
|
|
118
|
-
.mockResolvedValueOnce(undefined)
|
|
119
|
-
.mockResolvedValueOnce(undefined)
|
|
120
|
-
.mockResolvedValueOnce(undefined)
|
|
121
|
-
.mockResolvedValueOnce(undefined)
|
|
122
|
-
.mockResolvedValueOnce([{ "?column?": 1 }])
|
|
123
|
-
.mockResolvedValueOnce(undefined)
|
|
124
|
-
.mockResolvedValueOnce(undefined)
|
|
125
|
-
.mockResolvedValueOnce(undefined);
|
|
126
|
-
|
|
127
|
-
const migration = new CreateSecurityRoles1739510000000();
|
|
128
79
|
await migration.up({ query });
|
|
129
80
|
await migration.down({ query });
|
|
130
81
|
|
|
131
|
-
expect(query).toHaveBeenCalledWith(
|
|
132
|
-
expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_role"'),
|
|
133
|
-
);
|
|
134
82
|
expect(query).toHaveBeenCalledWith(
|
|
135
83
|
expect.stringContaining(
|
|
136
84
|
'CREATE TABLE IF NOT EXISTS "security_user_role"',
|
|
@@ -141,33 +89,6 @@ describe("security migrations", () => {
|
|
|
141
89
|
);
|
|
142
90
|
});
|
|
143
91
|
|
|
144
|
-
it("skips legacy role backfill when role column absent", async () => {
|
|
145
|
-
const query = vi
|
|
146
|
-
.fn()
|
|
147
|
-
.mockResolvedValueOnce(undefined)
|
|
148
|
-
.mockResolvedValueOnce(undefined)
|
|
149
|
-
.mockResolvedValueOnce(undefined)
|
|
150
|
-
.mockResolvedValueOnce(undefined)
|
|
151
|
-
.mockResolvedValueOnce(undefined)
|
|
152
|
-
.mockResolvedValueOnce(undefined)
|
|
153
|
-
.mockResolvedValueOnce([]);
|
|
154
|
-
|
|
155
|
-
await new CreateSecurityRoles1739510000000().up({ query });
|
|
156
|
-
|
|
157
|
-
expect(query).not.toHaveBeenCalledWith(
|
|
158
|
-
expect.stringContaining('DROP COLUMN IF EXISTS "role"'),
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("throws for invalid identifiers in roles migration", async () => {
|
|
163
|
-
process.env.USER_TABLE = "bad-name*";
|
|
164
|
-
const query = vi.fn().mockResolvedValue(undefined);
|
|
165
|
-
|
|
166
|
-
await expect(
|
|
167
|
-
new CreateSecurityRoles1739510000000().up({ query }),
|
|
168
|
-
).rejects.toThrow("Invalid SQL identifier");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
92
|
it("runs password reset token migration up/down", async () => {
|
|
172
93
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
173
94
|
const migration = new CreatePasswordResetTokens1739520000000();
|
|
@@ -187,22 +108,38 @@ describe("security migrations", () => {
|
|
|
187
108
|
);
|
|
188
109
|
});
|
|
189
110
|
|
|
190
|
-
it("
|
|
111
|
+
it("runs security user migration up/down", async () => {
|
|
191
112
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
113
|
+
const migration = new CreateSecurityUser1739530000000();
|
|
114
|
+
|
|
115
|
+
await migration.up({ query });
|
|
116
|
+
await migration.down({ query });
|
|
117
|
+
|
|
118
|
+
expect(query).toHaveBeenCalledWith(
|
|
119
|
+
expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_user"'),
|
|
120
|
+
);
|
|
121
|
+
expect(query).toHaveBeenCalledWith(
|
|
122
|
+
expect.stringContaining('DROP TABLE IF EXISTS "security_user"'),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
192
125
|
|
|
193
|
-
|
|
126
|
+
it("uses user schema/table env safely", async () => {
|
|
127
|
+
process.env.USER_TABLE = "users";
|
|
128
|
+
process.env.USER_TABLE_SCHEMA = "security";
|
|
129
|
+
const query = vi.fn().mockResolvedValue(undefined);
|
|
194
130
|
|
|
131
|
+
await new CreateSecurityUser1739530000000().up({ query });
|
|
195
132
|
expect(query).toHaveBeenCalledWith(
|
|
196
|
-
expect.stringContaining('REFERENCES "
|
|
133
|
+
expect.stringContaining('REFERENCES "security"."users" ("id")'),
|
|
197
134
|
);
|
|
198
135
|
});
|
|
199
136
|
|
|
200
|
-
it("throws for invalid identifiers
|
|
201
|
-
process.env.
|
|
137
|
+
it("throws for invalid identifiers", async () => {
|
|
138
|
+
process.env.USER_TABLE = "bad-name!";
|
|
202
139
|
const query = vi.fn().mockResolvedValue(undefined);
|
|
203
140
|
|
|
204
141
|
await expect(
|
|
205
|
-
new
|
|
142
|
+
new CreateSecurityUserRoles1739515000000().up({ query }),
|
|
206
143
|
).rejects.toThrow("Invalid SQL identifier");
|
|
207
144
|
});
|
|
208
145
|
});
|
|
@@ -15,8 +15,6 @@ describe("notification-workflows", () => {
|
|
|
15
15
|
user: {
|
|
16
16
|
id: "user-1",
|
|
17
17
|
email: "user@example.com",
|
|
18
|
-
firstName: "User",
|
|
19
|
-
lastName: "One",
|
|
20
18
|
},
|
|
21
19
|
listAdminEmails,
|
|
22
20
|
notifyAdmins,
|
|
@@ -55,14 +53,13 @@ describe("notification-workflows", () => {
|
|
|
55
53
|
|
|
56
54
|
const result = await notifyUserOnAdminApproval({
|
|
57
55
|
approved: true,
|
|
58
|
-
user: { email: "user@example.com"
|
|
56
|
+
user: { email: "user@example.com" },
|
|
59
57
|
notifyUser,
|
|
60
58
|
});
|
|
61
59
|
|
|
62
60
|
expect(result).toEqual({ notified: true });
|
|
63
61
|
expect(notifyUser).toHaveBeenCalledWith({
|
|
64
62
|
email: "user@example.com",
|
|
65
|
-
firstName: "User",
|
|
66
63
|
});
|
|
67
64
|
});
|
|
68
65
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export type VerificationNotificationUser = {
|
|
2
2
|
id: string;
|
|
3
3
|
email: string;
|
|
4
|
-
firstName?: string | null;
|
|
5
|
-
lastName?: string | null;
|
|
6
4
|
};
|
|
7
5
|
|
|
8
6
|
export const notifyAdminsOnEmailVerified = async (params: {
|
|
@@ -26,12 +24,8 @@ export const notifyUserOnAdminApproval = async (params: {
|
|
|
26
24
|
approved: boolean;
|
|
27
25
|
user: {
|
|
28
26
|
email: string;
|
|
29
|
-
firstName?: string | null;
|
|
30
27
|
};
|
|
31
|
-
notifyUser: (payload: {
|
|
32
|
-
email: string;
|
|
33
|
-
firstName?: string | null;
|
|
34
|
-
}) => Promise<void>;
|
|
28
|
+
notifyUser: (payload: { email: string }) => Promise<void>;
|
|
35
29
|
}) => {
|
|
36
30
|
if (!params.approved) {
|
|
37
31
|
return { notified: false as const };
|
|
@@ -39,7 +33,6 @@ export const notifyUserOnAdminApproval = async (params: {
|
|
|
39
33
|
|
|
40
34
|
await params.notifyUser({
|
|
41
35
|
email: params.user.email,
|
|
42
|
-
firstName: params.user.firstName,
|
|
43
36
|
});
|
|
44
37
|
return { notified: true as const };
|
|
45
38
|
};
|
package/src/app/client.test.ts
CHANGED
package/src/app/client.ts
CHANGED
|
@@ -52,12 +52,7 @@ export const createSecurityClient = (options: SecurityClientOptions) => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
|
-
register: (payload: {
|
|
56
|
-
email: string;
|
|
57
|
-
password: string;
|
|
58
|
-
firstName?: string;
|
|
59
|
-
lastName?: string;
|
|
60
|
-
}) =>
|
|
55
|
+
register: (payload: { email: string; password: string }) =>
|
|
61
56
|
request<RegisterResponse>("/security/auth/register", {
|
|
62
57
|
method: "POST",
|
|
63
58
|
body: JSON.stringify(payload),
|
|
@@ -121,7 +121,7 @@ describe("database integration", () => {
|
|
|
121
121
|
await client.query(`SET search_path TO "${schema}", public`);
|
|
122
122
|
await client.query(`
|
|
123
123
|
CREATE TABLE IF NOT EXISTS "${schema}"."app_user" (
|
|
124
|
-
"id"
|
|
124
|
+
"id" uuid PRIMARY KEY NOT NULL,
|
|
125
125
|
"email" varchar NOT NULL
|
|
126
126
|
)
|
|
127
127
|
`);
|
package/src/nest/contracts.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export type SecurityWorkflowUser = {
|
|
2
2
|
id: string;
|
|
3
3
|
email: string;
|
|
4
|
-
firstName: string | null;
|
|
5
|
-
lastName: string | null;
|
|
6
4
|
};
|
|
7
5
|
|
|
8
6
|
export type SecurityWorkflowNotifier = {
|
|
@@ -18,8 +16,5 @@ export type SecurityWorkflowNotifier = {
|
|
|
18
16
|
adminEmails: string[];
|
|
19
17
|
user: SecurityWorkflowUser;
|
|
20
18
|
}) => Promise<void>;
|
|
21
|
-
sendUserAccountApproved: (params: {
|
|
22
|
-
email: string;
|
|
23
|
-
firstName: string | null;
|
|
24
|
-
}) => Promise<void>;
|
|
19
|
+
sendUserAccountApproved: (params: { email: string }) => Promise<void>;
|
|
25
20
|
};
|
package/src/nest/dto/auth.dto.ts
CHANGED
|
@@ -6,12 +6,6 @@ export class RegisterDto {
|
|
|
6
6
|
|
|
7
7
|
@ApiProperty({ example: "StrongPass1" })
|
|
8
8
|
password!: string;
|
|
9
|
-
|
|
10
|
-
@ApiProperty({ required: false, nullable: true, example: "John" })
|
|
11
|
-
firstName?: string | null;
|
|
12
|
-
|
|
13
|
-
@ApiProperty({ required: false, nullable: true, example: "Doe" })
|
|
14
|
-
lastName?: string | null;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
11
|
export class LoginDto {
|
|
@@ -1,31 +1,10 @@
|
|
|
1
|
-
import { Column, Entity,
|
|
1
|
+
import { Column, Entity, PrimaryColumn } from "typeorm";
|
|
2
2
|
|
|
3
3
|
@Entity({ name: "app_user" })
|
|
4
4
|
export class AppUserEntity {
|
|
5
|
-
@
|
|
5
|
+
@PrimaryColumn({ type: "uuid" })
|
|
6
6
|
id!: string;
|
|
7
7
|
|
|
8
8
|
@Column({ type: "varchar" })
|
|
9
9
|
email!: string;
|
|
10
|
-
|
|
11
|
-
@Column({ type: "varchar", name: "password_hash" })
|
|
12
|
-
passwordHash!: string;
|
|
13
|
-
|
|
14
|
-
@Column({ type: "varchar", name: "first_name", nullable: true })
|
|
15
|
-
firstName!: string | null;
|
|
16
|
-
|
|
17
|
-
@Column({ type: "varchar", name: "last_name", nullable: true })
|
|
18
|
-
lastName!: string | null;
|
|
19
|
-
|
|
20
|
-
@Column({ type: "timestamptz", name: "email_verified_at", nullable: true })
|
|
21
|
-
emailVerifiedAt!: Date | null;
|
|
22
|
-
|
|
23
|
-
@Column({ type: "varchar", name: "email_verification_token", nullable: true })
|
|
24
|
-
emailVerificationToken!: string | null;
|
|
25
|
-
|
|
26
|
-
@Column({ type: "timestamptz", name: "admin_approved_at", nullable: true })
|
|
27
|
-
adminApprovedAt!: Date | null;
|
|
28
|
-
|
|
29
|
-
@Column({ type: "boolean", name: "is_active", default: true })
|
|
30
|
-
isActive!: boolean;
|
|
31
10
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BeforeInsert,
|
|
2
3
|
Column,
|
|
3
4
|
CreateDateColumn,
|
|
4
5
|
Entity,
|
|
5
|
-
|
|
6
|
+
PrimaryColumn,
|
|
6
7
|
} from "typeorm";
|
|
8
|
+
import { v7 as uuidv7 } from "uuid";
|
|
7
9
|
|
|
8
10
|
@Entity({ name: "security_password_reset_token" })
|
|
9
11
|
export class PasswordResetTokenEntity {
|
|
10
|
-
@
|
|
12
|
+
@PrimaryColumn({ type: "uuid" })
|
|
11
13
|
id!: string;
|
|
12
14
|
|
|
13
|
-
@Column({ type: "
|
|
15
|
+
@Column({ type: "uuid", name: "user_id" })
|
|
14
16
|
userId!: string;
|
|
15
17
|
|
|
16
18
|
@Column({ type: "varchar", unique: true })
|
|
@@ -24,4 +26,11 @@ export class PasswordResetTokenEntity {
|
|
|
24
26
|
|
|
25
27
|
@CreateDateColumn({ name: "created_at" })
|
|
26
28
|
createdAt!: Date;
|
|
29
|
+
|
|
30
|
+
@BeforeInsert()
|
|
31
|
+
ensureId() {
|
|
32
|
+
if (!this.id) {
|
|
33
|
+
this.id = uuidv7();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
27
36
|
}
|
|
@@ -2,7 +2,7 @@ import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm";
|
|
|
2
2
|
|
|
3
3
|
@Entity({ name: "refresh_token" })
|
|
4
4
|
export class RefreshTokenEntity {
|
|
5
|
-
@PrimaryColumn({ type: "
|
|
5
|
+
@PrimaryColumn({ type: "uuid" })
|
|
6
6
|
id!: string;
|
|
7
7
|
|
|
8
8
|
@Column({ type: "varchar", name: "token_hash" })
|
|
@@ -14,7 +14,7 @@ export class RefreshTokenEntity {
|
|
|
14
14
|
@Column({ type: "timestamptz", name: "revoked_at", nullable: true })
|
|
15
15
|
revokedAt!: Date | null;
|
|
16
16
|
|
|
17
|
-
@Column({ type: "
|
|
17
|
+
@Column({ type: "uuid", name: "userId", nullable: true })
|
|
18
18
|
userId!: string | null;
|
|
19
19
|
|
|
20
20
|
@CreateDateColumn({ name: "created_at" })
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v7 as uuidv7 } from "uuid";
|
|
2
|
+
import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm";
|
|
2
3
|
|
|
3
4
|
@Entity({ name: "security_role" })
|
|
4
5
|
export class SecurityRoleEntity {
|
|
5
|
-
@
|
|
6
|
+
@PrimaryColumn({ type: "uuid" })
|
|
6
7
|
id!: string;
|
|
7
8
|
|
|
8
9
|
@Column({ type: "varchar", name: "role_key", unique: true })
|
|
@@ -13,4 +14,11 @@ export class SecurityRoleEntity {
|
|
|
13
14
|
|
|
14
15
|
@Column({ type: "boolean", name: "is_system", default: false })
|
|
15
16
|
isSystem!: boolean;
|
|
17
|
+
|
|
18
|
+
@BeforeInsert()
|
|
19
|
+
ensureId() {
|
|
20
|
+
if (!this.id) {
|
|
21
|
+
this.id = uuidv7();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
16
24
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v7 as uuidv7 } from "uuid";
|
|
2
|
+
import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm";
|
|
2
3
|
|
|
3
4
|
@Entity({ name: "security_user_role" })
|
|
4
5
|
export class SecurityUserRoleEntity {
|
|
5
|
-
@
|
|
6
|
+
@PrimaryColumn({ type: "uuid" })
|
|
6
7
|
id!: string;
|
|
7
8
|
|
|
8
|
-
@Column({ type: "
|
|
9
|
+
@Column({ type: "uuid", name: "user_id" })
|
|
9
10
|
userId!: string;
|
|
10
11
|
|
|
11
12
|
@Column({ type: "uuid", name: "role_id" })
|
|
12
13
|
roleId!: string;
|
|
14
|
+
|
|
15
|
+
@BeforeInsert()
|
|
16
|
+
ensureId() {
|
|
17
|
+
if (!this.id) {
|
|
18
|
+
this.id = uuidv7();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm";
|
|
2
|
+
|
|
3
|
+
@Entity({ name: "security_user" })
|
|
4
|
+
export class SecurityUserEntity {
|
|
5
|
+
@PrimaryColumn({ type: "uuid", name: "user_id" })
|
|
6
|
+
userId!: string;
|
|
7
|
+
|
|
8
|
+
@Column({ type: "varchar", name: "password_hash" })
|
|
9
|
+
passwordHash!: string;
|
|
10
|
+
|
|
11
|
+
@Column({ type: "timestamptz", name: "email_verified_at", nullable: true })
|
|
12
|
+
emailVerifiedAt!: Date | null;
|
|
13
|
+
|
|
14
|
+
@Column({ type: "varchar", name: "email_verification_token", nullable: true })
|
|
15
|
+
emailVerificationToken!: string | null;
|
|
16
|
+
|
|
17
|
+
@Column({ type: "timestamptz", name: "admin_approved_at", nullable: true })
|
|
18
|
+
adminApprovedAt!: Date | null;
|
|
19
|
+
|
|
20
|
+
@Column({ type: "boolean", name: "is_active", default: true })
|
|
21
|
+
isActive!: boolean;
|
|
22
|
+
|
|
23
|
+
@CreateDateColumn({ name: "created_at" })
|
|
24
|
+
createdAt!: Date;
|
|
25
|
+
}
|
package/src/nest/index.ts
CHANGED
|
@@ -15,4 +15,5 @@ export * from "./entities/app-user.entity";
|
|
|
15
15
|
export * from "./entities/refresh-token.entity";
|
|
16
16
|
export * from "./entities/password-reset-token.entity";
|
|
17
17
|
export * from "./entities/security-role.entity";
|
|
18
|
+
export * from "./entities/security-user.entity";
|
|
18
19
|
export * from "./entities/security-user-role.entity";
|
|
@@ -34,15 +34,11 @@ describe("SecurityAuthController", () => {
|
|
|
34
34
|
const result = await controller.register({
|
|
35
35
|
email: "user@example.com",
|
|
36
36
|
password: "Secret123",
|
|
37
|
-
firstName: "A",
|
|
38
|
-
lastName: "B",
|
|
39
37
|
});
|
|
40
38
|
expect(result).toEqual({ success: true });
|
|
41
39
|
expect(service.register).toHaveBeenCalledWith({
|
|
42
40
|
email: "user@example.com",
|
|
43
41
|
password: "Secret123",
|
|
44
|
-
firstName: "A",
|
|
45
|
-
lastName: "B",
|
|
46
42
|
});
|
|
47
43
|
});
|
|
48
44
|
|
|
@@ -44,8 +44,6 @@ export class SecurityAuthController {
|
|
|
44
44
|
body: {
|
|
45
45
|
email?: string;
|
|
46
46
|
password?: string;
|
|
47
|
-
firstName?: string;
|
|
48
|
-
lastName?: string;
|
|
49
47
|
},
|
|
50
48
|
) {
|
|
51
49
|
if (!body.email || !body.password) {
|
|
@@ -54,8 +52,6 @@ export class SecurityAuthController {
|
|
|
54
52
|
return this.authService.register({
|
|
55
53
|
email: body.email,
|
|
56
54
|
password: body.password,
|
|
57
|
-
firstName: body.firstName ?? null,
|
|
58
|
-
lastName: body.lastName ?? null,
|
|
59
55
|
});
|
|
60
56
|
}
|
|
61
57
|
|
|
@@ -5,6 +5,7 @@ import { AppUserEntity } from "./entities/app-user.entity";
|
|
|
5
5
|
import { PasswordResetTokenEntity } from "./entities/password-reset-token.entity";
|
|
6
6
|
import { RefreshTokenEntity } from "./entities/refresh-token.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 { SecurityAdminGuard } from "./security-admin.guard";
|
|
10
11
|
import { SecurityAuthController } from "./security-auth.controller";
|
|
@@ -39,6 +40,7 @@ export class SecurityAuthModule {
|
|
|
39
40
|
imports: [
|
|
40
41
|
TypeOrmModule.forFeature([
|
|
41
42
|
AppUserEntity,
|
|
43
|
+
SecurityUserEntity,
|
|
42
44
|
RefreshTokenEntity,
|
|
43
45
|
PasswordResetTokenEntity,
|
|
44
46
|
SecurityRoleEntity,
|