@azlib/identity 0.2.0

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 (69) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +387 -0
  3. package/dist/errors-BGMwaW5s.d.mts +481 -0
  4. package/dist/errors-BGMwaW5s.d.mts.map +1 -0
  5. package/dist/errors-Bcjx9o6g.cjs +111 -0
  6. package/dist/errors-C2xZAatu.d.cts +481 -0
  7. package/dist/errors-C2xZAatu.d.cts.map +1 -0
  8. package/dist/errors-CEmnZxIn.mjs +66 -0
  9. package/dist/errors-CEmnZxIn.mjs.map +1 -0
  10. package/dist/express.cjs +405 -0
  11. package/dist/express.d.cts +87 -0
  12. package/dist/express.d.cts.map +1 -0
  13. package/dist/express.d.mts +87 -0
  14. package/dist/express.d.mts.map +1 -0
  15. package/dist/express.mjs +401 -0
  16. package/dist/express.mjs.map +1 -0
  17. package/dist/identity-4eP45YIP.cjs +461 -0
  18. package/dist/identity-Bz9RDOvT.mjs +410 -0
  19. package/dist/identity-Bz9RDOvT.mjs.map +1 -0
  20. package/dist/identity-router-DBL20UWT.d.mts +115 -0
  21. package/dist/identity-router-DBL20UWT.d.mts.map +1 -0
  22. package/dist/identity-router-Dib30Waj.d.cts +115 -0
  23. package/dist/identity-router-Dib30Waj.d.cts.map +1 -0
  24. package/dist/identity-service-B9zrvE9z.d.mts +128 -0
  25. package/dist/identity-service-B9zrvE9z.d.mts.map +1 -0
  26. package/dist/identity-service-CLzKx8Z7.d.cts +128 -0
  27. package/dist/identity-service-CLzKx8Z7.d.cts.map +1 -0
  28. package/dist/identity-store-BRRahxcS.d.cts +272 -0
  29. package/dist/identity-store-BRRahxcS.d.cts.map +1 -0
  30. package/dist/identity-store-BRRahxcS.d.mts +272 -0
  31. package/dist/identity-store-BRRahxcS.d.mts.map +1 -0
  32. package/dist/index-CpufYgyn.d.cts +30 -0
  33. package/dist/index-CpufYgyn.d.cts.map +1 -0
  34. package/dist/index-CpufYgyn.d.mts +30 -0
  35. package/dist/index-CpufYgyn.d.mts.map +1 -0
  36. package/dist/index.cjs +18 -0
  37. package/dist/index.d.cts +4 -0
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.mjs +3 -0
  40. package/dist/logger-Be1wDzBC.cjs +48 -0
  41. package/dist/logger-CcCHJVVe.mjs +33 -0
  42. package/dist/logger-CcCHJVVe.mjs.map +1 -0
  43. package/dist/nestjs.cjs +516 -0
  44. package/dist/nestjs.d.cts +102 -0
  45. package/dist/nestjs.d.cts.map +1 -0
  46. package/dist/nestjs.d.mts +102 -0
  47. package/dist/nestjs.d.mts.map +1 -0
  48. package/dist/nestjs.mjs +500 -0
  49. package/dist/nestjs.mjs.map +1 -0
  50. package/dist/node.cjs +946 -0
  51. package/dist/node.d.cts +260 -0
  52. package/dist/node.d.cts.map +1 -0
  53. package/dist/node.d.mts +260 -0
  54. package/dist/node.d.mts.map +1 -0
  55. package/dist/node.mjs +890 -0
  56. package/dist/node.mjs.map +1 -0
  57. package/dist/test-utils.cjs +169 -0
  58. package/dist/test-utils.d.cts +12 -0
  59. package/dist/test-utils.d.cts.map +1 -0
  60. package/dist/test-utils.d.mts +12 -0
  61. package/dist/test-utils.d.mts.map +1 -0
  62. package/dist/test-utils.mjs +170 -0
  63. package/dist/test-utils.mjs.map +1 -0
  64. package/package.json +92 -0
  65. package/schema/model.ts +100 -0
  66. package/schema/mysql.sql +102 -0
  67. package/schema/postgres.sql +92 -0
  68. package/schema/prisma.schema +122 -0
  69. package/schema/sqlite.sql +92 -0
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@azlib/identity",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/hanhn-dev/azlib.git"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "schema",
13
+ "README.md"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.mts",
18
+ "import": "./dist/index.mjs",
19
+ "require": "./dist/index.cjs"
20
+ },
21
+ "./node": {
22
+ "types": "./dist/node.d.mts",
23
+ "import": "./dist/node.mjs",
24
+ "require": "./dist/node.cjs"
25
+ },
26
+ "./express": {
27
+ "types": "./dist/express.d.mts",
28
+ "import": "./dist/express.mjs",
29
+ "require": "./dist/express.cjs"
30
+ },
31
+ "./nestjs": {
32
+ "types": "./dist/nestjs.d.mts",
33
+ "import": "./dist/nestjs.mjs",
34
+ "require": "./dist/nestjs.cjs"
35
+ },
36
+ "./test-utils": {
37
+ "types": "./dist/test-utils.d.mts",
38
+ "import": "./dist/test-utils.mjs",
39
+ "require": "./dist/test-utils.cjs"
40
+ },
41
+ "./schema/*": "./schema/*"
42
+ },
43
+ "dependencies": {
44
+ "jose": "^5.9.6"
45
+ },
46
+ "peerDependencies": {
47
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
48
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
49
+ "express": "^4.18.0 || ^5.0.0",
50
+ "reflect-metadata": "^0.2.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@nestjs/common": {
54
+ "optional": true
55
+ },
56
+ "@nestjs/core": {
57
+ "optional": true
58
+ },
59
+ "express": {
60
+ "optional": true
61
+ },
62
+ "reflect-metadata": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@nestjs/common": "^11.0.0",
68
+ "@nestjs/core": "^11.0.0",
69
+ "@types/express": "^5.0.0",
70
+ "@types/node": "^22.10.0",
71
+ "@types/supertest": "^6.0.2",
72
+ "express": "^5.1.0",
73
+ "reflect-metadata": "^0.2.2",
74
+ "rxjs": "^7.8.1",
75
+ "supertest": "^7.0.0",
76
+ "tsdown": "^0.22.1",
77
+ "typescript": "5.5.4",
78
+ "vitest": "^4.1.5",
79
+ "@repo/typescript-config": "0.0.0"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public",
83
+ "registry": "https://registry.npmjs.org"
84
+ },
85
+ "scripts": {
86
+ "build": "rm -rf dist && tsdown",
87
+ "dev": "tsdown --watch",
88
+ "lint": "tsc -p tsconfig.json --noEmit",
89
+ "test": "vitest run",
90
+ "clean": "rm -rf .turbo node_modules dist"
91
+ }
92
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Logical description of the relational schema `@azlib/identity` expects.
3
+ *
4
+ * Concrete DDL is shipped alongside this module:
5
+ * - `@azlib/identity/schema/postgres.sql`
6
+ * - `@azlib/identity/schema/mysql.sql`
7
+ * - `@azlib/identity/schema/sqlite.sql`
8
+ * - `@azlib/identity/schema/prisma.schema`
9
+ *
10
+ * This object is documentation-as-data so consumers and tooling can introspect the
11
+ * expected tables without parsing SQL.
12
+ */
13
+ export interface SchemaColumn {
14
+ name: string;
15
+ description: string;
16
+ nullable: boolean;
17
+ }
18
+
19
+ export interface SchemaTable {
20
+ name: string;
21
+ description: string;
22
+ columns: readonly SchemaColumn[];
23
+ }
24
+
25
+ export interface SchemaModel {
26
+ tables: readonly SchemaTable[];
27
+ }
28
+
29
+ export const identitySchemaModel: SchemaModel = {
30
+ tables: [
31
+ {
32
+ name: "identity_users",
33
+ description: "Core user accounts.",
34
+ columns: [
35
+ { name: "user_id", description: "Primary key.", nullable: false },
36
+ { name: "email", description: "Unique, case-insensitive login email.", nullable: false },
37
+ { name: "display_name", description: "Optional display name.", nullable: true },
38
+ { name: "status", description: "active | disabled | locked.", nullable: false },
39
+ { name: "email_verified_at", description: "When email was verified.", nullable: true },
40
+ { name: "auth_version", description: "Token invalidation counter.", nullable: false },
41
+ { name: "created_at", description: "Creation timestamp.", nullable: false },
42
+ { name: "updated_at", description: "Last update timestamp.", nullable: false },
43
+ ],
44
+ },
45
+ {
46
+ name: "identity_credentials",
47
+ description: "Password hashes, one per user.",
48
+ columns: [
49
+ { name: "user_id", description: "FK to identity_users.", nullable: false },
50
+ { name: "password_hash", description: "Algorithm-tagged hash.", nullable: false },
51
+ { name: "updated_at", description: "Last update timestamp.", nullable: false },
52
+ ],
53
+ },
54
+ {
55
+ name: "identity_sessions",
56
+ description: "Server-side refresh sessions with hashed tokens.",
57
+ columns: [
58
+ { name: "session_id", description: "Primary key.", nullable: false },
59
+ { name: "user_id", description: "FK to identity_users.", nullable: false },
60
+ { name: "refresh_token_hash", description: "Hash of the active refresh token.", nullable: false },
61
+ { name: "created_at", description: "Creation timestamp.", nullable: false },
62
+ { name: "expires_at", description: "Expiry timestamp.", nullable: false },
63
+ { name: "revoked_at", description: "Set when rotated or revoked.", nullable: true },
64
+ ],
65
+ },
66
+ {
67
+ name: "identity_roles",
68
+ description: "Named roles.",
69
+ columns: [
70
+ { name: "role_id", description: "Primary key.", nullable: false },
71
+ { name: "name", description: "Unique role name.", nullable: false },
72
+ { name: "description", description: "Optional description.", nullable: true },
73
+ ],
74
+ },
75
+ {
76
+ name: "identity_user_roles",
77
+ description: "User-to-role assignments.",
78
+ columns: [
79
+ { name: "user_id", description: "FK to identity_users.", nullable: false },
80
+ { name: "role_id", description: "FK to identity_roles.", nullable: false },
81
+ ],
82
+ },
83
+ {
84
+ name: "identity_role_permissions",
85
+ description: "Permissions granted to roles.",
86
+ columns: [
87
+ { name: "role_id", description: "FK to identity_roles.", nullable: false },
88
+ { name: "permission", description: "Permission string.", nullable: false },
89
+ ],
90
+ },
91
+ {
92
+ name: "identity_user_permissions",
93
+ description: "Permissions granted directly to users.",
94
+ columns: [
95
+ { name: "user_id", description: "FK to identity_users.", nullable: false },
96
+ { name: "permission", description: "Permission string.", nullable: false },
97
+ ],
98
+ },
99
+ ],
100
+ };
@@ -0,0 +1,102 @@
1
+ -- @azlib/identity — MySQL schema
2
+ -- Apply with: mysql DATABASE < mysql.sql
3
+
4
+ CREATE TABLE IF NOT EXISTS identity_users (
5
+ user_id VARCHAR(64) PRIMARY KEY,
6
+ email VARCHAR(320) NOT NULL,
7
+ display_name VARCHAR(255),
8
+ status ENUM('active', 'disabled', 'locked') NOT NULL DEFAULT 'active',
9
+ email_verified_at DATETIME,
10
+ auth_version INT NOT NULL DEFAULT 0,
11
+ failed_login_attempts INT NOT NULL DEFAULT 0,
12
+ locked_until DATETIME,
13
+ two_factor_enabled TINYINT(1) NOT NULL DEFAULT 0,
14
+ created_at DATETIME NOT NULL,
15
+ updated_at DATETIME NOT NULL,
16
+ UNIQUE KEY identity_users_email_key (email)
17
+ );
18
+
19
+ CREATE TABLE IF NOT EXISTS identity_credentials (
20
+ user_id VARCHAR(64) PRIMARY KEY,
21
+ password_hash VARCHAR(512) NOT NULL,
22
+ updated_at DATETIME NOT NULL,
23
+ CONSTRAINT fk_credentials_user FOREIGN KEY (user_id)
24
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS identity_sessions (
28
+ session_id VARCHAR(64) PRIMARY KEY,
29
+ user_id VARCHAR(64) NOT NULL,
30
+ refresh_token_hash VARCHAR(512) NOT NULL,
31
+ created_at DATETIME NOT NULL,
32
+ expires_at DATETIME NOT NULL,
33
+ revoked_at DATETIME,
34
+ KEY identity_sessions_user_idx (user_id),
35
+ CONSTRAINT fk_sessions_user FOREIGN KEY (user_id)
36
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS identity_roles (
40
+ role_id VARCHAR(64) PRIMARY KEY,
41
+ name VARCHAR(128) NOT NULL UNIQUE,
42
+ description VARCHAR(512)
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS identity_user_roles (
46
+ user_id VARCHAR(64) NOT NULL,
47
+ role_id VARCHAR(64) NOT NULL,
48
+ PRIMARY KEY (user_id, role_id),
49
+ CONSTRAINT fk_user_roles_user FOREIGN KEY (user_id)
50
+ REFERENCES identity_users (user_id) ON DELETE CASCADE,
51
+ CONSTRAINT fk_user_roles_role FOREIGN KEY (role_id)
52
+ REFERENCES identity_roles (role_id) ON DELETE CASCADE
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS identity_role_permissions (
56
+ role_id VARCHAR(64) NOT NULL,
57
+ permission VARCHAR(255) NOT NULL,
58
+ PRIMARY KEY (role_id, permission),
59
+ CONSTRAINT fk_role_permissions_role FOREIGN KEY (role_id)
60
+ REFERENCES identity_roles (role_id) ON DELETE CASCADE
61
+ );
62
+
63
+ CREATE TABLE IF NOT EXISTS identity_user_permissions (
64
+ user_id VARCHAR(64) NOT NULL,
65
+ permission VARCHAR(255) NOT NULL,
66
+ PRIMARY KEY (user_id, permission),
67
+ CONSTRAINT fk_user_permissions_user FOREIGN KEY (user_id)
68
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS identity_oauth_linked_accounts (
72
+ user_id VARCHAR(64) NOT NULL,
73
+ provider VARCHAR(64) NOT NULL,
74
+ provider_user_id VARCHAR(255) NOT NULL,
75
+ email VARCHAR(320),
76
+ display_name VARCHAR(255),
77
+ linked_at DATETIME NOT NULL,
78
+ PRIMARY KEY (provider, provider_user_id),
79
+ KEY identity_oauth_user_idx (user_id),
80
+ CONSTRAINT fk_oauth_links_user FOREIGN KEY (user_id)
81
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
82
+ );
83
+
84
+ CREATE TABLE IF NOT EXISTS identity_verification_tokens (
85
+ token_hash VARCHAR(128) PRIMARY KEY,
86
+ user_id VARCHAR(64) NOT NULL,
87
+ type ENUM('email_verification', 'password_reset') NOT NULL,
88
+ expires_at DATETIME NOT NULL,
89
+ used_at DATETIME,
90
+ KEY identity_vtokens_user_idx (user_id),
91
+ CONSTRAINT fk_vtokens_user FOREIGN KEY (user_id)
92
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
93
+ );
94
+
95
+ CREATE TABLE IF NOT EXISTS identity_totp_secrets (
96
+ user_id VARCHAR(64) PRIMARY KEY,
97
+ secret_base32 VARCHAR(128) NOT NULL,
98
+ enabled_at DATETIME,
99
+ created_at DATETIME NOT NULL,
100
+ CONSTRAINT fk_totp_user FOREIGN KEY (user_id)
101
+ REFERENCES identity_users (user_id) ON DELETE CASCADE
102
+ );
@@ -0,0 +1,92 @@
1
+ -- @azlib/identity — PostgreSQL schema
2
+ -- Apply with: psql "$DATABASE_URL" -f postgres.sql
3
+
4
+ CREATE TABLE IF NOT EXISTS identity_users (
5
+ user_id TEXT PRIMARY KEY,
6
+ email TEXT NOT NULL,
7
+ display_name TEXT,
8
+ status TEXT NOT NULL DEFAULT 'active'
9
+ CHECK (status IN ('active', 'disabled', 'locked')),
10
+ email_verified_at TIMESTAMPTZ,
11
+ auth_version INTEGER NOT NULL DEFAULT 0,
12
+ failed_login_attempts INTEGER NOT NULL DEFAULT 0,
13
+ locked_until TIMESTAMPTZ,
14
+ two_factor_enabled BOOLEAN NOT NULL DEFAULT FALSE,
15
+ created_at TIMESTAMPTZ NOT NULL,
16
+ updated_at TIMESTAMPTZ NOT NULL
17
+ );
18
+
19
+ CREATE UNIQUE INDEX IF NOT EXISTS identity_users_email_key
20
+ ON identity_users (LOWER(email));
21
+
22
+ CREATE TABLE IF NOT EXISTS identity_credentials (
23
+ user_id TEXT PRIMARY KEY REFERENCES identity_users (user_id) ON DELETE CASCADE,
24
+ password_hash TEXT NOT NULL,
25
+ updated_at TIMESTAMPTZ NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS identity_sessions (
29
+ session_id TEXT PRIMARY KEY,
30
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
31
+ refresh_token_hash TEXT NOT NULL,
32
+ created_at TIMESTAMPTZ NOT NULL,
33
+ expires_at TIMESTAMPTZ NOT NULL,
34
+ revoked_at TIMESTAMPTZ
35
+ );
36
+
37
+ CREATE INDEX IF NOT EXISTS identity_sessions_user_idx ON identity_sessions (user_id);
38
+
39
+ CREATE TABLE IF NOT EXISTS identity_roles (
40
+ role_id TEXT PRIMARY KEY,
41
+ name TEXT NOT NULL UNIQUE,
42
+ description TEXT
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS identity_user_roles (
46
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
47
+ role_id TEXT NOT NULL REFERENCES identity_roles (role_id) ON DELETE CASCADE,
48
+ PRIMARY KEY (user_id, role_id)
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS identity_role_permissions (
52
+ role_id TEXT NOT NULL REFERENCES identity_roles (role_id) ON DELETE CASCADE,
53
+ permission TEXT NOT NULL,
54
+ PRIMARY KEY (role_id, permission)
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS identity_user_permissions (
58
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
59
+ permission TEXT NOT NULL,
60
+ PRIMARY KEY (user_id, permission)
61
+ );
62
+
63
+ CREATE TABLE IF NOT EXISTS identity_oauth_linked_accounts (
64
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
65
+ provider TEXT NOT NULL,
66
+ provider_user_id TEXT NOT NULL,
67
+ email TEXT,
68
+ display_name TEXT,
69
+ linked_at TIMESTAMPTZ NOT NULL,
70
+ PRIMARY KEY (provider, provider_user_id)
71
+ );
72
+
73
+ CREATE INDEX IF NOT EXISTS identity_oauth_user_idx
74
+ ON identity_oauth_linked_accounts (user_id);
75
+
76
+ CREATE TABLE IF NOT EXISTS identity_verification_tokens (
77
+ token_hash TEXT PRIMARY KEY,
78
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
79
+ type TEXT NOT NULL CHECK (type IN ('email_verification', 'password_reset')),
80
+ expires_at TIMESTAMPTZ NOT NULL,
81
+ used_at TIMESTAMPTZ
82
+ );
83
+
84
+ CREATE INDEX IF NOT EXISTS identity_vtokens_user_idx
85
+ ON identity_verification_tokens (user_id);
86
+
87
+ CREATE TABLE IF NOT EXISTS identity_totp_secrets (
88
+ user_id TEXT PRIMARY KEY REFERENCES identity_users (user_id) ON DELETE CASCADE,
89
+ secret_base32 TEXT NOT NULL,
90
+ enabled_at TIMESTAMPTZ,
91
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
92
+ );
@@ -0,0 +1,122 @@
1
+ // @azlib/identity — Prisma schema fragment
2
+ // Copy these models into your schema.prisma and adjust the datasource as needed.
3
+
4
+ model IdentityUser {
5
+ userId String @id @map("user_id")
6
+ email String @unique
7
+ displayName String? @map("display_name")
8
+ status String @default("active")
9
+ emailVerifiedAt DateTime? @map("email_verified_at")
10
+ authVersion Int @default(0) @map("auth_version")
11
+ failedLoginAttempts Int @default(0) @map("failed_login_attempts")
12
+ lockedUntil DateTime? @map("locked_until")
13
+ twoFactorEnabled Boolean @default(false) @map("two_factor_enabled")
14
+ createdAt DateTime @map("created_at")
15
+ updatedAt DateTime @map("updated_at")
16
+
17
+ credential IdentityCredential?
18
+ sessions IdentitySession[]
19
+ roles IdentityUserRole[]
20
+ permissions IdentityUserPermission[]
21
+ oauthLinks IdentityOAuthLinkedAccount[]
22
+ verificationTokens IdentityVerificationToken[]
23
+ totpSecret IdentityTotpSecret?
24
+
25
+ @@map("identity_users")
26
+ }
27
+
28
+ model IdentityCredential {
29
+ userId String @id @map("user_id")
30
+ passwordHash String @map("password_hash")
31
+ updatedAt DateTime @map("updated_at")
32
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
33
+
34
+ @@map("identity_credentials")
35
+ }
36
+
37
+ model IdentitySession {
38
+ sessionId String @id @map("session_id")
39
+ userId String @map("user_id")
40
+ refreshTokenHash String @map("refresh_token_hash")
41
+ createdAt DateTime @map("created_at")
42
+ expiresAt DateTime @map("expires_at")
43
+ revokedAt DateTime? @map("revoked_at")
44
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
45
+
46
+ @@index([userId])
47
+ @@map("identity_sessions")
48
+ }
49
+
50
+ model IdentityRole {
51
+ roleId String @id @map("role_id")
52
+ name String @unique
53
+ description String?
54
+ users IdentityUserRole[]
55
+ permissions IdentityRolePermission[]
56
+
57
+ @@map("identity_roles")
58
+ }
59
+
60
+ model IdentityUserRole {
61
+ userId String @map("user_id")
62
+ roleId String @map("role_id")
63
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
64
+ role IdentityRole @relation(fields: [roleId], references: [roleId], onDelete: Cascade)
65
+
66
+ @@id([userId, roleId])
67
+ @@map("identity_user_roles")
68
+ }
69
+
70
+ model IdentityRolePermission {
71
+ roleId String @map("role_id")
72
+ permission String
73
+ role IdentityRole @relation(fields: [roleId], references: [roleId], onDelete: Cascade)
74
+
75
+ @@id([roleId, permission])
76
+ @@map("identity_role_permissions")
77
+ }
78
+
79
+ model IdentityUserPermission {
80
+ userId String @map("user_id")
81
+ permission String
82
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
83
+
84
+ @@id([userId, permission])
85
+ @@map("identity_user_permissions")
86
+ }
87
+
88
+ model IdentityOAuthLinkedAccount {
89
+ userId String @map("user_id")
90
+ provider String
91
+ providerUserId String @map("provider_user_id")
92
+ email String?
93
+ displayName String? @map("display_name")
94
+ linkedAt DateTime @map("linked_at")
95
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
96
+
97
+ @@id([provider, providerUserId])
98
+ @@index([userId])
99
+ @@map("identity_oauth_linked_accounts")
100
+ }
101
+
102
+ model IdentityVerificationToken {
103
+ tokenHash String @id @map("token_hash")
104
+ userId String @map("user_id")
105
+ type String // 'email_verification' | 'password_reset'
106
+ expiresAt DateTime @map("expires_at")
107
+ usedAt DateTime? @map("used_at")
108
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
109
+
110
+ @@index([userId])
111
+ @@map("identity_verification_tokens")
112
+ }
113
+
114
+ model IdentityTotpSecret {
115
+ userId String @id @map("user_id")
116
+ secretBase32 String @map("secret_base32")
117
+ enabledAt DateTime? @map("enabled_at")
118
+ createdAt DateTime @default(now()) @map("created_at")
119
+ user IdentityUser @relation(fields: [userId], references: [userId], onDelete: Cascade)
120
+
121
+ @@map("identity_totp_secrets")
122
+ }
@@ -0,0 +1,92 @@
1
+ -- @azlib/identity — SQLite schema
2
+ -- Apply with: sqlite3 app.db < sqlite.sql
3
+
4
+ CREATE TABLE IF NOT EXISTS identity_users (
5
+ user_id TEXT PRIMARY KEY,
6
+ email TEXT NOT NULL,
7
+ display_name TEXT,
8
+ status TEXT NOT NULL DEFAULT 'active'
9
+ CHECK (status IN ('active', 'disabled', 'locked')),
10
+ email_verified_at TEXT,
11
+ auth_version INTEGER NOT NULL DEFAULT 0,
12
+ failed_login_attempts INTEGER NOT NULL DEFAULT 0,
13
+ locked_until TEXT,
14
+ two_factor_enabled INTEGER NOT NULL DEFAULT 0,
15
+ created_at TEXT NOT NULL,
16
+ updated_at TEXT NOT NULL
17
+ );
18
+
19
+ CREATE UNIQUE INDEX IF NOT EXISTS identity_users_email_key
20
+ ON identity_users (email COLLATE NOCASE);
21
+
22
+ CREATE TABLE IF NOT EXISTS identity_credentials (
23
+ user_id TEXT PRIMARY KEY REFERENCES identity_users (user_id) ON DELETE CASCADE,
24
+ password_hash TEXT NOT NULL,
25
+ updated_at TEXT NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS identity_sessions (
29
+ session_id TEXT PRIMARY KEY,
30
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
31
+ refresh_token_hash TEXT NOT NULL,
32
+ created_at TEXT NOT NULL,
33
+ expires_at TEXT NOT NULL,
34
+ revoked_at TEXT
35
+ );
36
+
37
+ CREATE INDEX IF NOT EXISTS identity_sessions_user_idx ON identity_sessions (user_id);
38
+
39
+ CREATE TABLE IF NOT EXISTS identity_roles (
40
+ role_id TEXT PRIMARY KEY,
41
+ name TEXT NOT NULL UNIQUE,
42
+ description TEXT
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS identity_user_roles (
46
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
47
+ role_id TEXT NOT NULL REFERENCES identity_roles (role_id) ON DELETE CASCADE,
48
+ PRIMARY KEY (user_id, role_id)
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS identity_role_permissions (
52
+ role_id TEXT NOT NULL REFERENCES identity_roles (role_id) ON DELETE CASCADE,
53
+ permission TEXT NOT NULL,
54
+ PRIMARY KEY (role_id, permission)
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS identity_user_permissions (
58
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
59
+ permission TEXT NOT NULL,
60
+ PRIMARY KEY (user_id, permission)
61
+ );
62
+
63
+ CREATE TABLE IF NOT EXISTS identity_oauth_linked_accounts (
64
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
65
+ provider TEXT NOT NULL,
66
+ provider_user_id TEXT NOT NULL,
67
+ email TEXT,
68
+ display_name TEXT,
69
+ linked_at TEXT NOT NULL,
70
+ PRIMARY KEY (provider, provider_user_id)
71
+ );
72
+
73
+ CREATE INDEX IF NOT EXISTS identity_oauth_user_idx
74
+ ON identity_oauth_linked_accounts (user_id);
75
+
76
+ CREATE TABLE IF NOT EXISTS identity_verification_tokens (
77
+ token_hash TEXT PRIMARY KEY,
78
+ user_id TEXT NOT NULL REFERENCES identity_users (user_id) ON DELETE CASCADE,
79
+ type TEXT NOT NULL CHECK (type IN ('email_verification', 'password_reset')),
80
+ expires_at TEXT NOT NULL,
81
+ used_at TEXT
82
+ );
83
+
84
+ CREATE INDEX IF NOT EXISTS identity_vtokens_user_idx
85
+ ON identity_verification_tokens (user_id);
86
+
87
+ CREATE TABLE IF NOT EXISTS identity_totp_secrets (
88
+ user_id TEXT PRIMARY KEY REFERENCES identity_users (user_id) ON DELETE CASCADE,
89
+ secret_base32 TEXT NOT NULL,
90
+ enabled_at TEXT,
91
+ created_at TEXT NOT NULL
92
+ );