@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.
Files changed (196) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +309 -18
  3. package/dist/auth/auth.controller.d.ts +3 -2
  4. package/dist/auth/auth.controller.js +32 -5
  5. package/dist/auth/auth.controller.js.map +1 -1
  6. package/dist/auth/auth.module.js +116 -68
  7. package/dist/auth/auth.module.js.map +1 -1
  8. package/dist/auth/auth.service.d.ts +52 -25
  9. package/dist/auth/auth.service.js +238 -327
  10. package/dist/auth/auth.service.js.map +1 -1
  11. package/dist/auth/core/auth-mapper.d.ts +1 -1
  12. package/dist/auth/core/database-otp.provider.d.ts +12 -0
  13. package/dist/auth/core/database-otp.provider.js +122 -0
  14. package/dist/auth/core/database-otp.provider.js.map +1 -0
  15. package/dist/auth/dto/requests/magic-link.dto.d.ts +1 -0
  16. package/dist/auth/dto/requests/magic-link.dto.js +6 -0
  17. package/dist/auth/dto/requests/magic-link.dto.js.map +1 -1
  18. package/dist/auth/dto/requests/mfa.dto.d.ts +5 -1
  19. package/dist/auth/dto/requests/mfa.dto.js +29 -8
  20. package/dist/auth/dto/requests/mfa.dto.js.map +1 -1
  21. package/dist/auth/enums/identifier-type.enum.d.ts +11 -0
  22. package/dist/auth/enums/identifier-type.enum.js +17 -0
  23. package/dist/auth/enums/identifier-type.enum.js.map +1 -0
  24. package/dist/auth/enums/mfa-type.enum.d.ts +5 -0
  25. package/dist/auth/enums/mfa-type.enum.js +10 -0
  26. package/dist/auth/enums/mfa-type.enum.js.map +1 -0
  27. package/dist/auth/enums/otp-purpose.enum.d.ts +8 -0
  28. package/dist/auth/enums/otp-purpose.enum.js +13 -0
  29. package/dist/auth/enums/otp-purpose.enum.js.map +1 -0
  30. package/dist/auth/enums/session-event.enum.d.ts +7 -0
  31. package/dist/auth/enums/session-event.enum.js +12 -0
  32. package/dist/auth/enums/session-event.enum.js.map +1 -0
  33. package/dist/auth/interfaces/auth-module-options.interface.d.ts +36 -1
  34. package/dist/auth/interfaces/auth-notification-provider.interface.d.ts +7 -1
  35. package/dist/auth/interfaces/auth-otp-provider.interface.d.ts +60 -0
  36. package/dist/auth/interfaces/auth-otp-provider.interface.js +7 -0
  37. package/dist/auth/interfaces/auth-otp-provider.interface.js.map +1 -0
  38. package/dist/auth/interfaces/models.interface.d.ts +91 -0
  39. package/dist/auth/interfaces/models.interface.js +3 -0
  40. package/dist/auth/interfaces/models.interface.js.map +1 -0
  41. package/dist/auth/interfaces/oauth-strategy.interface.d.ts +2 -2
  42. package/dist/auth/interfaces/repositories.interface.d.ts +70 -0
  43. package/dist/auth/interfaces/repositories.interface.js +3 -0
  44. package/dist/auth/interfaces/repositories.interface.js.map +1 -0
  45. package/dist/auth/interfaces/repository-tokens.d.ts +7 -0
  46. package/dist/auth/interfaces/repository-tokens.js +11 -0
  47. package/dist/auth/interfaces/repository-tokens.js.map +1 -0
  48. package/dist/auth/interfaces/signup-event-body.interface.d.ts +1 -1
  49. package/dist/auth/strategies/local-auth.strategy.d.ts +3 -5
  50. package/dist/auth/strategies/local-auth.strategy.js +64 -99
  51. package/dist/auth/strategies/local-auth.strategy.js.map +1 -1
  52. package/dist/auth/strategies/oauth/apple.strategy.d.ts +4 -6
  53. package/dist/auth/strategies/oauth/apple.strategy.js +57 -70
  54. package/dist/auth/strategies/oauth/apple.strategy.js.map +1 -1
  55. package/dist/auth/strategies/oauth/facebook.strategy.d.ts +4 -6
  56. package/dist/auth/strategies/oauth/facebook.strategy.js +58 -71
  57. package/dist/auth/strategies/oauth/facebook.strategy.js.map +1 -1
  58. package/dist/auth/strategies/oauth/google.strategy.d.ts +4 -6
  59. package/dist/auth/strategies/oauth/google.strategy.js +84 -155
  60. package/dist/auth/strategies/oauth/google.strategy.js.map +1 -1
  61. package/dist/auth/strategies/oauth/oauth.strategy.d.ts +2 -2
  62. package/dist/database/prisma/prisma-auth.adapter.d.ts +11 -0
  63. package/dist/database/prisma/prisma-auth.adapter.js +64 -0
  64. package/dist/database/prisma/prisma-auth.adapter.js.map +1 -0
  65. package/dist/database/prisma/repositories/auth-identifier.repository.d.ts +16 -0
  66. package/dist/database/prisma/repositories/auth-identifier.repository.js +41 -0
  67. package/dist/database/prisma/repositories/auth-identifier.repository.js.map +1 -0
  68. package/dist/database/prisma/repositories/auth.repository.d.ts +19 -0
  69. package/dist/database/prisma/repositories/auth.repository.js +49 -0
  70. package/dist/database/prisma/repositories/auth.repository.js.map +1 -0
  71. package/dist/database/prisma/repositories/index.d.ts +7 -0
  72. package/dist/database/prisma/repositories/index.js +24 -0
  73. package/dist/database/prisma/repositories/index.js.map +1 -0
  74. package/dist/database/prisma/repositories/mfa-method.repository.d.ts +11 -0
  75. package/dist/database/prisma/repositories/mfa-method.repository.js +33 -0
  76. package/dist/database/prisma/repositories/mfa-method.repository.js.map +1 -0
  77. package/dist/database/prisma/repositories/oauth-provider.repository.d.ts +14 -0
  78. package/dist/database/prisma/repositories/oauth-provider.repository.js +39 -0
  79. package/dist/database/prisma/repositories/oauth-provider.repository.js.map +1 -0
  80. package/dist/database/prisma/repositories/otp-token.repository.d.ts +13 -0
  81. package/dist/database/prisma/repositories/otp-token.repository.js +52 -0
  82. package/dist/database/prisma/repositories/otp-token.repository.js.map +1 -0
  83. package/dist/database/prisma/repositories/session-log.repository.d.ts +12 -0
  84. package/dist/database/prisma/repositories/session-log.repository.js +42 -0
  85. package/dist/database/prisma/repositories/session-log.repository.js.map +1 -0
  86. package/dist/database/prisma/repositories/session.repository.d.ts +16 -0
  87. package/dist/database/prisma/repositories/session.repository.js +41 -0
  88. package/dist/database/prisma/repositories/session.repository.js.map +1 -0
  89. package/dist/database/typeorm/entities/auth-identify.entity.d.ts +23 -0
  90. package/dist/database/typeorm/entities/auth-identify.entity.js +61 -0
  91. package/dist/database/typeorm/entities/auth-identify.entity.js.map +1 -0
  92. package/dist/database/typeorm/entities/auth.entity.d.ts +45 -0
  93. package/dist/database/typeorm/entities/auth.entity.js +129 -0
  94. package/dist/database/typeorm/entities/auth.entity.js.map +1 -0
  95. package/dist/database/typeorm/entities/base.entity.d.ts +7 -0
  96. package/dist/database/typeorm/entities/base.entity.js +44 -0
  97. package/dist/database/typeorm/entities/base.entity.js.map +1 -0
  98. package/dist/database/typeorm/entities/entities.d.ts +7 -0
  99. package/dist/database/typeorm/entities/entities.js +18 -0
  100. package/dist/database/typeorm/entities/entities.js.map +1 -0
  101. package/dist/database/typeorm/entities/mfa-method.entity.d.ts +10 -0
  102. package/dist/database/typeorm/entities/mfa-method.entity.js +47 -0
  103. package/dist/database/typeorm/entities/mfa-method.entity.js.map +1 -0
  104. package/dist/database/typeorm/entities/oauth-provider.entity.d.ts +38 -0
  105. package/dist/database/typeorm/entities/oauth-provider.entity.js +88 -0
  106. package/dist/database/typeorm/entities/oauth-provider.entity.js.map +1 -0
  107. package/dist/database/typeorm/entities/otp-token.entity.d.ts +15 -0
  108. package/dist/database/typeorm/entities/otp-token.entity.js +53 -0
  109. package/dist/database/typeorm/entities/otp-token.entity.js.map +1 -0
  110. package/dist/database/typeorm/entities/session.entity.d.ts +21 -0
  111. package/dist/database/typeorm/entities/session.entity.js +71 -0
  112. package/dist/database/typeorm/entities/session.entity.js.map +1 -0
  113. package/dist/database/typeorm/entities/session_log.entity.d.ts +25 -0
  114. package/dist/database/typeorm/entities/session_log.entity.js +78 -0
  115. package/dist/database/typeorm/entities/session_log.entity.js.map +1 -0
  116. package/dist/database/typeorm/migrations/auth-entities.d.ts +7 -0
  117. package/dist/database/typeorm/migrations/auth-entities.js +18 -0
  118. package/dist/database/typeorm/migrations/auth-entities.js.map +1 -0
  119. package/dist/database/typeorm/migrations/auth-schema.initializer.d.ts +9 -0
  120. package/dist/database/typeorm/migrations/auth-schema.initializer.js +36 -0
  121. package/dist/database/typeorm/migrations/auth-schema.initializer.js.map +1 -0
  122. package/dist/database/typeorm/migrations/auth.migration.v1.d.ts +12 -0
  123. package/dist/database/typeorm/migrations/auth.migration.v1.js +157 -0
  124. package/dist/database/typeorm/migrations/auth.migration.v1.js.map +1 -0
  125. package/dist/database/typeorm/migrations/auth.migration.v2.d.ts +8 -0
  126. package/dist/database/typeorm/migrations/auth.migration.v2.js +23 -0
  127. package/dist/database/typeorm/migrations/auth.migration.v2.js.map +1 -0
  128. package/dist/database/typeorm/migrations/auth.migrations.d.ts +2 -0
  129. package/dist/database/typeorm/migrations/auth.migrations.js +10 -0
  130. package/dist/database/typeorm/migrations/auth.migrations.js.map +1 -0
  131. package/dist/database/typeorm/migrations/migration.service.d.ts +9 -0
  132. package/dist/database/typeorm/migrations/migration.service.js +75 -0
  133. package/dist/database/typeorm/migrations/migration.service.js.map +1 -0
  134. package/dist/database/typeorm/repositories/auth-identifier.repository.d.ts +18 -0
  135. package/dist/database/typeorm/repositories/auth-identifier.repository.js +64 -0
  136. package/dist/database/typeorm/repositories/auth-identifier.repository.js.map +1 -0
  137. package/dist/database/typeorm/repositories/auth.repository.d.ts +21 -0
  138. package/dist/database/typeorm/repositories/auth.repository.js +71 -0
  139. package/dist/database/typeorm/repositories/auth.repository.js.map +1 -0
  140. package/dist/database/typeorm/repositories/index.d.ts +7 -0
  141. package/dist/database/typeorm/repositories/index.js +24 -0
  142. package/dist/database/typeorm/repositories/index.js.map +1 -0
  143. package/dist/database/typeorm/repositories/mfa-method.repository.d.ts +13 -0
  144. package/dist/database/typeorm/repositories/mfa-method.repository.js +46 -0
  145. package/dist/database/typeorm/repositories/mfa-method.repository.js.map +1 -0
  146. package/dist/database/typeorm/repositories/oauth-provider.repository.d.ts +16 -0
  147. package/dist/database/typeorm/repositories/oauth-provider.repository.js +52 -0
  148. package/dist/database/typeorm/repositories/oauth-provider.repository.js.map +1 -0
  149. package/dist/database/typeorm/repositories/otp-token.repository.d.ts +13 -0
  150. package/dist/database/typeorm/repositories/otp-token.repository.js +52 -0
  151. package/dist/database/typeorm/repositories/otp-token.repository.js.map +1 -0
  152. package/dist/database/typeorm/repositories/session-log.repository.d.ts +12 -0
  153. package/dist/database/typeorm/repositories/session-log.repository.js +47 -0
  154. package/dist/database/typeorm/repositories/session-log.repository.js.map +1 -0
  155. package/dist/database/typeorm/repositories/session.repository.d.ts +18 -0
  156. package/dist/database/typeorm/repositories/session.repository.js +84 -0
  157. package/dist/database/typeorm/repositories/session.repository.js.map +1 -0
  158. package/dist/database/typeorm/typeorm-auth.adapter.d.ts +2 -0
  159. package/dist/database/typeorm/typeorm-auth.adapter.js +50 -0
  160. package/dist/database/typeorm/typeorm-auth.adapter.js.map +1 -0
  161. package/dist/index.d.ts +11 -9
  162. package/dist/index.js +18 -10
  163. package/dist/index.js.map +1 -1
  164. package/dist/tsconfig.tsbuildinfo +1 -1
  165. package/fix-imports.js +46 -0
  166. package/fix-syntax.js +66 -0
  167. package/package.json +22 -20
  168. package/patch-local-specs.js +52 -0
  169. package/patch-oauth-specs.js +41 -0
  170. package/patch-specs.js +36 -0
  171. package/prompts/database_adapter.md +157 -0
  172. package/prompts/otp_provider.md +292 -0
  173. package/scratch/create-prisma-repos-final.js +139 -0
  174. package/scratch/create-prisma-repos.js +280 -0
  175. package/scratch/fix-events-spec.js +12 -0
  176. package/scratch/fix-imports.js +53 -0
  177. package/scratch/fix-prisma-interfaces.js +41 -0
  178. package/scratch/fix-prisma-interfaces2.js +36 -0
  179. package/scratch/fix-prisma-interfaces3.js +26 -0
  180. package/scratch/fix-prisma.js +28 -0
  181. package/scratch/fix-test-imports.js +51 -0
  182. package/scratch/fix-tests-advanced.js +68 -0
  183. package/scratch/fix-tests-advanced2.js +23 -0
  184. package/scratch/fix-tests-concrete.js +40 -0
  185. package/scratch/fix-tests-final.js +35 -0
  186. package/scratch/fix-tests-final2.js +28 -0
  187. package/scratch/fix-tests-final3.js +26 -0
  188. package/scratch/fix-tests-methods.js +40 -0
  189. package/scratch/fix-tests-proxy.js +67 -0
  190. package/scratch/fix-tests-types.js +24 -0
  191. package/scratch/rewrite-tests.js +110 -0
  192. package/tsc_errors.txt +60 -0
  193. package/tsc_errors2.txt +93 -0
  194. package/tmp-cv-test/package-lock.json +0 -250
  195. package/tmp-cv-test/package.json +0 -17
  196. package/tmp-cv-test/test.ts +0 -11
