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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ DOMAIN=localhost
FEEDBACK_FORWARD_EMAIL=

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

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
4 changes: 3 additions & 1 deletion 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,6 +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],
Expand All @@ -38,6 +39,7 @@ import { WorkspaceMembershipModule } from '@/workspace-membership/workspace-memb
abortEarly: true
}
}),

ScheduleModule.forRoot(),
PassportModule,
AuthModule,
Expand Down
14 changes: 13 additions & 1 deletion apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { GoogleOAuthStrategyFactory } from '@/config/factory/google/google-strat
import { GoogleStrategy } from '@/config/oauth-strategy/google/google.strategy'
import { GitlabOAuthStrategyFactory } from '@/config/factory/gitlab/gitlab-strategy.factory'
import { GitlabStrategy } from '@/config/oauth-strategy/gitlab/gitlab.strategy'
import { seconds, ThrottlerModule } from '@nestjs/throttler'
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
imports: [
Expand All @@ -21,7 +23,17 @@ import { GitlabStrategy } from '@/config/oauth-strategy/gitlab/gitlab.strategy'
algorithm: 'HS256'
}
}),
UserModule
UserModule,
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => [
{
ttl: seconds(config.get('THROTTLE_TTL')),
Prakhargarg-2010196 marked this conversation as resolved.
Show resolved Hide resolved
limit: config.get('THROTTLE_LIMIT')
}
],
inject: [ConfigService]
})
],
providers: [
AuthService,
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/auth/controller/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
sendOAuthSuccessRedirect
} from '@/common/redirect'
import { setCookie } from '@/common/util'
import { ThrottlerGuard } from '@nestjs/throttler'

@Controller('auth')
export class AuthController {
Expand All @@ -46,6 +47,16 @@ export class AuthController {
await this.authService.sendOtp(email)
}

@Public()
@Post('resend-otp/:email')
@UseGuards(ThrottlerGuard)
async resendOtp(
@Param('email')
email: string
): Promise<void> {
return await this.authService.resendOtp(email)
}

/* istanbul ignore next */
@Public()
@Post('validate-otp')
Expand Down
14 changes: 12 additions & 2 deletions apps/api/src/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,23 @@ export class AuthService {
}

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> {
const user = await getUserByEmailOrId(email, this.prisma)
const otp = await generateOtp(email, user.id, this.prisma)
await this.mailService.sendOtp(email, otp.code)
}

/* istanbul ignore next */
/**
* Validates a login code sent to the given email address
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/common/env/env.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ const generalSchema = z.object({
MINIO_BUCKET_NAME: z.string().optional(),
MINIO_USE_SSL: z.string().optional(),

FEEDBACK_FORWARD_EMAIL: z.string().email()
FEEDBACK_FORWARD_EMAIL: z.string().email(),
THROTTLE_TTL: z.string().transform((val) => parseInt(val, 10)), // Convert string to number
THROTTLE_LIMIT: z.string().transform((val) => parseInt(val, 10)) // Convert string to number
})

export type EnvSchemaType = z.infer<typeof generalSchema>
Expand Down
Loading