Skip to content

Commit

Permalink
feat: add module resend
Browse files Browse the repository at this point in the history
  • Loading branch information
DeVoresyah committed Dec 30, 2023
1 parent 26a9b62 commit 4093412
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 4 deletions.
95 changes: 91 additions & 4 deletions app/Controllers/Http/v1/Auth/AuthsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import RegisterWithPasswordValidator from 'App/Validators/v1/Auth/RegisterWithPa
import LoginWithPasswordValidator from 'App/Validators/v1/Auth/LoginWithPasswordValidator'
import LoginWithOtpValidator from 'App/Validators/v1/Auth/LoginWithOtpValidator'
import LogoutValidator from 'App/Validators/v1/Auth/LogoutValidator'
import ResendValidator from 'App/Validators/v1/Auth/ResendValidator'

// Models
import User from 'App/Models/User'
Expand All @@ -42,7 +43,7 @@ export default class AuthsController {
const payload = await request.validate(RegisterWithPasswordValidator)

if (!payload.email && !payload.phone) {
return response.api({ message: 'Credential cannot be empty.' }, StatusCodes.BAD_REQUEST)
return response.api({ message: 'Identity cannot be empty.' }, StatusCodes.BAD_REQUEST)
}

const otpCode = StringTransform.generateOtpNumber()
Expand Down Expand Up @@ -96,7 +97,7 @@ export default class AuthsController {

if (result.user && result.identity) {
if (payload.email) {
this.mailer.sendVerification(payload.email, confirmationToken, 'http://localhost:3333')
this.mailer.sendVerification(payload.email, confirmationToken, Env.get('APP_URL'))
} else {
if (payload.channel === 'whatsapp') {
this.twilio.sendOtpWhatsapp(otpCode, payload.phone!)
Expand All @@ -118,7 +119,93 @@ export default class AuthsController {
{
message: 'Failed to create new account.',
},
StatusCodes.BAD_REQUEST
StatusCodes.INTERNAL_SERVER_ERROR
)
}
}

public async resend({ request, response }: HttpContextContract) {
const payload = await request.validate(ResendValidator)

if (!payload.email && !payload.phone) {
return response.api({ message: 'Identity cannot be empty.' }, StatusCodes.BAD_REQUEST)
}

const userQuery = User.query()

if (payload.email) {
userQuery.where('email', payload.email)
}

if (payload.phone) {
userQuery.where('phone', payload.phone)
}

const user = await userQuery.first()

if (!user) {
return response.api({ message: 'Invalid user account.' }, StatusCodes.UNPROCESSABLE_ENTITY)
}

if (payload.type === 'signup') {
if (payload.email && user.emailConfirmedAt) {
return response.api(
{ message: 'Email already confirmed.' },
StatusCodes.UNPROCESSABLE_ENTITY
)
}

if (payload.phone && user.phoneConfirmedAt) {
return response.api(
{ message: 'Phone already confirmed.' },
StatusCodes.UNPROCESSABLE_ENTITY
)
}
}

if (payload.type === 'signup' && !StringTransform.isResendAvailable(user.confirmationSentAt)) {
return response.api(
{ message: 'You can resend new confirmation after 2 minutes.' },
StatusCodes.TOO_MANY_REQUESTS
)
}

const otpCode = StringTransform.generateOtpNumber()
const confirmationToken = this.md5.generate(otpCode)

try {
user.confirmationToken = confirmationToken
await user.save()

if (payload.email) {
this.mailer.sendVerification(
payload.email,
confirmationToken,
payload.options?.redirect_uri ?? Env.get('APP_URL')
)
} else {
if (payload.options?.channel === 'whatsapp') {
this.twilio.sendOtpWhatsapp(otpCode, payload.phone!)
} else {
this.twilio.sendOtpSms(otpCode, payload.phone!)
}
}

user.confirmationSentAt = DateTime.now()
await user.save()

return response.api(
{
message: payload.email
? `We've sent new verification link to ${payload.email}.`
: `We've sent new verification code to ${payload.phone}.`,
},
StatusCodes.OK
)
} catch (e) {
return response.api(
{ message: 'Failed to resend new confirmation.' },
StatusCodes.INTERNAL_SERVER_ERROR
)
}
}
Expand Down Expand Up @@ -229,7 +316,7 @@ export default class AuthsController {
const userQuery = User.query()

if (!payload.email && !payload.phone) {
return response.api({ message: 'Credential cannot be empty.' }, StatusCodes.BAD_REQUEST)
return response.api({ message: 'Identity cannot be empty.' }, StatusCodes.BAD_REQUEST)
}

if (payload.email) {
Expand Down
1 change: 1 addition & 0 deletions app/Controllers/Http/v1/Auth/VerifiesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export default class VerifiesController {
access_token: userToken.token,
expires_at: expiresAt,
refresh_token: newSession.refreshToken.token,
type: payload.type,
status: 'success',
})
.toPath(payload.redirect)
Expand Down
11 changes: 11 additions & 0 deletions app/Helpers/StringTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,15 @@ export default class StringTransform {
// Check if the difference is more than 60 minutes
return diffInMinutes > 60
}

public static isResendAvailable(confirmationSentAt: DateTime<boolean>) {
const sentAt = DateTime.fromISO(confirmationSentAt.toString())
const now = DateTime.now()

// Calculate the difference in minutes
const diffInMinutes = now.diff(sentAt, 'minutes').minutes

// Check if the difference is more than 2 minutes
return diffInMinutes >= 2
}
}
18 changes: 18 additions & 0 deletions app/Validators/v1/Auth/ResendValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { schema, rules, CustomMessages } from '@ioc:Adonis/Core/Validator'
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class ResendValidator {
constructor(protected ctx: HttpContextContract) {}

public schema = schema.create({
email: schema.string.optional({}, [rules.email()]),
phone: schema.string.optional(),
type: schema.enum(['signup', 'email_change', 'phone_change']),
options: schema.object.optional().members({
redirect_uri: schema.string.optional(),
channel: schema.enum.optional(['sms', 'whatsapp']),
}),
})

public messages: CustomMessages = {}
}
1 change: 1 addition & 0 deletions routes/auth/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Route.group(() => {
Route.post('/register', 'AuthsController.signUpWithPassword')
Route.post('/login/password', 'AuthsController.signInWithPassword')
Route.post('/login/otp', 'AuthsController.signInWithOtp')
Route.post('/resend', 'AuthsController.resend')
Route.delete('/logout', 'AuthsController.signOut').middleware('userSession')
require('./verify')
})
Expand Down

0 comments on commit 4093412

Please sign in to comment.