@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
package/README.md CHANGED
@@ -41,8 +41,8 @@ import { EmailService } from "./notifications/email.service";
41
41
  adminEmails,
42
42
  user,
43
43
  ),
44
- sendUserAccountApproved: ({ email, firstName }) =>
45
- emailService.sendAccountApproved(email, firstName),
44
+ sendUserAccountApproved: ({ email }) =>
45
+ emailService.sendAccountApproved(email),
46
46
  }),
47
47
  inject: [EmailService],
48
48
  },
@@ -52,6 +52,51 @@ import { EmailService } from "./notifications/email.service";
52
52
  export class AppModule {}
53
53
  ```
54
54
 
55
+ ### User Table Ownership Model
56
+
57
+ Consuming apps keep ownership of their own `app_user` table. `sdr-security`
58
+ stores security/auth state in its own tables and links them by user id.
59
+
60
+ - App-owned table:
61
+ - `app_user` (at minimum: `id`, `email`, plus any app-specific columns)
62
+ - `sdr-security` tables:
63
+ - `security_user` (password hash, verified/approved/active flags)
64
+ - `security_identity` (provider links such as Google subject)
65
+ - `security_role`, `security_user_role`
66
+ - `refresh_token`
67
+ - `security_password_reset_token`
68
+
69
+ Link key:
70
+
71
+ - `security_* .user_id` -> `app_user.id`
72
+
73
+ This lets each app evolve its user schema independently while reusing the same
74
+ security workflows, guards, controllers, and migrations.
75
+
76
+ Typical app query pattern is a join when you need security state:
77
+
78
+ ```sql
79
+ SELECT u.id, u.email, su.is_active, su.admin_approved_at, su.email_verified_at
80
+ FROM app_user u
81
+ LEFT JOIN security_user su ON su.user_id = u.id
82
+ WHERE u.id = $1;
83
+ ```
84
+
85
+ Nest/TypeORM equivalent:
86
+
87
+ ```ts
88
+ const row = await usersRepo
89
+ .createQueryBuilder("user")
90
+ .leftJoin("security_user", "securityUser", "securityUser.user_id = user.id")
91
+ .select("user.id", "id")
92
+ .addSelect("user.email", "email")
93
+ .addSelect("securityUser.is_active", "isActive")
94
+ .addSelect("securityUser.admin_approved_at", "adminApprovedAt")
95
+ .addSelect("securityUser.email_verified_at", "emailVerifiedAt")
96
+ .where("user.id = :id", { id: userId })
97
+ .getRawOne();
98
+ ```
99
+
55
100
  Optional Swagger setup in consuming app:
56
101
 
57
102
  ```ts
