infoalloggi-monorepo/apps/infoalloggi/src/server/api/routers/invite.ts

165 lines
4.3 KiB
TypeScript
Raw Normal View History

import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { env } from "~/env";
import {
adminProcedure,
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
import { swapAuth } from "~/server/controllers/auth.controller";
import {
consumeInviteToken,
createInviteToken,
sendInvitoEmail,
verifyInviteToken,
} from "~/server/controllers/invites.controller";
import { editAccountHandler } from "~/server/controllers/user.controller";
import { db } from "~/server/db";
import { generateSalt, hashPassword } from "~/server/services/password.service";
import { findUser_byEmail, getUser } from "~/server/services/user.service";
import { zUserId } from "~/server/utils/zod_types";
export const inviteRouter = createTRPCRouter({
// Admin sends invite
sendInvite: adminProcedure
.input(
z.object({
id: zUserId,
}),
)
.mutation(async ({ input }) => {
try {
const user = await getUser({ userId: input.id, db });
const token = await createInviteToken(user.email);
const inviteUrl = `${env.BASE_URL}/auth/accetta-invito/${token}?e=${btoa(user.email)}`;
await sendInvitoEmail({
userId: input.id,
email: user.email,
nome: user.nome,
//password: user.password,
inviteUrl,
});
return { success: true };
} catch (e) {
console.error("Error in sendInvite:", e);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Errore durante l'invio dell'invito: " +
(e instanceof Error ? e.message : "Unknown error"),
});
}
}),
requestNewInvite: publicProcedure
.input(z.object({ email: z.email() }))
.mutation(async ({ input }) => {
try {
const user = await findUser_byEmail({ email: input.email });
if (!user) {
// For security, we don't reveal whether the email exists or not
return { success: true };
}
const token = await createInviteToken(user.email);
const inviteUrl = `${env.BASE_URL}/auth/accetta-invito/${token}?e=${btoa(user.email)}`;
await sendInvitoEmail({
userId: user.id,
email: user.email,
nome: user.nome,
//password: user.password,
inviteUrl,
});
return { success: true };
} catch (e) {
console.error("Error in requestNewInvite:", e);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Errore durante la richiesta di un nuovo invito: " +
(e instanceof Error ? e.message : "Unknown error"),
});
}
}),
// Verify invite token (public - user clicking the link)
verifyInvite: publicProcedure
.input(z.object({ token: z.string(), email: z.string().nullable() }))
.output(
z.discriminatedUnion("status", [
z.object({ status: z.literal("valid"), email: z.email() }),
z.object({
status: z.literal("invalid"),
reason: z.enum(["not_found", "expired"]),
}),
z.object({ status: z.literal("already_user") }),
]),
)
.query(async ({ input }) => {
if (input.email) {
const existingUser = await findUser_byEmail({ email: input.email });
if (existingUser) {
return { status: "already_user" };
}
}
const ver = await verifyInviteToken(input.token);
if (!ver.status) {
return { status: "invalid", reason: ver.reason };
}
return { status: "valid", email: ver.invite.email };
}),
// Accept invite and set password
acceptInvite: publicProcedure
.input(
z.object({
email: z.email(),
token: z.string(),
password: z.string().min(8),
}),
)
.mutation(async ({ input, ctx }) => {
const user = await findUser_byEmail({ email: input.email });
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Accept Invite: Utente non trovato con email: ${input.email}`,
});
}
const ver = await verifyInviteToken(input.token);
if (!ver.status) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Accept Invite: Invito non valido o scaduto per email: ${input.email}`,
});
}
// Create user with password
const salt = generateSalt();
const hashedPassword = await hashPassword(input.password, salt);
await editAccountHandler({
id: user.id,
data: {
password: hashedPassword,
salt,
},
});
// Mark invite as used
await consumeInviteToken(input.token);
await swapAuth({ ctx, userId: user.id });
return { success: true };
}),
});