@ofeklabs/horizon-auth 0.2.1 → 0.4.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 (132) hide show
  1. package/README.md +544 -175
  2. package/dist/account/account.module.d.ts +2 -0
  3. package/dist/account/account.module.js +23 -0
  4. package/dist/account/account.module.js.map +1 -0
  5. package/dist/account/account.service.d.ts +10 -0
  6. package/dist/account/account.service.js +69 -0
  7. package/dist/account/account.service.js.map +1 -0
  8. package/dist/account/dto/deactivate-account.dto.d.ts +3 -0
  9. package/dist/account/dto/deactivate-account.dto.js +22 -0
  10. package/dist/account/dto/deactivate-account.dto.js.map +1 -0
  11. package/dist/account/dto/index.d.ts +1 -0
  12. package/dist/account/dto/index.js +18 -0
  13. package/dist/account/dto/index.js.map +1 -0
  14. package/dist/auth/auth.controller.d.ts +81 -2
  15. package/dist/auth/auth.controller.js +304 -6
  16. package/dist/auth/auth.controller.js.map +1 -1
  17. package/dist/auth/auth.module.d.ts +2 -1
  18. package/dist/auth/auth.module.js +36 -14
  19. package/dist/auth/auth.module.js.map +1 -1
  20. package/dist/auth/auth.service.d.ts +19 -2
  21. package/dist/auth/auth.service.js +85 -3
  22. package/dist/auth/auth.service.js.map +1 -1
  23. package/dist/common/exceptions/account-deactivated.exception.d.ts +4 -0
  24. package/dist/common/exceptions/account-deactivated.exception.js +13 -0
  25. package/dist/common/exceptions/account-deactivated.exception.js.map +1 -0
  26. package/dist/common/exceptions/backup-code-already-used.exception.d.ts +4 -0
  27. package/dist/common/exceptions/backup-code-already-used.exception.js +11 -0
  28. package/dist/common/exceptions/backup-code-already-used.exception.js.map +1 -0
  29. package/dist/common/exceptions/feature-disabled.exception.d.ts +4 -0
  30. package/dist/common/exceptions/feature-disabled.exception.js +11 -0
  31. package/dist/common/exceptions/feature-disabled.exception.js.map +1 -0
  32. package/dist/common/exceptions/index.d.ts +6 -0
  33. package/dist/common/exceptions/index.js +23 -0
  34. package/dist/common/exceptions/index.js.map +1 -0
  35. package/dist/common/exceptions/invalid-two-factor-code.exception.d.ts +4 -0
  36. package/dist/common/exceptions/invalid-two-factor-code.exception.js +11 -0
  37. package/dist/common/exceptions/invalid-two-factor-code.exception.js.map +1 -0
  38. package/dist/common/exceptions/social-account-already-linked.exception.d.ts +4 -0
  39. package/dist/common/exceptions/social-account-already-linked.exception.js +11 -0
  40. package/dist/common/exceptions/social-account-already-linked.exception.js.map +1 -0
  41. package/dist/common/exceptions/two-factor-required.exception.d.ts +5 -0
  42. package/dist/common/exceptions/two-factor-required.exception.js +12 -0
  43. package/dist/common/exceptions/two-factor-required.exception.js.map +1 -0
  44. package/dist/devices/device.module.d.ts +2 -0
  45. package/dist/devices/device.module.js +24 -0
  46. package/dist/devices/device.module.js.map +1 -0
  47. package/dist/devices/device.service.d.ts +48 -0
  48. package/dist/devices/device.service.js +149 -0
  49. package/dist/devices/device.service.js.map +1 -0
  50. package/dist/devices/dto/device-info.dto.d.ts +3 -0
  51. package/dist/devices/dto/device-info.dto.js +22 -0
  52. package/dist/devices/dto/device-info.dto.js.map +1 -0
  53. package/dist/devices/dto/device-response.dto.d.ts +9 -0
  54. package/dist/devices/dto/device-response.dto.js +7 -0
  55. package/dist/devices/dto/device-response.dto.js.map +1 -0
  56. package/dist/devices/dto/index.d.ts +2 -0
  57. package/dist/devices/dto/index.js +19 -0
  58. package/dist/devices/dto/index.js.map +1 -0
  59. package/dist/index.d.ts +17 -0
  60. package/dist/index.js +17 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/lib/horizon-auth-config.interface.d.ts +29 -0
  63. package/dist/lib/horizon-auth-env.config.d.ts +3 -0
  64. package/dist/lib/horizon-auth-env.config.js +173 -0
  65. package/dist/lib/horizon-auth-env.config.js.map +1 -0
  66. package/dist/lib/horizon-auth.module.d.ts +1 -1
  67. package/dist/lib/horizon-auth.module.js +10 -8
  68. package/dist/lib/horizon-auth.module.js.map +1 -1
  69. package/dist/push-tokens/dto/index.d.ts +1 -0
  70. package/dist/push-tokens/dto/index.js +18 -0
  71. package/dist/push-tokens/dto/index.js.map +1 -0
  72. package/dist/push-tokens/dto/register-push-token.dto.d.ts +5 -0
  73. package/dist/push-tokens/dto/register-push-token.dto.js +30 -0
  74. package/dist/push-tokens/dto/register-push-token.dto.js.map +1 -0
  75. package/dist/push-tokens/push-token.module.d.ts +2 -0
  76. package/dist/push-tokens/push-token.module.js +24 -0
  77. package/dist/push-tokens/push-token.module.js.map +1 -0
  78. package/dist/push-tokens/push-token.service.d.ts +52 -0
  79. package/dist/push-tokens/push-token.service.js +129 -0
  80. package/dist/push-tokens/push-token.service.js.map +1 -0
  81. package/dist/social-auth/dto/facebook-callback.dto.d.ts +3 -0
  82. package/dist/social-auth/dto/facebook-callback.dto.js +22 -0
  83. package/dist/social-auth/dto/facebook-callback.dto.js.map +1 -0
  84. package/dist/social-auth/dto/google-callback.dto.d.ts +3 -0
  85. package/dist/social-auth/dto/google-callback.dto.js +22 -0
  86. package/dist/social-auth/dto/google-callback.dto.js.map +1 -0
  87. package/dist/social-auth/dto/index.d.ts +2 -0
  88. package/dist/social-auth/dto/index.js +19 -0
  89. package/dist/social-auth/dto/index.js.map +1 -0
  90. package/dist/social-auth/social-auth.module.d.ts +2 -0
  91. package/dist/social-auth/social-auth.module.js +25 -0
  92. package/dist/social-auth/social-auth.module.js.map +1 -0
  93. package/dist/social-auth/social-auth.service.d.ts +43 -0
  94. package/dist/social-auth/social-auth.service.js +130 -0
  95. package/dist/social-auth/social-auth.service.js.map +1 -0
  96. package/dist/social-auth/strategies/facebook.strategy.d.ts +9 -0
  97. package/dist/social-auth/strategies/facebook.strategy.js +51 -0
  98. package/dist/social-auth/strategies/facebook.strategy.js.map +1 -0
  99. package/dist/social-auth/strategies/google.strategy.d.ts +9 -0
  100. package/dist/social-auth/strategies/google.strategy.js +49 -0
  101. package/dist/social-auth/strategies/google.strategy.js.map +1 -0
  102. package/dist/tsconfig.build.tsbuildinfo +1 -1
  103. package/dist/two-factor/dto/enable-two-factor.dto.d.ts +2 -0
  104. package/dist/two-factor/dto/enable-two-factor.dto.js +7 -0
  105. package/dist/two-factor/dto/enable-two-factor.dto.js.map +1 -0
  106. package/dist/two-factor/dto/index.d.ts +2 -0
  107. package/dist/two-factor/dto/index.js +19 -0
  108. package/dist/two-factor/dto/index.js.map +1 -0
  109. package/dist/two-factor/dto/two-factor-enabled-response.dto.d.ts +3 -0
  110. package/dist/two-factor/dto/two-factor-enabled-response.dto.js +7 -0
  111. package/dist/two-factor/dto/two-factor-enabled-response.dto.js.map +1 -0
  112. package/dist/two-factor/dto/two-factor-setup-response.dto.d.ts +4 -0
  113. package/dist/two-factor/dto/two-factor-setup-response.dto.js +7 -0
  114. package/dist/two-factor/dto/two-factor-setup-response.dto.js.map +1 -0
  115. package/dist/two-factor/dto/verify-two-factor-login.dto.d.ts +3 -0
  116. package/dist/two-factor/dto/verify-two-factor-login.dto.js +22 -0
  117. package/dist/two-factor/dto/verify-two-factor-login.dto.js.map +1 -0
  118. package/dist/two-factor/dto/verify-two-factor-setup.dto.d.ts +3 -0
  119. package/dist/two-factor/dto/verify-two-factor-setup.dto.js +22 -0
  120. package/dist/two-factor/dto/verify-two-factor-setup.dto.js.map +1 -0
  121. package/dist/two-factor/two-factor.module.d.ts +2 -0
  122. package/dist/two-factor/two-factor.module.js +23 -0
  123. package/dist/two-factor/two-factor.module.js.map +1 -0
  124. package/dist/two-factor/two-factor.service.d.ts +19 -0
  125. package/dist/two-factor/two-factor.service.js +215 -0
  126. package/dist/two-factor/two-factor.service.js.map +1 -0
  127. package/dist/users/users.service.d.ts +1 -1
  128. package/dist/users/users.service.js.map +1 -1
  129. package/package.json +26 -4
  130. package/prisma/migrations/20260218105110_add_enhanced_auth_features/migration.sql +192 -0
  131. package/prisma/migrations/migration_lock.toml +3 -0
  132. package/prisma/schema.prisma +106 -13
