import { TRPCError } from "@trpc/server"; import { deleteCookie, getCookies, type OptionsType } from "cookies-next"; import { setCookie } from "cookies-next/server"; import type { Context } from "~/server/api/trpc"; import { findUser_byId, findUser_byEmail, addUser, findUser_byId_MINI, } from "~/server/services/user.service"; import { ACCESS_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME, signToken, } from "~/server/auth/jwt"; import { checkCtx } from "~/server/utils/ctxChecker"; import type { UsersId } from "~/schemas/public/Users"; import { db } from "~/server/db"; import { isBanned } from "~/server/services/banlist.service"; import { env } from "~/env.mjs"; import { add } from "date-fns"; import { comparePasswords, generateSalt, hashPassword, } from "~/server/services/password.service"; // [...] Cookie options const cookieOptions: OptionsType = { httpOnly: true, secure: env.NODE_ENV === "production", sameSite: "lax", path: "/", }; 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({ db, data: { email } }); if (banned) { throw new TRPCError({ code: "FORBIDDEN", message: "Utente bannato", }); } const salt = generateSalt(); const hashedPassword = await hashPassword(password, salt); const user = await addUser({ db, data: { email, password: hashedPassword, username: `${nome.trim()} ${cognome.trim()}`, nome: nome.trim(), cognome: cognome.trim(), telefono, isVerified: true, isAdminMade: true, salt, }, }); 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: "Utente non trovato", }); } //Check if user is blocked or unverified if (user.isBlocked) { throw new TRPCError({ code: "FORBIDDEN", message: "Utente bloccato", }); } // If Admin and password is "changeme", skip password check const skipPwCheck = user.isAdmin && user.password == "changeme"; if (!skipPwCheck) { const passworIsMatch = await comparePasswords({ password, salt: user.salt, hashedPassword: user.password, }); if (!passworIsMatch) { throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid email or password", }); } } const payload = { id: user.id, username: user.username, email: user.email, isAdmin: user.isAdmin, isBlocked: user.isBlocked, nome: user.nome, }; 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", }); } } 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({ db, data: { email: dbUser.email } }); if (banned) { throw new TRPCError({ code: "FORBIDDEN", message: "Utente bannato", }); } const isPasswordCorrect = await comparePasswords({ password: oldpassword, salt: dbUser.salt, hashedPassword: dbUser.password, }); 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", }; };