@scryan7371/sdr-security 0.1.3 → 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 (36) hide show
  1. package/dist/api/migrations/1700000000001-add-refresh-tokens.js +4 -2
  2. package/dist/api/migrations/1739500000000-create-security-identity.js +3 -1
  3. package/dist/api/migrations/1739510000000-create-security-roles.js +2 -1
  4. package/dist/api/migrations/1739515000000-create-security-user-roles.js +4 -1
  5. package/dist/api/migrations/1739520000000-create-password-reset-tokens.js +3 -1
  6. package/dist/api/migrations/1739530000000-create-security-user.js +2 -1
  7. package/dist/integration/database.integration.test.js +1 -1
  8. package/dist/nest/entities/app-user.entity.js +1 -1
  9. package/dist/nest/entities/password-reset-token.entity.d.ts +1 -0
  10. package/dist/nest/entities/password-reset-token.entity.js +14 -2
  11. package/dist/nest/entities/refresh-token.entity.js +2 -2
  12. package/dist/nest/entities/security-role.entity.d.ts +1 -0
  13. package/dist/nest/entities/security-role.entity.js +13 -1
  14. package/dist/nest/entities/security-user-role.entity.d.ts +1 -0
  15. package/dist/nest/entities/security-user-role.entity.js +14 -2
  16. package/dist/nest/entities/security-user.entity.js +1 -1
  17. package/dist/nest/security-auth.service.js +4 -1
  18. package/dist/nest/security-auth.service.test.js +3 -1
  19. package/dist/nest/security-workflows.service.js +4 -0
  20. package/package.json +3 -2
  21. package/src/api/migrations/1700000000001-add-refresh-tokens.ts +4 -2
  22. package/src/api/migrations/1739500000000-create-security-identity.ts +3 -1
  23. package/src/api/migrations/1739510000000-create-security-roles.ts +2 -1
  24. package/src/api/migrations/1739515000000-create-security-user-roles.ts +4 -1
  25. package/src/api/migrations/1739520000000-create-password-reset-tokens.ts +3 -1
  26. package/src/api/migrations/1739530000000-create-security-user.ts +2 -1
  27. package/src/integration/database.integration.test.ts +1 -1
  28. package/src/nest/entities/app-user.entity.ts +2 -2
  29. package/src/nest/entities/password-reset-token.entity.ts +12 -3
  30. package/src/nest/entities/refresh-token.entity.ts +2 -2
  31. package/src/nest/entities/security-role.entity.ts +10 -2
  32. package/src/nest/entities/security-user-role.entity.ts +11 -3
  33. package/src/nest/entities/security-user.entity.ts +1 -1
  34. package/src/nest/security-auth.service.test.ts +4 -1
  35. package/src/nest/security-auth.service.ts +5 -2
  36. package/src/nest/security-workflows.service.ts +4 -0
@@ -7,12 +7,14 @@ class AddRefreshTokens1700000000001 {
7
7
  const userTableRef = getUserTableReference();
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE "refresh_token" (
10
- "id" varchar PRIMARY KEY NOT NULL,
10
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
11
  "token_hash" varchar NOT NULL,
12
12
  "expires_at" timestamptz NOT NULL,
13
13
  "revoked_at" timestamptz,
14
- "userId" varchar,
14
+ "userId" uuid,
15
15
  "created_at" timestamptz NOT NULL DEFAULT (CURRENT_TIMESTAMP),
16
+ CONSTRAINT "CHK_refresh_token_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
+ CONSTRAINT "CHK_refresh_token_userId_uuidv7" CHECK ("userId" IS NULL OR "userId"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
16
18
  CONSTRAINT "FK_refresh_token_user" FOREIGN KEY ("userId") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE ON UPDATE NO ACTION
17
19
  )
18
20
  `);
@@ -8,11 +8,13 @@ class CreateSecurityIdentity1739500000000 {
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE IF NOT EXISTS "security_identity" (
10
10
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
- "user_id" varchar NOT NULL,
11
+ "user_id" uuid NOT NULL,
12
12
  "provider" varchar NOT NULL,
13
13
  "provider_subject" varchar NOT NULL,
14
14
  "created_at" timestamptz NOT NULL DEFAULT now(),
15
15
  "updated_at" timestamptz NOT NULL DEFAULT now(),
16
+ CONSTRAINT "CHK_security_identity_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
+ CONSTRAINT "CHK_security_identity_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
16
18
  CONSTRAINT "FK_security_identity_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
17
19
  )
18
20
  `);
@@ -11,7 +11,8 @@ class CreateSecurityRoles1739510000000 {
11
11
  "description" text,
12
12
  "is_system" boolean NOT NULL DEFAULT false,
13
13
  "created_at" timestamptz NOT NULL DEFAULT now(),
14
- "updated_at" timestamptz NOT NULL DEFAULT now()
14
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
15
+ CONSTRAINT "CHK_security_role_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$')
15
16
  )
16
17
  `);
17
18
  await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_security_role_key" ON "security_role" ("role_key")`);