@@ -0,0 +1,192 @@
1
+ -- CreateTable
2
+ CREATE TABLE "users" (
3
+ "id" TEXT NOT NULL,
4
+ "email" TEXT NOT NULL,
5
+ "fullName" TEXT,
6
+ "passwordHash" TEXT,
7
+ "emailVerified" BOOLEAN NOT NULL DEFAULT false,
8
+ "emailVerifyToken" TEXT,
9
+ "resetToken" TEXT,
10
+ "resetTokenExpiry" TIMESTAMP(3),
11
+ "tenantId" TEXT NOT NULL DEFAULT 'default',
12
+ "roles" TEXT[] DEFAULT ARRAY['user']::TEXT[],
13
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
14
+ "deactivationReason" TEXT,
15
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ "updatedAt" TIMESTAMP(3) NOT NULL,
17
+
18
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
19
+ );
20
+
21
+ -- CreateTable
22
+ CREATE TABLE "refresh_tokens" (
23
+ "id" TEXT NOT NULL,
24
+ "hashedToken" TEXT NOT NULL,
25
+ "userId" TEXT NOT NULL,
26
+ "deviceId" TEXT,
27
+ "expiresAt" TIMESTAMP(3) NOT NULL,
28
+ "parentTokenId" TEXT,
29
+ "revoked" BOOLEAN NOT NULL DEFAULT false,
30
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
31
+
32
+ CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id")
33
+ );
34
+
35
+ -- CreateTable
36
+ CREATE TABLE "social_accounts" (
37
+ "id" TEXT NOT NULL,
38
+ "userId" TEXT NOT NULL,
39
+ "provider" TEXT NOT NULL,
40
+ "providerId" TEXT NOT NULL,
41
+ "email" TEXT,
42
+ "displayName" TEXT,
43
+ "profileData" JSONB,
44
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
45
+ "updatedAt" TIMESTAMP(3) NOT NULL,
46
+
47
+ CONSTRAINT "social_accounts_pkey" PRIMARY KEY ("id")
48
+ );
49
+
50
+ -- CreateTable
51
+ CREATE TABLE "devices" (
52
+ "id" TEXT NOT NULL,
53
+ "userId" TEXT NOT NULL,
54
+ "deviceName" TEXT,
55
+ "deviceType" TEXT,
56
+ "os" TEXT,
57
+ "browser" TEXT,
58
+ "fingerprint" TEXT NOT NULL,
59
+ "lastActiveAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
60
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
61
+
62
+ CONSTRAINT "devices_pkey" PRIMARY KEY ("id")
63
+ );
64
+
65
+ -- CreateTable
66
+ CREATE TABLE "push_tokens" (
67
+ "id" TEXT NOT NULL,
68
+ "userId" TEXT NOT NULL,
69
+ "deviceId" TEXT NOT NULL,
70
+ "token" TEXT NOT NULL,
71
+ "tokenType" TEXT NOT NULL,
72
+ "active" BOOLEAN NOT NULL DEFAULT true,
73
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
74
+ "updatedAt" TIMESTAMP(3) NOT NULL,
75
+
76
+ CONSTRAINT "push_tokens_pkey" PRIMARY KEY ("id")
77
+ );
78
+
79
+ -- CreateTable
80
+ CREATE TABLE "two_factor_auth" (
81
+ "id" TEXT NOT NULL,
82
+ "userId" TEXT NOT NULL,
83
+ "totpSecret" TEXT NOT NULL,
84
+ "enabled" BOOLEAN NOT NULL DEFAULT false,
85
+ "enabledAt" TIMESTAMP(3),
86
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
87
+ "updatedAt" TIMESTAMP(3) NOT NULL,
88
+
89
+ CONSTRAINT "two_factor_auth_pkey" PRIMARY KEY ("id")
90
+ );
91
+
92
+ -- CreateTable
93
+ CREATE TABLE "backup_codes" (
94
+ "id" TEXT NOT NULL,
95
+ "userId" TEXT NOT NULL,
96
+ "codeHash" TEXT NOT NULL,
97
+ "used" BOOLEAN NOT NULL DEFAULT false,
98
+ "usedAt" TIMESTAMP(3),
99
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
100
+
101
+ CONSTRAINT "backup_codes_pkey" PRIMARY KEY ("id")
102
+ );
103
+
104
+ -- CreateIndex
105
+ CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
106
+
107
+ -- CreateIndex
108
+ CREATE UNIQUE INDEX "users_emailVerifyToken_key" ON "users"("emailVerifyToken");
109
+
110
+ -- CreateIndex
111
+ CREATE UNIQUE INDEX "users_resetToken_key" ON "users"("resetToken");
112
+
113
+ -- CreateIndex
114
+ CREATE INDEX "users_email_idx" ON "users"("email");
115
+
116
+ -- CreateIndex
117
+ CREATE INDEX "users_tenantId_idx" ON "users"("tenantId");
118
+
119
+ -- CreateIndex
120
+ CREATE INDEX "users_isActive_idx" ON "users"("isActive");
121
+
122
+ -- CreateIndex
123
+ CREATE UNIQUE INDEX "refresh_tokens_hashedToken_key" ON "refresh_tokens"("hashedToken");
124
+
125
+ -- CreateIndex
126
+ CREATE INDEX "refresh_tokens_userId_idx" ON "refresh_tokens"("userId");
127
+
128
+ -- CreateIndex
129
+ CREATE INDEX "refresh_tokens_deviceId_idx" ON "refresh_tokens"("deviceId");
130
+
131
+ -- CreateIndex
132
+ CREATE INDEX "refresh_tokens_hashedToken_idx" ON "refresh_tokens"("hashedToken");
133
+
134
+ -- CreateIndex
135
+ CREATE INDEX "social_accounts_userId_idx" ON "social_accounts"("userId");
136
+
137
+ -- CreateIndex
138
+ CREATE INDEX "social_accounts_provider_idx" ON "social_accounts"("provider");
139
+
140
+ -- CreateIndex
141
+ CREATE UNIQUE INDEX "social_accounts_provider_providerId_key" ON "social_accounts"("provider", "providerId");
142
+
143
+ -- CreateIndex
144
+ CREATE INDEX "devices_userId_idx" ON "devices"("userId");
145
+
146
+ -- CreateIndex
147
+ CREATE INDEX "devices_fingerprint_idx" ON "devices"("fingerprint");
148
+
149
+ -- CreateIndex
150
+ CREATE UNIQUE INDEX "push_tokens_token_key" ON "push_tokens"("token");
151
+
152
+ -- CreateIndex
153
+ CREATE INDEX "push_tokens_userId_idx" ON "push_tokens"("userId");
154
+
155
+ -- CreateIndex
156
+ CREATE INDEX "push_tokens_deviceId_idx" ON "push_tokens"("deviceId");
157
+
158
+ -- CreateIndex
159
+ CREATE INDEX "push_tokens_active_idx" ON "push_tokens"("active");
160
+
161
+ -- CreateIndex
162
+ CREATE UNIQUE INDEX "two_factor_auth_userId_key" ON "two_factor_auth"("userId");
163
+
164
+ -- CreateIndex
165
+ CREATE INDEX "backup_codes_userId_idx" ON "backup_codes"("userId");
166
+
167
+ -- CreateIndex
168
+ CREATE INDEX "backup_codes_used_idx" ON "backup_codes"("used");
169
+
170
+ -- AddForeignKey
171
+ ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
172
+
173
+ -- AddForeignKey
174
+ ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_deviceId_fkey" FOREIGN KEY ("deviceId") REFERENCES "devices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
175
+
176
+ -- AddForeignKey
177
+ ALTER TABLE "social_accounts" ADD CONSTRAINT "social_accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
178
+
179
+ -- AddForeignKey
180
+ ALTER TABLE "devices" ADD CONSTRAINT "devices_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
181
+
182
+ -- AddForeignKey
183
+ ALTER TABLE "push_tokens" ADD CONSTRAINT "push_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
184
+
185
+ -- AddForeignKey
186
+ ALTER TABLE "push_tokens" ADD CONSTRAINT "push_tokens_deviceId_fkey" FOREIGN KEY ("deviceId") REFERENCES "devices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
187
+
188
+ -- AddForeignKey
189
+ ALTER TABLE "two_factor_auth" ADD CONSTRAINT "two_factor_auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
190
+
191
+ -- AddForeignKey
192
+ ALTER TABLE "backup_codes" ADD CONSTRAINT "backup_codes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (i.e. Git)
3
+ provider = "postgresql"
@@ -11,22 +11,30 @@ datasource db {
11
11
  }
