2025-08-04 17:45:44 +02:00
|
|
|
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",
|
2025-08-25 12:33:29 +02:00
|
|
|
sameSite: "lax",
|
2025-08-04 17:45:44 +02:00
|
|
|
path: "/",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const accessTokenCookieOptions: OptionsType = {
|
|
|
|
|
...cookieOptions,
|
|
|
|
|
expires: add(new Date(), { hours: 1 }),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const refreshTokenCookieOptions: OptionsType = {
|
|
|
|
|
...cookieOptions,
|
2025-08-20 14:17:26 +02:00
|
|
|
expires: add(new Date(), { days: 1 }),
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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",
|
2025-08-27 10:56:15 +02:00
|
|
|
message: "Errore in Login: Utente non trovato",
|
2025-08-04 17:45:44 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
//Check if user is blocked or unverified
|
|
|
|
|
|
|
|
|
|
if (user.isBlocked) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: "FORBIDDEN",
|
2025-08-27 10:56:15 +02:00
|
|
|
message: "Errore in Login: Utente bloccato",
|
2025-08-04 17:45:44 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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",
|
2025-08-27 10:56:15 +02:00
|
|
|
message: "Errore in Login: Invalid email or password",
|
2025-08-04 17:45:44 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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");
|
2025-08-20 14:17:26 +02:00
|
|
|
const refreshToken = await signToken(payload, "1d", "refreshToken");
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
// 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",
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-27 10:56:15 +02:00
|
|
|
return { status: "success" as const };
|
2025-08-04 17:45:44 +02:00
|
|
|
} 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");
|
2025-08-20 14:17:26 +02:00
|
|
|
const refreshToken = await signToken(user, "1d", "refreshToken");
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
// 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",
|
|
|
|
|
};
|
|
|
|
|
};
|