generated from nhcarrigan/template
feat: security and auditing
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user