@@ -17,16 +17,14 @@ exports.AuthService = void 0;
17
17
  const common_1 = require("@nestjs/common");
18
18
  const event_emitter_1 = require("@nestjs/event-emitter");
19
19
  const jwt_1 = require("@nestjs/jwt");
20
- const typeorm_1 = require("@nestjs/typeorm");
21
- const typeorm_2 = require("typeorm");
20
+ const repository_tokens_1 = require("./interfaces/repository-tokens");
21
+ const auth_otp_provider_interface_1 = require("./interfaces/auth-otp-provider.interface");
22
22
  const bcrypt = require("bcrypt");
23
23
  const local_auth_strategy_1 = require("./strategies/local-auth.strategy");
24
24
  const oauth_strategy_1 = require("./strategies/oauth/oauth.strategy");
25
25
  const auth_type_enum_1 = require("./enums/auth-type.enum");
26
- const auth_entity_1 = require("./entities/auth.entity");
27
- const session_entity_1 = require("./entities/session.entity");
28
- const otp_token_entity_1 = require("./entities/otp-token.entity");
29
- const mfa_method_entity_1 = require("./entities/mfa-method.entity");
26
+ const otp_purpose_enum_1 = require("./enums/otp-purpose.enum");
27
+ const mfa_type_enum_1 = require("./enums/mfa-type.enum");
30
28
  const otplib_1 = require("otplib");
