feat: security and auditing

This commit is contained in:
2026-02-04 16:48:08 -08:00
parent 11be34cd21
commit 0a654f423a
42 changed files with 2195 additions and 160 deletions
+56 -5
View File
@@ -1,6 +1,7 @@
import { FastifyPluginAsync } from "fastify";
import { AuthService } from "../../services/auth.service";
import { AuthResponse } from "@library/shared-types";
import { AuditService } from "../../services/audit.service";
import { AuthResponse, AuditAction, AuditCategory } from "@library/shared-types";
const authRoutes: FastifyPluginAsync = async (app) => {
const authService = new AuthService(app);
@@ -33,7 +34,16 @@ const authRoutes: FastifyPluginAsync = async (app) => {
// Generate JWT
const jwt = await authService.generateToken(user);
// Set cookie and redirect to frontend
// Log successful login
await AuditService.log({
action: AuditAction.LOGIN,
category: AuditCategory.AUTH,
userId: user.id,
details: `User ${user.username} logged in via Discord`,
success: true,
}, request);
// Set signed cookie and redirect to frontend
reply
.setCookie("auth-token", jwt, {
path: "/",
@@ -41,13 +51,22 @@ const authRoutes: FastifyPluginAsync = async (app) => {
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 7 * 24 * 60 * 60, // 7 days
signed: true,
})
.redirect("/"); // Redirect to root since API serves frontend
} catch (error) {
// Log failed login attempt
await AuditService.log({
action: AuditAction.LOGIN_FAILED,
category: AuditCategory.SECURITY,
details: error instanceof Error ? error.message : String(error),
success: false,
}, request);
app.log.error({ err: error }, "Auth callback error");
reply
.code(401)
.send({ error: "Authentication failed", details: error instanceof Error ? error.message : String(error) });
.send({ error: "Authentication failed" });
}
});
@@ -59,8 +78,14 @@ const authRoutes: FastifyPluginAsync = async (app) => {
{
preValidation: [app.authenticate],
},
async (request) => {
const user = request.user as any;
async (request, reply) => {
const jwtUser = request.user as { id: string };
const user = await authService.getUserById(jwtUser.id);
if (!user) {
return reply.code(404).send({ error: "User not found" });
}
const token = await authService.generateToken(user);
return {
@@ -74,12 +99,38 @@ const authRoutes: FastifyPluginAsync = async (app) => {
* Logout.
*/
app.post("/logout", async (request, reply) => {
// Try to get user ID from JWT if available
try {
await request.jwtVerify();
const user = request.user as { id?: string; username?: string };
if (user?.id) {
await AuditService.log({
action: AuditAction.LOGOUT,
category: AuditCategory.AUTH,
userId: user.id,
details: `User ${user.username ?? "unknown"} logged out`,
success: true,
}, request);
}
} catch {
// User wasn't authenticated, just proceed with logout
}
reply
.clearCookie("auth-token", {
path: "/",
signed: true,
})
.send({ message: "Logged out successfully" });
});
/**
* Get CSRF token for state-changing requests.
*/
app.get("/csrf-token", async (request, reply) => {
const token = reply.generateCsrf();
return { csrfToken: token };
});
};
export default authRoutes;