55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { Request, Response, RequestHandler } from "express";
|
|
import { config } from "../config";
|
|
|
|
const ADMIN_KEY = config.adminKey;
|
|
const failedAttempts = new Map<string, number>();
|
|
|
|
type AuthorizeParams = { via: "cookie" | "header" };
|
|
|
|
export const authorize: ({ via }: AuthorizeParams) => RequestHandler =
|
|
({ via }) =>
|
|
(req, res, next) => {
|
|
const bearerToken = req.headers.authorization?.slice("Bearer ".length);
|
|
const cookieToken = req.session.adminToken;
|
|
const token = via === "cookie" ? cookieToken : bearerToken;
|
|
const attempts = failedAttempts.get(req.ip) ?? 0;
|
|
|
|
if (!ADMIN_KEY) {
|
|
req.log.warn(
|
|
{ ip: req.ip },
|
|
`Blocked admin request because no admin key is configured`
|
|
);
|
|
return res.status(401).json({ error: "Unauthorized" });
|
|
}
|
|
|
|
if (attempts > 5) {
|
|
req.log.warn(
|
|
{ ip: req.ip, token: bearerToken },
|
|
`Blocked admin request due to too many failed attempts`
|
|
);
|
|
return res.status(401).json({ error: "Too many attempts" });
|
|
}
|
|
|
|
if (token && token === ADMIN_KEY) {
|
|
return next();
|
|
}
|
|
|
|
req.log.warn(
|
|
{ ip: req.ip, attempts, invalidToken: String(token) },
|
|
`Attempted admin request with invalid token`
|
|
);
|
|
return handleFailedLogin(req, res);
|
|
};
|
|
|
|
function handleFailedLogin(req: Request, res: Response) {
|
|
const attempts = failedAttempts.get(req.ip) ?? 0;
|
|
const newAttempts = attempts + 1;
|
|
failedAttempts.set(req.ip, newAttempts);
|
|
if (req.accepts("json", "html") === "json") {
|
|
return res.status(401).json({ error: "Unauthorized" });
|
|
}
|
|
delete req.session.adminToken;
|
|
req.session.flash = { type: "error", message: `Invalid admin key.` };
|
|
return res.redirect("/admin/login");
|
|
}
|