import { TRPCError } from "@trpc/server"; import { deleteCookie, getCookies, type OptionsType } from "cookies-next"; import { setCookie } from "cookies-next/server"; import { add } from "date-fns"; import { env } from "~/env.mjs"; import type { UsersId } from "~/schemas/public/Users"; import type { Context } from "~/server/api/trpc"; import { ACCESS_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME, signToken, } from "~/server/auth/jwt"; import { db } from "~/server/db"; import { isBanned } from "~/server/services/banlist.service"; import { comparePasswords, generateSalt, hashPassword, } from "~/server/services/password.service"; import { addUser, findUser_byEmail, findUser_byId, findUser_byId_MINI, } from "~/server/services/user.service"; import { checkCtx } from "~/server/utils/ctxChecker"; // [...] Cookie options const cookieOptions: OptionsType = { httpOnly: true, path: "/", sameSite: "lax", secure: env.NODE_ENV === "production", }; const accessTokenCookieOptions: OptionsType = { ...cookieOptions, expires: add(new Date(), { hours: 1 }), }; const refreshTokenCookieOptions: OptionsType = { ...cookieOptions, expires: add(new Date(), { days: 1 }), }; export const createUserAdmin = async ({ email, password, nome, cognome, telefono, }: { email: string; password: string; nome: string; cognome: string; telefono: string; }) => { const banned = await isBanned({ data: { email }, db }); if (banned) { throw new TRPCError({ code: "FORBIDDEN", message: "Utente bannato", }); } const salt = generateSalt(); const hashedPassword = await hashPassword(password, salt); const user = await addUser({ data: { cognome: cognome.trim(), email, isAdminMade: true, isVerified: true, nome: nome.trim(), password: hashedPassword, salt, telefono, username: `${nome.trim()} ${cognome.trim()}`, }, db, }); if (!user) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore in fase di registrazione", }); } return { status: "success" as const }; }; export const logIn = async ({ email, password, ctx: { req, res }, }: { email: string; password: string; ctx: Context; }) => { try { checkCtx({ req, res }); // Get the user from the collection const user = await findUser_byEmail({ email }); if (!user) { throw new TRPCError({ code: "NOT_FOUND", message: "Errore in Login: Utente non trovato", }); } //Check if user is blocked or unverified if (user.isBlocked) { throw new TRPCError({ code: "FORBIDDEN", message: "Errore in Login: Utente bloccato", }); } // If Admin and password is "changeme", skip password check const skipPwCheck = user.isAdmin && user.password === "changeme"; if (!skipPwCheck) { const passworIsMatch = await comparePasswords({ hashedPassword: user.password, password, salt: user.salt, }); if (!passworIsMatch) { throw new TRPCError({ code: "BAD_REQUEST", message: "Errore in Login: Invalid email or password", }); } } const payload = { email: user.email, id: user.id, isAdmin: user.isAdmin, isBlocked: user.isBlocked, nome: user.nome, username: user.username, }; const accessToken = await signToken(payload, "1h", "accessToken"); const refreshToken = await signToken(payload, "1d", "refreshToken"); // Send Access Token in Cookie await setCookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, { ...accessTokenCookieOptions, req, res, }); await setCookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken, { ...refreshTokenCookieOptions, req, res, }); const cookies = await getCookies({ req, res }); if (!cookies) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore durante il login, cookies non impostati", }); } if (!cookies[ACCESS_TOKEN_COOKIE_NAME]) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore durante il login, access token non impostato", }); } if (!cookies[REFRESH_TOKEN_COOKIE_NAME]) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore durante il login, refresh token non impostato", }); } return { status: "success" as const }; } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Errore login: ${(e as Error).message}`, }); } }; export const swapAuth = async ({ ctx: { req, res }, userId, }: { ctx: Context; userId: UsersId; }) => { checkCtx({ req, res }); try { const user = await findUser_byId_MINI({ userId }); if (!user) { throw new TRPCError({ code: "NOT_FOUND", message: "Utente non trovato", }); } const accessToken = await signToken(user, "1h", "accessToken"); const refreshToken = await signToken(user, "1d", "refreshToken"); // Send Access Token in Cookie await setCookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, { ...accessTokenCookieOptions, req, res, }); await setCookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken, { ...refreshTokenCookieOptions, req, res, }); } catch { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore interno", }); } }; export const logOut = async ({ ctx: { req, res } }: { ctx: Context }) => { checkCtx({ req, res }); try { await deleteCookie(ACCESS_TOKEN_COOKIE_NAME, { req, res }); await deleteCookie(REFRESH_TOKEN_COOKIE_NAME, { req, res }); return { status: "success" }; } catch (err: unknown) { console.error(err); throw err; } }; export const passwordChange = async ({ userId, oldpassword, newpassword, }: { userId: UsersId; oldpassword: string; newpassword: string; }) => { const dbUser = await findUser_byId({ id: userId }); if (!dbUser) { throw new TRPCError({ code: "NOT_FOUND", message: "Utente non trovato", }); } const banned = await isBanned({ data: { email: dbUser.email }, db }); if (banned) { throw new TRPCError({ code: "FORBIDDEN", message: "Utente bannato", }); } const isPasswordCorrect = await comparePasswords({ hashedPassword: dbUser.password, password: oldpassword, salt: dbUser.salt, }); if (!isPasswordCorrect) { throw new TRPCError({ code: "BAD_REQUEST", message: "Password errata", }); } const salt = generateSalt(); const hashedPassword = await hashPassword(newpassword, salt); await db .updateTable("users") .set({ password: hashedPassword, salt, }) .where("id", "=", userId) .execute(); return { status: "success", }; };