import { FastifyInstance } from "fastify"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { generateRandomPassword, hashPassword } from "../../utils/password" import { sendMail } from "../../utils/mailer" export default async function authRoutes(server: FastifyInstance) { // Registrierung server.post("/auth/register",{ schema: { tags: ["Auth"], summary: "Register User", body: { type: "object", required: ["email", "password"], properties: { email: { type: "string", format: "email" }, password: { type: "string" }, }, }, response: { 200: { type: "object", properties: { user: { type: "object" }, }, }, }, }, }, async (req, reply) => { const body = req.body as { email: string; password: string }; if (!body.email || !body.password) { return reply.code(400).send({ error: "Email and password required" }); } // Passwort hashen const passwordHash = await bcrypt.hash(body.password, 10); // User speichern const { data, error } = await server.supabase .from("auth_users") .insert({ email: body.email, password_hash: passwordHash }) .select("id, email") .single(); if (error) { return reply.code(400).send({ error: error.message }); } return { user: data }; }); // Login server.post("/auth/login",{ schema: { tags: ["Auth"], summary: "Login User", body: { type: "object", required: ["email", "password"], properties: { email: { type: "string", format: "email" }, password: { type: "string" }, }, }, response: { 200: { type: "object", properties: { token: { type: "string" }, }, }, }, }, }, async (req, reply) => { const body = req.body as { email: string; password: string }; if (!body.email || !body.password) { return reply.code(400).send({ error: "Email and password required" }); } console.log(req.tenant) /** * Wenn das Tenant Objekt verfügbar ist, befindet sich das Backend im Single Tenant Modus. * Es werden nur Benutzer zugelassen, welche auschließlich diesem Tenant angehören. * Das zeigt sich über das im User gesetzte Tenant Feld * * */ let user = null let error = null if(req.tenant) { // User finden const { data, error } = await server.supabase .from("auth_users") .select("*, tenants!auth_tenant_users(*)") .eq("email", body.email) console.log(data) console.log(error) // @ts-ignore user = (data || []).find(i => i.tenants.find(x => x.id === req.tenant.id)) console.log(user) if(error) { return reply.code(500).send({ error: "Internal Server Error" }); } } else { // User finden const { data, error } = await server.supabase .from("auth_users") .select("*") .eq("email", body.email) .single(); user = data if(error) { console.log(error); return reply.code(500).send({ error: "Internal Server Error" }); } } if(!user) { return reply.code(401).send({ error: "Invalid credentials" }); } else { console.log(user); console.log(body) const valid = await bcrypt.compare(body.password, user.password_hash); if (!valid) { return reply.code(401).send({ error: "Invalid credentials" }); } else { const token = jwt.sign( { user_id: user.id, email: user.email, tenant_id: req.tenant ? req.tenant.id : null }, process.env.JWT_SECRET!, { expiresIn: "3h" } ); reply.setCookie("token", token, { path: "/", httpOnly: true, sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", secure: process.env.NODE_ENV === "production", // lokal: false, prod: true maxAge: 60 * 60 * 3, // 3 Stunden }) return { token }; } } }); server.post("/auth/logout", { schema: { tags: ["Auth"], summary: "Logout User (löscht Cookie)" }, }, async (req, reply) => { reply.clearCookie("token", { path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", }) return { success: true } }) server.post("/auth/password/reset", { schema: { tags: ["Auth"], summary: "Reset Password", body: { type: "object", required: ["email"], properties: { email: { type: "string", format: "email" } } } } }, async (req, reply) => { const { email } = req.body as { email: string } // User finden const { data: user, error } = await server.supabase .from("auth_users") .select("id, email") .eq("email", email) .single() if (error || !user) { return reply.code(404).send({ error: "User not found" }) } // Neues Passwort generieren const plainPassword = generateRandomPassword() const passwordHash = await hashPassword(plainPassword) // In DB updaten const { error: updateError } = await server.supabase .from("auth_users") .update({ password_hash: passwordHash, must_change_password: true }) .eq("id", user.id) if (updateError) { return reply.code(500).send({ error: "Could not update password" }) } // Mail verschicken await sendMail( user.email, "FEDEO | Dein neues Passwort", `
Hallo,
dein Passwort wurde zurückgesetzt.
Neues Passwort: ${plainPassword}
Bitte ändere es nach dem Login umgehend.
` ) return { success: true } }) }