Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Add resend otp implementation #445

Merged
merged 10 commits into from
Oct 13, 2024
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ DOMAIN=localhost
FEEDBACK_FORWARD_EMAIL=

BACKEND_URL=http://localhost:4200
NEXT_PUBLIC_BACKEND_URL=http://localhost:4200
NEXT_PUBLIC_BACKEND_URL=http://localhost:4200


# Enter time in secs
THROTTLE_TTL=
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
THROTTLE_LIMIT=
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@nestjs/platform-socket.io": "^10.3.7",
"@nestjs/schedule": "^4.0.1",
"@nestjs/swagger": "^7.3.0",
"@nestjs/throttler": "^6.2.1",
"@nestjs/websockets": "^10.3.7",
"@socket.io/redis-adapter": "^8.3.0",
"@supabase/supabase-js": "^2.39.6",
Expand All @@ -50,7 +51,6 @@
"uuid": "^9.0.1"
},
"devDependencies": {
"reflect-metadata": "^0.2.2",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
Expand All @@ -67,6 +67,7 @@
"jest-mock-extended": "^3.0.5",
"prettier": "^3.0.0",
"prisma": "5.19.1",
"reflect-metadata": "^0.2.2",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
Expand Down
18 changes: 16 additions & 2 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { ConfigModule } from '@nestjs/config'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { PassportModule } from '@nestjs/passport'
import { AuthModule } from '@/auth/auth.module'
import { PrismaModule } from '@/prisma/prisma.module'
Expand All @@ -25,7 +25,7 @@ import { IntegrationModule } from '@/integration/integration.module'
import { FeedbackModule } from '@/feedback/feedback.module'
import { CacheModule } from '@/cache/cache.module'
import { WorkspaceMembershipModule } from '@/workspace-membership/workspace-membership.module'

import { seconds, ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'
@Module({
controllers: [AppController],
imports: [
Expand All @@ -38,6 +38,16 @@ import { WorkspaceMembershipModule } from '@/workspace-membership/workspace-memb
abortEarly: true
}
}),
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => [
{
ttl: seconds(config.get('THROTTLE_TTL')),
limit: config.get('THROTTLE_LIMIT')
}
]
}),
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
ScheduleModule.forRoot(),
PassportModule,
AuthModule,
Expand Down Expand Up @@ -67,6 +77,10 @@ import { WorkspaceMembershipModule } from '@/workspace-membership/workspace-memb
{
provide: APP_GUARD,
useClass: ApiKeyGuard
},
{
provide: APP_GUARD,
useClass: ThrottlerGuard
}
]
})
Expand Down
13 changes: 12 additions & 1 deletion apps/api/src/auth/controller/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
sendOAuthSuccessRedirect
} from '@/common/redirect'
import { setCookie } from '@/common/util'

import { seconds, Throttle, ThrottlerGuard } from '@nestjs/throttler'
@Controller('auth')
export class AuthController {
private readonly logger = new Logger(AuthController.name)
Expand All @@ -46,6 +46,17 @@ export class AuthController {
await this.authService.sendOtp(email)
}

@Public()
@Post('resend-otp/:email')
@UseGuards(ThrottlerGuard)
@Throttle({ default: { ttl: seconds(1), limit: 2 } })
async resendOtp(
@Param('email')
email: string
): Promise<void> {
return await this.authService.resendOtp(email)
}

/* istanbul ignore next */
@Public()
@Post('validate-otp')
Expand Down
34 changes: 29 additions & 5 deletions apps/api/src/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,39 @@ export class AuthService {
this.logger.error(`Invalid email address: ${email}`)
throw new BadRequestException('Please enter a valid email address')
}
try {
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
const user = await this.createUserIfNotExists(
email,
AuthProvider.EMAIL_OTP
)
const otp = await generateOtp(email, user.id, this.prisma)
await this.mailService.sendOtp(email, otp.code)
} catch (e) {
throw e
}

const user = await this.createUserIfNotExists(email, AuthProvider.EMAIL_OTP)

const otp = await generateOtp(email, user.id, this.prisma)

await this.mailService.sendOtp(email, otp.code)
this.logger.log(`Login code sent to ${email}`)
}

/**
* resend a login code to the given email address after resend otp button is pressed
* @throws {BadRequestException} If the email address is invalid
* @param email The email address to resend the login code to
*/
async resendOtp(email: string): Promise<void> {
/**
*TODO Resend the otp based on another function send otp but
*TODO with a throttler to rate limit the api
*/
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
try {
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
const user = await getUserByEmailOrId(email, this.prisma)
const otp = await generateOtp(email, user.id, this.prisma)
await this.mailService.sendOtp(email, otp.code)
} catch (e) {
this.logger.error(`Resending OTP failed ${e}`)
}
}

/* istanbul ignore next */
/**
* Validates a login code sent to the given email address
Expand Down
Loading