@@ -17,6 +17,7 @@ import { isEmpty } from 'lodash';
1717import ms from 'ms' ;
1818import { ClsService } from 'nestjs-cls' ;
1919import { CacheService } from '../../../cache/cache.service' ;
20+ import type { ICacheStore } from '../../../cache/types' ;
2021import { AuthConfig , type IAuthConfig } from '../../../configs/auth.config' ;
2122import { BaseConfig , IBaseConfig } from '../../../configs/base.config' ;
2223import { MailConfig , type IMailConfig } from '../../../configs/mail.config' ;
@@ -161,11 +162,20 @@ export class LocalAuthService {
161162 turnstileToken ?: string ,
162163 remoteIp ?: string
163164 ) : Promise < void > {
164- if ( ! this . turnstileService . isTurnstileEnabled ( ) ) {
165- return ; // Turnstile is not enabled, skip validation
165+ const isTurnstileEnabled = this . turnstileService . isTurnstileEnabled ( ) ;
166+
167+ this . logger . log (
168+ `Turnstile validation check - enabled: ${ isTurnstileEnabled } , hasToken: ${ ! ! turnstileToken } , tokenLength: ${ turnstileToken ?. length } , remoteIp: ${ remoteIp } `
169+ ) ;
170+
171+ if ( ! isTurnstileEnabled ) {
172+ return ;
166173 }
167174
168175 if ( ! turnstileToken ) {
176+ this . logger . error (
177+ `Turnstile token is missing - enabled: ${ isTurnstileEnabled } , remoteIp: ${ remoteIp } `
178+ ) ;
169179 throw new BadRequestException ( 'Turnstile token is required' ) ;
170180 }
171181
@@ -202,17 +212,15 @@ export class LocalAuthService {
202212
203213 throw new BadRequestException ( errorMessage ) ;
204214 }
205-
206- this . logger . debug ( 'Turnstile validation successful' , {
207- hostname : validation . data ?. hostname ,
208- action : validation . data ?. action ,
209- } ) ;
210215 }
211216
212217 async signup ( body : ISignup , remoteIp ?: string ) {
213218 const { email, password, defaultSpaceName, refMeta, inviteCode, turnstileToken } = body ;
214219
215- // Validate Turnstile token if enabled
220+ this . logger . log (
221+ `Signup attempt - email: ${ email } , hasPassword: ${ ! ! password } , hasTurnstileToken: ${ ! ! turnstileToken } , tokenLength: ${ turnstileToken ?. length } , hasVerification: ${ ! ! body . verification } , remoteIp: ${ remoteIp } `
222+ ) ;
223+
216224 await this . validateTurnstileIfEnabled ( turnstileToken , remoteIp ) ;
217225
218226 await this . verifySignup ( body ) ;
@@ -251,18 +259,54 @@ export class LocalAuthService {
251259 return res ;
252260 }
253261
254- async sendSignupVerificationCode ( email : string ) {
262+ async sendSignupVerificationCode ( email : string , turnstileToken ?: string , remoteIp ?: string ) {
263+ this . logger . log (
264+ `Send verification code attempt - email: ${ email } , hasTurnstileToken: ${ ! ! turnstileToken } , tokenLength: ${ turnstileToken ?. length } , remoteIp: ${ remoteIp } `
265+ ) ;
266+
267+ // Validate Turnstile token if enabled
268+ await this . validateTurnstileIfEnabled ( turnstileToken , remoteIp ) ;
269+
270+ // Check rate limit: ensure interval between emails for the same address
271+ // Backend rate limit is configured limit - 2 seconds (to account for network latency)
272+ // If configured limit is 0, skip rate limiting entirely
273+ const configuredLimit = this . authConfig . signupVerificationCodeRateLimitSeconds ;
274+ const backendRateLimit = configuredLimit > 0 ? configuredLimit - 2 : 0 ;
275+
276+ if ( backendRateLimit > 0 ) {
277+ const rateLimitKey = `signup-verification-rate-limit:${ email } ` as keyof ICacheStore ;
278+ const existingRateLimit = await this . cacheService . get ( rateLimitKey ) ;
279+
280+ if ( existingRateLimit ) {
281+ this . logger . warn (
282+ `Signup verification rate limit exceeded - email: ${ email } , remoteIp: ${ remoteIp } , timestamp: ${ new Date ( ) . toISOString ( ) } `
283+ ) ;
284+ throw new BadRequestException (
285+ `Please wait ${ configuredLimit } seconds before requesting a new code`
286+ ) ;
287+ }
288+ }
289+
255290 const code = getRandomString ( 4 , RandomType . Number ) ;
256291 const token = await this . jwtSignupCode ( email , code ) ;
292+
257293 if ( this . baseConfig . enableEmailCodeConsole ) {
258294 console . info ( 'Signup Verification code: ' , '\x1b[34m' + code + '\x1b[0m' ) ;
259295 }
296+
260297 const user = await this . userService . getUserByEmail ( email ) ;
261298 this . isRegisteredValidate ( user ) ;
299+
300+ // Log verification code sending
301+ this . logger . log (
302+ `Sending signup verification code - email: ${ email } , remoteIp: ${ remoteIp } , timestamp: ${ new Date ( ) . toISOString ( ) } , turnstileVerified: ${ ! ! turnstileToken } `
303+ ) ;
304+
262305 const emailOptions = await this . mailSenderService . sendEmailVerifyCodeEmailOptions ( {
263306 title : 'Signup verification' ,
264307 message : `Your verification code is ${ code } , expires in ${ this . authConfig . signupVerificationExpiresIn } .` ,
265308 } ) ;
309+
266310 await this . mailSenderService . sendMail (
267311 {
268312 to : email ,
@@ -274,6 +318,17 @@ export class LocalAuthService {
274318 transporterName : MailTransporterType . Notify ,
275319 }
276320 ) ;
321+
322+ // Set rate limit using setDetail for exact TTL without random addition
323+ if ( backendRateLimit > 0 ) {
324+ const rateLimitKey = `signup-verification-rate-limit:${ email } ` as keyof ICacheStore ;
325+ await this . cacheService . setDetail (
326+ rateLimitKey ,
327+ { email, timestamp : Date . now ( ) } ,
328+ backendRateLimit
329+ ) ;
330+ }
331+
277332 return {
278333 token,
279334 expiresTime : new Date (
0 commit comments