12
12
 
13
13
  model User {
14
- id String @id @default(cuid())
15
- email String @unique
16
- fullName String?
17
- passwordHash String?
18
- emailVerified Boolean @default(false)
19
- emailVerifyToken String? @unique
20
- resetToken String? @unique
21
- resetTokenExpiry DateTime?
22
- tenantId String @default("default")
23
- roles String[] @default(["user"])
24
- refreshTokens RefreshToken[]
25
- createdAt DateTime @default(now())
26
- updatedAt DateTime @updatedAt
14
+ id String @id @default(cuid())
15
+ email String @unique
16
+ fullName String?
17
+ passwordHash String?
18
+ emailVerified Boolean @default(false)
19
+ emailVerifyToken String? @unique
20
+ resetToken String? @unique
21
+ resetTokenExpiry DateTime?
22
+ tenantId String @default("default")
23
+ roles String[] @default(["user"])
24
+ isActive Boolean @default(true)
25
+ deactivationReason String?
26
+ refreshTokens RefreshToken[]
27
+ socialAccounts SocialAccount[]
28
+ devices Device[]
29
+ pushTokens PushToken[]
30
+ twoFactorAuth TwoFactorAuth?
31
+ backupCodes BackupCode[]
32
+ createdAt DateTime @default(now())
33
+ updatedAt DateTime @updatedAt
27
34
 
28
35
  @@index([email])
29
36
  @@index([tenantId])
37
+ @@index([isActive])
30
38
  @@map("users")
31
39
  }
