Skip to content

Commit

Permalink
fixes and refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
rayaanoidPrime committed May 21, 2024
1 parent 160fe90 commit 7b90b09
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 136 deletions.
15 changes: 9 additions & 6 deletions apps/api/src/common/generate-otp.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { PrismaClient, User } from '@prisma/client'
import { randomUUID } from 'crypto'
import { Otp, PrismaClient, User } from '@prisma/client'

const OTP_EXPIRY = 5 * 60 * 1000 // 5 minutes

export default async function generateOtp(
email: User['email'],
userId: User['id'],
prisma: PrismaClient
): Promise<{ code: string; expiresAt: Date }> {
): Promise<Otp> {
const otp = await prisma.otp.upsert({
where: {
userId: userId
},
update: {
code: randomUUID().slice(0, 6).toUpperCase(),
code: BigInt(`0x${crypto.randomUUID().replace(/-/g, '')}`)
.toString()
.substring(0, 6),
expiresAt: new Date(new Date().getTime() + OTP_EXPIRY)
},
create: {
code: randomUUID().slice(0, 6).toUpperCase(),
code: BigInt(`0x${crypto.randomUUID().replace(/-/g, '')}`)
.toString()
.substring(0, 6),
expiresAt: new Date(new Date().getTime() + OTP_EXPIRY),
user: {
connect: {
Expand All @@ -27,5 +30,5 @@ export default async function generateOtp(
}
})

return { code: otp.code, expiresAt: otp.expiresAt }
return otp
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Warnings:
- The required column `id` was added to the `Otp` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
*/
-- AlterTable
ALTER TABLE "Otp" ADD COLUMN "id" TEXT NOT NULL,
ADD CONSTRAINT "Otp_pkey" PRIMARY KEY ("id");

-- CreateTable
CREATE TABLE "UserEmailChange" (
"id" TEXT NOT NULL,
"otpId" TEXT NOT NULL,
"newEmail" TEXT NOT NULL,

CONSTRAINT "UserEmailChange_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "UserEmailChange_otpId_key" ON "UserEmailChange"("otpId");

-- AddForeignKey
ALTER TABLE "UserEmailChange" ADD CONSTRAINT "UserEmailChange_otpId_fkey" FOREIGN KEY ("otpId") REFERENCES "Otp"("id") ON DELETE CASCADE ON UPDATE CASCADE;
26 changes: 11 additions & 15 deletions apps/api/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,13 @@ model ApiKey {
}

model Otp {
code String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String @unique
createdAt DateTime @default(now())
expiresAt DateTime
id String @id @default(cuid())
code String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String @unique
createdAt DateTime @default(now())
expiresAt DateTime
emailChange UserEmailChange?
@@unique([userId, code], name: "userCode")
@@index([expiresAt], name: "expiresAt")
Expand Down Expand Up @@ -520,14 +522,8 @@ model ChangeNotificationSocketMap {
}

model UserEmailChange {
id String @id @default(cuid())
userId String
newEmail String
otp String
createdOn DateTime @default(now())
expiresOn DateTime
@@unique([userId,otp])
@@index([expiresOn], name:"expiresOn")
id String @id @default(cuid())
otp Otp @relation(fields: [otpId], references: [id], onDelete: Cascade, onUpdate: Cascade)
otpId String @unique
newEmail String
}
83 changes: 33 additions & 50 deletions apps/api/src/user/service/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,16 @@ export class UserService {

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

await this.prisma.userEmailChange.create({
data: {
await this.prisma.userEmailChange.upsert({
where: {
otpId: otp.id
},
update: {
newEmail: dto.email
},
create: {
newEmail: dto.email,
userId: user.id,
otp: otp.code,
expiresOn: otp.expiresAt
otpId: otp.id
}
})

Expand Down Expand Up @@ -97,23 +101,14 @@ export class UserService {
throw new ConflictException('User with this email already exists')
}

const user = await this.prisma.user.findUnique({
//directly updating email when admin triggered
await this.prisma.user.update({
where: {
id: userId
},
select: {
email: true
}
})

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

await this.prisma.userEmailChange.create({
data: {
newEmail: dto.email,
userId: userId,
otp: otp.code,
expiresOn: otp.expiresAt
email: dto.email,
authProvider: AuthProvider.EMAIL_OTP
}
})
}
Expand All @@ -127,37 +122,34 @@ export class UserService {
})
}

async validateEmailChangeOtp(user: User, otp: string): Promise<User> {
const userEmailChange = await this.prisma.userEmailChange.findUnique({
async validateEmailChangeOtp(user: User, otpCode: string): Promise<User> {
const otp = await this.prisma.otp.findUnique({
where: {
userId_otp: {
userId: user.id,
otp: otp
},
expiresOn: {
gt: new Date()
}
userId: user.id,
code: otpCode
}
})

if (!userEmailChange) {
if (!otp || otp.expiresAt < new Date()) {
this.log.log(`OTP expired or invalid`)
throw new UnauthorizedException('Invalid or expired OTP')
}
const userEmailChange = await this.prisma.userEmailChange.findUnique({
where: {
otpId: otp.id
}
})

const deleteEmailChangeRecord = this.prisma.userEmailChange.delete({
where: {
userId_otp: {
userId: user.id,
otp: otp
}
otpId: otp.id
}
})

const deleteOtp = this.prisma.otp.delete({
where: {
userId: user.id,
code: otp
code: otpCode
}
})

Expand Down Expand Up @@ -187,31 +179,22 @@ export class UserService {
const oldOtp = await this.prisma.otp.findUnique({
where: {
userId: user.id
},
include: {
emailChange: true
}
})

if (!oldOtp) {
throw new ConflictException(`No previous OTP exists for user ${user.id}`)
if (!oldOtp || !oldOtp.emailChange) {
throw new ConflictException(
`No previous OTP for email change exists for user ${user.id}`
)
}

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

const newUserEmailChange = await this.prisma.userEmailChange.update({
where: {
userId_otp: {
userId: user.id,
otp: oldOtp.code
}
},
data: {
otp: newOtp.code,
createdOn: new Date(),
expiresOn: newOtp.expiresAt
}
})

await this.mailService.sendEmailChangedOtp(
newUserEmailChange.newEmail,
oldOtp.emailChange.newEmail,
newOtp.code
)
}
Expand Down
Loading

0 comments on commit 7b90b09

Please sign in to comment.