@nauth-toolkit/core 0.1.0 → 0.1.5

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 (184) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +9 -0
  3. package/package.json +8 -3
  4. package/jest.config.js +0 -15
  5. package/jest.setup.ts +0 -6
  6. package/src/adapters/database-columns.ts +0 -165
  7. package/src/adapters/express.adapter.ts +0 -385
  8. package/src/adapters/fastify.adapter.ts +0 -416
  9. package/src/adapters/index.ts +0 -16
  10. package/src/adapters/storage.factory.ts +0 -143
  11. package/src/bootstrap.ts +0 -374
  12. package/src/dto/auth-challenge.dto.ts +0 -231
  13. package/src/dto/auth-response.dto.ts +0 -253
  14. package/src/dto/challenge-response.dto.ts +0 -234
  15. package/src/dto/change-password-request.dto.ts +0 -50
  16. package/src/dto/change-password-response.dto.ts +0 -29
  17. package/src/dto/change-password.dto.ts +0 -57
  18. package/src/dto/error-response.dto.ts +0 -136
  19. package/src/dto/get-available-methods.dto.ts +0 -55
  20. package/src/dto/get-challenge-data-response.dto.ts +0 -28
  21. package/src/dto/get-challenge-data.dto.ts +0 -69
  22. package/src/dto/get-client-info.dto.ts +0 -104
  23. package/src/dto/get-device-token-response.dto.ts +0 -25
  24. package/src/dto/get-events-by-type.dto.ts +0 -76
  25. package/src/dto/get-ip-address-response.dto.ts +0 -24
  26. package/src/dto/get-mfa-status.dto.ts +0 -94
  27. package/src/dto/get-risk-assessment-history.dto.ts +0 -39
  28. package/src/dto/get-session-id-response.dto.ts +0 -25
  29. package/src/dto/get-setup-data-response.dto.ts +0 -31
  30. package/src/dto/get-setup-data.dto.ts +0 -75
  31. package/src/dto/get-suspicious-activity.dto.ts +0 -42
  32. package/src/dto/get-user-agent-response.dto.ts +0 -23
  33. package/src/dto/get-user-auth-history.dto.ts +0 -95
  34. package/src/dto/get-user-by-email.dto.ts +0 -61
  35. package/src/dto/get-user-by-id.dto.ts +0 -46
  36. package/src/dto/get-user-devices.dto.ts +0 -53
  37. package/src/dto/get-user-response.dto.ts +0 -17
  38. package/src/dto/has-provider.dto.ts +0 -56
  39. package/src/dto/index.ts +0 -57
  40. package/src/dto/is-trusted-device-response.dto.ts +0 -34
  41. package/src/dto/list-providers-response.dto.ts +0 -23
  42. package/src/dto/login.dto.ts +0 -95
  43. package/src/dto/logout-all-response.dto.ts +0 -24
  44. package/src/dto/logout-all.dto.ts +0 -65
  45. package/src/dto/logout-response.dto.ts +0 -25
  46. package/src/dto/logout.dto.ts +0 -64
  47. package/src/dto/refresh-token.dto.ts +0 -36
  48. package/src/dto/remove-devices.dto.ts +0 -85
  49. package/src/dto/resend-code-response.dto.ts +0 -32
  50. package/src/dto/resend-code.dto.ts +0 -51
  51. package/src/dto/reset-password.dto.ts +0 -115
  52. package/src/dto/respond-challenge.dto.ts +0 -272
  53. package/src/dto/set-mfa-exemption.dto.ts +0 -112
  54. package/src/dto/set-must-change-password-response.dto.ts +0 -27
  55. package/src/dto/set-must-change-password.dto.ts +0 -46
  56. package/src/dto/set-preferred-method.dto.ts +0 -80
  57. package/src/dto/setup-mfa.dto.ts +0 -98
  58. package/src/dto/signup.dto.ts +0 -174
  59. package/src/dto/social-auth.dto.ts +0 -422
  60. package/src/dto/trust-device-response.dto.ts +0 -30
  61. package/src/dto/trust-device.dto.ts +0 -9
  62. package/src/dto/update-user-attributes-request.dto.ts +0 -51
  63. package/src/dto/user-response.dto.ts +0 -138
  64. package/src/dto/user-update.dto.ts +0 -222
  65. package/src/dto/verify-email.dto.ts +0 -313
  66. package/src/dto/verify-mfa-code.dto.ts +0 -103
  67. package/src/dto/verify-phone-by-sub.dto.ts +0 -78
  68. package/src/dto/verify-phone.dto.ts +0 -245
  69. package/src/entities/auth-audit.entity.ts +0 -232
  70. package/src/entities/challenge-session.entity.ts +0 -116
  71. package/src/entities/index.ts +0 -29
  72. package/src/entities/login-attempt.entity.ts +0 -64
  73. package/src/entities/mfa-device.entity.ts +0 -151
  74. package/src/entities/rate-limit.entity.ts +0 -44
  75. package/src/entities/session.entity.ts +0 -180
  76. package/src/entities/social-account.entity.ts +0 -96
  77. package/src/entities/storage-lock.entity.ts +0 -39
  78. package/src/entities/trusted-device.entity.ts +0 -112
  79. package/src/entities/user.entity.ts +0 -243
  80. package/src/entities/verification-token.entity.ts +0 -141
  81. package/src/enums/auth-audit-event-type.enum.ts +0 -360
  82. package/src/enums/error-codes.enum.ts +0 -420
  83. package/src/enums/mfa-method.enum.ts +0 -97
  84. package/src/enums/risk-factor.enum.ts +0 -111
  85. package/src/exceptions/nauth.exception.ts +0 -231
  86. package/src/handlers/auth.handler.ts +0 -260
  87. package/src/handlers/client-info.handler.ts +0 -101
  88. package/src/handlers/csrf.handler.ts +0 -156
  89. package/src/handlers/token-delivery.handler.ts +0 -118
  90. package/src/index.ts +0 -118
  91. package/src/interfaces/client-info.interface.ts +0 -85
  92. package/src/interfaces/config.interface.ts +0 -2135
  93. package/src/interfaces/entities.interface.ts +0 -226
  94. package/src/interfaces/index.ts +0 -15
  95. package/src/interfaces/logger.interface.ts +0 -283
  96. package/src/interfaces/mfa-provider.interface.ts +0 -154
  97. package/src/interfaces/oauth.interface.ts +0 -148
  98. package/src/interfaces/provider.interface.ts +0 -47
  99. package/src/interfaces/social-auth-provider.interface.ts +0 -131
  100. package/src/interfaces/storage-adapter.interface.ts +0 -82
  101. package/src/interfaces/template.interface.ts +0 -510
  102. package/src/interfaces/token-verifier.interface.ts +0 -110
  103. package/src/internal.ts +0 -178
  104. package/src/platform/interfaces.ts +0 -299
  105. package/src/schemas/auth-config.schema.ts +0 -646
  106. package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
  107. package/src/services/adaptive-mfa-decision.service.ts +0 -457
  108. package/src/services/auth-audit.service.spec.ts +0 -675
  109. package/src/services/auth-audit.service.ts +0 -558
  110. package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
  111. package/src/services/auth-challenge-helper.service.ts +0 -825
  112. package/src/services/auth-flow-context-builder.service.ts +0 -520
  113. package/src/services/auth-flow-rules.ts +0 -202
  114. package/src/services/auth-flow-state-definitions.ts +0 -190
  115. package/src/services/auth-flow-state-machine.service.ts +0 -207
  116. package/src/services/auth-flow-state-machine.types.ts +0 -316
  117. package/src/services/auth.service.spec.ts +0 -4195
  118. package/src/services/auth.service.ts +0 -3727
  119. package/src/services/challenge.service.spec.ts +0 -1363
  120. package/src/services/challenge.service.ts +0 -696
  121. package/src/services/client-info.service.spec.ts +0 -572
  122. package/src/services/client-info.service.ts +0 -374
  123. package/src/services/csrf.service.ts +0 -54
  124. package/src/services/email-verification.service.spec.ts +0 -1229
  125. package/src/services/email-verification.service.ts +0 -578
  126. package/src/services/geo-location.service.spec.ts +0 -603
  127. package/src/services/geo-location.service.ts +0 -599
  128. package/src/services/index.ts +0 -13
  129. package/src/services/jwt.service.spec.ts +0 -882
  130. package/src/services/jwt.service.ts +0 -621
  131. package/src/services/mfa-base.service.spec.ts +0 -246
  132. package/src/services/mfa-base.service.ts +0 -611
  133. package/src/services/mfa.service.spec.ts +0 -693
  134. package/src/services/mfa.service.ts +0 -960
  135. package/src/services/password.service.spec.ts +0 -166
  136. package/src/services/password.service.ts +0 -309
  137. package/src/services/phone-verification.service.spec.ts +0 -1120
  138. package/src/services/phone-verification.service.ts +0 -751
  139. package/src/services/risk-detection.service.spec.ts +0 -1292
  140. package/src/services/risk-detection.service.ts +0 -1012
  141. package/src/services/risk-scoring.service.spec.ts +0 -204
  142. package/src/services/risk-scoring.service.ts +0 -131
  143. package/src/services/session.service.spec.ts +0 -1293
  144. package/src/services/session.service.ts +0 -803
  145. package/src/services/social-account.service.spec.ts +0 -725
  146. package/src/services/social-auth-base.service.spec.ts +0 -418
  147. package/src/services/social-auth-base.service.ts +0 -581
  148. package/src/services/social-auth.service.spec.ts +0 -238
  149. package/src/services/social-auth.service.ts +0 -436
  150. package/src/services/social-provider-registry.service.spec.ts +0 -238
  151. package/src/services/social-provider-registry.service.ts +0 -122
  152. package/src/services/trusted-device.service.spec.ts +0 -505
  153. package/src/services/trusted-device.service.ts +0 -339
  154. package/src/storage/account-lockout-storage.service.spec.ts +0 -310
  155. package/src/storage/account-lockout-storage.service.ts +0 -89
  156. package/src/storage/index.ts +0 -3
  157. package/src/storage/memory-storage.adapter.ts +0 -443
  158. package/src/storage/rate-limit-storage.service.spec.ts +0 -247
  159. package/src/storage/rate-limit-storage.service.ts +0 -38
  160. package/src/templates/html-template.engine.spec.ts +0 -161
  161. package/src/templates/html-template.engine.ts +0 -688
  162. package/src/templates/index.ts +0 -7
  163. package/src/utils/common-passwords.spec.ts +0 -230
  164. package/src/utils/common-passwords.ts +0 -170
  165. package/src/utils/context-storage.ts +0 -188
  166. package/src/utils/cookie-names.util.ts +0 -67
  167. package/src/utils/cookies.util.ts +0 -94
  168. package/src/utils/index.ts +0 -12
  169. package/src/utils/ip-extractor.spec.ts +0 -330
  170. package/src/utils/ip-extractor.ts +0 -220
  171. package/src/utils/nauth-logger.spec.ts +0 -388
  172. package/src/utils/nauth-logger.ts +0 -215
  173. package/src/utils/pii-redactor.spec.ts +0 -130
  174. package/src/utils/pii-redactor.ts +0 -288
  175. package/src/utils/setup/get-repositories.ts +0 -140
  176. package/src/utils/setup/init-services.ts +0 -422
  177. package/src/utils/setup/init-social.ts +0 -189
  178. package/src/utils/setup/init-storage.ts +0 -94
  179. package/src/utils/setup/register-mfa.ts +0 -165
  180. package/src/utils/setup/run-nauth-migrations.ts +0 -61
  181. package/src/utils/token-delivery-policy.ts +0 -38
  182. package/src/validators/template.validator.ts +0 -219
  183. package/tsconfig.json +0 -37
  184. package/tsconfig.lint.json +0 -6