32
40
 
@@ -34,13 +42,98 @@ model RefreshToken {
34
42
  id String @id @default(cuid())
35
43
  hashedToken String @unique
36
44
  userId String
45
+ deviceId String?
37
46
  expiresAt DateTime
38
47
  parentTokenId String?
39
48
  revoked Boolean @default(false)
40
49
  createdAt DateTime @default(now())
41
50
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51
+ device Device? @relation(fields: [deviceId], references: [id], onDelete: SetNull)
42
52
 
43
53
  @@index([userId])
54
+ @@index([deviceId])
44
55
  @@index([hashedToken])
45
56
  @@map("refresh_tokens")
46
57
  }
58
+
59
+ model SocialAccount {
60
+ id String @id @default(cuid())
61
+ userId String
62
+ provider String // 'google' | 'facebook'
63
+ providerId String // Provider's user ID
64
+ email String?
65
+ displayName String?
66
+ profileData Json? // Additional profile info
67
+ createdAt DateTime @default(now())
68
+ updatedAt DateTime @updatedAt
69
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
70
+
71
+ @@unique([provider, providerId])
72
+ @@index([userId])
73
+ @@index([provider])
74
+ @@map("social_accounts")
75
+ }
76
+
77
+ model Device {
78
+ id String @id @default(cuid())
79
+ userId String
80
+ deviceName String? // e.g., "iPhone 13"
81
+ deviceType String? // 'mobile' | 'tablet' | 'desktop'
82
+ os String? // e.g., "iOS 16.0"
83
+ browser String? // e.g., "Safari 16.0"
84
+ fingerprint String // Hash of user agent
85
+ lastActiveAt DateTime @default(now())
86
+ createdAt DateTime @default(now())
87
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
88
+ refreshTokens RefreshToken[]
89
+ pushTokens PushToken[]
90
+
91
+ @@index([userId])
92
+ @@index([fingerprint])
93
+ @@map("devices")
94
+ }
95
+
96
+ model PushToken {
97
+ id String @id @default(cuid())
98
+ userId String
99
+ deviceId String
100
+ token String @unique
101
+ tokenType String // 'FCM' | 'APNS'
102
+ active Boolean @default(true)
103
+ createdAt DateTime @default(now())
104
+ updatedAt DateTime @updatedAt
105
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
106
+ device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade)
107
+
108
+ @@index([userId])
109
+ @@index([deviceId])
110
+ @@index([active])
111
+ @@map("push_tokens")
112
+ }
113
+
114
+ model TwoFactorAuth {
115
+ id String @id @default(cuid())
116
+ userId String @unique
117
+ totpSecret String // Encrypted TOTP secret
118
+ enabled Boolean @default(false)
119
+ enabledAt DateTime?
120
+ createdAt DateTime @default(now())
121
+ updatedAt DateTime @updatedAt
122
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
123
+
124
+ @@map("two_factor_auth")
125
+ }
126
+
127
+ model BackupCode {
128
+ id String @id @default(cuid())
129
+ userId String
130
+ codeHash String // Bcrypt hash of the code
131
+ used Boolean @default(false)
132
+ usedAt DateTime?
133
+ createdAt DateTime @default(now())
134
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
135
+
136
+ @@index([userId])
137
+ @@index([used])
138
+ @@map("backup_codes")
139
+ }