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

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

NODE_ENV=
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
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
21 changes: 20 additions & 1 deletion apps/api/src/auth/controller/auth.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { GoogleOAuthStrategyFactory } from '@/config/factory/google/google-strat
import { GitlabOAuthStrategyFactory } from '@/config/factory/gitlab/gitlab-strategy.factory'
import { CacheService } from '@/cache/cache.service'
import { REDIS_CLIENT } from '@/provider/redis.provider'
import { ThrottlerGuard, ThrottlerStorage } from '@nestjs/throttler'
import { Reflector } from '@nestjs/core'

describe('AuthController', () => {
let controller: AuthController
Expand Down Expand Up @@ -40,7 +42,21 @@ describe('AuthController', () => {
keys: jest.fn()
}
}
}
},
//Mocked values for throttler
{
provide: ThrottlerGuard,
useValue: { canActivate: jest.fn(() => true) } // Mocking ThrottlerGuard
},
{
provide: 'THROTTLER:MODULE_OPTIONS', // Mocking THROTTLER:MODULE_OPTIONS
useValue: {} // Empty or default value to satisfy dependency
},
{
provide: ThrottlerStorage, // Mocking Symbol(ThrottlerStorage)
useValue: {} // Empty or default value to satisfy dependency
},
Reflector
]
})
.overrideProvider(PrismaService)
Expand All @@ -53,4 +69,7 @@ describe('AuthController', () => {
it('should be defined', () => {
expect(controller).toBeDefined()
})
it('should be defined', () => {
expect(controller).toBeDefined()
})
})
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 @@ -91,7 +91,9 @@ const devSchema = 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
})

const prodSchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/environment/list.environment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BaseCommand from '../base.command'
import ControllerInstance from '@/util/controller-instance'
import {
CommandOption,
type CommandOption,
type CommandActionData,
type CommandArgument
} from 'src/types/command/command.types'
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/src/commands/workspace/list.workspace.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import BaseCommand from '@/commands/base.command'
import { Logger } from '@/util/logger'
import ControllerInstance from '@/util/controller-instance'
import { CommandActionData, CommandOption } from '@/types/command/command.types'
import {
type CommandActionData,
type CommandOption
} from '@/types/command/command.types'
import { PAGINATION_OPTION } from '@/util/pagination-options'

export default class ListWorkspace extends BaseCommand {
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/src/commands/workspace/role/get.role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ export default class GetRoleCommand extends BaseCommand {
)

if (success) {
Logger.info(`Workspace role fetched successfully!`)
Logger.info('Workspace role fetched successfully!')
Logger.info(`Workspace role: ${data.name} (${data.slug})`)
Logger.info(`Description: ${data.description || 'N/A'}`)
Logger.info(`Created at ${data.createdAt}`)
Logger.info(`Updated at ${data.updatedAt}`)
Logger.info(`Authorities:`)
Logger.info('Authorities:')
for (const authority of data.authorities) {
Logger.info(`- ${authority}`)
}
Logger.info(`Projects:`)
Logger.info('Projects:')
for (const project of data.projects) {
Logger.info(`- ${project.project.name} (${project.project.slug})`)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/workspace/role/list.role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import BaseCommand from '@/commands/base.command'
import {
type CommandActionData,
type CommandArgument,
CommandOption
type CommandOption
} from '@/types/command/command.types'
import { Logger } from '@/util/logger'
import ControllerInstance from '@/util/controller-instance'
Expand Down
8 changes: 4 additions & 4 deletions apps/cli/src/commands/workspace/role/update.role.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BaseCommand from '@/commands/base.command'
import {
CommandOption,
type CommandOption,
type CommandActionData,
type CommandArgument
} from '@/types/command/command.types'
Expand Down Expand Up @@ -76,17 +76,17 @@ export default class UpdateRoleCommand extends BaseCommand {
)

if (success) {
Logger.info(`Workspace role updated successfully:`)
Logger.info('Workspace role updated successfully:')
Logger.info(`Workspace role: ${data.name} (${data.slug})`)
Logger.info(`Description: ${data.description || 'N/A'}`)
Logger.info(`Created at ${data.createdAt}`)
Logger.info(`Updated at ${data.updatedAt}`)
Logger.info(`Color code: ${data.colorCode}`)
Logger.info(`Authorities:`)
Logger.info('Authorities:')
for (const authority of data.authorities) {
Logger.info(`- ${authority}`)
}
Logger.info(`Projects:`)
Logger.info('Projects:')
for (const project of data.projects) {
Logger.info(`- ${project.project.name} (${project.project.slug})`)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/util/pagination-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommandOption } from '@/types/command/command.types'
import { type CommandOption } from '@/types/command/command.types'

export const PAGINATION_OPTION: CommandOption[] = [
{
Expand Down
7 changes: 6 additions & 1 deletion apps/platform/src/app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { useRouter } from 'next/navigation'
import { useAtom } from 'jotai'
import Cookies from 'js-cookie'
import { LoadingSVG } from '@public/svg/shared'
import { GithubSVG, GoogleSVG, KeyshadeBigSVG ,GitlabSVG} from '@public/svg/auth'
import {
GithubSVG,
GoogleSVG,
KeyshadeBigSVG,
GitlabSVG
} from '@public/svg/auth'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { authEmailAtom } from '@/store'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/(main)/career/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ColorBGSVG } from '@public/hero'
import Link from 'next/link'
import { ColorBGSVG } from '@public/hero'
import EncryptButton from '@/components/ui/encrypt-btn'

function Career(): React.JSX.Element {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export default class WorkspaceMembershipController {
request: GetMembersRequest,
headers?: Record<string, string>
): Promise<ClientResponse<GetMembersResponse>> {
let url = parsePaginationUrl(
const url = parsePaginationUrl(
`/api/workspace-membership/${request.workspaceSlug}/members`,
request
)
Expand Down
Loading