Introduced New DB
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import bcrypt from "bcrypt";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { generateRandomPassword, hashPassword } from "../../utils/password"
|
||||
import { sendMail } from "../../utils/mailer"
|
||||
import {secrets} from "../../utils/secrets";
|
||||
import { generateRandomPassword, hashPassword } from "../../utils/password";
|
||||
import { sendMail } from "../../utils/mailer";
|
||||
import { secrets } from "../../utils/secrets";
|
||||
|
||||
import { authUsers } from "../../../db/schema";
|
||||
import { authTenantUsers } from "../../../db/schema";
|
||||
import { tenants } from "../../../db/schema";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
|
||||
export default async function authRoutes(server: FastifyInstance) {
|
||||
// Registrierung
|
||||
server.post("/auth/register",{
|
||||
|
||||
// -----------------------------------------------------
|
||||
// REGISTER
|
||||
// -----------------------------------------------------
|
||||
server.post("/auth/register", {
|
||||
schema: {
|
||||
tags: ["Auth"],
|
||||
summary: "Register User",
|
||||
@@ -19,43 +27,31 @@ export default async function authRoutes(server: FastifyInstance) {
|
||||
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) {
|
||||
// @ts-ignore
|
||||
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();
|
||||
const [user] = await server.db
|
||||
.insert(authUsers)
|
||||
.values({
|
||||
email: body.email.toLowerCase(),
|
||||
passwordHash,
|
||||
})
|
||||
.returning({
|
||||
id: authUsers.id,
|
||||
email: authUsers.email,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
// @ts-ignore
|
||||
return reply.code(400).send({ error: error.message });
|
||||
}
|
||||
|
||||
return { user: data };
|
||||
return { user };
|
||||
});
|
||||
|
||||
// Login
|
||||
server.post("/auth/login",{
|
||||
|
||||
// -----------------------------------------------------
|
||||
// LOGIN
|
||||
// -----------------------------------------------------
|
||||
server.post("/auth/login", {
|
||||
schema: {
|
||||
tags: ["Auth"],
|
||||
summary: "Login User",
|
||||
@@ -67,103 +63,110 @@ export default async function authRoutes(server: FastifyInstance) {
|
||||
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) {
|
||||
// @ts-ignore
|
||||
return reply.code(400).send({ error: "Email and password required" });
|
||||
}
|
||||
let user: any = null;
|
||||
|
||||
/**
|
||||
* 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.toLowerCase())
|
||||
// -------------------------------
|
||||
// SINGLE TENANT MODE
|
||||
// -------------------------------
|
||||
/* if (req.tenant) {
|
||||
const tenantId = req.tenant.id;
|
||||
|
||||
// @ts-ignore
|
||||
user = (data || []).find(i => i.tenants.find(x => x.id === req.tenant.id))
|
||||
if(error) {
|
||||
// @ts-ignore
|
||||
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) {
|
||||
// @ts-ignore
|
||||
return reply.code(500).send({ error: "Internal Server Error" });
|
||||
}
|
||||
}
|
||||
|
||||
if(!user) {
|
||||
// @ts-ignore
|
||||
return reply.code(401).send({ error: "Invalid credentials" });
|
||||
} else {
|
||||
|
||||
const valid = await bcrypt.compare(body.password, user.password_hash);
|
||||
if (!valid) {
|
||||
// @ts-ignore
|
||||
return reply.code(401).send({ error: "Invalid credentials" });
|
||||
} else {
|
||||
const token = jwt.sign(
|
||||
{ user_id: user.id, email: user.email, tenant_id: null },
|
||||
secrets.JWT_SECRET!,
|
||||
{ expiresIn: "6h" }
|
||||
);
|
||||
|
||||
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
|
||||
const result = await server.db
|
||||
.select({
|
||||
user: authUsers,
|
||||
})
|
||||
.from(authUsers)
|
||||
.innerJoin(
|
||||
authTenantUsers,
|
||||
eq(authTenantUsers.userId, authUsers.id)
|
||||
)
|
||||
.innerJoin(
|
||||
tenants,
|
||||
eq(authTenantUsers.tenantId, tenants.id)
|
||||
)
|
||||
.where(and(
|
||||
eq(authUsers.email, body.email.toLowerCase()),
|
||||
eq(authTenantUsers.tenantId, tenantId)
|
||||
));
|
||||
|
||||
return { token };
|
||||
if (result.length === 0) {
|
||||
return reply.code(401).send({ error: "Invalid credentials" });
|
||||
}
|
||||
|
||||
user = result[0].user;
|
||||
|
||||
// -------------------------------
|
||||
// MULTI TENANT MODE
|
||||
// -------------------------------
|
||||
} else {*/
|
||||
const [found] = await server.db
|
||||
.select()
|
||||
.from(authUsers)
|
||||
.where(eq(authUsers.email, body.email.toLowerCase()))
|
||||
.limit(1);
|
||||
|
||||
if (!found) {
|
||||
return reply.code(401).send({ error: "Invalid credentials" });
|
||||
}
|
||||
|
||||
user = found;
|
||||
/*}*/
|
||||
|
||||
// Passwort prüfen
|
||||
const valid = await bcrypt.compare(body.password, user.passwordHash);
|
||||
if (!valid) {
|
||||
return reply.code(401).send({ error: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
user_id: user.id,
|
||||
email: user.email,
|
||||
tenant_id: req.tenant?.id ?? null,
|
||||
},
|
||||
secrets.JWT_SECRET!,
|
||||
{ expiresIn: "6h" }
|
||||
);
|
||||
|
||||
reply.setCookie("token", token, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 60 * 60 * 3,
|
||||
});
|
||||
|
||||
return { token };
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
// LOGOUT
|
||||
// -----------------------------------------------------
|
||||
server.post("/auth/logout", {
|
||||
schema: {
|
||||
tags: ["Auth"],
|
||||
summary: "Logout User (löscht Cookie)"
|
||||
},
|
||||
summary: "Logout User"
|
||||
}
|
||||
}, async (req, reply) => {
|
||||
reply.clearCookie("token", {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
})
|
||||
});
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
// PASSWORD RESET
|
||||
// -----------------------------------------------------
|
||||
server.post("/auth/password/reset", {
|
||||
schema: {
|
||||
tags: ["Auth"],
|
||||
@@ -177,43 +180,43 @@ export default async function authRoutes(server: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
}, async (req, reply) => {
|
||||
const { email } = req.body as { email: string }
|
||||
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()
|
||||
const [user] = await server.db
|
||||
.select({
|
||||
id: authUsers.id,
|
||||
email: authUsers.email,
|
||||
})
|
||||
.from(authUsers)
|
||||
.where(eq(authUsers.email, email.toLowerCase()))
|
||||
.limit(1);
|
||||
|
||||
if (error || !user) {
|
||||
return reply.code(404).send({ error: "User not found" })
|
||||
if (!user) {
|
||||
return reply.code(404).send({ error: "User not found" });
|
||||
}
|
||||
|
||||
// Neues Passwort generieren
|
||||
const plainPassword = generateRandomPassword()
|
||||
const passwordHash = await hashPassword(plainPassword)
|
||||
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)
|
||||
await server.db
|
||||
.update(authUsers)
|
||||
.set({
|
||||
passwordHash,
|
||||
mustChangePassword: true,
|
||||
})
|
||||
.where(eq(authUsers.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",
|
||||
`<p>Hallo,</p>
|
||||
<p>dein Passwort wurde zurückgesetzt.</p>
|
||||
<p><strong>Neues Passwort:</strong> ${plainPassword}</p>
|
||||
<p>Bitte ändere es nach dem Login umgehend.</p>`
|
||||
)
|
||||
`
|
||||
<p>Hallo,</p>
|
||||
<p>Dein Passwort wurde zurückgesetzt.</p>
|
||||
<p><strong>Neues Passwort:</strong> ${plainPassword}</p>
|
||||
<p>Bitte ändere es nach dem Login umgehend.</p>
|
||||
`
|
||||
);
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
}
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user