198 lines
4.6 KiB
TypeScript
198 lines
4.6 KiB
TypeScript
|
|
import { z } from "zod"
|
||
|
|
import { publicProcedure, authProcedure } from "../trpc"
|
||
|
|
import { prisma } from "../utils/prisma"
|
||
|
|
import { comparePasswords, generateToken, hashPassword } from "../utils/auth"
|
||
|
|
import { TRPCError } from "@trpc/server"
|
||
|
|
|
||
|
|
const signUpSchema = z.object({
|
||
|
|
email: z.string().email().min(1, "Email is required"),
|
||
|
|
password: z.string().min(1, "Password is required"),
|
||
|
|
name: z.string().min(1, "Name is required"),
|
||
|
|
})
|
||
|
|
|
||
|
|
export const signup = publicProcedure
|
||
|
|
.input(signUpSchema)
|
||
|
|
.mutation(async ({ input }) => {
|
||
|
|
const { email, password, name } = input
|
||
|
|
|
||
|
|
if (await prisma.user.count()) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "BAD_REQUEST",
|
||
|
|
message: "Bad request",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const existingUser = await prisma.user.findUnique({
|
||
|
|
where: {
|
||
|
|
email,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
if (existingUser) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "BAD_REQUEST",
|
||
|
|
message: `User with email ${email} already exists`,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const hashedPassword = await hashPassword(password)
|
||
|
|
const user = await prisma.user.create({
|
||
|
|
data: {
|
||
|
|
email,
|
||
|
|
password: hashedPassword,
|
||
|
|
name,
|
||
|
|
},
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
pwdVersion: true,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
const token = generateToken(user.id, user.pwdVersion)
|
||
|
|
|
||
|
|
return {
|
||
|
|
token,
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
export const login = publicProcedure
|
||
|
|
.input(
|
||
|
|
z.object({
|
||
|
|
email: z.string().email().min(1, "Email is required"),
|
||
|
|
password: z.string().min(1, "Password is required"),
|
||
|
|
})
|
||
|
|
)
|
||
|
|
.mutation(async ({ input }) => {
|
||
|
|
const { email, password } = input
|
||
|
|
|
||
|
|
const user = await prisma.user.findUnique({
|
||
|
|
where: { email },
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
password: true,
|
||
|
|
pwdVersion: true,
|
||
|
|
UserOrganizations: true,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
if (!user) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "FORBIDDEN",
|
||
|
|
message: "Invalid credentials",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const isValidPassword = await comparePasswords(password, user.password)
|
||
|
|
if (!isValidPassword) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "FORBIDDEN",
|
||
|
|
message: "Invalid credentials",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const token = generateToken(user.id, user.pwdVersion)
|
||
|
|
|
||
|
|
return {
|
||
|
|
token,
|
||
|
|
user,
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
const updateProfileSchema = z.object({
|
||
|
|
name: z.string().min(1, "Name is required."),
|
||
|
|
email: z.string().email("Invalid email address.").toLowerCase(),
|
||
|
|
})
|
||
|
|
|
||
|
|
export const updateProfile = authProcedure
|
||
|
|
.input(updateProfileSchema)
|
||
|
|
.mutation(async ({ ctx, input }) => {
|
||
|
|
const { name, email } = input
|
||
|
|
const userId = ctx.user.id
|
||
|
|
|
||
|
|
const currentUser = await prisma.user.findUnique({
|
||
|
|
where: { id: userId },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (currentUser?.email !== email) {
|
||
|
|
const existingUserWithEmail = await prisma.user.findFirst({
|
||
|
|
where: {
|
||
|
|
email: email,
|
||
|
|
id: { not: userId },
|
||
|
|
},
|
||
|
|
})
|
||
|
|
if (existingUserWithEmail) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "BAD_REQUEST",
|
||
|
|
message: "Email address is already in use by another account.",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const updatedUser = await prisma.user.update({
|
||
|
|
where: { id: userId },
|
||
|
|
data: {
|
||
|
|
name,
|
||
|
|
email: email.toLowerCase(),
|
||
|
|
},
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
name: true,
|
||
|
|
email: true,
|
||
|
|
UserOrganizations: {
|
||
|
|
include: {
|
||
|
|
Organization: true,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
return { user: updatedUser }
|
||
|
|
})
|
||
|
|
|
||
|
|
const changePasswordSchema = z.object({
|
||
|
|
currentPassword: z.string(),
|
||
|
|
newPassword: z.string().min(8, "New password must be at least 8 characters."),
|
||
|
|
})
|
||
|
|
|
||
|
|
export const changePassword = authProcedure
|
||
|
|
.input(changePasswordSchema)
|
||
|
|
.mutation(async ({ ctx, input }) => {
|
||
|
|
const userId = ctx.user.id
|
||
|
|
const { currentPassword, newPassword } = input
|
||
|
|
|
||
|
|
const user = await prisma.user.findUnique({
|
||
|
|
where: { id: userId },
|
||
|
|
select: { password: true, pwdVersion: true },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (!user) {
|
||
|
|
throw new TRPCError({ code: "NOT_FOUND", message: "User not found." })
|
||
|
|
}
|
||
|
|
|
||
|
|
const isValidPassword = await comparePasswords(
|
||
|
|
currentPassword,
|
||
|
|
user.password
|
||
|
|
)
|
||
|
|
if (!isValidPassword) {
|
||
|
|
throw new TRPCError({
|
||
|
|
code: "BAD_REQUEST",
|
||
|
|
message: "Incorrect current password.",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const hashedNewPassword = await hashPassword(newPassword)
|
||
|
|
const newPwdVersion = (user.pwdVersion || 0) + 1
|
||
|
|
|
||
|
|
await prisma.user.update({
|
||
|
|
where: { id: userId },
|
||
|
|
data: {
|
||
|
|
password: hashedNewPassword,
|
||
|
|
pwdVersion: newPwdVersion,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
const newToken = generateToken(userId, newPwdVersion)
|
||
|
|
|
||
|
|
return { success: true, token: newToken }
|
||
|
|
})
|