@vynelix/nestjs-multi-auth 0.3.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +309 -18
- package/dist/auth/auth.controller.d.ts +3 -2
- package/dist/auth/auth.controller.js +32 -5
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.module.js +116 -68
- package/dist/auth/auth.module.js.map +1 -1
- package/dist/auth/auth.service.d.ts +52 -25
- package/dist/auth/auth.service.js +238 -327
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/core/auth-mapper.d.ts +1 -1
- package/dist/auth/core/database-otp.provider.d.ts +12 -0
- package/dist/auth/core/database-otp.provider.js +122 -0
- package/dist/auth/core/database-otp.provider.js.map +1 -0
- package/dist/auth/dto/requests/magic-link.dto.d.ts +1 -0
- package/dist/auth/dto/requests/magic-link.dto.js +6 -0
- package/dist/auth/dto/requests/magic-link.dto.js.map +1 -1
- package/dist/auth/dto/requests/mfa.dto.d.ts +5 -1
- package/dist/auth/dto/requests/mfa.dto.js +29 -8
- package/dist/auth/dto/requests/mfa.dto.js.map +1 -1
- package/dist/auth/enums/identifier-type.enum.d.ts +11 -0
- package/dist/auth/enums/identifier-type.enum.js +17 -0
- package/dist/auth/enums/identifier-type.enum.js.map +1 -0
- package/dist/auth/enums/mfa-type.enum.d.ts +5 -0
- package/dist/auth/enums/mfa-type.enum.js +10 -0
- package/dist/auth/enums/mfa-type.enum.js.map +1 -0
- package/dist/auth/enums/otp-purpose.enum.d.ts +8 -0
- package/dist/auth/enums/otp-purpose.enum.js +13 -0
- package/dist/auth/enums/otp-purpose.enum.js.map +1 -0
- package/dist/auth/enums/session-event.enum.d.ts +7 -0
- package/dist/auth/enums/session-event.enum.js +12 -0
- package/dist/auth/enums/session-event.enum.js.map +1 -0
- package/dist/auth/interfaces/auth-module-options.interface.d.ts +36 -1
- package/dist/auth/interfaces/auth-notification-provider.interface.d.ts +7 -1
- package/dist/auth/interfaces/auth-otp-provider.interface.d.ts +60 -0
- package/dist/auth/interfaces/auth-otp-provider.interface.js +7 -0
- package/dist/auth/interfaces/auth-otp-provider.interface.js.map +1 -0
- package/dist/auth/interfaces/models.interface.d.ts +91 -0
- package/dist/auth/interfaces/models.interface.js +3 -0
- package/dist/auth/interfaces/models.interface.js.map +1 -0
- package/dist/auth/interfaces/oauth-strategy.interface.d.ts +2 -2
- package/dist/auth/interfaces/repositories.interface.d.ts +70 -0
- package/dist/auth/interfaces/repositories.interface.js +3 -0
- package/dist/auth/interfaces/repositories.interface.js.map +1 -0
- package/dist/auth/interfaces/repository-tokens.d.ts +7 -0
- package/dist/auth/interfaces/repository-tokens.js +11 -0
- package/dist/auth/interfaces/repository-tokens.js.map +1 -0
- package/dist/auth/interfaces/signup-event-body.interface.d.ts +1 -1
- package/dist/auth/strategies/local-auth.strategy.d.ts +3 -5
- package/dist/auth/strategies/local-auth.strategy.js +64 -99
- package/dist/auth/strategies/local-auth.strategy.js.map +1 -1
- package/dist/auth/strategies/oauth/apple.strategy.d.ts +4 -6
- package/dist/auth/strategies/oauth/apple.strategy.js +57 -70
- package/dist/auth/strategies/oauth/apple.strategy.js.map +1 -1
- package/dist/auth/strategies/oauth/facebook.strategy.d.ts +4 -6
- package/dist/auth/strategies/oauth/facebook.strategy.js +58 -71
- package/dist/auth/strategies/oauth/facebook.strategy.js.map +1 -1
- package/dist/auth/strategies/oauth/google.strategy.d.ts +4 -6
- package/dist/auth/strategies/oauth/google.strategy.js +84 -155
- package/dist/auth/strategies/oauth/google.strategy.js.map +1 -1
- package/dist/auth/strategies/oauth/oauth.strategy.d.ts +2 -2
- package/dist/database/prisma/prisma-auth.adapter.d.ts +11 -0
- package/dist/database/prisma/prisma-auth.adapter.js +64 -0
- package/dist/database/prisma/prisma-auth.adapter.js.map +1 -0
- package/dist/database/prisma/repositories/auth-identifier.repository.d.ts +16 -0
- package/dist/database/prisma/repositories/auth-identifier.repository.js +41 -0
- package/dist/database/prisma/repositories/auth-identifier.repository.js.map +1 -0
- package/dist/database/prisma/repositories/auth.repository.d.ts +19 -0
- package/dist/database/prisma/repositories/auth.repository.js +49 -0
- package/dist/database/prisma/repositories/auth.repository.js.map +1 -0
- package/dist/database/prisma/repositories/index.d.ts +7 -0
- package/dist/database/prisma/repositories/index.js +24 -0
- package/dist/database/prisma/repositories/index.js.map +1 -0
- package/dist/database/prisma/repositories/mfa-method.repository.d.ts +11 -0
- package/dist/database/prisma/repositories/mfa-method.repository.js +33 -0
- package/dist/database/prisma/repositories/mfa-method.repository.js.map +1 -0
- package/dist/database/prisma/repositories/oauth-provider.repository.d.ts +14 -0
- package/dist/database/prisma/repositories/oauth-provider.repository.js +39 -0
- package/dist/database/prisma/repositories/oauth-provider.repository.js.map +1 -0
- package/dist/database/prisma/repositories/otp-token.repository.d.ts +13 -0
- package/dist/database/prisma/repositories/otp-token.repository.js +52 -0
- package/dist/database/prisma/repositories/otp-token.repository.js.map +1 -0
- package/dist/database/prisma/repositories/session-log.repository.d.ts +12 -0
- package/dist/database/prisma/repositories/session-log.repository.js +42 -0
- package/dist/database/prisma/repositories/session-log.repository.js.map +1 -0
- package/dist/database/prisma/repositories/session.repository.d.ts +16 -0
- package/dist/database/prisma/repositories/session.repository.js +41 -0
- package/dist/database/prisma/repositories/session.repository.js.map +1 -0
- package/dist/database/typeorm/entities/auth-identify.entity.d.ts +23 -0
- package/dist/database/typeorm/entities/auth-identify.entity.js +61 -0
- package/dist/database/typeorm/entities/auth-identify.entity.js.map +1 -0
- package/dist/database/typeorm/entities/auth.entity.d.ts +45 -0
- package/dist/database/typeorm/entities/auth.entity.js +129 -0
- package/dist/database/typeorm/entities/auth.entity.js.map +1 -0
- package/dist/database/typeorm/entities/base.entity.d.ts +7 -0
- package/dist/database/typeorm/entities/base.entity.js +44 -0
- package/dist/database/typeorm/entities/base.entity.js.map +1 -0
- package/dist/database/typeorm/entities/entities.d.ts +7 -0
- package/dist/database/typeorm/entities/entities.js +18 -0
- package/dist/database/typeorm/entities/entities.js.map +1 -0
- package/dist/database/typeorm/entities/mfa-method.entity.d.ts +10 -0
- package/dist/database/typeorm/entities/mfa-method.entity.js +47 -0
- package/dist/database/typeorm/entities/mfa-method.entity.js.map +1 -0
- package/dist/database/typeorm/entities/oauth-provider.entity.d.ts +38 -0
- package/dist/database/typeorm/entities/oauth-provider.entity.js +88 -0
- package/dist/database/typeorm/entities/oauth-provider.entity.js.map +1 -0
- package/dist/database/typeorm/entities/otp-token.entity.d.ts +15 -0
- package/dist/database/typeorm/entities/otp-token.entity.js +53 -0
- package/dist/database/typeorm/entities/otp-token.entity.js.map +1 -0
- package/dist/database/typeorm/entities/session.entity.d.ts +21 -0
- package/dist/database/typeorm/entities/session.entity.js +71 -0
- package/dist/database/typeorm/entities/session.entity.js.map +1 -0
- package/dist/database/typeorm/entities/session_log.entity.d.ts +25 -0
- package/dist/database/typeorm/entities/session_log.entity.js +78 -0
- package/dist/database/typeorm/entities/session_log.entity.js.map +1 -0
- package/dist/database/typeorm/migrations/auth-entities.d.ts +7 -0
- package/dist/database/typeorm/migrations/auth-entities.js +18 -0
- package/dist/database/typeorm/migrations/auth-entities.js.map +1 -0
- package/dist/database/typeorm/migrations/auth-schema.initializer.d.ts +9 -0
- package/dist/database/typeorm/migrations/auth-schema.initializer.js +36 -0
- package/dist/database/typeorm/migrations/auth-schema.initializer.js.map +1 -0
- package/dist/database/typeorm/migrations/auth.migration.v1.d.ts +12 -0
- package/dist/database/typeorm/migrations/auth.migration.v1.js +157 -0
- package/dist/database/typeorm/migrations/auth.migration.v1.js.map +1 -0
- package/dist/database/typeorm/migrations/auth.migration.v2.d.ts +8 -0
- package/dist/database/typeorm/migrations/auth.migration.v2.js +23 -0
- package/dist/database/typeorm/migrations/auth.migration.v2.js.map +1 -0
- package/dist/database/typeorm/migrations/auth.migrations.d.ts +2 -0
- package/dist/database/typeorm/migrations/auth.migrations.js +10 -0
- package/dist/database/typeorm/migrations/auth.migrations.js.map +1 -0
- package/dist/database/typeorm/migrations/migration.service.d.ts +9 -0
- package/dist/database/typeorm/migrations/migration.service.js +75 -0
- package/dist/database/typeorm/migrations/migration.service.js.map +1 -0
- package/dist/database/typeorm/repositories/auth-identifier.repository.d.ts +18 -0
- package/dist/database/typeorm/repositories/auth-identifier.repository.js +64 -0
- package/dist/database/typeorm/repositories/auth-identifier.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/auth.repository.d.ts +21 -0
- package/dist/database/typeorm/repositories/auth.repository.js +71 -0
- package/dist/database/typeorm/repositories/auth.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/index.d.ts +7 -0
- package/dist/database/typeorm/repositories/index.js +24 -0
- package/dist/database/typeorm/repositories/index.js.map +1 -0
- package/dist/database/typeorm/repositories/mfa-method.repository.d.ts +13 -0
- package/dist/database/typeorm/repositories/mfa-method.repository.js +46 -0
- package/dist/database/typeorm/repositories/mfa-method.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/oauth-provider.repository.d.ts +16 -0
- package/dist/database/typeorm/repositories/oauth-provider.repository.js +52 -0
- package/dist/database/typeorm/repositories/oauth-provider.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/otp-token.repository.d.ts +13 -0
- package/dist/database/typeorm/repositories/otp-token.repository.js +52 -0
- package/dist/database/typeorm/repositories/otp-token.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/session-log.repository.d.ts +12 -0
- package/dist/database/typeorm/repositories/session-log.repository.js +47 -0
- package/dist/database/typeorm/repositories/session-log.repository.js.map +1 -0
- package/dist/database/typeorm/repositories/session.repository.d.ts +18 -0
- package/dist/database/typeorm/repositories/session.repository.js +84 -0
- package/dist/database/typeorm/repositories/session.repository.js.map +1 -0
- package/dist/database/typeorm/typeorm-auth.adapter.d.ts +2 -0
- package/dist/database/typeorm/typeorm-auth.adapter.js +50 -0
- package/dist/database/typeorm/typeorm-auth.adapter.js.map +1 -0
- package/dist/index.d.ts +11 -9
- package/dist/index.js +18 -10
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/fix-imports.js +46 -0
- package/fix-syntax.js +66 -0
- package/package.json +22 -20
- package/patch-local-specs.js +52 -0
- package/patch-oauth-specs.js +41 -0
- package/patch-specs.js +36 -0
- package/prompts/database_adapter.md +157 -0
- package/prompts/otp_provider.md +292 -0
- package/scratch/create-prisma-repos-final.js +139 -0
- package/scratch/create-prisma-repos.js +280 -0
- package/scratch/fix-events-spec.js +12 -0
- package/scratch/fix-imports.js +53 -0
- package/scratch/fix-prisma-interfaces.js +41 -0
- package/scratch/fix-prisma-interfaces2.js +36 -0
- package/scratch/fix-prisma-interfaces3.js +26 -0
- package/scratch/fix-prisma.js +28 -0
- package/scratch/fix-test-imports.js +51 -0
- package/scratch/fix-tests-advanced.js +68 -0
- package/scratch/fix-tests-advanced2.js +23 -0
- package/scratch/fix-tests-concrete.js +40 -0
- package/scratch/fix-tests-final.js +35 -0
- package/scratch/fix-tests-final2.js +28 -0
- package/scratch/fix-tests-final3.js +26 -0
- package/scratch/fix-tests-methods.js +40 -0
- package/scratch/fix-tests-proxy.js +67 -0
- package/scratch/fix-tests-types.js +24 -0
- package/scratch/rewrite-tests.js +110 -0
- package/tsc_errors.txt +60 -0
- package/tsc_errors2.txt +93 -0
- package/tmp-cv-test/package-lock.json +0 -250
- package/tmp-cv-test/package.json +0 -17
- package/tmp-cv-test/test.ts +0 -11
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Refactor OTP Architecture — nestjs-multi-auth
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
You are working on **nestjs-multi-auth**, a production-grade authentication library for NestJS.
|
|
6
|
+
|
|
7
|
+
## Objective
|
|
8
|
+
|
|
9
|
+
Refactor the OTP architecture to separate OTP lifecycle management from notification delivery, while maintaining full backward compatibility.
|
|
10
|
+
|
|
11
|
+
## Problem
|
|
12
|
+
|
|
13
|
+
The current implementation assumes the library always:
|
|
14
|
+
|
|
15
|
+
1. Generates the OTP
|
|
16
|
+
2. Stores it in the database
|
|
17
|
+
3. Passes the generated code to `AuthNotificationProvider.sendVerificationCode()`
|
|
18
|
+
|
|
19
|
+
This assumption prevents integration with providers like **Twilio Verify**, **Firebase Phone Authentication**, **Vonage Verify**, and **MessageBird Verify**, because these services generate, store, expire, and validate OTPs themselves — the raw code is never exposed to the library.
|
|
20
|
+
|
|
21
|
+
The authentication library must support both:
|
|
22
|
+
|
|
23
|
+
- **Internal database-managed OTPs** (current behavior)
|
|
24
|
+
- **External OTP providers** (Twilio Verify, Firebase Phone, etc.)
|
|
25
|
+
|
|
26
|
+
...without changing the public authentication API.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Design Principles
|
|
31
|
+
|
|
32
|
+
### The authentication library IS responsible for
|
|
33
|
+
|
|
34
|
+
- Authentication
|
|
35
|
+
- Authorization
|
|
36
|
+
- Identity verification workflow
|
|
37
|
+
- Session management
|
|
38
|
+
- Password reset
|
|
39
|
+
- Email verification
|
|
40
|
+
- Phone verification
|
|
41
|
+
- MFA orchestration
|
|
42
|
+
|
|
43
|
+
### The authentication library is NOT responsible for
|
|
44
|
+
|
|
45
|
+
- SMS delivery
|
|
46
|
+
- Email delivery
|
|
47
|
+
- Push notifications
|
|
48
|
+
- WhatsApp
|
|
49
|
+
- OTP transport
|
|
50
|
+
|
|
51
|
+
Notification delivery remains the responsibility of the consuming application.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## New Architecture
|
|
56
|
+
|
|
57
|
+
Split the current OTP implementation into two abstractions.
|
|
58
|
+
|
|
59
|
+
### 1. `AuthOtpProvider` (NEW)
|
|
60
|
+
|
|
61
|
+
Responsible for the **lifecycle** of an OTP: generate, store, validate, resend, expire.
|
|
62
|
+
|
|
63
|
+
**Example implementations:**
|
|
64
|
+
|
|
65
|
+
- `DatabaseOtpProvider`
|
|
66
|
+
- `TwilioVerifyOtpProvider`
|
|
67
|
+
- `FirebasePhoneOtpProvider`
|
|
68
|
+
- `CustomOtpProvider`
|
|
69
|
+
|
|
70
|
+
**Interface:**
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
export interface AuthOtpProvider {
|
|
74
|
+
issue(request: IssueOtpRequest): Promise<IssueOtpResult>;
|
|
75
|
+
verify(request: VerifyOtpRequest): Promise<VerifyOtpResult>;
|
|
76
|
+
resend?(request: ResendOtpRequest): Promise<ResendOtpResult>;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**`IssueOtpRequest`** — should contain enough information for any provider:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
{
|
|
84
|
+
uid,
|
|
85
|
+
authId,
|
|
86
|
+
identifier,
|
|
87
|
+
identifierType,
|
|
88
|
+
purpose,
|
|
89
|
+
expiresIn
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`IssueOtpResult`** — must support both internal and external providers.
|
|
94
|
+
|
|
95
|
+
For database providers:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{
|
|
99
|
+
handledDelivery: false,
|
|
100
|
+
code: "483921",
|
|
101
|
+
expiresAt: Date
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For Twilio Verify:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
{
|
|
109
|
+
handledDelivery: true,
|
|
110
|
+
verificationId: "...",
|
|
111
|
+
expiresAt: Date | undefined
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> **Note:** External providers will not return the OTP.
|
|
116
|
+
|
|
117
|
+
**`VerifyOtpResult`** — should be provider-independent:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
{
|
|
121
|
+
success: true,
|
|
122
|
+
metadata?: {}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. `AuthNotificationProvider` (existing — to be improved)
|
|
127
|
+
|
|
128
|
+
Keep the notification provider, but enrich its signature.
|
|
129
|
+
|
|
130
|
+
**Current:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
sendVerificationCode(to, code, type)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Replace with:**
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
sendVerificationCode({
|
|
140
|
+
to,
|
|
141
|
+
code,
|
|
142
|
+
type,
|
|
143
|
+
purpose,
|
|
144
|
+
expiresAt
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This lets applications customize messages based on purpose (email verification, phone verification, password reset, login verification, etc.)
|
|
149
|
+
|
|
150
|
+
> The notification provider should **never** know where the OTP came from.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Default OTP Provider
|
|
155
|
+
|
|
156
|
+
Create a built-in provider named `DatabaseOtpProvider`.
|
|
157
|
+
|
|
158
|
+
Move all current logic from:
|
|
159
|
+
|
|
160
|
+
- `sendVerification()`
|
|
161
|
+
- `forgotPassword()`
|
|
162
|
+
- `resetPassword()`
|
|
163
|
+
- `verifyCode()`
|
|
164
|
+
- `resendVerification()`
|
|
165
|
+
|
|
166
|
+
...into this provider. It must preserve current behavior exactly:
|
|
167
|
+
|
|
168
|
+
- Generate OTP
|
|
169
|
+
- Hash OTP
|
|
170
|
+
- Store hash
|
|
171
|
+
- Handle expiration
|
|
172
|
+
- Resend logic
|
|
173
|
+
- Verification logic
|
|
174
|
+
|
|
175
|
+
**Nothing should change functionally.**
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Twilio Verify Support
|
|
180
|
+
|
|
181
|
+
Design the architecture so that implementing `TwilioVerifyOtpProvider` requires **no changes to `AuthService`**.
|
|
182
|
+
|
|
183
|
+
**`issue()` flow:**
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
issue()
|
|
187
|
+
→ Call Twilio Verify
|
|
188
|
+
→ Twilio generates OTP
|
|
189
|
+
→ Twilio stores OTP
|
|
190
|
+
→ Twilio sends SMS
|
|
191
|
+
→ Return { handledDelivery: true, verificationId }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**`verify()` flow:**
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
verify()
|
|
198
|
+
→ Call Twilio Verify Check
|
|
199
|
+
→ Return success
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> Twilio Verify never exposes the OTP — the library must support this.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## `AuthService` Changes
|
|
207
|
+
|
|
208
|
+
Replace all direct OTP generation with `otpProvider.issue(...)`.
|
|
209
|
+
|
|
210
|
+
**Before:**
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
generate OTP → hash → save → notificationProvider.send(...)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**After:**
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const result = await otpProvider.issue(...);
|
|
220
|
+
|
|
221
|
+
if (!result.handledDelivery) {
|
|
222
|
+
await notificationProvider.sendVerificationCode(...);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The authentication flow should **not** know whether the OTP came from the database, Twilio, Firebase, or a custom provider.
|
|
227
|
+
|
|
228
|
+
Replace every direct OTP verification with:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
await otpProvider.verify(...);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`AuthService` should **never** call `bcrypt.compare()` directly anymore — that logic belongs inside `DatabaseOtpProvider`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Existing Features (must keep working, no breaking changes)
|
|
239
|
+
|
|
240
|
+
- Signup verification
|
|
241
|
+
- Login verification
|
|
242
|
+
- Resend verification
|
|
243
|
+
- Forgot password
|
|
244
|
+
- Password reset
|
|
245
|
+
- Secure account
|
|
246
|
+
- Magic link
|
|
247
|
+
- MFA
|
|
248
|
+
- Passwordless authentication
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## OTP Purposes
|
|
253
|
+
|
|
254
|
+
Continue using the existing enum:
|
|
255
|
+
|
|
256
|
+
- `VERIFY_EMAIL`
|
|
257
|
+
- `VERIFY_PHONE`
|
|
258
|
+
- `PASSWORD_RESET`
|
|
259
|
+
- `MAGIC_LINK`
|
|
260
|
+
- `SECURE_ACCOUNT`
|
|
261
|
+
|
|
262
|
+
Providers should receive the purpose.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Dependency Injection
|
|
267
|
+
|
|
268
|
+
Introduce `AUTH_OTP_PROVIDER`, similar to `AUTH_NOTIFICATION_PROVIDER`.
|
|
269
|
+
|
|
270
|
+
If no provider is registered, automatically register `DatabaseOtpProvider` so existing users do not need to change anything.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Backward Compatibility
|
|
275
|
+
|
|
276
|
+
This refactor must **not** break existing applications.
|
|
277
|
+
|
|
278
|
+
- Applications that already implement `AuthNotificationProvider` should continue working exactly as before.
|
|
279
|
+
- Only developers who wish to use Twilio Verify or another external OTP provider need to implement `AuthOtpProvider`.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Code Quality Requirements
|
|
284
|
+
|
|
285
|
+
- Follow SOLID principles.
|
|
286
|
+
- Separate orchestration from provider implementations.
|
|
287
|
+
- Avoid duplicating OTP logic between `AuthService` and providers.
|
|
288
|
+
- Keep `AuthService` focused on authentication workflows, not OTP implementation details.
|
|
289
|
+
- Use dependency injection throughout.
|
|
290
|
+
- Ensure each provider is independently unit testable.
|
|
291
|
+
- Preserve all current tests and behavior where possible.
|
|
292
|
+
- Keep public authentication endpoints unchanged — only the internal OTP architecture should be refactored.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const repoDir = path.join(__dirname, '../src/database/prisma/repositories');
|
|
5
|
+
|
|
6
|
+
const files = {
|
|
7
|
+
'auth.repository.ts': `
|
|
8
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
9
|
+
import { AuthRepository } from '../../../auth/interfaces/repositories.interface';
|
|
10
|
+
import { Auth as CoreAuth } from '../../../auth/interfaces/models.interface';
|
|
11
|
+
import { AuthStrategy } from '../../../auth/enums/auth-type.enum';
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class PrismaAuthRepository implements AuthRepository {
|
|
15
|
+
constructor(@Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: any) {}
|
|
16
|
+
|
|
17
|
+
async create(data: Partial<CoreAuth>): Promise<CoreAuth> {
|
|
18
|
+
const { identifiers, ...rest } = data;
|
|
19
|
+
return this.prisma.auth.create({
|
|
20
|
+
data: {
|
|
21
|
+
...rest,
|
|
22
|
+
identifiers: identifiers ? { create: identifiers } : undefined,
|
|
23
|
+
} as any,
|
|
24
|
+
include: { identifiers: true }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findById(id: string): Promise<CoreAuth | null> { return this.prisma.auth.findUnique({ where: { id }, include: { identifiers: true } }); }
|
|
29
|
+
async findByUid(uid: string): Promise<CoreAuth | null> { return this.prisma.auth.findUnique({ where: { uid }, include: { identifiers: true } }); }
|
|
30
|
+
async findAllByUid(uid: string): Promise<CoreAuth[]> { return this.prisma.auth.findMany({ where: { uid } }); }
|
|
31
|
+
async findAll(): Promise<CoreAuth[]> { return this.prisma.auth.findMany(); }
|
|
32
|
+
async findByUidAndStrategy(uid: string, strategy: AuthStrategy): Promise<CoreAuth | null> { return this.prisma.auth.findFirst({ where: { uid, strategy } }); }
|
|
33
|
+
async findByUidAndStrategies(uid: string, strategies: AuthStrategy[]): Promise<CoreAuth | null> { return this.prisma.auth.findFirst({ where: { uid, strategy: { in: strategies } } }); }
|
|
34
|
+
async save(auth: CoreAuth): Promise<CoreAuth> { return this.prisma.auth.update({ where: { id: auth.id }, data: auth as any }); }
|
|
35
|
+
async update(id: string, data: Partial<CoreAuth>): Promise<void> { await this.prisma.auth.update({ where: { id }, data: data as any }); }
|
|
36
|
+
async delete(id: string): Promise<void> { await this.prisma.auth.delete({ where: { id } }); }
|
|
37
|
+
async deleteByUid(uid: string): Promise<void> { await this.prisma.auth.deleteMany({ where: { uid } }); }
|
|
38
|
+
async findWithIdentifiers(id: string): Promise<CoreAuth | null> { return this.prisma.auth.findUnique({ where: { id }, include: { identifiers: true } }); }
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
|
|
42
|
+
'auth-identifier.repository.ts': `
|
|
43
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
44
|
+
import { AuthIdentifierRepository } from '../../../auth/interfaces/repositories.interface';
|
|
45
|
+
import { AuthIdentifier as CoreAuthIdentifier, Auth as CoreAuth } from '../../../auth/interfaces/models.interface';
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class PrismaAuthIdentifierRepository implements AuthIdentifierRepository {
|
|
49
|
+
constructor(@Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: any) {}
|
|
50
|
+
|
|
51
|
+
async create(data: Partial<CoreAuthIdentifier>): Promise<CoreAuthIdentifier> { return this.prisma.authIdentifier.create({ data: data as any }); }
|
|
52
|
+
async findByValue(value: string): Promise<CoreAuthIdentifier | null> { return this.prisma.authIdentifier.findUnique({ where: { value } }); }
|
|
53
|
+
async findByAuthId(authId: string): Promise<CoreAuthIdentifier[]> { return this.prisma.authIdentifier.findMany({ where: { authId } }); }
|
|
54
|
+
async findByUidAndTypes(uid: string, types: string[]): Promise<CoreAuthIdentifier | null> { return this.prisma.authIdentifier.findFirst({ where: { auth: { uid }, type: { in: types } }}); }
|
|
55
|
+
|
|
56
|
+
async findWithAuthByValue(value: string): Promise<{ identifier: CoreAuthIdentifier; auth: CoreAuth } | null> {
|
|
57
|
+
const res = await this.prisma.authIdentifier.findUnique({ where: { value }, include: { auth: true } });
|
|
58
|
+
if (!res) return null;
|
|
59
|
+
const { auth, ...identifier } = res;
|
|
60
|
+
return { identifier: identifier as any, auth: auth as any };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async save(identifier: CoreAuthIdentifier): Promise<CoreAuthIdentifier> { return this.prisma.authIdentifier.update({ where: { id: identifier.id }, data: identifier as any }); }
|
|
64
|
+
async markVerifiedByAuthId(authId: string): Promise<void> { await this.prisma.authIdentifier.updateMany({ where: { authId }, data: { isVerified: true } }); }
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
|
|
68
|
+
'mfa-method.repository.ts': `
|
|
69
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
70
|
+
import { MfaMethodRepository } from '../../../auth/interfaces/repositories.interface';
|
|
71
|
+
import { MfaMethod as CoreMfaMethod } from '../../../auth/interfaces/models.interface';
|
|
72
|
+
|
|
73
|
+
@Injectable()
|
|
74
|
+
export class PrismaMfaMethodRepository implements MfaMethodRepository {
|
|
75
|
+
constructor(@Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: any) {}
|
|
76
|
+
|
|
77
|
+
async create(data: Partial<CoreMfaMethod>): Promise<CoreMfaMethod> { return this.prisma.mfaMethod.create({ data: data as any }); }
|
|
78
|
+
async findByUidAndType(uid: string, type: string): Promise<CoreMfaMethod | null> { return this.prisma.mfaMethod.findFirst({ where: { auth: { uid }, type } }); }
|
|
79
|
+
async findByUidAndEnabled(uid: string): Promise<CoreMfaMethod | null> { return this.prisma.mfaMethod.findFirst({ where: { auth: { uid }, isEnabled: true } }); }
|
|
80
|
+
async save(method: CoreMfaMethod): Promise<CoreMfaMethod> { return this.prisma.mfaMethod.update({ where: { id: method.id }, data: method as any }); }
|
|
81
|
+
async deleteByUid(uid: string): Promise<void> { await this.prisma.mfaMethod.deleteMany({ where: { auth: { uid } } }); }
|
|
82
|
+
}
|
|
83
|
+
`,
|
|
84
|
+
|
|
85
|
+
'oauth-provider.repository.ts': `
|
|
86
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
87
|
+
import { OAuthProviderRepository } from '../../../auth/interfaces/repositories.interface';
|
|
88
|
+
import { OAuthProvider as CoreOAuthProvider, Auth as CoreAuth } from '../../../auth/interfaces/models.interface';
|
|
89
|
+
|
|
90
|
+
@Injectable()
|
|
91
|
+
export class PrismaOAuthProviderRepository implements OAuthProviderRepository {
|
|
92
|
+
constructor(@Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: any) {}
|
|
93
|
+
|
|
94
|
+
async create(data: Partial<CoreOAuthProvider>): Promise<CoreOAuthProvider> { return this.prisma.oAuthProvider.create({ data: data as any }); }
|
|
95
|
+
async findByProviderUserId(provider: string, providerUserId: string): Promise<CoreOAuthProvider | null> { return this.prisma.oAuthProvider.findUnique({ where: { providerUserId } }); }
|
|
96
|
+
|
|
97
|
+
async findWithAuthByProviderUserId(provider: string, providerUserId: string): Promise<{ provider: CoreOAuthProvider; auth: CoreAuth } | null> {
|
|
98
|
+
const res = await this.prisma.oAuthProvider.findUnique({ where: { providerUserId }, include: { auth: true } });
|
|
99
|
+
if (!res) return null;
|
|
100
|
+
const { auth, ...prov } = res;
|
|
101
|
+
return { provider: prov as any, auth: auth as any };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async save(provider: CoreOAuthProvider): Promise<CoreOAuthProvider> { return this.prisma.oAuthProvider.update({ where: { id: provider.id }, data: provider as any }); }
|
|
105
|
+
async update(id: string, data: Partial<CoreOAuthProvider>): Promise<void> { await this.prisma.oAuthProvider.update({ where: { id }, data: data as any }); }
|
|
106
|
+
}
|
|
107
|
+
`,
|
|
108
|
+
|
|
109
|
+
'session.repository.ts': `
|
|
110
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
111
|
+
import { SessionRepository } from '../../../auth/interfaces/repositories.interface';
|
|
112
|
+
import { Session as CoreSession } from '../../../auth/interfaces/models.interface';
|
|
113
|
+
|
|
114
|
+
@Injectable()
|
|
115
|
+
export class PrismaSessionRepository implements SessionRepository {
|
|
116
|
+
constructor(@Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: any) {}
|
|
117
|
+
|
|
118
|
+
async create(data: Partial<CoreSession>): Promise<CoreSession> { return this.prisma.session.create({ data: data as any }); }
|
|
119
|
+
async findById(id: string): Promise<CoreSession | null> { return this.prisma.session.findUnique({ where: { id } }); }
|
|
120
|
+
async findDeviceSession(uid: string, namespace: string | undefined, deviceFingerprint: string): Promise<CoreSession | null> {
|
|
121
|
+
// simplified lookup since Prisma cannot easily query inside Json without raw queries across different DBs.
|
|
122
|
+
return this.prisma.session.findFirst({ where: { uid } });
|
|
123
|
+
}
|
|
124
|
+
async findByUid(uid: string): Promise<CoreSession[]> { return this.prisma.session.findMany({ where: { uid } }); }
|
|
125
|
+
async findByIdWithDetails(id: string, namespace?: string): Promise<CoreSession | null> { return this.prisma.session.findUnique({ where: { id } }); }
|
|
126
|
+
async save(session: CoreSession): Promise<CoreSession> { return this.prisma.session.update({ where: { id: session.id }, data: session as any }); }
|
|
127
|
+
async update(id: string, data: Partial<CoreSession>): Promise<void> { await this.prisma.session.update({ where: { id }, data: data as any }); }
|
|
128
|
+
async delete(id: string): Promise<void> { await this.prisma.session.delete({ where: { id } }); }
|
|
129
|
+
async deleteByUid(uid: string): Promise<void> { await this.prisma.session.deleteMany({ where: { uid } }); }
|
|
130
|
+
async transaction(runInTransaction: (repo: SessionRepository) => Promise<void>): Promise<void> { await runInTransaction(this); }
|
|
131
|
+
}
|
|
132
|
+
`
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
136
|
+
fs.writeFileSync(path.join(repoDir, filename), content.trim(), 'utf8');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('Fixed Prisma repositories completely');
|