146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
|
import bcrypt from "bcrypt"
|
|
import { eq } from "drizzle-orm"
|
|
import jwt from "jsonwebtoken"
|
|
import { secrets } from "../../utils/secrets"
|
|
|
|
import { authUsers } from "../../../db/schema" // wichtig: Drizzle Schema importieren!
|
|
|
|
export default async function authRoutesAuthenticated(server: FastifyInstance) {
|
|
|
|
server.post("/auth/refresh", {
|
|
schema: {
|
|
tags: ["Auth"],
|
|
summary: "Refresh JWT for current authenticated user",
|
|
response: {
|
|
200: {
|
|
type: "object",
|
|
properties: {
|
|
token: { type: "string" },
|
|
},
|
|
required: ["token"],
|
|
},
|
|
401: {
|
|
type: "object",
|
|
properties: {
|
|
error: { type: "string" },
|
|
},
|
|
required: ["error"],
|
|
},
|
|
},
|
|
},
|
|
}, async (req, reply) => {
|
|
if (!req.user?.user_id) {
|
|
return reply.code(401).send({ error: "Unauthorized" })
|
|
}
|
|
|
|
const token = jwt.sign(
|
|
{
|
|
user_id: req.user.user_id,
|
|
email: req.user.email,
|
|
tenant_id: req.user.tenant_id,
|
|
},
|
|
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 * 6,
|
|
})
|
|
|
|
return { token }
|
|
})
|
|
|
|
server.post("/auth/password/change", {
|
|
schema: {
|
|
tags: ["Auth"],
|
|
summary: "Change password (after login or forced reset)",
|
|
body: {
|
|
type: "object",
|
|
required: ["old_password", "new_password"],
|
|
properties: {
|
|
old_password: { type: "string" },
|
|
new_password: { type: "string" },
|
|
},
|
|
},
|
|
response: {
|
|
200: {
|
|
type: "object",
|
|
properties: {
|
|
success: { type: "boolean" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, async (req, reply) => {
|
|
|
|
try {
|
|
const { old_password, new_password } = req.body as {
|
|
old_password: string
|
|
new_password: string
|
|
}
|
|
|
|
const userId = req.user?.user_id
|
|
if (!userId) {
|
|
//@ts-ignore
|
|
return reply.code(401).send({ error: "Unauthorized" })
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
// 1) User laden
|
|
// -----------------------------------------------------
|
|
const [user] = await server.db
|
|
.select({
|
|
id: authUsers.id,
|
|
passwordHash: authUsers.passwordHash,
|
|
mustChangePassword: authUsers.must_change_password
|
|
})
|
|
.from(authUsers)
|
|
.where(eq(authUsers.id, userId))
|
|
.limit(1)
|
|
|
|
if (!user) {
|
|
//@ts-ignore
|
|
return reply.code(404).send({ error: "User not found" })
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
// 2) Altes PW prüfen
|
|
// -----------------------------------------------------
|
|
const valid = await bcrypt.compare(old_password, user.passwordHash)
|
|
if (!valid) {
|
|
//@ts-ignore
|
|
return reply.code(401).send({ error: "Old password incorrect" })
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
// 3) Neues PW hashen
|
|
// -----------------------------------------------------
|
|
const newHash = await bcrypt.hash(new_password, 10)
|
|
|
|
// -----------------------------------------------------
|
|
// 4) Updaten
|
|
// -----------------------------------------------------
|
|
await server.db
|
|
.update(authUsers)
|
|
.set({
|
|
passwordHash: newHash,
|
|
must_change_password: false,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(authUsers.id, userId))
|
|
|
|
return { success: true }
|
|
|
|
} catch (err) {
|
|
console.error("POST /auth/password/change ERROR:", err)
|
|
//@ts-ignore
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
}
|
|
})
|
|
}
|