@@ -1,138 +0,0 @@
1
- import { IUser } from '../interfaces/entities.interface';
2
-
3
- /**
4
- * User Response DTO
5
- *
6
- * Sanitized user object for API responses.
7
- * Excludes all sensitive and internal fields.
8
- *
9
- * Security:
10
- * - Never exposes password hash
11
- * - Never exposes MFA secrets
12
- * - Never exposes internal tracking fields
13
- * - Exposes 'sub' (external UUID) instead of internal 'id'
14
- *
15
- * No validators needed - this is generated internally by the library via fromEntity().
16
- *
17
- * @example
18
- * ```typescript
19
- * const user = await userRepository.findOne({ where: { sub } });
20
- * return UserResponseDto.fromEntity(user);
21
- * ```
22
- */
23
- export class UserResponseDto {
24
- /**
25
- * External user identifier (UUID v4)
26
- * This is the 'sub' (subject) field from JWT tokens
27
- */
28
- sub!: string;
29
-
30
- /**
31
- * User's email address
32
- */
33
- email!: string;
34
-
35
- /**
36
- * User's username (optional)
37
- */
38
- username?: string | null;
39
-
40
- /**
41
- * User's first name (optional)
42
- */
43
- firstName?: string | null;
44
-
45
- /**
46
- * User's last name (optional)
47
- */
48
- lastName?: string | null;
49
-
50
- /**
51
- * User's phone number (optional)
52
- * E.164 format validated in service layer if present
53
- */
54
- phone?: string | null;
55
-
56
- /**
57
- * Email verification status
58
- */
59
- isEmailVerified!: boolean;
60
-
61
- /**
62
- * Phone verification status
63
- */
64
- isPhoneVerified!: boolean;
65
-
66
- /**
67
- * Account active status
68
- */
69
- isActive!: boolean;
70
-
71
- /**
72
- * MFA enabled status
73
- */
74
- mfaEnabled!: boolean;
75
-
76
- /**
77
- * Array of social providers linked to this account
78
- *
79
- * Examples: ['google', 'apple', 'facebook']
80
- * null/undefined means no social auth, only password-based
81
- */
82
- socialProviders?: string[] | null;
83
-
84
- /**
85
- * Whether this user has a password set
86
- * Used to determine if user can use password-based authentication
87
- * or is a pure social signup (no password, only social auth)
88
- */
89
- hasPasswordHash!: boolean;
90
-
91
- /**
92
- * Account creation timestamp
93
- */
94
- createdAt!: Date;
95
-
96
- /**
97
- * Last account update timestamp
98
- */
99
- updatedAt!: Date;
100
-
101
- /**
102
- * Convert User entity to safe response DTO
103
- *
104
- * @param user - User entity from database
105
- * @returns Sanitized user object with external identifier (sub)
106
- */
107
- static fromEntity(user: IUser): UserResponseDto {
108
- const dto = new UserResponseDto();
109
-
110
- // Essential fields only
111
- dto.sub = user.sub; // External UUID identifier
112
- dto.email = user.email;
113
- dto.username = user.username;
114
- dto.firstName = user.firstName;
115
- dto.lastName = user.lastName;
116
- dto.phone = user.phone;
117
- dto.isEmailVerified = user.isEmailVerified;
118
- dto.isPhoneVerified = user.isPhoneVerified;
119
- dto.isActive = user.isActive;
120
- dto.mfaEnabled = user.mfaEnabled;
121
- dto.socialProviders = user.socialProviders;
122
- dto.hasPasswordHash = !!user.passwordHash; // Check if password exists
123
- dto.createdAt = user.createdAt;
124
- dto.updatedAt = user.updatedAt;
125
-
126
- return dto;
127
- }
128
-
129
- /**
130
- * Convert array of User entities to safe response DTOs
131
- *
132
- * @param users - Array of User entities
133
- * @returns Array of sanitized user objects
134
- */
135
- static fromEntities(users: IUser[]): UserResponseDto[] {
136
- return users.map((user) => UserResponseDto.fromEntity(user));
137
- }
138
- }
@@ -1,222 +0,0 @@
1
- import { IsEmail, IsString, MinLength, MaxLength, IsOptional, Matches, IsBoolean, IsEnum } from 'class-validator';
2
- import { Transform } from 'class-transformer';
3
- import { MFADeviceMethod, MFAMethod } from '../enums/mfa-method.enum';
4
-
5
- /**
6
- * DTO for updating user attributes
7
- *
8
- * Security:
9
- * - All fields validated against DB constraints
10
- * - Input sanitization applied automatically
11
- * - Email uniqueness checked in service layer
12
- * - Phone uniqueness checked in service layer
13
- * - Username uniqueness checked in service layer
14
- *
15
- * @example
16
- * ```typescript
17
- * const updateData: UserUpdateDTO = {
18
- * firstName: 'John',
19
- * lastName: 'Doe',
20
- * email: 'john.doe@example.com',
21
- * phone: '+61444567890'
22
- * };
23
- * ```
24
- */
25
- export class UserUpdateDTO {
26
- /**
27
- * Optional username update
28
- *
29
- * Validation:
30
- * - 3-50 characters
31
- * - Alphanumeric, underscores, and hyphens only
32
- * - Max 255 characters (DB limit)
33
- * - Uniqueness checked in service layer
34
- *
35
- * Sanitization:
36
- * - Trimmed
37
- * - Case preserved (username can be case-sensitive per config)
38
- */
39
- @IsOptional()
40
- @IsString({ message: 'Username must be a string' })
41
- @MinLength(3, { message: 'Username must be at least 3 characters' })
42
- @MaxLength(255, { message: 'Username must not exceed 255 characters' })
43
- @Matches(/^[a-zA-Z0-9_-]+$/, {
44
- message: 'Username can only contain letters, numbers, underscores, and hyphens',
45
- })
46
- @Transform(({ value }) => {
47
- if (typeof value === 'string') {
48
- return value.trim();
49
- }
50
- return value;
51
- })
52
- username?: string;
53
-
54
- /**
55
- * Optional first name update
56
- *
57
- * Validation:
58
- * - 1-100 characters
59
- * - Letters, spaces, hyphens, and apostrophes only
60
- * - Max 100 characters (DB limit)
61
- *
62
- * Sanitization:
63
- * - Trimmed
64
- * - Title case preserved
65
- */
66
- @IsOptional()
67
- @IsString({ message: 'First name must be a string' })
68
- @MinLength(1, { message: 'First name must be at least 1 character' })
69
- @MaxLength(100, { message: 'First name must not exceed 100 characters' })
70
- @Matches(/^[a-zA-Z\s\-']+$/, {
71
- message: 'First name can only contain letters, spaces, hyphens, and apostrophes',
72
- })
73
- @Transform(({ value }) => {
74
- if (typeof value === 'string') {
75
- return value.trim();
76
- }
77
- return value;
78
- })
79
- firstName?: string;
80
-
81
- /**
82
- * Optional last name update
83
- *
84
- * Validation:
85
- * - 1-100 characters
86
- * - Letters, spaces, hyphens, and apostrophes only
87
- * - Max 100 characters (DB limit)
88
- *
89
- * Sanitization:
90
- * - Trimmed
91
- * - Title case preserved
92
- */
93
- @IsOptional()
94
- @IsString({ message: 'Last name must be a string' })
95
- @MinLength(1, { message: 'Last name must be at least 1 character' })
96
- @MaxLength(100, { message: 'Last name must not exceed 100 characters' })
97
- @Matches(/^[a-zA-Z\s\-']+$/, {
98
- message: 'Last name can only contain letters, spaces, hyphens, and apostrophes',
99
- })
100
- @Transform(({ value }) => {
101
- if (typeof value === 'string') {
102
- return value.trim();
103
- }
104
- return value;
105
- })
106
- lastName?: string;
107
-
108
- /**
109
- * Optional email address update
110
- *
111
- * Validation:
112
- * - Valid email format (RFC 5322)
113
- * - Max 255 characters (matches DB limit)
114
- * - Uniqueness checked in service layer
115
- *
116
- * Sanitization:
117
- * - Trimmed and lowercased
118
- */
119
- @IsOptional()
120
- @IsEmail({}, { message: 'Invalid email format' })
121
- @MaxLength(255, { message: 'Email must not exceed 255 characters' })
122
- @Transform(({ value }) => {
123
- if (typeof value === 'string') {
124
- return value.trim().toLowerCase();
125
- }
126
- return value;
127
- })
128
- email?: string;
129
-
130
- /**
131
- * Optional phone number update
132
- *
133
- * Validation:
134
- * - E.164 format (international standard)
135
- * - MUST start with + (required for security)
136
- * - Max 20 characters (DB limit)
137
- * - Uniqueness checked in service layer
138
- *
139
- * Sanitization:
140
- * - Whitespace removed
141
- * - Only digits and leading + preserved
142
- *
143
- * Security:
144
- * - Strict E.164 validation prevents SQL injection
145
- * - Max length prevents oversized inputs
146
- */
147
- @IsOptional()
148
- @IsString({ message: 'Phone must be a string' })
149
- @MaxLength(20, { message: 'Phone must not exceed 20 characters' })
150
- @Matches(/^\+[1-9]\d{1,14}$/, {
151
- message: 'Phone must be in E.164 format with + prefix (e.g., +14155552671)',
152
- })
153
- @Transform(({ value }) => {
154
- if (typeof value === 'string') {
155
- // Remove all whitespace and keep only digits and +
156
- return value.replace(/\s/g, '');
157
- }
158
- return value;
159
- })
160
- phone?: string;
161
-
162
- /**
163
- * Optional metadata update (custom fields)
164
- *
165
- * Security:
166
- * - Validated in service layer if used
167
- * - Max depth/size limits should be enforced
168
- * - Existing metadata merged with new values
169
- */
170
- @IsOptional()
171
- metadata?: Record<string, unknown>;
172
-
173
- /**
174
- * Optional preferred MFA method
175
- *
176
- * Sets the user's preferred MFA method for authentication.
177
- * Must be one of the MFA device methods the user has configured.
178
- *
179
- * Validation:
180
- * - Must be one of: totp, sms, email, passkey
181
- * - Max 50 characters (matches typical method name length)
182
- *
183
- * @example
184
- * ```typescript
185
- * await authService.updateUserAttributes(userId, {
186
- * preferredMfaMethod: 'totp'
187
- * });
188
- * ```
189
- */
190
- @IsOptional()
191
- @IsEnum([MFAMethod.TOTP, MFAMethod.SMS, MFAMethod.EMAIL, MFAMethod.PASSKEY], {
192
- message: 'Preferred MFA method must be one of: totp, sms, email, passkey',
193
- })
194
- @MaxLength(50, { message: 'Preferred MFA method must not exceed 50 characters' })
195
- preferredMfaMethod?: MFADeviceMethod;
196
-
197
- /**
198
- * Optional flag to retain verification status when updating email/phone
199
- *
200
- * When true:
201
- * - Email verification status is preserved when email is updated
202
- * - Phone verification status is preserved when phone is updated
203
- * - Useful when verification was done externally or outside nauth-toolkit
204
- *
205
- * When false or undefined (default):
206
- * - Email verification is reset to false when email is updated
207
- * - Phone verification is reset to false when phone is updated
208
- * - User must re-verify the new email/phone
209
- *
210
- * @example
211
- * ```typescript
212
- * // Update email but keep verification status (external verification)
213
- * await authService.updateUserAttributes(userId, {
214
- * email: 'new@example.com',
215
- * retainVerification: true
216
- * });
217
- * ```
218
- */
219
- @IsOptional()
220
- @IsBoolean({ message: 'retainVerification must be a boolean' })
221
- retainVerification?: boolean;
222
- }
@@ -1,313 +0,0 @@
1
- import {
2
- IsEmail,
3
- IsString,
4
- IsNumberString,
5
- IsUrl,
6
- MaxLength,
7
- Length,
8
- Matches,
9
- IsBoolean,
10
- IsOptional,
11
- IsUUID,
12
- IsInt,
13
- Min,
14
- } from 'class-validator';
15
- import { Transform } from 'class-transformer';
16
-
17
- /**
18
- * DTO for verifying email with code (6-digit OTP)
19
- *
20
- * Security:
21
- * - Email must be valid format and match DB limits
22
- * - Code must be exactly 6 digits (no more, no less)
23
- * - All fields are required (no optional fields to prevent attacks)
24
- * - Input sanitization applied automatically
25
- */
26
- export class VerifyEmailWithCodeDTO {
27
- /**
28
- * User's email address
29
- * Must match the email used during signup
30
- *
31
- * Validation:
32
- * - Valid email format (RFC 5322)
33
- * - Max 255 characters (matches DB column limit)
34
- * - Automatically trimmed and lowercased
35
- *
36
- * Sanitization:
37
- * - Removes leading/trailing whitespace
38
- * - Converts to lowercase for case-insensitive matching
39
- */
40
- @IsEmail({}, { message: 'Invalid email format' })
41
- @MaxLength(255, { message: 'Email must not exceed 255 characters' })
42
- @Transform(({ value }) => {
43
- if (typeof value === 'string') {
44
- return value.trim().toLowerCase();
45
- }
46
- return value;
47
- })
48
- email!: string;
49
-
50
- /**
51
- * 6-digit verification code from email
52
- *
53
- * Validation:
54
- * - Must be numeric string (digits only)
55
- * - Exactly 6 characters long
56
- * - Fixed length prevents timing attacks
57
- *
58
- * Sanitization:
59
- * - Removes all whitespace (users might copy "123 456")
60
- * - Removes non-digit characters
61
- */
62
- @IsNumberString({}, { message: 'Verification code must contain only digits' })
63
- @MaxLength(6, { message: 'Verification code must be exactly 6 digits' })
64
- @Transform(({ value }) => {
65
- if (typeof value === 'string') {
66
- // Remove all whitespace and non-digit characters, then validate length
67
- const cleaned = value.replace(/\D/g, '');
68
- return cleaned.length === 6 ? cleaned : value; // Return original if not 6 digits (let validator catch it)
69
- }
70
- return value;
71
- })
72
- code!: string;
73
-
74
- /**
75
- * Challenge session ID (internal use)
76
- * Optional - used internally to link verification to specific challenge session.
77
- * Provides security by ensuring codes are only valid for the session they were created for.
78
- *
79
- * Validation:
80
- * - Must be a positive integer if provided
81
- * - Optional (for backward compatibility and direct verification flows)
82
- */
83
- @IsOptional()
84
- @IsInt({ message: 'challengeSessionId must be an integer' })
85
- @Min(1, { message: 'challengeSessionId must be a positive integer' })
86
- challengeSessionId?: number;
87
- }
88
-
89
- /**
90
- * DTO for verifying email with URL token
91
- *
92
- * Security:
93
- * - Token must be valid hex format
94
- * - Exact length enforced (64 chars = 32 bytes SHA-256 hash)
95
- * - No SQL injection or XSS possible
96
- * - Input sanitization prevents malformed tokens
97
- */
98
- export class VerifyEmailWithTokenDTO {
99
- /**
100
- * Verification token from email link
101
- *
102
- * Validation:
103
- * - Exactly 64 hexadecimal characters (SHA-256 hash output)
104
- * - Only 0-9 and a-f characters allowed
105
- * - Case-insensitive
106
- *
107
- * Sanitization:
108
- * - Removes whitespace
109
- * - Converts to lowercase for consistent hashing
110
- */
111
- @IsString({ message: 'Token must be a string' })
112
- @Length(64, 64, { message: 'Invalid token format' })
113
- @Matches(/^[a-f0-9]{64}$/i, {
114
- message: 'Token must be a valid hexadecimal string',
115
- })
116
- @Transform(({ value }) => {
117
- if (typeof value === 'string') {
118
- return value.trim().toLowerCase();
119
- }
120
- return value;
121
- })
122
- token!: string;
123
- }
124
-
125
- /**
126
- * DTO for sending a verification email
127
- *
128
- * Security:
129
- * - User sub validated as UUID v4
130
- * - BaseURL validated as max length
131
- * - Skip flag is boolean (prevents injection)
132
- */
133
- export class SendVerificationEmailDTO {
134
- /**
135
- * User identifier (UUID v4)
136
- *
137
- * Validation:
138
- * - Must be valid UUID v4 format
139
- *
140
- * Sanitization:
141
- * - Trimmed and lowercased
142
- */
143
- @IsUUID('4', { message: 'User ID must be a valid UUID v4 format' })
144
- @Transform(({ value }) => {
145
- if (typeof value === 'string') {
146
- return value.trim().toLowerCase();
147
- }
148
- return value;
149
- })
150
- sub!: string;
151
-
152
- /**
153
- * Base URL for verification link (optional)
154
- *
155
- * Validation:
156
- * - Must be valid URL format (http:// or https://)
157
- * - Max 2048 characters (typical URL length limit)
158
- * - Optional field
159
- *
160
- * Sanitization:
161
- * - Trimmed
162
- */
163
- @IsOptional()
164
- @IsUrl(
165
- { require_protocol: true, protocols: ['http', 'https'] },
166
- { message: 'Base URL must be a valid URL with http:// or https://' },
167
- )
168
- @MaxLength(2048, { message: 'Base URL must not exceed 2048 characters' })
169
- @Transform(({ value }) => {
170
- if (typeof value === 'string') {
171
- return value.trim();
172
- }
173
- return value;
174
- })
175
- baseUrl?: string;
176
-
177
- /**
178
- * Skip the "already verified" check
179
- * Used for MFA contexts where codes are needed even if email is verified
180
- *
181
- * Validation:
182
- * - Must be boolean
183
- * - Optional (defaults to false)
184
- */
185
- @IsOptional()
186
- @IsBoolean({ message: 'skipAlreadyVerifiedCheck must be a boolean' })
187
- skipAlreadyVerifiedCheck?: boolean;
188
-
189
- /**
190
- * Challenge session ID to link this verification token to
191
- * Optional - for linking verification tokens to specific challenge sessions.
192
- * Provides security by preventing old tokens from being used with new sessions.
193
- *
194
- * Validation:
195
- * - Must be a positive integer
196
- * - Optional (for backward compatibility and non-challenge flows like password reset)
197
- */
198
- @IsOptional()
199
- @IsInt({ message: 'challengeSessionId must be an integer' })
200
- @Min(1, { message: 'challengeSessionId must be a positive integer' })
201
- challengeSessionId?: number;
202
- }
203
-
204
- /**
205
- * Response DTO for sendVerificationEmail
206
- */
207
- export class SendVerificationEmailResponseDTO {
208
- /**
209
- * Verification token ID (internal integer)
210
- */
211
- tokenId!: number;
212
- }
213
-
214
- /**
215
- * DTO for requesting a verification email resend
216
- *
217
- * Supports both overload patterns:
218
- * 1. Resend by user sub (string)
219
- * 2. Resend by email address (object with email property)
220
- *
221
- * Security:
222
- * - Either sub or email must be provided (conditional validation)
223
- * - Rate limiting applied in service layer
224
- * - Input sanitization prevents abuse
225
- */
226
- export class ResendVerificationEmailDTO {
227
- /**
228
- * User identifier (UUID v4) - optional if email provided
229
- *
230
- * Validation:
231
- * - Must be valid UUID v4 format if provided
232
- * - Required if email is not provided
233
- *
234
- * Sanitization:
235
- * - Trimmed and lowercased
236
- */
237
- @IsOptional()
238
- @IsUUID('4', { message: 'User ID must be a valid UUID v4 format' })
239
- @Transform(({ value }) => {
240
- if (typeof value === 'string') {
241
- return value.trim().toLowerCase();
242
- }
243
- return value;
244
- })
245
- sub?: string;
246
-
247
- /**
248
- * User's email address - optional if sub provided
249
- *
250
- * Validation:
251
- * - Valid email format if provided
252
- * - Max 255 characters (DB limit)
253
- * - Required if sub is not provided
254
- *
255
- * Sanitization:
256
- * - Trimmed and lowercased
257
- */
258
- @IsOptional()
259
- @IsEmail({}, { message: 'Invalid email format' })
260
- @MaxLength(255, { message: 'Email must not exceed 255 characters' })
261
- @Transform(({ value }) => {
262
- if (typeof value === 'string') {
263
- return value.trim().toLowerCase();
264
- }
265
- return value;
266
- })
267
- email?: string;
268
-
269
- /**
270
- * Base URL for verification link (optional)
271
- *
272
- * Validation:
273
- * - Must be valid URL format (http:// or https://)
274
- * - Max 2048 characters
275
- * - Optional field
276
- *
277
- * Sanitization:
278
- * - Trimmed
279
- */
280
- @IsOptional()
281
- @IsUrl(
282
- { require_protocol: true, protocols: ['http', 'https'] },
283
- { message: 'Base URL must be a valid URL with http:// or https://' },
284
- )
285
- @MaxLength(2048, { message: 'Base URL must not exceed 2048 characters' })
286
- @Transform(({ value }) => {
287
- if (typeof value === 'string') {
288
- return value.trim();
289
- }
290
- return value;
291
- })
292
- baseUrl?: string;
293
- }
294
-
295
- /**
296
- * Response DTO for resendVerificationEmail
297
- */
298
- export class ResendVerificationEmailResponseDTO {
299
- /**
300
- * Verification token ID (internal integer)
301
- */
302
- tokenId!: number;
303
- }
304
-
305
- /**
306
- * Response DTO for verifyEmailWithCode and verifyEmailWithToken
307
- */
308
- export class VerifyEmailResponseDTO {
309
- /**
310
- * Success message
311
- */
312
- message!: string;
313
- }