From efa493a93ec39ac5ebc3942ea323b561b22fe135 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:12:24 +0330 Subject: [PATCH] Import source: backend/src/middleware/adminAuth Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/src/middleware/adminAuth.ts | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 backend/src/middleware/adminAuth.ts diff --git a/backend/src/middleware/adminAuth.ts b/backend/src/middleware/adminAuth.ts new file mode 100644 index 0000000..15006ed --- /dev/null +++ b/backend/src/middleware/adminAuth.ts @@ -0,0 +1,33 @@ +import { timingSafeEqual } from 'node:crypto'; +import type { RequestHandler } from 'express'; +import { env } from '../config/env.js'; +import { HttpError } from './errorHandler.js'; +import { sha256 } from '../utils/crypto.js'; + +function safeEqual(left: string, right: string) { + const a = Buffer.from(sha256(left)); + const b = Buffer.from(sha256(right)); + return a.length === b.length && timingSafeEqual(a, b); +} + +export const requireAdmin: RequestHandler = (req, _res, next) => { + if (!env.adminApiKey) { + throw new HttpError(503, 'ADMIN_DISABLED', 'Admin API is not configured'); + } + + // IP Whitelisting (defense-in-depth) + const allowedIpsStr = process.env.ADMIN_ALLOWED_IPS; + if (allowedIpsStr) { + const allowedIps = allowedIpsStr.split(',').map((ip) => ip.trim()); + const clientIp = req.ip ?? req.socket.remoteAddress ?? ''; + if (!allowedIps.includes(clientIp) && !allowedIps.includes('::ffff:' + clientIp)) { + throw new HttpError(403, 'ADMIN_IP_RESTRICTED', 'Admin access denied from this IP address'); + } + } + + const key = req.header('x-admin-key') ?? ''; + if (!key || !safeEqual(key, env.adminApiKey)) { + throw new HttpError(401, 'ADMIN_KEY_REQUIRED', 'Admin API key is required'); + } + next(); +};