31
29
  const auth_module_options_interface_1 = require("./interfaces/auth-module-options.interface");
32
30
  const auth_notification_provider_interface_1 = require("./interfaces/auth-notification-provider.interface");
@@ -34,16 +32,19 @@ const crypto = require("crypto");
34
32
  const duration_util_1 = require("./utils/duration.util");
35
33
  const auth_mapper_1 = require("./core/auth-mapper");
36
34
  const auth_events_1 = require("./enums/auth.events");
37
- const session_log_entity_1 = require("./entities/session_log.entity");
35
+ const session_event_enum_1 = require("./enums/session-event.enum");
38
36
  let AuthService = AuthService_1 = class AuthService {
39
- constructor(jwtService, passwordStrategy, oauthStrategy, sessionRepository, sessionLogRepo, authRepo, otpRepo, mfaRepo, options, notificationProvider, eventEmitter) {
37
+ constructor(jwtService, passwordStrategy, oauthStrategy, sessionRepository, sessionLogRepo, authRepo, authIdentifierRepo, otpProvider, otpProviderEmail, otpProviderPhone, mfaRepo, options, notificationProvider, eventEmitter) {
40
38
  this.jwtService = jwtService;
41
39
  this.passwordStrategy = passwordStrategy;
42
40
  this.oauthStrategy = oauthStrategy;
43
41
  this.sessionRepository = sessionRepository;
44
42
  this.sessionLogRepo = sessionLogRepo;
45
43
  this.authRepo = authRepo;
46
- this.otpRepo = otpRepo;
44
+ this.authIdentifierRepo = authIdentifierRepo;
45
+ this.otpProvider = otpProvider;
46
+ this.otpProviderEmail = otpProviderEmail;
47
+ this.otpProviderPhone = otpProviderPhone;
47
48
  this.mfaRepo = mfaRepo;
48
49
  this.options = options;
49
50
  this.notificationProvider = notificationProvider;
@@ -51,6 +52,14 @@ let AuthService = AuthService_1 = class AuthService {
51
52
  this.logger = new common_1.Logger(AuthService_1.name);
52
53
  this.createSessionLog = this.options.createSessionLogOnInvalid ?? false;
53
54
  }
55
+ /** Returns the correct OTP provider for the given identifier type. */
56
+ resolveOtpProvider(identifierType) {
57
+ if (identifierType === 'email')
58
+ return this.otpProviderEmail;
59
+ if (identifierType === 'phone')
60
+ return this.otpProviderPhone;
61
+ return this.otpProvider;
62
+ }
54
63
  // --- INTERNAL HELPER: Generate Token Pair ---
55
64
  async generateTokens(uid, sessionId, namespace) {
56
65
  const refreshJti = crypto.randomUUID();
@@ -76,7 +85,7 @@ let AuthService = AuthService_1 = class AuthService {
76
85
  expiresAt.setSeconds(expiresAt.getSeconds() + durationSeconds);
77
86
  const deviceFingerprint = this.fingerprint(userAgent);
78
87
  let session;
79
- const _createSession = () => {
88
+ const _createSession = async () => {
80
89
  return this.sessionRepository.create({
81
90
  id: crypto.randomUUID(),
82
91
  uid,
@@ -89,13 +98,7 @@ let AuthService = AuthService_1 = class AuthService {
89
98
  });
90
99
  };
91
100
  if (this.options.sessionCreationPolicy === auth_module_options_interface_1.SessionCreationPolicy.REUSE_DEVICE) {
92
- session = await this.sessionRepository.findOne({
93
- where: {
94
- uid,
95
- namespace,
96
- deviceFingerprint,
97
- },
98
- });
101
+ session = await this.sessionRepository.findDeviceSession(uid, namespace, deviceFingerprint);
99
102
  if (session) {
100
103
  session.expiresAt = expiresAt;
101
104
  session.ipAddress = ip;
@@ -103,11 +106,11 @@ let AuthService = AuthService_1 = class AuthService {
103
106
  session.deviceFingerprint = deviceFingerprint;
104
107
  }
105
108
  else {
106
- session = _createSession();
109
+ session = await _createSession();
107
110
  }
108
111
  }
109
112
  else {
110
- session = _createSession();
113
+ session = await _createSession();
111
114
  }
112
115
  const tokens = await this.generateTokens(uid, session.id, namespace);
113
116
  session.refreshTokenHash = await bcrypt.hash(tokens.refreshToken, 10);
@@ -117,7 +120,7 @@ let AuthService = AuthService_1 = class AuthService {
117
120
  async createSessionLogFromSessionIfEnabled({ event, reason, session, }) {
118
121
  if (!this.createSessionLog)
119
122
  return;
120
- const sessionLog = this.sessionLogRepo.create({
123
+ const sessionLog = await this.sessionLogRepo.create({
121
124
  ...session,
122
125
  sessionId: session.id,
123
126
  event,
@@ -130,7 +133,10 @@ let AuthService = AuthService_1 = class AuthService {
130
133
  throw new Error("Either session or sessionId must be provided.");
131
134
  }
132
135
  if (!session) {
133
- session = await this.sessionRepository.findOneByOrFail({ id: sessionId });
136
+ const foundSession = await this.sessionRepository.findById(sessionId);
137
+ if (!foundSession)
138
+ throw new Error("Session not found");
139
+ session = foundSession;
134
140
  }
135
141
  await this.createSessionLogFromSessionIfEnabled({
136
142
  session,
@@ -140,26 +146,24 @@ let AuthService = AuthService_1 = class AuthService {
140
146
  await this.sessionRepository.delete(session.id);
141
147
  }
142
148
  async invalidateSessions({ uid, event, reason, }) {
143
- await this.sessionRepository.manager.transaction(async (manager) => {
144
- const sessions = await manager.find(session_entity_1.Session, {
145
- where: { uid },
146
- });
147
- if (sessions.length === 0) {
148
- return;
149
- }
150
- if (this.createSessionLog) {
151
- const logs = sessions.map((session) => this.sessionLogRepo.create({
152
- ...session,
153
- sessionId: session.id,
154
- event,
155
- reason,
156
- }));
157
- await manager.save(logs);
149
+ const sessions = await this.sessionRepository.findByUid(uid);
150
+ if (sessions.length === 0) {
151
+ return;
152
+ }
153
+ if (this.createSessionLog) {
154
+ const logs = sessions.map((session) => ({
155
+ ...session,
156
+ id: undefined,
157
+ sessionId: session.id,
158
+ event,
159
+ reason,
160
+ timestamp: new Date()
161
+ }));
162
+ for (const log of logs) {
163
+ await this.sessionLogRepo.save(log);
158
164
  }
159
- await manager.delete(session_entity_1.Session, {
160
- uid,
161
- });
162
- });
165
+ }
166
+ await this.sessionRepository.deleteByUid(uid);
163
167
  }
164
168
  async signup({ dto, uid, userAgent, ip, namespace }) {
165
169
  if (!dto.method)
@@ -197,7 +201,7 @@ let AuthService = AuthService_1 = class AuthService {
197
201
  // Force verification if no password was provided for local strategies (passwordless signup)
198
202
  const isPasswordless = [auth_type_enum_1.AuthStrategy.EMAIL, auth_type_enum_1.AuthStrategy.PHONE, auth_type_enum_1.AuthStrategy.USERNAME, auth_type_enum_1.AuthStrategy.LOCAL].includes(dto.method) && !dto.password;
199
203
  // Check if user has 2FA enabled
200
- const mfaMethod = await this.mfaRepo.findOne({ where: { uid: auth.uid, isEnabled: true } });
204
+ const mfaMethod = await this.mfaRepo.findByUidAndEnabled(auth.uid);
201
205
  const has2FA = !!mfaMethod;
202
206
  const triggerVerification = isPasswordless ||
203
207
  (this.options.verificationRequired && !identifier?.isVerified) ||
@@ -266,7 +270,7 @@ let AuthService = AuthService_1 = class AuthService {
266
270
  // Force verification if no password was provided for local strategies (passwordless login)
267
271
  const isPasswordless = [auth_type_enum_1.AuthStrategy.EMAIL, auth_type_enum_1.AuthStrategy.PHONE, auth_type_enum_1.AuthStrategy.USERNAME, auth_type_enum_1.AuthStrategy.LOCAL].includes(dto.method) && !dto.password;
268
272
  // Check if user has 2FA enabled
269
- const mfaMethod = await this.mfaRepo.findOne({ where: { uid: auth.uid, isEnabled: true } });
273
+ const mfaMethod = await this.mfaRepo.findByUidAndEnabled(auth.uid);
270
274
  const has2FA = !!mfaMethod;
271
275
  // Trigger email/phone verification only if required and identifier not verified.
272
276
  // MFA (2FA) is handled separately via dedicated endpoints.
@@ -307,26 +311,14 @@ let AuthService = AuthService_1 = class AuthService {
307
311
  }
308
312
  // --- VERIFICATION LOGIC ---
309
313
  async sendVerification(auth, currentIdentifier) {
310
- if (!this.notificationProvider)
314
+ if (!this.notificationProvider && !this.otpProvider && !this.otpProviderEmail && !this.otpProviderPhone)
311
315
  return;
312
- // 1. Determine primary identifier (email or phone) to send the code to
313
- // If we have a verified email/phone on the same UID, use that.
314
- // Otherwise use the current identifier if it's verifiable.
315
316
  let primaryIdentifier = currentIdentifier?.type !== 'USERNAME' ? currentIdentifier : null;
316
317
  if (!primaryIdentifier) {
317
- // Look for any EMAIL or PHONE linked to this UID
318
- const allIdentifiers = await this.authRepo.query(`SELECT ai.* FROM auth_identifiers ai
319
- JOIN auth a ON ai."authId" = a.id
320
- WHERE a.uid = $1 AND ai.type IN ('EMAIL', 'PHONE')
321
- ORDER BY ai."isVerified" DESC, ai."createdAt" ASC LIMIT 1`, [auth.uid]);
322
- primaryIdentifier = allIdentifiers[0];
318
+ primaryIdentifier = await this.authIdentifierRepo.findByUidAndTypes(auth.uid, ['EMAIL', 'PHONE']);
323
319
  }
324
320
  if (!primaryIdentifier) {
325
- // As a last resort, reload auth with identifiers and find first EMAIL/PHONE
326
- const fullAuth = await this.authRepo.findOne({
327
- where: { id: auth.id },
328
- relations: ['identifiers']
329
- });
321
+ const fullAuth = await this.authRepo.findWithIdentifiers(auth.id);
330
322
  primaryIdentifier = fullAuth?.identifiers?.find(id => id.type === 'EMAIL' || id.type === 'PHONE');
331
323
  }
332
324
  if (!primaryIdentifier) {
@@ -335,64 +327,45 @@ let AuthService = AuthService_1 = class AuthService {
335
327
  }
336
328
  return;
337
329
  }
338
- // 2. Generate 6-digit code
339
- const code = (this.options.debugMode && this.options.defaultOtp)
340
- ? this.options.defaultOtp
341
- : Math.floor(100000 + Math.random() * 900000).toString();
342
- const hash = await bcrypt.hash(code, 10);
343
- // 3. Save OTP Token
344
- const expiresAt = new Date();
345
- const otpExpMins = this.options.otpExpiresIn || 15;
346
- expiresAt.setMinutes(expiresAt.getMinutes() + otpExpMins);
347
- const otpToken = this.otpRepo.create({
330
+ const purpose = primaryIdentifier.type === 'EMAIL' ? otp_purpose_enum_1.OtpPurpose.VERIFY_EMAIL : otp_purpose_enum_1.OtpPurpose.VERIFY_PHONE;
331
+ const idType = primaryIdentifier.type === 'EMAIL' ? 'email' : 'phone';
332
+ const result = await this.resolveOtpProvider(idType).issue({
333
+ uid: auth.uid,
334
+ authId: auth.id,
348
335
  identifier: primaryIdentifier.value,
349
- purpose: primaryIdentifier.type === 'EMAIL' ? otp_token_entity_1.OtpPurpose.VERIFY_EMAIL : otp_token_entity_1.OtpPurpose.VERIFY_PHONE,
350
- codeHash: hash,
351
- expiresAt,
352
- requestUserId: auth.uid,
353
- requestAuthId: auth.id,
336
+ identifierType: idType,
337
+ purpose,
338
+ expiresIn: this.options.otpExpiresIn,
354
339
  });
355
- await this.otpRepo.save(otpToken);
356
- // 4. Send via Provider
357
- try {
358
- await this.notificationProvider.sendVerificationCode(primaryIdentifier.value, code, primaryIdentifier.type === 'EMAIL' ? 'email' : 'phone');
359
- }
360
- catch (e) {
361
- this.logger.error(`Failed to send verification code to ${primaryIdentifier.value}`, e);
362
- throw new common_1.BadRequestException('Failed to send verification code');
340
+ if (!result.handledDelivery && this.notificationProvider && result.code) {
341
+ try {
342
+ await this.notificationProvider.sendVerificationCode({
343
+ to: primaryIdentifier.value,
344
+ code: result.code,
345
+ type: primaryIdentifier.type === 'EMAIL' ? 'email' : 'phone',
346
+ purpose,
347
+ expiresAt: result.expiresAt
348
+ });
349
+ }
350
+ catch (e) {
351
+ this.logger.error(`Failed to send verification code to ${primaryIdentifier.value}`, e);
352
+ throw new common_1.BadRequestException('Failed to send verification code');
353
+ }
363
354
  }
364
355
  }
365
356
  async verifyCode({ uid, code, userAgent, ip, namespace }) {
366
- const auth = await this.authRepo.findOne({ where: { uid } });
357
+ const auth = await this.authRepo.findByUid(uid);
367
358
  if (!auth)
368
359
  throw new common_1.BadRequestException('Identity not found');
369
- // Find the latest unused OTP for this UID
370
- const otp = await this.otpRepo.findOne({
371
- where: { requestUserId: uid, isUsed: false },
372
- order: { createdAt: 'DESC' },
373
- });
374
- if (!otp) {
375
- if (auth.isVerified)
376
- return { message: 'Identity already verified' };
377
- throw new common_1.BadRequestException('No verification code found');
378
- }
379
- if (new Date() > otp.expiresAt) {
380
- throw new common_1.BadRequestException('Verification code expired');
381
- }
382
- const isMatch = await bcrypt.compare(code, otp.codeHash);
383
- if (!isMatch)
384
- throw new common_1.BadRequestException('Invalid verification code');
385
- // Success!
386
- otp.isUsed = true;
387
- await this.otpRepo.save(otp);
388
- auth.isVerified = true;
389
- await this.authRepo.save(auth);
390
- // Also mark all identifiers for this Auth as verified
391
- await this.authRepo.query(`UPDATE auth_identifiers SET "isVerified" = true WHERE "authId" = $1`, [auth.id]);
392
- // If there's a requestAuthId on the OTP (which there should be), update that one too if different
393
- if (otp.requestAuthId && otp.requestAuthId !== auth.id) {
394
- await this.authRepo.update(otp.requestAuthId, { isVerified: true });
395
- await this.authRepo.query(`UPDATE auth_identifiers SET "isVerified" = true WHERE "authId" = $1`, [otp.requestAuthId]);
360
+ const result = await this.resolveOtpProvider().verify({ uid, code });
361
+ if (result.success) {
362
+ auth.isVerified = true;
363
+ await this.authRepo.save(auth);
364
+ await this.authIdentifierRepo.markVerifiedByAuthId(auth.id);
365
+ if (result.authId && result.authId !== auth.id) {
366
+ await this.authRepo.update(result.authId, { isVerified: true });
367
+ await this.authIdentifierRepo.markVerifiedByAuthId(result.authId);
368
+ }
396
369
  }
397
370
  const tokens = await this.createSession(auth.uid, userAgent, ip, namespace);
398
371
  if (this.eventEmitter) {
@@ -401,72 +374,54 @@ let AuthService = AuthService_1 = class AuthService {
401
374
  return { message: 'Identity verified successfully', tokens, auth };
402
375
  }
403
376
  async resendVerification(uid) {
404
- const auth = await this.authRepo.findOne({ where: { uid } });
377
+ const auth = await this.authRepo.findByUid(uid);
405
378
  if (!auth)
406
379
  throw new common_1.BadRequestException('Identity not found');
407
- if (!this.notificationProvider) {
408
- throw new common_1.BadRequestException('Verification is not configured');
409
- }
410
- // Check resend interval
411
- const latestOtp = await this.otpRepo.findOne({
412
- where: { requestUserId: uid },
413
- order: { createdAt: 'DESC' },
414
- });
415
- if (latestOtp) {
416
- const intervalSeconds = this.options.otpResendInterval || 60;
417
- const diffMs = Date.now() - latestOtp.createdAt.getTime();
418
- if (diffMs < intervalSeconds * 1000) {
419
- const wait = Math.ceil(intervalSeconds - diffMs / 1000);
420
- throw new common_1.BadRequestException(`Please wait ${wait} seconds before requesting a new code.`);
421
- }
380
+ if (!this.resolveOtpProvider().resend) {
381
+ await this.sendVerification(auth);
382
+ return { message: 'Verification code resent' };
383
+ }
384
+ const result = await this.resolveOtpProvider().resend({ uid });
385
+ if (!result.handledDelivery && this.notificationProvider && result.code && result.metadata) {
386
+ await this.notificationProvider.sendVerificationCode({
387
+ to: result.metadata.identifier,
388
+ code: result.code,
389
+ type: result.metadata.identifierType,
390
+ purpose: result.metadata.purpose,
391
+ expiresAt: result.expiresAt
392
+ });
422
393
  }
423
- await this.sendVerification(auth);
424
394
  return { message: 'Verification code resent' };
425
395
  }
426
396
  async refreshTokens({ refreshToken, currentUserAgent, currentIp, namespace }) {
427
397
  try {
428
398
  const payload = await this.jwtService.verifyAsync(refreshToken, { secret: this.options.jwtRefreshSecret || process.env.JWT_REFRESH_SECRET });
429
399
  const resolvedNamespace = namespace ?? payload.namespace;
430
- const whereClause = { id: payload.sessionId };
431
- if (resolvedNamespace) {
432
- whereClause.namespace = resolvedNamespace;
433
- }
434
- const session = await this.sessionRepository.findOne({
435
- where: whereClause,
436
- select: [
437
- 'id',
438
- 'uid',
439
- 'refreshTokenHash',
440
- 'expiresAt',
441
- 'deviceFingerprint',
442
- 'ipAddress',
443
- 'namespace',
444
- ],
445
- });
400
+ const session = await this.sessionRepository.findByIdWithDetails(payload.sessionId, resolvedNamespace);
446
401
  if (this.options.debug) {
447
- this.logger.debug(`Namespace from JWT: ${payload.namespace}, Namespace passed to refresh method: ${namespace}, Where clause: ${JSON.stringify(whereClause)}`);
402
+ this.logger.debug(`Namespace from JWT: ${payload.namespace}, Namespace passed to refresh method: ${namespace}`);
448
403
  }
449
404
  if (!session)
450
405
  throw new common_1.ForbiddenException('Session not found');
451
406
  const incomingFingerprint = this.fingerprint(currentUserAgent);
452
407
  if (session.deviceFingerprint !== incomingFingerprint) {
453
- this.invalidateSession({ session, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'Device mismatch during refresh' });
408
+ this.invalidateSession({ session, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'Device mismatch during refresh' });
454
409
  throw new common_1.ForbiddenException('Device mismatch');
455
410
  }
456
411
  if (session.namespace !== namespace && session.namespace !== resolvedNamespace) {
457
412
  if (this.options.debug) {
458
413
  this.logger.debug(`Namespace provided: ${namespace}, Session namespace: ${session.namespace}.\nNamespace mismatch: ${session.namespace !== namespace}`);
459
414
  }
460
- this.invalidateSession({ session, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'Namespace mismatch during refresh' });
415
+ this.invalidateSession({ session, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'Namespace mismatch during refresh' });
461
416
  throw new common_1.ForbiddenException("Namespace mismatch");
462
417
  }
463
418
  if (new Date() > session.expiresAt) {
464
- this.invalidateSession({ session, event: session_log_entity_1.SessionEvent.EXPIRE, reason: 'Session expired during refresh' });
419
+ this.invalidateSession({ session, event: session_event_enum_1.SessionEvent.EXPIRE, reason: 'Session expired during refresh' });
465
420
  throw new common_1.ForbiddenException('Session expired');
466
421
  }
467
422
  const isMatch = await bcrypt.compare(refreshToken, session.refreshTokenHash);
468
423
  if (!isMatch) {
469
- this.invalidateSession({ session, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'Invalid refresh token during refresh' });
424
+ this.invalidateSession({ session, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'Invalid refresh token during refresh' });
470
425
  throw new common_1.ForbiddenException('Invalid refresh token');
471
426
  }
472
427
  const tokens = await this.generateTokens(session.uid, session.id, namespace);
@@ -497,8 +452,8 @@ let AuthService = AuthService_1 = class AuthService {
497
452
  try {
498
453
  const payload = this.jwtService.decode(refreshToken);
499
454
  if (payload?.sessionId) {
500
- const session = await this.sessionRepository.findOne({ where: { id: payload.sessionId } });
501
- this.invalidateSession({ session, event: session_log_entity_1.SessionEvent.LOGOUT, reason: 'User logout' });
455
+ const session = await this.sessionRepository.findById(payload.sessionId);
456
+ this.invalidateSession({ session, event: session_event_enum_1.SessionEvent.LOGOUT, reason: 'User logout' });
502
457
  if (session && this.eventEmitter) {
503
458
  this.eventEmitter.emit(auth_events_1.AuthEvents.LOGOUT, { uid: session.uid });
504
459
  }
@@ -515,35 +470,28 @@ let AuthService = AuthService_1 = class AuthService {
515
470
  const value = dto.email || dto.phone || dto.username;
516
471
  if (!value)
517
472
  throw new common_1.BadRequestException('Identifier is required');
518
- // 1. Find identifier
519
- const identifier = await this.authRepo.query(`SELECT ai.*, a.uid, a.id as "authId" FROM auth_identifiers ai
520
- JOIN auth a ON ai."authId" = a.id
521
- WHERE ai.value = $1 LIMIT 1`, [value.toLowerCase()]);
522
- if (!identifier[0]) {
523
- // Security: Don't reveal if user exists.
524
- // But typically for forgot-password, users expect an error if email is wrong.
525
- // We'll return success anyway to prevent enumeration.
473
+ const result = await this.authIdentifierRepo.findWithAuthByValue(value);
474
+ if (!result) {
526
475
  return { message: 'If an account exists, a reset code has been sent.' };
527
476
  }
528
- const primaryAuth = identifier[0];
529
- // 2. Generate OTP
530
- const code = (this.options.debugMode && this.options.defaultOtp)
531
- ? this.options.defaultOtp
532
- : Math.floor(100000 + Math.random() * 900000).toString();
533
- const hash = await bcrypt.hash(code, 10);
534
- const expiresAt = new Date();
535
- expiresAt.setMinutes(expiresAt.getMinutes() + (this.options.otpExpiresIn || 15));
536
- await this.otpRepo.save(this.otpRepo.create({
477
+ const { identifier: primaryAuth, auth: primaryAuthObj } = result;
478
+ const resetIdType = primaryAuth.type === 'PHONE' ? 'phone' : 'email';
479
+ const issueResult = await this.resolveOtpProvider(resetIdType).issue({
480
+ uid: primaryAuthObj.uid,
481
+ authId: primaryAuthObj.id,
537
482
  identifier: primaryAuth.value,
538
- purpose: otp_token_entity_1.OtpPurpose.PASSWORD_RESET,
539
- codeHash: hash,
540
- expiresAt,
541
- requestUserId: primaryAuth.uid,
542
- requestAuthId: primaryAuth.authId,
543
- }));
544
- // 3. Send notification
545
- if (this.notificationProvider) {
546
- await this.notificationProvider.sendVerificationCode(primaryAuth.value, code, 'email');
483
+ identifierType: resetIdType,
484
+ purpose: otp_purpose_enum_1.OtpPurpose.PASSWORD_RESET,
485
+ expiresIn: this.options.otpExpiresIn || 15
486
+ });
487
+ if (!issueResult.handledDelivery && this.notificationProvider && issueResult.code) {
488
+ await this.notificationProvider.sendVerificationCode({
489
+ to: primaryAuth.value,
490
+ code: issueResult.code,
491
+ type: primaryAuth.type === 'PHONE' ? 'phone' : 'email',
492
+ purpose: otp_purpose_enum_1.OtpPurpose.PASSWORD_RESET,
493
+ expiresAt: issueResult.expiresAt
494
+ });
547
495
  }
548
496
  if (this.eventEmitter) {
549
497
  this.eventEmitter.emit(auth_events_1.AuthEvents.PASSWORD_RESET, { auth: primaryAuth });
@@ -551,39 +499,26 @@ let AuthService = AuthService_1 = class AuthService {
551
499
  return { message: 'If an account exists, a reset code has been sent.' };
552
500
  }
553
501
  async resetPassword(dto) {
554
- const otp = await this.otpRepo.findOne({
555
- where: { requestUserId: dto.uid, purpose: otp_token_entity_1.OtpPurpose.PASSWORD_RESET, isUsed: false },
556
- order: { createdAt: 'DESC' },
557
- });
558
- if (!otp || new Date() > otp.expiresAt) {
502
+ const result = await this.resolveOtpProvider().verify({ uid: dto.uid, code: dto.code, purpose: otp_purpose_enum_1.OtpPurpose.PASSWORD_RESET });
503
+ if (!result.success || !result.authId) {
559
504
  throw new common_1.BadRequestException('Invalid or expired reset code');
560
505
  }
561
- const isMatch = await bcrypt.compare(dto.code, otp.codeHash);
562
- if (!isMatch)
563
- throw new common_1.BadRequestException('Invalid reset code');
564
- // Update password
565
506
  const hash = await bcrypt.hash(dto.newPassword, 10);
566
- await this.authRepo.update(otp.requestAuthId, {
507
+ await this.authRepo.update(result.authId, {
567
508
  secretHash: hash,
568
509
  isActive: true // Unlock account on successful reset
569
510
  });
570
- // Mark OTP as used
571
- otp.isUsed = true;
572
- await this.otpRepo.save(otp);
573
511
  // Security: Invalidate all sessions
574
- await this.invalidateSessions({ uid: dto.uid, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'User reset password' });
512
+ await this.invalidateSessions({ uid: dto.uid, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'User reset password' });
575
513
  if (this.eventEmitter) {
576
- const auth = await this.authRepo.findOne({ where: { uid: dto.uid } });
514
+ const auth = await this.authRepo.findByUid(dto.uid);
577
515
  this.eventEmitter.emit(auth_events_1.AuthEvents.PASSWORD_RESET, { auth });
578
516
  }
579
517
  return { message: 'Password reset successful. All active sessions have been logged out.' };
580
518
  }
581
519
  async updatePassword(uid, dto, userAgent, ip) {
582
520
  // 1. Get primary LOCAL auth
583
- const auth = await this.authRepo.findOne({
584
- where: { uid, strategy: (0, typeorm_2.In)([auth_type_enum_1.AuthStrategy.LOCAL, auth_type_enum_1.AuthStrategy.EMAIL, auth_type_enum_1.AuthStrategy.PHONE, auth_type_enum_1.AuthStrategy.USERNAME]) },
585
- select: ['id', 'secretHash', 'uid']
586
- });
521
+ const auth = await this.authRepo.findByUidAndStrategies(uid, [auth_type_enum_1.AuthStrategy.LOCAL, auth_type_enum_1.AuthStrategy.EMAIL, auth_type_enum_1.AuthStrategy.PHONE, auth_type_enum_1.AuthStrategy.USERNAME]);
587
522
  if (!auth || !auth.secretHash) {
588
523
  throw new common_1.BadRequestException('Password update only available for local accounts');
589
524
  }
@@ -596,27 +531,24 @@ let AuthService = AuthService_1 = class AuthService {
596
531
  await this.authRepo.save(auth);
597
532
  // 4. Notification with Security Link
598
533
  if (this.notificationProvider?.sendPasswordChangedNotification) {
599
- const secureToken = crypto.randomBytes(32).toString('hex');
600
- const tokenHash = await bcrypt.hash(secureToken, 10);
601
- const expiresAt = new Date();
602
- expiresAt.setHours(expiresAt.getHours() + 24);
603
- // Get user email
604
- const identifier = await this.authRepo.query(`SELECT value FROM auth_identifiers WHERE "authId" = $1 AND type = 'EMAIL' LIMIT 1`, [auth.id]);
605
- if (identifier[0]) {
606
- await this.otpRepo.save(this.otpRepo.create({
607
- identifier: identifier[0].value,
608
- purpose: otp_token_entity_1.OtpPurpose.SECURE_ACCOUNT,
609
- codeHash: tokenHash,
610
- expiresAt,
611
- requestUserId: auth.uid,
612
- requestAuthId: auth.id,
613
- }));
614
- const secureLink = `${this.options.frontendUrl || ''}/auth/secure?token=${secureToken}&uid=${auth.uid}`;
615
- await this.notificationProvider.sendPasswordChangedNotification(identifier[0].value, {
616
- ip: ip || 'Unknown',
617
- userAgent: userAgent || 'Unknown',
618
- secureAccountLink: secureLink,
534
+ const identifier = await this.authIdentifierRepo.findByUidAndTypes(auth.uid, ['EMAIL']);
535
+ if (identifier) {
536
+ const issueResult = await this.resolveOtpProvider('email').issue({
537
+ uid: auth.uid,
538
+ authId: auth.id,
539
+ identifier: identifier.value,
540
+ identifierType: 'email',
541
+ purpose: otp_purpose_enum_1.OtpPurpose.SECURE_ACCOUNT,
542
+ expiresIn: 24 * 60
619
543
  });
544
+ if (issueResult.code) {
545
+ const secureLink = `${this.options.frontendUrl || ''}/auth/secure?token=${issueResult.code}&uid=${auth.uid}`;
546
+ await this.notificationProvider.sendPasswordChangedNotification(identifier.value, {
547
+ ip: ip || 'Unknown',
548
+ userAgent: userAgent || 'Unknown',
549
+ secureAccountLink: secureLink,
550
+ });
551
+ }
620
552
  }
621
553
  }
622
554
  if (this.eventEmitter) {
@@ -625,23 +557,21 @@ let AuthService = AuthService_1 = class AuthService {
625
557
  return { message: 'Password updated successfully' };
626
558
  }
627
559
  async secureAccount(dto) {
628
- const otp = await this.otpRepo.findOne({
629
- where: { requestUserId: dto.uid, purpose: otp_token_entity_1.OtpPurpose.SECURE_ACCOUNT, isUsed: false },
630
- order: { createdAt: 'DESC' },
560
+ const result = await this.resolveOtpProvider('email').verify({
561
+ uid: dto.uid,
562
+ code: dto.token,
563
+ purpose: otp_purpose_enum_1.OtpPurpose.SECURE_ACCOUNT,
631
564
  });
632
- if (!otp || new Date() > otp.expiresAt) {
565
+ if (!result.success) {
633
566
  throw new common_1.BadRequestException('Invalid or expired security token');
634
567
  }
635
- const isMatch = await bcrypt.compare(dto.token, otp.codeHash);
636
- if (!isMatch)
637
- throw new common_1.BadRequestException('Invalid security token');
638
568
  // 1. Lock all auth methods
639
- await this.authRepo.update({ uid: dto.uid }, { isActive: false });
569
+ const auths = await this.authRepo.findAllByUid(dto.uid);
570
+ for (const auth of auths) {
571
+ await this.authRepo.update(auth.id, { isActive: false });
572
+ }
640
573
  // 2. Invalidate sessions
641
- this.invalidateSessions({ uid: dto.uid, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'User secured account by invalidating all sessions' });
642
- // 3. Mark OTP as used
643
- otp.isUsed = true;
644
- await this.otpRepo.save(otp);
574
+ this.invalidateSessions({ uid: dto.uid, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'User secured account by invalidating all sessions' });
645
575
  if (this.eventEmitter) {
646
576
  this.eventEmitter.emit(auth_events_1.AuthEvents.ACCOUNT_SECURED, { uid: dto.uid });
647
577
  }
@@ -649,33 +579,22 @@ let AuthService = AuthService_1 = class AuthService {
649
579
  }
650
580
  // --- MAGIC LINK ---
651
581
  async requestMagicLink(dto) {
652
- const identifier = await this.authRepo.query(`SELECT ai.*, a.id as "authId", a.uid FROM auth_identifiers ai
653
- JOIN auth a ON ai."authId" = a.id
654
- WHERE ai.value = $1 LIMIT 1`, [dto.email.toLowerCase()]);
655
- let authId;
656
- let uid;
657
- if (!identifier[0]) {
582
+ const result = await this.authIdentifierRepo.findWithAuthByValue(dto.email.toLowerCase());
583
+ if (!result) {
658
584
  // Optional: Auto-signup if not exists, but let's stick to existing for now
659
585
  throw new common_1.BadRequestException('No account found with this email');
660
586
  }
661
- else {
662
- authId = identifier[0].authId;
663
- uid = identifier[0].uid;
664
- }
665
- const token = crypto.randomBytes(32).toString('hex');
666
- const hash = await bcrypt.hash(token, 10);
667
- const expiresAt = new Date();
668
- expiresAt.setMinutes(expiresAt.getMinutes() + 15);
669
- await this.otpRepo.save(this.otpRepo.create({
587
+ const { auth: primaryAuthObj } = result;
588
+ const issueResult = await this.resolveOtpProvider('email').issue({
589
+ uid: primaryAuthObj.uid,
590
+ authId: primaryAuthObj.id,
670
591
  identifier: dto.email.toLowerCase(),
671
- purpose: otp_token_entity_1.OtpPurpose.MAGIC_LINK,
672
- codeHash: hash,
673
- expiresAt,
674
- requestUserId: uid,
675
- requestAuthId: authId,
676
- }));
677
- if (this.notificationProvider?.sendMagicLink) {
678
- const link = `${this.options.frontendUrl || ''}/auth/magic-callback?token=${token}&email=${dto.email}`;
592
+ identifierType: 'email',
593
+ purpose: otp_purpose_enum_1.OtpPurpose.MAGIC_LINK,
594
+ expiresIn: 15
595
+ });
596
+ if (this.notificationProvider?.sendMagicLink && issueResult.code) {
597
+ const link = `${this.options.frontendUrl || ''}/auth/magic-callback?token=${issueResult.code}&email=${dto.email}`;
679
598
  await this.notificationProvider.sendMagicLink(dto.email, link);
680
599
  }
681
600
  if (this.eventEmitter) {
@@ -684,19 +603,18 @@ let AuthService = AuthService_1 = class AuthService {
684
603
  return { message: 'Magic link sent to your email.' };
685
604
  }
686
605
  async verifyMagicLink({ dto, userAgent, ip, namespace }) {
687
- const otp = await this.otpRepo.findOne({
688
- where: { purpose: otp_token_entity_1.OtpPurpose.MAGIC_LINK, isUsed: false },
689
- order: { createdAt: 'DESC' },
606
+ const identifier = await this.authIdentifierRepo.findWithAuthByValue(dto.email.toLowerCase());
607
+ if (!identifier)
608
+ throw new common_1.BadRequestException('Identity not found');
609
+ const result = await this.resolveOtpProvider('email').verify({
610
+ uid: identifier.auth.uid,
611
+ code: dto.token,
612
+ purpose: otp_purpose_enum_1.OtpPurpose.MAGIC_LINK
690
613
  });
691
- if (!otp || new Date() > otp.expiresAt) {
614
+ if (!result.success || !result.authId) {
692
615
  throw new common_1.BadRequestException('Invalid or expired magic link');
693
616
  }
694
- const isMatch = await bcrypt.compare(dto.token, otp.codeHash);
695
- if (!isMatch)
696
- throw new common_1.BadRequestException('Invalid magic link');
697
- otp.isUsed = true;
698
- await this.otpRepo.save(otp);
699
- const auth = await this.authRepo.findOne({ where: { id: otp.requestAuthId } });
617
+ const auth = await this.authRepo.findById(result.authId);
700
618
  if (!auth)
701
619
  throw new common_1.BadRequestException('Identity not found');
702
620
  const tokens = await this.createSession(auth.uid, userAgent, ip, namespace);
@@ -707,10 +625,10 @@ let AuthService = AuthService_1 = class AuthService {
707
625
  }
708
626
  // --- MFA (2FA) LOGIC ---
709
627
  async enrollMfa(uid, type) {
710
- if (type !== mfa_method_entity_1.MfaType.TOTP) {
628
+ if (type !== mfa_type_enum_1.MfaType.TOTP) {
711
629
  throw new common_1.BadRequestException('Currently only TOTP MFA is supported');
712
630
  }
713
- let mfa = await this.mfaRepo.findOne({ where: { uid, type }, select: ['id', 'secret', 'isEnabled', 'type'] });
631
+ let mfa = await this.mfaRepo.findByUidAndType(uid, type);
714
632
  if (mfa?.isEnabled) {
715
633
  throw new common_1.BadRequestException('MFA is already enabled for this account');
716
634
  }
@@ -718,7 +636,7 @@ let AuthService = AuthService_1 = class AuthService {
718
636
  const appName = this.options.appName || 'NestJS Auth';
719
637
  const otpauth = otplib_1.authenticator.keyuri(uid, appName, secret);
720
638
  if (!mfa) {
721
- mfa = this.mfaRepo.create({
639
+ mfa = await this.mfaRepo.create({
722
640
  uid,
723
641
  type,
724
642
  secret,
@@ -732,16 +650,10 @@ let AuthService = AuthService_1 = class AuthService {
732
650
  if (this.eventEmitter) {
733
651
  this.eventEmitter.emit(auth_events_1.AuthEvents.MFA_ENROLLED, { uid, type });
734
652
  }
735
- return {
736
- secret,
737
- otpauth,
738
- };
653
+ return { secret, otpauth };
739
654
  }
740
655
  async activateMfa(uid, type, code) {
741
- const mfa = await this.mfaRepo.findOne({
742
- where: { uid, type },
743
- select: ['id', 'secret', 'isEnabled']
744
- });
656
+ const mfa = await this.mfaRepo.findByUidAndType(uid, type);
745
657
  if (!mfa) {
746
658
  throw new common_1.BadRequestException('No MFA enrollment found');
747
659
  }
@@ -763,80 +675,81 @@ let AuthService = AuthService_1 = class AuthService {
763
675
  }
764
676
  return { message: 'MFA activated successfully' };
765
677
  }
678
+ async mfaLogin(uid, code, userAgent, ip, namespace) {
679
+ const auth = await this.authRepo.findByUid(uid);
680
+ if (!auth)
681
+ throw new common_1.BadRequestException('Identity not found');
682
+ const mfa = await this.mfaRepo.findByUidAndEnabled(uid);
683
+ if (!mfa)
684
+ throw new common_1.BadRequestException('MFA is not enabled for this account');
685
+ const isValid = otplib_1.authenticator.verify({
686
+ token: code,
687
+ secret: mfa.secret,
688
+ });
689
+ if (!isValid)
690
+ throw new common_1.BadRequestException('Invalid MFA code');
691
+ const { secretHash, ...filteredAuth } = auth;
692
+ const tokens = await this.createSession(auth.uid, userAgent, ip, namespace);
693
+ if (this.eventEmitter) {
694
+ this.eventEmitter.emit(auth_events_1.AuthEvents.LOGIN, { auth: filteredAuth, tokens });
695
+ }
696
+ return { message: 'Login successful', tokens, auth: filteredAuth };
697
+ }
766
698
  async viewAll() {
767
- const auths = await this.authRepo.find({ relations: ['identifiers', 'oauthProviders'] });
699
+ const auths = await this.authRepo.findAll();
768
700
  return auth_mapper_1.AuthMapper.toDtoList(auths);
769
701
  }
770
702
  async me(uid) {
771
- const auth = await this.authRepo.findOne({ where: { uid }, relations: ['identifiers', 'oauthProviders'] });
772
- return auth_mapper_1.AuthMapper.toDto(auth);
703
+ const auths = await this.authRepo.findAllByUid(uid);
704
+ return auth_mapper_1.AuthMapper.toDto(auths[0]);
773
705
  }
774
706
  async viewAllMyAuthMethods(uid) {
775
- const auths = await this.authRepo.find({ where: { uid }, relations: ['identifiers', 'oauthProviders'] });
707
+ const auths = await this.authRepo.findAllByUid(uid);
776
708
  return auth_mapper_1.AuthMapper.toDtoList(auths);
777
709
  }
778
710
  async deleteAccount(uid) {
779
711
  // 1. Delete all sessions for this UID
780
- await this.invalidateSessions({ uid, event: session_log_entity_1.SessionEvent.DELETE, reason: 'User deleted account' });
712
+ await this.invalidateSessions({ uid, event: session_event_enum_1.SessionEvent.DELETE, reason: 'User deleted account' });
781
713
  // 2. Delete all MFA methods for this UID
782
- await this.mfaRepo.delete({ uid });
783
- // 3. Delete all OTP tokens requested by this UID
784
- await this.otpRepo.delete({ requestUserId: uid });
785
- // 4. Delete all Auth methods for this UID.
786
- // TypeORM should handle cascading deletion of AuthIdentifier and OAuthProvider if configured.
787
- // However, if not configured with ON DELETE CASCADE in the DB, it's safer to use repo.remove or repo.delete.
788
- // Using repo.delete with uid will delete all matching Auth records.
789
- await this.authRepo.delete({ uid });
714
+ await this.mfaRepo.deleteByUid(uid);
715
+ // 3. Delete all Auth methods for this UID.
716
+ await this.authRepo.deleteByUid(uid);
790
717
  }
791
718
  async deleteAuthMethod(uid, authId) {
792
- const auth = await this.authRepo.findOne({ where: { id: authId, uid } });
793
- if (!auth) {
719
+ const auth = await this.authRepo.findById(authId);
720
+ if (!auth || auth.uid !== uid) {
794
721
  throw new common_1.BadRequestException('Authentication method not found or does not belong to user');
795
722
  }
796
723
  // Check if this is the last auth method
797
- const count = await this.authRepo.count({ where: { uid } });
798
- if (count <= 1) {
724
+ const allAuths = await this.authRepo.findAllByUid(uid);
725
+ if (allAuths.length <= 1) {
799
726
  throw new common_1.BadRequestException('Cannot delete the last authentication method. Delete account instead.');
800
727
  }
801
- // If deleting the primary auth method, we might need to assign a new one
802
- if (auth.isPrimary) {
803
- const nextAuth = await this.authRepo.findOne({ where: { uid, id: authId } }); // This is wrong, should be NOT authId
804
- }
805
- // Actually, let's keep it simple for now: just delete it.
806
- // If it was primary, the next available one should ideally become primary.
807
728
  await this.authRepo.delete(authId);
808
729
  // After deletion, find if there's any primary left.
809
730
  // If not, assign the first available one as primary.
810
- const hasPrimary = await this.authRepo.findOne({ where: { uid, isPrimary: true } });
811
- if (!hasPrimary) {
812
- const remainingAuth = await this.authRepo.findOne({ where: { uid } });
813
- if (remainingAuth) {
814
- remainingAuth.isPrimary = true;
815
- await this.authRepo.save(remainingAuth);
816
- }
731
+ const remainingAuths = await this.authRepo.findAllByUid(uid);
732
+ if (remainingAuths.length > 0 && !remainingAuths.some(a => a.isPrimary)) {
733
+ const remainingAuth = remainingAuths[0];
734
+ remainingAuth.isPrimary = true;
735
+ await this.authRepo.save(remainingAuth);
817
736
  }
818
737
  }
819
738
  async getSessions({ uid, namespace }) {
820
- const whereClause = { uid };
821
- if (typeof namespace === "string") {
822
- whereClause.namespace = namespace;
739
+ const sessions = await this.sessionRepository.findByUid(uid);
740
+ if (namespace !== undefined) {
741
+ return sessions.filter(s => s.namespace === namespace);
823
742
  }
824
- const sessions = this.sessionRepository.find({ where: whereClause, relations: [] });
825
- return (await sessions).map((d) => Object.assign(new session_entity_1.Session(), d.toMap()));
743
+ return sessions;
826
744
  }
827
745
  async getSession(id) {
828
- return this.sessionRepository.findOne({ where: { id } });
746
+ return this.sessionRepository.findById(id);
829
747
  }
830
748
  async revokeSession({ sessionId }) {
831
- await this.invalidateSession({ sessionId, event: session_log_entity_1.SessionEvent.REVOKE, reason: 'User requested session revoke' });
749
+ await this.invalidateSession({ sessionId, event: session_event_enum_1.SessionEvent.REVOKE, reason: 'User requested session revoke' });
832
750
  }
833
751
  async getSessionLogs({ uid, namespace }) {
834
- const whereClause = { uid };
835
- if (typeof namespace === "string") {
836
- whereClause.namespace = namespace;
837
- }
838
- const sessions = this.sessionLogRepo.find({ where: whereClause, relations: [] });
839
- return (await sessions).map((d) => Object.assign(new session_log_entity_1.SessionLog(), d.toMap()));
752
+ return this.sessionLogRepo.findByUidAndNamespace(uid, namespace);
840
753
  }
841
754
  };
842
755
  exports.AuthService = AuthService;
@@ -844,22 +757,20 @@ exports.AuthService = AuthService = AuthService_1 = __decorate([
844
757
  (0, common_1.Injectable)(),
845
758
  __param(1, (0, common_1.Optional)()),
846
759
  __param(2, (0, common_1.Optional)()),
847
- __param(3, (0, typeorm_1.InjectRepository)(session_entity_1.Session)),
848
- __param(4, (0, typeorm_1.InjectRepository)(session_log_entity_1.SessionLog)),
849
- __param(5, (0, typeorm_1.InjectRepository)(auth_entity_1.Auth)),
850
- __param(6, (0, typeorm_1.InjectRepository)(otp_token_entity_1.OtpToken)),
851
- __param(7, (0, typeorm_1.InjectRepository)(mfa_method_entity_1.MfaMethod)),
852
- __param(8, (0, common_1.Inject)(auth_module_options_interface_1.AUTH_MODULE_OPTIONS)),
853
- __param(9, (0, common_1.Optional)()),
854
- __param(9, (0, common_1.Inject)(auth_notification_provider_interface_1.AUTH_NOTIFICATION_PROVIDER)),
855
- __param(10, (0, common_1.Optional)()),
760
+ __param(3, (0, common_1.Inject)(repository_tokens_1.SESSION_REPOSITORY_TOKEN)),
761
+ __param(4, (0, common_1.Inject)(repository_tokens_1.SESSION_LOG_REPOSITORY_TOKEN)),
762
+ __param(5, (0, common_1.Inject)(repository_tokens_1.AUTH_REPOSITORY_TOKEN)),
763
+ __param(6, (0, common_1.Inject)(repository_tokens_1.AUTH_IDENTIFIER_REPOSITORY_TOKEN)),
764
+ __param(7, (0, common_1.Inject)(auth_otp_provider_interface_1.AUTH_OTP_PROVIDER)),
765
+ __param(8, (0, common_1.Inject)(auth_otp_provider_interface_1.AUTH_OTP_PROVIDER_EMAIL)),
766
+ __param(9, (0, common_1.Inject)(auth_otp_provider_interface_1.AUTH_OTP_PROVIDER_PHONE)),
767
+ __param(10, (0, common_1.Inject)(repository_tokens_1.MFA_METHOD_REPOSITORY_TOKEN)),
768
+ __param(11, (0, common_1.Inject)(auth_module_options_interface_1.AUTH_MODULE_OPTIONS)),
769
+ __param(12, (0, common_1.Optional)()),
770
+ __param(12, (0, common_1.Inject)(auth_notification_provider_interface_1.AUTH_NOTIFICATION_PROVIDER)),
771
+ __param(13, (0, common_1.Optional)()),
856
772
  __metadata("design:paramtypes", [jwt_1.JwtService,
857
773
  local_auth_strategy_1.LocalAuthStrategy,
858
- oauth_strategy_1.OAuthAuthStrategy,
859
- typeorm_2.Repository,
860
- typeorm_2.Repository,
861
- typeorm_2.Repository,
862
- typeorm_2.Repository,
863
- typeorm_2.Repository, Object, Object, event_emitter_1.EventEmitter2])
774
+ oauth_strategy_1.OAuthAuthStrategy, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, event_emitter_1.EventEmitter2])
864
775
  ], AuthService);
865
776
  //# sourceMappingURL=auth.service.js.map