@@ -96,8 +141,6 @@ await sdrSecurity.notifyAdminsOnEmailVerified({
96
141
  user: {
97
142
  id: user.id,
98
143
  email: user.email,
99
- firstName: user.firstName,
100
- lastName: user.lastName,
101
144
  },
102
145
  listAdminEmails: () => usersService.listAdminEmails(),
103
146
  notifyAdmins: ({ adminEmails, user }) =>
@@ -108,10 +151,8 @@ await sdrSecurity.notifyUserOnAdminApproval({
108
151
  approved: body.approved,
109
152
  user: {
110
153
  email: user.email,
111
- firstName: user.firstName,
112
154
  },
113
- notifyUser: ({ email, firstName }) =>
114
- emailService.sendAccountApproved(email, firstName),
155
+ notifyUser: ({ email }) => emailService.sendAccountApproved(email),
115
156
  });
116
157
  ```
117
158
 
@@ -3,8 +3,6 @@ export type UserRole = string;
3
3
  export type SafeUser = {
4
4
  id: string;
5
5
  email: string;
6
- firstName: string | null;
7
- lastName: string | null;
8
6
  phone: string | null;
9
7
  roles: UserRole[];
10
8
  emailVerifiedAt: string | Date | null;
@@ -1,7 +1,7 @@
1
1
  export declare class CreateSecurityIdentity1739500000000 {
2
2
  name: string;
3
3
  up(queryRunner: {
4
- query: (sql: string, params?: unknown[]) => Promise<unknown>;
4
+ query: (sql: string) => Promise<unknown>;
5
5
  }): Promise<void>;
6
6
  down(queryRunner: {
7
7
  query: (sql: string) => Promise<unknown>;
@@ -4,13 +4,10 @@ exports.CreateSecurityIdentity1739500000000 = void 0;
4
4
  class CreateSecurityIdentity1739500000000 {
5
5
  name = "CreateSecurityIdentity1739500000000";
6
6
  async up(queryRunner) {
7
- const userTable = getSafeIdentifier(process.env.USER_TABLE, "app_user");
8
- const userSchema = getSafeIdentifier(process.env.USER_TABLE_SCHEMA, "public");
9
- const userTableRef = `"${userSchema}"."${userTable}"`;
10
- await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
7
+ const userTableRef = getUserTableReference();
11
8
  await queryRunner.query(`
12
9
  CREATE TABLE IF NOT EXISTS "security_identity" (
13
- "id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
10
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
14
11
  "user_id" varchar NOT NULL,
15
12
  "provider" varchar NOT NULL,
16
13
  "provider_subject" varchar NOT NULL,
@@ -21,36 +18,6 @@ class CreateSecurityIdentity1739500000000 {
21
18
  `);
22
19
  await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_identity_provider_subject" ON "security_identity" ("provider", "provider_subject")`);
23
20
  await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_identity_user_provider" ON "security_identity" ("user_id", "provider")`);
24
- const hasGoogleSubjectColumn = (await queryRunner.query(`
25
- SELECT 1
26
- FROM information_schema.columns
27
- WHERE table_schema = $1
28
- AND table_name = $2
29
- AND column_name = 'google_subject'
30
- LIMIT 1
31
- `, [userSchema, userTable]));
32
- if (hasGoogleSubjectColumn.length > 0) {
33
- await queryRunner.query(`
34
- INSERT INTO "security_identity" (
35
- "user_id",
36
- "provider",
37
- "provider_subject",
38
- "created_at",
39
- "updated_at"
40
- )
41
- SELECT
42
- "id",
43
- 'google',
44
- "google_subject",
45
- now(),
46
- now()
47
- FROM ${userTableRef}
48
- WHERE "google_subject" IS NOT NULL
49
- ON CONFLICT ("provider", "provider_subject") DO NOTHING
50
- `);
51
- await queryRunner.query(`DROP INDEX IF EXISTS "IDX_app_user_google_subject"`);
52
- await queryRunner.query(`ALTER TABLE ${userTableRef} DROP COLUMN IF EXISTS "google_subject"`);
53
- }
54
21
  }
55
22
  async down(queryRunner) {
56
23
  await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_identity_user_provider"`);
@@ -66,3 +33,10 @@ const getSafeIdentifier = (value, fallback) => {
66
33
  }
67
34
  return resolved;
68
35
  };
36
+ const getUserTableReference = () => {
37
+ const table = getSafeIdentifier(process.env.USER_TABLE, "app_user");
38
+ const schema = process.env.USER_TABLE_SCHEMA
39
+ ? getSafeIdentifier(process.env.USER_TABLE_SCHEMA, "public")
40
+ : "public";
41
+ return `"${schema}"."${table}"`;
42
+ };
@@ -1,7 +1,7 @@
1
1
  export declare class CreateSecurityRoles1739510000000 {
2
2
  name: string;
3
3
  up(queryRunner: {
4
- query: (sql: string, params?: unknown[]) => Promise<unknown>;
4
+ query: (sql: string) => Promise<unknown>;
5
5
  }): Promise<void>;
6
6
  down(queryRunner: {
7
7
  query: (sql: string) => Promise<unknown>;
@@ -4,13 +4,9 @@ exports.CreateSecurityRoles1739510000000 = void 0;
4
4
  class CreateSecurityRoles1739510000000 {
5
5
  name = "CreateSecurityRoles1739510000000";
6
6
  async up(queryRunner) {
7
- const userTable = getSafeIdentifier(process.env.USER_TABLE, "app_user");
8
- const userSchema = getSafeIdentifier(process.env.USER_TABLE_SCHEMA, "public");
9
- const userTableRef = `"${userSchema}"."${userTable}"`;
10
- await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
11
7
  await queryRunner.query(`
12
8
  CREATE TABLE IF NOT EXISTS "security_role" (
13
- "id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
9
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
14
10
  "role_key" varchar NOT NULL,
15
11
  "description" text,
16
12
  "is_system" boolean NOT NULL DEFAULT false,
@@ -20,76 +16,14 @@ class CreateSecurityRoles1739510000000 {
20
16
  `);
21
17
  await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_role_key" ON "security_role" ("role_key")`);
22
18
  await queryRunner.query(`
23
- CREATE TABLE IF NOT EXISTS "security_user_role" (
24
- "id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
25
- "user_id" varchar NOT NULL,
26
- "role_id" uuid NOT NULL,
27
- "created_at" timestamptz NOT NULL DEFAULT now(),
28
- CONSTRAINT "FK_security_user_role_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE,
29
- CONSTRAINT "FK_security_user_role_role_id" FOREIGN KEY ("role_id") REFERENCES "security_role" ("id") ON DELETE CASCADE
30
- )
31
- `);
32
- await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_user_role_user_role" ON "security_user_role" ("user_id", "role_id")`);
33
- await queryRunner.query(`
34
19
  INSERT INTO "security_role" ("role_key", "description", "is_system", "created_at", "updated_at")
35
20
  VALUES ('ADMIN', 'Administrative access', true, now(), now())
36
21
  ON CONFLICT ("role_key") DO NOTHING
37
22
  `);
38
- const hasRoleColumn = (await queryRunner.query(`
39
- SELECT 1
40
- FROM information_schema.columns
41
- WHERE table_schema = $1
42
- AND table_name = $2
43
- AND column_name = 'role'
44
- LIMIT 1
45
- `, [userSchema, userTable]));
46
- if (hasRoleColumn.length > 0) {
47
- await queryRunner.query(`
48
- INSERT INTO "security_role" ("role_key", "description", "is_system", "created_at", "updated_at")
49
- SELECT DISTINCT
50
- CASE
51
- WHEN UPPER(TRIM("role")) = 'ADMINISTRATOR' THEN 'ADMIN'
52
- ELSE UPPER(TRIM("role"))
53
- END AS "role_key",
54
- NULL,
55
- false,
56
- now(),
57
- now()
58
- FROM ${userTableRef}
59
- WHERE "role" IS NOT NULL
60
- AND LENGTH(TRIM("role")) > 0
61
- ON CONFLICT ("role_key") DO NOTHING
62
- `);
63
- await queryRunner.query(`
64
- INSERT INTO "security_user_role" ("user_id", "role_id", "created_at")
65
- SELECT
66
- u."id" AS "user_id",
67
- r."id" AS "role_id",
68
- now()
69
- FROM ${userTableRef} u
70
- INNER JOIN "security_role" r ON r."role_key" = CASE
71
- WHEN UPPER(TRIM(u."role")) = 'ADMINISTRATOR' THEN 'ADMIN'
72
- ELSE UPPER(TRIM(u."role"))
73
- END
74
- WHERE u."role" IS NOT NULL
75
- AND LENGTH(TRIM(u."role")) > 0
76
- ON CONFLICT ("user_id", "role_id") DO NOTHING
77
- `);
78
- await queryRunner.query(`ALTER TABLE ${userTableRef} DROP COLUMN IF EXISTS "role"`);
79
- }
80
23
  }
81
24
  async down(queryRunner) {
82
- await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_user_role_user_role"`);
83
- await queryRunner.query(`DROP TABLE IF EXISTS "security_user_role"`);
84
25
  await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_role_key"`);
85
26
  await queryRunner.query(`DROP TABLE IF EXISTS "security_role"`);
86
27
  }
87
28
  }
88
29
  exports.CreateSecurityRoles1739510000000 = CreateSecurityRoles1739510000000;
89
- const getSafeIdentifier = (value, fallback) => {
90
- const resolved = value?.trim() || fallback;
91
- if (!resolved || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(resolved)) {
92
- throw new Error(`Invalid SQL identifier: ${resolved}`);
93
- }
94
- return resolved;
95
- };
@@ -0,0 +1,9 @@
1
+ export declare class CreateSecurityUserRoles1739515000000 {
2
+ name: string;
3
+ up(queryRunner: {
4
+ query: (sql: string) => Promise<unknown>;
5
+ }): Promise<void>;
6
+ down(queryRunner: {
7
+ query: (sql: string) => Promise<unknown>;
8
+ }): Promise<void>;
9
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CreateSecurityUserRoles1739515000000 = void 0;
4
+ class CreateSecurityUserRoles1739515000000 {
5
+ name = "CreateSecurityUserRoles1739515000000";
6
+ async up(queryRunner) {
7
+ const userTableRef = getUserTableReference();
8
+ await queryRunner.query(`
9
+ CREATE TABLE IF NOT EXISTS "security_user_role" (
10
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
+ "user_id" varchar NOT NULL,
12
+ "role_id" uuid NOT NULL,
13
+ "created_at" timestamptz NOT NULL DEFAULT now(),
14
+ CONSTRAINT "FK_security_user_role_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE,
15
+ CONSTRAINT "FK_security_user_role_role_id" FOREIGN KEY ("role_id") REFERENCES "security_role" ("id") ON DELETE CASCADE
16
+ )
17
+ `);
18
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_user_role_user_role" ON "security_user_role" ("user_id", "role_id")`);
19
+ }
20
+ async down(queryRunner) {
21
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_user_role_user_role"`);
22
+ await queryRunner.query(`DROP TABLE IF EXISTS "security_user_role"`);
23
+ }
24
+ }
25
+ exports.CreateSecurityUserRoles1739515000000 = CreateSecurityUserRoles1739515000000;
26
+ const getUserTableReference = () => {
27
+ const table = getSafeIdentifier(process.env.USER_TABLE, "app_user");
28
+ const schema = process.env.USER_TABLE_SCHEMA
29
+ ? getSafeIdentifier(process.env.USER_TABLE_SCHEMA, "public")
30
+ : "public";
31
+ return `"${schema}"."${table}"`;
32
+ };
33
+ const getSafeIdentifier = (value, fallback) => {
34
+ const resolved = value?.trim() || fallback;
35
+ if (!resolved || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(resolved)) {
36
+ throw new Error(`Invalid SQL identifier: ${resolved}`);
37
+ }
38
+ return resolved;
39
+ };
@@ -7,7 +7,7 @@ class CreatePasswordResetTokens1739520000000 {
7
7
  const userTableRef = getUserTableReference();
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE IF NOT EXISTS "security_password_reset_token" (
10
- "id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
10
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
11
  "user_id" varchar NOT NULL,
12
12
  "token" varchar NOT NULL,
13
13
  "expires_at" timestamptz NOT NULL,
@@ -0,0 +1,9 @@
1
+ export declare class CreateSecurityUser1739530000000 {
2
+ name: string;
3
+ up(queryRunner: {
4
+ query: (sql: string) => Promise<unknown>;
5
+ }): Promise<void>;
6
+ down(queryRunner: {
7
+ query: (sql: string) => Promise<unknown>;
8
+ }): Promise<void>;
9
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CreateSecurityUser1739530000000 = void 0;
4
+ class CreateSecurityUser1739530000000 {
5
+ name = "CreateSecurityUser1739530000000";
6
+ async up(queryRunner) {
7
+ const userTableRef = getUserTableReference();
8
+ await queryRunner.query(`
9
+ CREATE TABLE IF NOT EXISTS "security_user" (
10
+ "user_id" varchar PRIMARY KEY NOT NULL,
11
+ "password_hash" varchar NOT NULL,
12
+ "email_verified_at" timestamptz,
13
+ "email_verification_token" varchar,
14
+ "admin_approved_at" timestamptz,
15
+ "is_active" boolean NOT NULL DEFAULT true,
16
+ "created_at" timestamptz NOT NULL DEFAULT now(),
17
+ CONSTRAINT "FK_security_user_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
18
+ )
19
+ `);
20
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_user_email_verification_token" ON "security_user" ("email_verification_token") WHERE "email_verification_token" IS NOT NULL`);
21
+ }
22
+ async down(queryRunner) {
23
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_user_email_verification_token"`);
24
+ await queryRunner.query(`DROP TABLE IF EXISTS "security_user"`);
25
+ }
26
+ }
27
+ exports.CreateSecurityUser1739530000000 = CreateSecurityUser1739530000000;
28
+ const getSafeIdentifier = (value, fallback) => {
29
+ const resolved = value?.trim() || fallback;
30
+ if (!resolved || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(resolved)) {
31
+ throw new Error(`Invalid SQL identifier: ${resolved}`);
32
+ }
33
+ return resolved;
34
+ };
35
+ const getUserTableReference = () => {
36
+ const table = getSafeIdentifier(process.env.USER_TABLE, "app_user");
37
+ const schema = process.env.USER_TABLE_SCHEMA
38
+ ? getSafeIdentifier(process.env.USER_TABLE_SCHEMA, "public")
39
+ : "public";
40
+ return `"${schema}"."${table}"`;
41
+ };
@@ -1,7 +1,8 @@
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
  export declare const securityMigrations: (typeof AddRefreshTokens1700000000001)[];
7
- export { AddRefreshTokens1700000000001, AddGoogleSubjectToUser1739490000000, CreateSecurityIdentity1739500000000, CreateSecurityRoles1739510000000, CreatePasswordResetTokens1739520000000, };
8
+ export { AddRefreshTokens1700000000001, CreateSecurityIdentity1739500000000, CreateSecurityRoles1739510000000, CreateSecurityUserRoles1739515000000, CreatePasswordResetTokens1739520000000, CreateSecurityUser1739530000000, };
@@ -1,20 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CreatePasswordResetTokens1739520000000 = exports.CreateSecurityRoles1739510000000 = exports.CreateSecurityIdentity1739500000000 = exports.AddGoogleSubjectToUser1739490000000 = exports.AddRefreshTokens1700000000001 = exports.securityMigrations = void 0;
3
+ exports.CreateSecurityUser1739530000000 = exports.CreatePasswordResetTokens1739520000000 = exports.CreateSecurityUserRoles1739515000000 = exports.CreateSecurityRoles1739510000000 = exports.CreateSecurityIdentity1739500000000 = exports.AddRefreshTokens1700000000001 = exports.securityMigrations = void 0;
4
4
  const _1700000000001_add_refresh_tokens_1 = require("./1700000000001-add-refresh-tokens");
5
5
  Object.defineProperty(exports, "AddRefreshTokens1700000000001", { enumerable: true, get: function () { return _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001; } });
6
- const _1739490000000_add_google_subject_to_user_1 = require("./1739490000000-add-google-subject-to-user");
7
- Object.defineProperty(exports, "AddGoogleSubjectToUser1739490000000", { enumerable: true, get: function () { return _1739490000000_add_google_subject_to_user_1.AddGoogleSubjectToUser1739490000000; } });
8
6
  const _1739500000000_create_security_identity_1 = require("./1739500000000-create-security-identity");
9
7
  Object.defineProperty(exports, "CreateSecurityIdentity1739500000000", { enumerable: true, get: function () { return _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000; } });
10
8
  const _1739510000000_create_security_roles_1 = require("./1739510000000-create-security-roles");
11
9
  Object.defineProperty(exports, "CreateSecurityRoles1739510000000", { enumerable: true, get: function () { return _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000; } });
10
+ const _1739515000000_create_security_user_roles_1 = require("./1739515000000-create-security-user-roles");
11
+ Object.defineProperty(exports, "CreateSecurityUserRoles1739515000000", { enumerable: true, get: function () { return _1739515000000_create_security_user_roles_1.CreateSecurityUserRoles1739515000000; } });
12
12
  const _1739520000000_create_password_reset_tokens_1 = require("./1739520000000-create-password-reset-tokens");
13
13
  Object.defineProperty(exports, "CreatePasswordResetTokens1739520000000", { enumerable: true, get: function () { return _1739520000000_create_password_reset_tokens_1.CreatePasswordResetTokens1739520000000; } });
14
+ const _1739530000000_create_security_user_1 = require("./1739530000000-create-security-user");
15
+ Object.defineProperty(exports, "CreateSecurityUser1739530000000", { enumerable: true, get: function () { return _1739530000000_create_security_user_1.CreateSecurityUser1739530000000; } });
14
16
  exports.securityMigrations = [
15
17
  _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001,
16
- _1739490000000_add_google_subject_to_user_1.AddGoogleSubjectToUser1739490000000,
17
18
  _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000,
18
19
  _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000,
20
+ _1739515000000_create_security_user_roles_1.CreateSecurityUserRoles1739515000000,
19
21
  _1739520000000_create_password_reset_tokens_1.CreatePasswordResetTokens1739520000000,
22
+ _1739530000000_create_security_user_1.CreateSecurityUser1739530000000,
20
23
  ];
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const vitest_1 = require("vitest");
4
4
  const _1700000000001_add_refresh_tokens_1 = require("./1700000000001-add-refresh-tokens");
5
- const _1739490000000_add_google_subject_to_user_1 = require("./1739490000000-add-google-subject-to-user");
6
5
  const _1739500000000_create_security_identity_1 = require("./1739500000000-create-security-identity");
7
6
  const _1739510000000_create_security_roles_1 = require("./1739510000000-create-security-roles");
7
+ const _1739515000000_create_security_user_roles_1 = require("./1739515000000-create-security-user-roles");
8
8
  const _1739520000000_create_password_reset_tokens_1 = require("./1739520000000-create-password-reset-tokens");
9
+ const _1739530000000_create_security_user_1 = require("./1739530000000-create-security-user");
9
10
  const index_1 = require("./index");
10
11
  const originalEnv = { ...process.env };
11
12
  (0, vitest_1.afterEach)(() => {
@@ -14,10 +15,17 @@ const originalEnv = { ...process.env };
14
15
  });
15
16
  (0, vitest_1.describe)("security migrations", () => {
16
17
  (0, vitest_1.it)("exports migration list", () => {
17
- (0, vitest_1.expect)(index_1.securityMigrations.length).toBe(5);
18
- (0, vitest_1.expect)(index_1.securityMigrations[0]).toBe(_1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001);
18
+ (0, vitest_1.expect)(index_1.securityMigrations.length).toBe(6);
19
+ (0, vitest_1.expect)(index_1.securityMigrations).toEqual([
20
+ _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001,
21
+ _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000,
22
+ _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000,
23
+ _1739515000000_create_security_user_roles_1.CreateSecurityUserRoles1739515000000,
24
+ _1739520000000_create_password_reset_tokens_1.CreatePasswordResetTokens1739520000000,
25
+ _1739530000000_create_security_user_1.CreateSecurityUser1739530000000,
26
+ ]);
19
27
  });
20
- (0, vitest_1.it)("runs add refresh tokens migration up/down", async () => {
28
+ (0, vitest_1.it)("runs refresh token migration up/down", async () => {
21
29
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
22
30
  const migration = new _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001();
23
31
  await migration.up({ query });
@@ -25,93 +33,29 @@ const originalEnv = { ...process.env };
25
33
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE "refresh_token"'));
26
34
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE "refresh_token"'));
27
35
  });
28
- (0, vitest_1.it)("uses schema/table env in refresh token migration", async () => {
29
- process.env.USER_TABLE = "users";
30
- process.env.USER_TABLE_SCHEMA = "security";
31
- const query = vitest_1.vi.fn().mockResolvedValue(undefined);
32
- await new _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001().up({ query });
33
- (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('REFERENCES "security"."users" ("id")'));
34
- });
35
- (0, vitest_1.it)("throws for invalid identifiers in refresh token migration", async () => {
36
- process.env.USER_TABLE = "bad-name;drop";
36
+ (0, vitest_1.it)("runs security identity migration up/down", async () => {
37
37
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
38
- await (0, vitest_1.expect)(new _1700000000001_add_refresh_tokens_1.AddRefreshTokens1700000000001().up({ query })).rejects.toThrow("Invalid SQL identifier");
39
- });
40
- (0, vitest_1.it)("keeps legacy google subject migration as no-op", async () => {
41
- const migration = new _1739490000000_add_google_subject_to_user_1.AddGoogleSubjectToUser1739490000000();
42
- await (0, vitest_1.expect)(migration.up()).resolves.toBeUndefined();
43
- await (0, vitest_1.expect)(migration.down()).resolves.toBeUndefined();
44
- });
45
- (0, vitest_1.it)("runs security identity migration path with google_subject present", async () => {
46
- const query = vitest_1.vi
47
- .fn()
48
- .mockResolvedValueOnce(undefined)
49
- .mockResolvedValueOnce(undefined)
50
- .mockResolvedValueOnce(undefined)
51
- .mockResolvedValueOnce(undefined)
52
- .mockResolvedValueOnce([{ "?column?": 1 }])
53
- .mockResolvedValueOnce(undefined)
54
- .mockResolvedValueOnce(undefined)
55
- .mockResolvedValueOnce(undefined);
56
38
  const migration = new _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000();
57
39
  await migration.up({ query });
58
40
  await migration.down({ query });
59
41
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_identity"'));
60
42
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_identity"'));
61
43
  });
62
- (0, vitest_1.it)("skips google_subject migration block when column absent", async () => {
63
- const query = vitest_1.vi
64
- .fn()
65
- .mockResolvedValueOnce(undefined)
66
- .mockResolvedValueOnce(undefined)
67
- .mockResolvedValueOnce(undefined)
68
- .mockResolvedValueOnce(undefined)
69
- .mockResolvedValueOnce([]);
70
- await new _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000().up({ query });
71
- (0, vitest_1.expect)(query).not.toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP COLUMN IF EXISTS "google_subject"'));
72
- });
73
- (0, vitest_1.it)("throws for invalid identifiers in identity migration", async () => {
74
- process.env.USER_TABLE_SCHEMA = "bad-schema!";
44
+ (0, vitest_1.it)("runs security role migration up/down", async () => {
75
45
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
76
- await (0, vitest_1.expect)(new _1739500000000_create_security_identity_1.CreateSecurityIdentity1739500000000().up({ query })).rejects.toThrow("Invalid SQL identifier");
77
- });
78
- (0, vitest_1.it)("runs security roles migration path with legacy role column", async () => {
79
- const query = vitest_1.vi
80
- .fn()
81
- .mockResolvedValueOnce(undefined)
82
- .mockResolvedValueOnce(undefined)
83
- .mockResolvedValueOnce(undefined)
84
- .mockResolvedValueOnce(undefined)
85
- .mockResolvedValueOnce(undefined)
86
- .mockResolvedValueOnce(undefined)
87
- .mockResolvedValueOnce([{ "?column?": 1 }])
88
- .mockResolvedValueOnce(undefined)
89
- .mockResolvedValueOnce(undefined)
90
- .mockResolvedValueOnce(undefined);
91
46
  const migration = new _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000();
92
47
  await migration.up({ query });
93
48
  await migration.down({ query });
94
49
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_role"'));
95
- (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_user_role"'));
96
- (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_user_role"'));
97
- });
98
- (0, vitest_1.it)("skips legacy role backfill when role column absent", async () => {
99
- const query = vitest_1.vi
100
- .fn()
101
- .mockResolvedValueOnce(undefined)
102
- .mockResolvedValueOnce(undefined)
103
- .mockResolvedValueOnce(undefined)
104
- .mockResolvedValueOnce(undefined)
105
- .mockResolvedValueOnce(undefined)
106
- .mockResolvedValueOnce(undefined)
107
- .mockResolvedValueOnce([]);
108
- await new _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000().up({ query });
109
- (0, vitest_1.expect)(query).not.toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP COLUMN IF EXISTS "role"'));
50
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_role"'));
110
51
  });
111
- (0, vitest_1.it)("throws for invalid identifiers in roles migration", async () => {
112
- process.env.USER_TABLE = "bad-name*";
52
+ (0, vitest_1.it)("runs security user role migration up/down", async () => {
113
53
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
114
- await (0, vitest_1.expect)(new _1739510000000_create_security_roles_1.CreateSecurityRoles1739510000000().up({ query })).rejects.toThrow("Invalid SQL identifier");
54
+ const migration = new _1739515000000_create_security_user_roles_1.CreateSecurityUserRoles1739515000000();
55
+ await migration.up({ query });
56
+ await migration.down({ query });
57
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_user_role"'));
58
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_user_role"'));
115
59
  });
116
60
  (0, vitest_1.it)("runs password reset token migration up/down", async () => {
117
61
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
@@ -121,14 +65,24 @@ const originalEnv = { ...process.env };
121
65
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_password_reset_token"'));
122
66
  (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_password_reset_token"'));
123
67
  });
124
- (0, vitest_1.it)("uses default public schema in password reset migration", async () => {
68
+ (0, vitest_1.it)("runs security user migration up/down", async () => {
125
69
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
126
- await new _1739520000000_create_password_reset_tokens_1.CreatePasswordResetTokens1739520000000().up({ query });
127
- (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('REFERENCES "public"."app_user" ("id")'));
70
+ const migration = new _1739530000000_create_security_user_1.CreateSecurityUser1739530000000();
71
+ await migration.up({ query });
72
+ await migration.down({ query });
73
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CREATE TABLE IF NOT EXISTS "security_user"'));
74
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('DROP TABLE IF EXISTS "security_user"'));
75
+ });
76
+ (0, vitest_1.it)("uses user schema/table env safely", async () => {
77
+ process.env.USER_TABLE = "users";
78
+ process.env.USER_TABLE_SCHEMA = "security";
79
+ const query = vitest_1.vi.fn().mockResolvedValue(undefined);
80
+ await new _1739530000000_create_security_user_1.CreateSecurityUser1739530000000().up({ query });
81
+ (0, vitest_1.expect)(query).toHaveBeenCalledWith(vitest_1.expect.stringContaining('REFERENCES "security"."users" ("id")'));
128
82
  });
129
- (0, vitest_1.it)("throws for invalid identifiers in password reset migration", async () => {
130
- process.env.USER_TABLE_SCHEMA = "bad.schema";
83
+ (0, vitest_1.it)("throws for invalid identifiers", async () => {
84
+ process.env.USER_TABLE = "bad-name!";
131
85
  const query = vitest_1.vi.fn().mockResolvedValue(undefined);
132
- await (0, vitest_1.expect)(new _1739520000000_create_password_reset_tokens_1.CreatePasswordResetTokens1739520000000().up({ query })).rejects.toThrow("Invalid SQL identifier");
86
+ await (0, vitest_1.expect)(new _1739515000000_create_security_user_roles_1.CreateSecurityUserRoles1739515000000().up({ query })).rejects.toThrow("Invalid SQL identifier");
133
87
  });
134
88
  });
@@ -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
  export declare const notifyAdminsOnEmailVerified: (params: {
8
6
  user: VerificationNotificationUser;
@@ -22,11 +20,9 @@ export declare const notifyUserOnAdminApproval: (params: {
22
20
  approved: boolean;
23
21
  user: {
24
22
  email: string;
25
- firstName?: string | null;
26
23
  };
27
24
  notifyUser: (payload: {
28
25
  email: string;
29
- firstName?: string | null;
30
26
  }) => Promise<void>;
31
27
  }) => Promise<{
32
28
  notified: false;
@@ -16,7 +16,6 @@ const notifyUserOnAdminApproval = async (params) => {
16
16
  }
17
17
  await params.notifyUser({
18
18
  email: params.user.email,
19
- firstName: params.user.firstName,
20
19
  });
21
20
  return { notified: true };
22
21
  };
@@ -12,8 +12,6 @@ const notification_workflows_1 = require("./notification-workflows");
12
12
  user: {
13
13
  id: "user-1",
14
14
  email: "user@example.com",
15
- firstName: "User",
16
- lastName: "One",
17
15
  },
18
16
  listAdminEmails,
19
17
  notifyAdmins,
@@ -44,13 +42,12 @@ const notification_workflows_1 = require("./notification-workflows");
44
42
  const notifyUser = vitest_1.vi.fn().mockResolvedValue(undefined);
45
43
  const result = await (0, notification_workflows_1.notifyUserOnAdminApproval)({
46
44
  approved: true,
47
- user: { email: "user@example.com", firstName: "User" },
45
+ user: { email: "user@example.com" },
48
46
  notifyUser,
49
47
  });
50
48
  (0, vitest_1.expect)(result).toEqual({ notified: true });
51
49
  (0, vitest_1.expect)(notifyUser).toHaveBeenCalledWith({
52
50
  email: "user@example.com",
53
- firstName: "User",
54
51
  });
55
52
  });
56
53
  (0, vitest_1.it)("skips user notification when approval is false", async () => {