portal-payment-be/src/middleware/logAuth.ts

216 lines
5.7 KiB
TypeScript

import { Request, Response, NextFunction } from "express";
import db from "../db";
import { createLogger, LogLevel } from "../lib/logger";
import { verifyJWT } from "./auth";
// Logger untuk middleware authentication
const authLogger = createLogger({
logDir: "./logs/auth",
filePrefix: "log-auth-middleware",
minLevel: LogLevel.INFO,
});
export interface AuthenticatedRequest extends Request {
user?: {
user_id: string;
username: string;
session_token: string;
};
}
// Middleware untuk autentikasi akses log
export async function authenticateLogAccess(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) {
try {
const authHeader = req.headers.authorization;
const tokenFromQuery = req.query.token as string;
// Get token from header or query parameter
let token: string | null = null;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7); // Remove 'Bearer ' prefix
} else if (tokenFromQuery) {
token = tokenFromQuery;
}
if (!token) {
authLogger.warning("Log access attempt without token", {
ip: req.ip,
path: req.path,
userAgent: req.get("User-Agent"),
});
return res.status(401).json({
error: "TOKEN_REQUIRED",
message: "Authorization token is required",
});
}
// Verify JWT token
const decoded = verifyJWT(token);
if (!decoded || decoded.type !== "LOG_ACCESS") {
authLogger.warning("Invalid or expired log access JWT token", {
token: token.substring(0, 10) + "...",
ip: req.ip,
path: req.path,
});
return res.status(401).json({
error: "INVALID_TOKEN",
message: "Invalid or expired JWT token",
});
}
// Verify user still exists and is active
const user = await db.users.findFirst({
where: {
user_id: decoded.user_id,
is_active: true,
},
});
if (!user) {
authLogger.warning("JWT token for inactive/deleted user", {
userId: decoded.user_id,
ip: req.ip,
path: req.path,
});
return res.status(401).json({
error: "USER_INACTIVE",
message: "User account is inactive",
});
}
// Attach user info to request
req.user = {
user_id: decoded.user_id,
username: decoded.username,
session_token: token,
};
// authLogger.info("Log access authenticated", {
// userId: req.user.user_id,
// username: req.user.username,
// ip: req.ip,
// path: req.path,
// });
next();
} catch (err) {
authLogger.error("Log authentication error", {
error: err instanceof Error ? err.message : String(err),
ip: req.ip,
path: req.path,
});
return res.status(500).json({
error: "INTERNAL_ERROR",
message: "Authentication failed",
});
}
}
// Middleware untuk rate limiting pada akses log
export function rateLimitLogAccess() {
const requestCounts = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT = 100; // requests per window
const WINDOW_SIZE = 15 * 60 * 1000; // 15 minutes
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
const key = `${req.ip}:${req.user?.user_id || "anonymous"}`;
const now = Date.now();
let userRequests = requestCounts.get(key);
if (!userRequests || now > userRequests.resetTime) {
userRequests = { count: 1, resetTime: now + WINDOW_SIZE };
} else {
userRequests.count++;
}
requestCounts.set(key, userRequests);
if (userRequests.count > RATE_LIMIT) {
authLogger.warning("Rate limit exceeded for log access", {
userId: req.user?.user_id,
ip: req.ip,
requestCount: userRequests.count,
});
return res.status(429).json({
error: "RATE_LIMIT_EXCEEDED",
message: "Too many requests. Please try again later.",
resetTime: new Date(userRequests.resetTime).toISOString(),
});
}
// Cleanup old entries periodically
if (Math.random() < 0.01) {
// 1% chance
for (const [k, v] of requestCounts.entries()) {
if (now > v.resetTime) {
requestCounts.delete(k);
}
}
}
next();
};
}
// Optional: Middleware untuk audit trail
export function auditLogAccess() {
return async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
const startTime = Date.now();
// Capture original res.json to log response
const originalJson = res.json;
let responseData: any;
res.json = function (data: any) {
responseData = data;
return originalJson.call(this, data);
};
res.on("finish", async () => {
const duration = Date.now() - startTime;
const auditData = {
userId: req.user?.user_id,
username: req.user?.username,
ip: req.ip,
method: req.method,
path: req.path,
query: req.query,
statusCode: res.statusCode,
duration,
userAgent: req.get("User-Agent"),
timestamp: new Date(),
};
try {
// Log to database audit table if exists, or just log to file
await db.activity_logs.create({
data: {
action: "LOG_ACCESS",
status: res.statusCode < 400 ? "SUCCESS" : "ERROR",
message: `${req.method} ${req.path}`,
extra_json: JSON.stringify(auditData),
},
});
// authLogger.info("Log access audit", auditData);
} catch (err) {
authLogger.error("Failed to create audit log", {
error: err instanceof Error ? err.message : String(err),
auditData,
});
}
});
next();
};
}