@@ -8,9 +8,12 @@ class CreateSecurityUserRoles1739515000000 {
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE IF NOT EXISTS "security_user_role" (
10
10
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
- "user_id" varchar NOT NULL,
11
+ "user_id" uuid NOT NULL,
12
12
  "role_id" uuid NOT NULL,
13
13
  "created_at" timestamptz NOT NULL DEFAULT now(),
14
+ CONSTRAINT "CHK_security_user_role_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
15
+ CONSTRAINT "CHK_security_user_role_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
16
+ CONSTRAINT "CHK_security_user_role_role_id_uuidv7" CHECK ("role_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
14
17
  CONSTRAINT "FK_security_user_role_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE,
15
18
  CONSTRAINT "FK_security_user_role_role_id" FOREIGN KEY ("role_id") REFERENCES "security_role" ("id") ON DELETE CASCADE
16
19
  )
@@ -8,11 +8,13 @@ class CreatePasswordResetTokens1739520000000 {
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE IF NOT EXISTS "security_password_reset_token" (
10
10
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
11
- "user_id" varchar NOT NULL,
11
+ "user_id" uuid NOT NULL,
12
12
  "token" varchar NOT NULL,
13
13
  "expires_at" timestamptz NOT NULL,
14
14
  "used_at" timestamptz,
15
15
  "created_at" timestamptz NOT NULL DEFAULT now(),
16
+ CONSTRAINT "CHK_security_password_reset_token_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
+ CONSTRAINT "CHK_security_password_reset_token_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
16
18
  CONSTRAINT "FK_security_password_reset_token_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
17
19
  )
18
20
  `);
@@ -7,13 +7,14 @@ class CreateSecurityUser1739530000000 {
7
7
  const userTableRef = getUserTableReference();
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE IF NOT EXISTS "security_user" (
10
- "user_id" varchar PRIMARY KEY NOT NULL,
10
+ "user_id" uuid PRIMARY KEY NOT NULL,
11
11
  "password_hash" varchar NOT NULL,
12
12
  "email_verified_at" timestamptz,
13
13
  "email_verification_token" varchar,
14
14
  "admin_approved_at" timestamptz,
15
15
  "is_active" boolean NOT NULL DEFAULT true,
16
16
  "created_at" timestamptz NOT NULL DEFAULT now(),
17
+ CONSTRAINT "CHK_security_user_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
18
  CONSTRAINT "FK_security_user_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
18
19
  )
19
20
  `);
@@ -97,7 +97,7 @@ const resetSchemaBeforeRun = process.env.SECURITY_TEST_RESET_SCHEMA !== "false";
97
97
  await client.query(`SET search_path TO "${schema}", public`);
98
98
  await client.query(`
99
99
  CREATE TABLE IF NOT EXISTS "${schema}"."app_user" (
100
- "id" varchar PRIMARY KEY NOT NULL,
100
+ "id" uuid PRIMARY KEY NOT NULL,
101
101
  "email" varchar NOT NULL
102
102
  )
103
103
  `);
@@ -17,7 +17,7 @@ let AppUserEntity = class AppUserEntity {
17
17
  };
18
18
  exports.AppUserEntity = AppUserEntity;
19
19
  __decorate([
20
- (0, typeorm_1.PrimaryGeneratedColumn)("uuid"),
20
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid" }),
21
21
  __metadata("design:type", String)
22
22
  ], AppUserEntity.prototype, "id", void 0);
23
23
  __decorate([
@@ -5,4 +5,5 @@ export declare class PasswordResetTokenEntity {
5
5
  expiresAt: Date;
6
6
  usedAt: Date | null;
7
7
  createdAt: Date;
8
+ ensureId(): void;
8
9
  }
@@ -11,6 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.PasswordResetTokenEntity = void 0;
13
13
  const typeorm_1 = require("typeorm");
14
+ const uuid_1 = require("uuid");
14
15
  let PasswordResetTokenEntity = class PasswordResetTokenEntity {
15
16
  id;
16
17
  userId;
@@ -18,14 +19,19 @@ let PasswordResetTokenEntity = class PasswordResetTokenEntity {
18
19
  expiresAt;
19
20
  usedAt;
20
21
  createdAt;
22
+ ensureId() {
23
+ if (!this.id) {
24
+ this.id = (0, uuid_1.v7)();
25
+ }
26
+ }
21
27
  };
22
28
  exports.PasswordResetTokenEntity = PasswordResetTokenEntity;
23
29
  __decorate([
24
- (0, typeorm_1.PrimaryGeneratedColumn)("uuid"),
30
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid" }),
25
31
  __metadata("design:type", String)
26
32
  ], PasswordResetTokenEntity.prototype, "id", void 0);
27
33
  __decorate([
28
- (0, typeorm_1.Column)({ type: "varchar", name: "user_id" }),
34
+ (0, typeorm_1.Column)({ type: "uuid", name: "user_id" }),
29
35
  __metadata("design:type", String)
30
36
  ], PasswordResetTokenEntity.prototype, "userId", void 0);
31
37
  __decorate([
@@ -44,6 +50,12 @@ __decorate([
44
50
  (0, typeorm_1.CreateDateColumn)({ name: "created_at" }),
45
51
  __metadata("design:type", Date)
46
52
  ], PasswordResetTokenEntity.prototype, "createdAt", void 0);
53
+ __decorate([
54
+ (0, typeorm_1.BeforeInsert)(),
55
+ __metadata("design:type", Function),
56
+ __metadata("design:paramtypes", []),
57
+ __metadata("design:returntype", void 0)
58
+ ], PasswordResetTokenEntity.prototype, "ensureId", null);
47
59
  exports.PasswordResetTokenEntity = PasswordResetTokenEntity = __decorate([
48
60
  (0, typeorm_1.Entity)({ name: "security_password_reset_token" })
49
61
  ], PasswordResetTokenEntity);
@@ -21,7 +21,7 @@ let RefreshTokenEntity = class RefreshTokenEntity {
21
21
  };
22
22
  exports.RefreshTokenEntity = RefreshTokenEntity;
23
23
  __decorate([
24
- (0, typeorm_1.PrimaryColumn)({ type: "varchar" }),
24
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid" }),
25
25
  __metadata("design:type", String)
26
26
  ], RefreshTokenEntity.prototype, "id", void 0);
27
27
  __decorate([
@@ -37,7 +37,7 @@ __decorate([
37
37
  __metadata("design:type", Object)
38
38
  ], RefreshTokenEntity.prototype, "revokedAt", void 0);
39
39
  __decorate([
40
- (0, typeorm_1.Column)({ type: "varchar", name: "userId", nullable: true }),
40
+ (0, typeorm_1.Column)({ type: "uuid", name: "userId", nullable: true }),
41
41
  __metadata("design:type", Object)
42
42
  ], RefreshTokenEntity.prototype, "userId", void 0);
43
43
  __decorate([
@@ -3,4 +3,5 @@ export declare class SecurityRoleEntity {
3
3
  roleKey: string;
4
4
  description: string | null;
5
5
  isSystem: boolean;
6
+ ensureId(): void;
6
7
  }
@@ -10,16 +10,22 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.SecurityRoleEntity = void 0;
13
+ const uuid_1 = require("uuid");
13
14
  const typeorm_1 = require("typeorm");
14
15
  let SecurityRoleEntity = class SecurityRoleEntity {
15
16
  id;
16
17
  roleKey;
17
18
  description;
18
19
  isSystem;
20
+ ensureId() {
21
+ if (!this.id) {
22
+ this.id = (0, uuid_1.v7)();
23
+ }
24
+ }
19
25
  };
20
26
  exports.SecurityRoleEntity = SecurityRoleEntity;
21
27
  __decorate([
22
- (0, typeorm_1.PrimaryGeneratedColumn)("uuid"),
28
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid" }),
23
29
  __metadata("design:type", String)
24
30
  ], SecurityRoleEntity.prototype, "id", void 0);
25
31
  __decorate([
@@ -34,6 +40,12 @@ __decorate([
34
40
  (0, typeorm_1.Column)({ type: "boolean", name: "is_system", default: false }),
35
41
  __metadata("design:type", Boolean)
36
42
  ], SecurityRoleEntity.prototype, "isSystem", void 0);
43
+ __decorate([
44
+ (0, typeorm_1.BeforeInsert)(),
45
+ __metadata("design:type", Function),
46
+ __metadata("design:paramtypes", []),
47
+ __metadata("design:returntype", void 0)
48
+ ], SecurityRoleEntity.prototype, "ensureId", null);
37
49
  exports.SecurityRoleEntity = SecurityRoleEntity = __decorate([
38
50
  (0, typeorm_1.Entity)({ name: "security_role" })
39
51
  ], SecurityRoleEntity);
@@ -2,4 +2,5 @@ export declare class SecurityUserRoleEntity {
2
2
  id: string;
3
3
  userId: string;
4
4
  roleId: string;
5
+ ensureId(): void;
5
6
  }
@@ -10,25 +10,37 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.SecurityUserRoleEntity = void 0;
13
+ const uuid_1 = require("uuid");
13
14
  const typeorm_1 = require("typeorm");
14
15
  let SecurityUserRoleEntity = class SecurityUserRoleEntity {
15
16
  id;
16
17
  userId;
17
18
  roleId;
19
+ ensureId() {
20
+ if (!this.id) {
21
+ this.id = (0, uuid_1.v7)();
22
+ }
23
+ }
18
24
  };
19
25
  exports.SecurityUserRoleEntity = SecurityUserRoleEntity;
20
26
  __decorate([
21
- (0, typeorm_1.PrimaryGeneratedColumn)("uuid"),
27
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid" }),
22
28
  __metadata("design:type", String)
23
29
  ], SecurityUserRoleEntity.prototype, "id", void 0);
24
30
  __decorate([
25
- (0, typeorm_1.Column)({ type: "varchar", name: "user_id" }),
31
+ (0, typeorm_1.Column)({ type: "uuid", name: "user_id" }),
26
32
  __metadata("design:type", String)
27
33
  ], SecurityUserRoleEntity.prototype, "userId", void 0);
28
34
  __decorate([
29
35
  (0, typeorm_1.Column)({ type: "uuid", name: "role_id" }),
30
36
  __metadata("design:type", String)
31
37
  ], SecurityUserRoleEntity.prototype, "roleId", void 0);
38
+ __decorate([
39
+ (0, typeorm_1.BeforeInsert)(),
40
+ __metadata("design:type", Function),
41
+ __metadata("design:paramtypes", []),
42
+ __metadata("design:returntype", void 0)
43
+ ], SecurityUserRoleEntity.prototype, "ensureId", null);
32
44
  exports.SecurityUserRoleEntity = SecurityUserRoleEntity = __decorate([
33
45
  (0, typeorm_1.Entity)({ name: "security_user_role" })
34
46
  ], SecurityUserRoleEntity);
@@ -22,7 +22,7 @@ let SecurityUserEntity = class SecurityUserEntity {
22
22
  };
23
23
  exports.SecurityUserEntity = SecurityUserEntity;
24
24
  __decorate([
25
- (0, typeorm_1.PrimaryColumn)({ type: "varchar", name: "user_id" }),
25
+ (0, typeorm_1.PrimaryColumn)({ type: "uuid", name: "user_id" }),
26
26
  __metadata("design:type", String)
27
27
  ], SecurityUserEntity.prototype, "userId", void 0);
28
28
  __decorate([
@@ -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");
@@ -58,6 +59,7 @@ let SecurityAuthService = class SecurityAuthService {
58
59
  throw new common_1.BadRequestException("Email already in use");
59
60
  }
60
61
  const appUser = await this.appUsersRepo.save(this.appUsersRepo.create({
62
+ id: (0, uuid_1.v7)(),
61
63
  email,
62
64
  }));
63
65
  const securityUser = await this.securityUsersRepo.save(this.securityUsersRepo.create({
@@ -161,6 +163,7 @@ let SecurityAuthService = class SecurityAuthService {
161
163
  const expiresAt = new Date(Date.now() +
162
164
  (this.options.passwordResetTokenExpiresInMinutes ?? 30) * 60_000);
163
165
  await this.passwordResetRepo.save(this.passwordResetRepo.create({
166
+ id: (0, uuid_1.v7)(),
164
167
  userId: appUser.id,
165
168
  token,
166
169
  expiresAt,
@@ -220,7 +223,7 @@ let SecurityAuthService = class SecurityAuthService {
220
223
  const refreshTokenExpiresAt = new Date(Date.now() +
221
224
  (this.options.refreshTokenExpiresInDays ?? 30) * 24 * 60 * 60 * 1000);
222
225
  await this.refreshTokenRepo.save(this.refreshTokenRepo.create({
223
- id: (0, crypto_1.randomUUID)(),
226
+ id: (0, uuid_1.v7)(),
224
227
  userId: appUser.id,
225
228
  tokenHash: refreshTokenHash,
226
229
  expiresAt: refreshTokenExpiresAt,
@@ -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");
@@ -16,6 +16,7 @@ exports.SecurityWorkflowsService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const typeorm_1 = require("@nestjs/typeorm");
18
18
  const typeorm_2 = require("typeorm");
19
+ const uuid_1 = require("uuid");
19
20
  const contracts_1 = require("../api/contracts");
20
21
  const roles_1 = require("../api/roles");
21
22
  const app_user_entity_1 = require("./entities/app-user.entity");
@@ -94,6 +95,7 @@ let SecurityWorkflowsService = class SecurityWorkflowsService {
94
95
  let role = await this.rolesRepo.findOne({ where: { roleKey } });
95
96
  if (!role) {
96
97
  role = this.rolesRepo.create({
98
+ id: (0, uuid_1.v7)(),
97
99
  roleKey,
98
100
  description: description?.trim() || null,
99
101
  isSystem: roleKey === contracts_1.ADMIN_ROLE,
@@ -139,6 +141,7 @@ let SecurityWorkflowsService = class SecurityWorkflowsService {
139
141
  await this.userRolesRepo.delete({ userId });
140
142
  if (roles.length > 0) {
141
143
  await this.userRolesRepo.save(roles.map((role) => this.userRolesRepo.create({
144
+ id: (0, uuid_1.v7)(),
142
145
  userId,
143
146
  roleId: role.id,
144
147
  })));
@@ -182,6 +185,7 @@ let SecurityWorkflowsService = class SecurityWorkflowsService {
182
185
  return;
183
186
  }
184
187
  await this.rolesRepo.save(missing.map((roleKey) => this.rolesRepo.create({
188
+ id: (0, uuid_1.v7)(),
185
189
  roleKey,
186
190
  description: null,
187
191
  isSystem: roleKey === contracts_1.ADMIN_ROLE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scryan7371/sdr-security",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Reusable auth/security capability for API and app clients.",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -34,7 +34,8 @@
34
34
  "dependencies": {
35
35
  "@babel/runtime": "7.28.6",
36
36
  "bcryptjs": "3.0.3",
37
- "jsonwebtoken": "9.0.3"
37
+ "jsonwebtoken": "9.0.3",
38
+ "uuid": "11.1.0"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "@nestjs/common": "^11.0.0",
@@ -8,12 +8,14 @@ export class AddRefreshTokens1700000000001 {
8
8
 
9
9
  await queryRunner.query(`
10
10
  CREATE TABLE "refresh_token" (
11
- "id" varchar PRIMARY KEY NOT NULL,
11
+ "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
12
12
  "token_hash" varchar NOT NULL,
13
13
  "expires_at" timestamptz NOT NULL,
14
14
  "revoked_at" timestamptz,
15
- "userId" varchar,
15
+ "userId" uuid,
16
16
  "created_at" timestamptz NOT NULL DEFAULT (CURRENT_TIMESTAMP),
17
+ CONSTRAINT "CHK_refresh_token_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
18
+ CONSTRAINT "CHK_refresh_token_userId_uuidv7" CHECK ("userId" IS NULL OR "userId"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
19
  CONSTRAINT "FK_refresh_token_user" FOREIGN KEY ("userId") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE ON UPDATE NO ACTION
18
20
  )
19
21
  `);
@@ -9,11 +9,13 @@ export class CreateSecurityIdentity1739500000000 {
9
9
  await queryRunner.query(`
10
10
  CREATE TABLE IF NOT EXISTS "security_identity" (
11
11
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
12
- "user_id" varchar NOT NULL,
12
+ "user_id" uuid NOT NULL,
13
13
  "provider" varchar NOT NULL,
14
14
  "provider_subject" varchar NOT NULL,
15
15
  "created_at" timestamptz NOT NULL DEFAULT now(),
16
16
  "updated_at" timestamptz NOT NULL DEFAULT now(),
17
+ CONSTRAINT "CHK_security_identity_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
18
+ CONSTRAINT "CHK_security_identity_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
19
  CONSTRAINT "FK_security_identity_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
18
20
  )
19
21
  `);
@@ -11,7 +11,8 @@ export class CreateSecurityRoles1739510000000 {
11
11
  "description" text,
12
12
  "is_system" boolean NOT NULL DEFAULT false,
13
13
  "created_at" timestamptz NOT NULL DEFAULT now(),
14
- "updated_at" timestamptz NOT NULL DEFAULT now()
14
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
15
+ CONSTRAINT "CHK_security_role_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$')
15
16
  )
16
17
  `);
17
18
 
@@ -9,9 +9,12 @@ export class CreateSecurityUserRoles1739515000000 {
9
9
  await queryRunner.query(`
10
10
  CREATE TABLE IF NOT EXISTS "security_user_role" (
11
11
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
12
- "user_id" varchar NOT NULL,
12
+ "user_id" uuid NOT NULL,
13
13
  "role_id" uuid NOT NULL,
14
14
  "created_at" timestamptz NOT NULL DEFAULT now(),
15
+ CONSTRAINT "CHK_security_user_role_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
16
+ CONSTRAINT "CHK_security_user_role_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
+ CONSTRAINT "CHK_security_user_role_role_id_uuidv7" CHECK ("role_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
15
18
  CONSTRAINT "FK_security_user_role_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE,
16
19
  CONSTRAINT "FK_security_user_role_role_id" FOREIGN KEY ("role_id") REFERENCES "security_role" ("id") ON DELETE CASCADE
17
20
  )
@@ -9,11 +9,13 @@ export class CreatePasswordResetTokens1739520000000 {
9
9
  await queryRunner.query(`
10
10
  CREATE TABLE IF NOT EXISTS "security_password_reset_token" (
11
11
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuidv7(),
12
- "user_id" varchar NOT NULL,
12
+ "user_id" uuid NOT NULL,
13
13
  "token" varchar NOT NULL,
14
14
  "expires_at" timestamptz NOT NULL,
15
15
  "used_at" timestamptz,
16
16
  "created_at" timestamptz NOT NULL DEFAULT now(),
17
+ CONSTRAINT "CHK_security_password_reset_token_id_uuidv7" CHECK ("id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
18
+ CONSTRAINT "CHK_security_password_reset_token_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
17
19
  CONSTRAINT "FK_security_password_reset_token_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
18
20
  )
19
21
  `);
@@ -8,13 +8,14 @@ export class CreateSecurityUser1739530000000 {
8
8
 
9
9
  await queryRunner.query(`
10
10
  CREATE TABLE IF NOT EXISTS "security_user" (
11
- "user_id" varchar PRIMARY KEY NOT NULL,
11
+ "user_id" uuid PRIMARY KEY NOT NULL,
12
12
  "password_hash" varchar NOT NULL,
13
13
  "email_verified_at" timestamptz,
14
14
  "email_verification_token" varchar,
15
15
  "admin_approved_at" timestamptz,
16
16
  "is_active" boolean NOT NULL DEFAULT true,
17
17
  "created_at" timestamptz NOT NULL DEFAULT now(),
18
+ CONSTRAINT "CHK_security_user_user_id_uuidv7" CHECK ("user_id"::text ~* '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
18
19
  CONSTRAINT "FK_security_user_user_id" FOREIGN KEY ("user_id") REFERENCES ${userTableRef} ("id") ON DELETE CASCADE
19
20
  )
20
21
  `);
@@ -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" varchar PRIMARY KEY NOT NULL,
124
+ "id" uuid PRIMARY KEY NOT NULL,
125
125
  "email" varchar NOT NULL
126
126
  )
127
127
  `);
@@ -1,8 +1,8 @@
1
- import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
1
+ import { Column, Entity, PrimaryColumn } from "typeorm";
2
2
 
3
3
  @Entity({ name: "app_user" })
4
4
  export class AppUserEntity {
5
- @PrimaryGeneratedColumn("uuid")
5
+ @PrimaryColumn({ type: "uuid" })
6
6
  id!: string;
7
7
 
8
8
  @Column({ type: "varchar" })
@@ -1,16 +1,18 @@
1
1
  import {
2
+ BeforeInsert,
2
3
  Column,
3
4
  CreateDateColumn,
4
5
  Entity,
5
- PrimaryGeneratedColumn,
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
- @PrimaryGeneratedColumn("uuid")
12
+ @PrimaryColumn({ type: "uuid" })
11
13
  id!: string;
12
14
 
13
- @Column({ type: "varchar", name: "user_id" })
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: "varchar" })
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: "varchar", name: "userId", nullable: true })
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 { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
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
- @PrimaryGeneratedColumn("uuid")
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 { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
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
- @PrimaryGeneratedColumn("uuid")
6
+ @PrimaryColumn({ type: "uuid" })
6
7
  id!: string;
7
8
 
8
- @Column({ type: "varchar", name: "user_id" })
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
  }
@@ -2,7 +2,7 @@ import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm";
2
2
 
3
3
  @Entity({ name: "security_user" })
4
4
  export class SecurityUserEntity {
5
- @PrimaryColumn({ type: "varchar", name: "user_id" })
5
+ @PrimaryColumn({ type: "uuid", name: "user_id" })
6
6
  userId!: string;
7
7
 
8
8
  @Column({ type: "varchar", name: "password_hash" })
@@ -10,13 +10,16 @@ vi.mock("bcryptjs", () => ({
10
10
 
11
11
  vi.mock("crypto", () => ({
12
12
  randomBytes: vi.fn(() => ({ toString: () => "token-bytes" })),
13
- randomUUID: vi.fn(() => "uuid-1"),
14
13
  }));
15
14
 
16
15
  vi.mock("jsonwebtoken", () => ({
17
16
  sign: vi.fn(() => "signed-access-token"),
18
17
  }));
19
18
 
19
+ vi.mock("uuid", () => ({
20
+ v7: vi.fn(() => "uuid-1"),
21
+ }));
22
+
20
23
  import { compare } from "bcryptjs";
21
24
  import { sign } from "jsonwebtoken";
22
25
  import { SecurityAuthService } from "./security-auth.service";
@@ -4,9 +4,10 @@ import {
4
4
  Injectable,
5
5
  UnauthorizedException,
6
6
  } from "@nestjs/common";
7
- import { randomBytes, randomUUID } from "crypto";
7
+ import { randomBytes } from "crypto";
8
8
  import { compare, hash } from "bcryptjs";
9
9
  import { sign, type SignOptions } from "jsonwebtoken";
10
+ import { v7 as uuidv7 } from "uuid";
10
11
  import { InjectRepository } from "@nestjs/typeorm";
11
12
  import { In, IsNull, Repository } from "typeorm";
12
13
  import { AuthResponse, RegisterResponse } from "../api/contracts";
@@ -60,6 +61,7 @@ export class SecurityAuthService {
60
61
 
61
62
  const appUser = await this.appUsersRepo.save(
62
63
  this.appUsersRepo.create({
64
+ id: uuidv7(),
63
65
  email,
64
66
  }),
65
67
  );
@@ -195,6 +197,7 @@ export class SecurityAuthService {
195
197
  );
196
198
  await this.passwordResetRepo.save(
197
199
  this.passwordResetRepo.create({
200
+ id: uuidv7(),
198
201
  userId: appUser.id,
199
202
  token,
200
203
  expiresAt,
@@ -283,7 +286,7 @@ export class SecurityAuthService {
283
286
 
284
287
  await this.refreshTokenRepo.save(
285
288
  this.refreshTokenRepo.create({
286
- id: randomUUID(),
289
+ id: uuidv7(),
287
290
  userId: appUser.id,
288
291
  tokenHash: refreshTokenHash,
289
292
  expiresAt: refreshTokenExpiresAt,
@@ -1,6 +1,7 @@
1
1
  import { Inject, Injectable, NotFoundException } from "@nestjs/common";
2
2
  import { InjectRepository } from "@nestjs/typeorm";
3
3
  import { In, Repository } from "typeorm";
4
+ import { v7 as uuidv7 } from "uuid";
4
5
  import { ADMIN_ROLE } from "../api/contracts";
5
6
  import { normalizeRoleName } from "../api/roles";
6
7
  import { AppUserEntity } from "./entities/app-user.entity";
@@ -106,6 +107,7 @@ export class SecurityWorkflowsService {
106
107
  let role = await this.rolesRepo.findOne({ where: { roleKey } });
107
108
  if (!role) {
108
109
  role = this.rolesRepo.create({
110
+ id: uuidv7(),
109
111
  roleKey,
110
112
  description: description?.trim() || null,
111
113
  isSystem: roleKey === ADMIN_ROLE,
@@ -158,6 +160,7 @@ export class SecurityWorkflowsService {
158
160
  await this.userRolesRepo.save(
159
161
  roles.map((role) =>
160
162
  this.userRolesRepo.create({
163
+ id: uuidv7(),
161
164
  userId,
162
165
  roleId: role.id,
163
166
  }),
@@ -210,6 +213,7 @@ export class SecurityWorkflowsService {
210
213
  await this.rolesRepo.save(
211
214
  missing.map((roleKey) =>
212
215
  this.rolesRepo.create({
216
+ id: uuidv7(),
213
217
  roleKey,
214
218
  description: null,
215
219
  isSystem: roleKey === ADMIN_ROLE,