import crypto from "crypto"; // ---- Single-admin auth via signed, HttpOnly cookie (no extra deps) ---- const COOKIE = "akis_auth"; const MAX_AGE_DAYS = 30; const PASSWORD = process.env.APP_PASSWORD || "admin"; if (!process.env.APP_PASSWORD) { console.warn( "⚠️ APP_PASSWORD ayarlı değil — geçici şifre 'admin'. Üretimde .env veya ortam değişkeni ile mutlaka değiştirin." ); } // Stable secret across restarts as long as the password is unchanged const SECRET = process.env.SESSION_SECRET || "akis::" + PASSWORD; const b64url = (buf) => Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); function sign(payloadStr) { return crypto.createHmac("sha256", SECRET).update(payloadStr).digest("base64") .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } function makeToken() { const payload = b64url(JSON.stringify({ u: "admin", exp: Date.now() + MAX_AGE_DAYS * 864e5 })); return `${payload}.${sign(payload)}`; } function verifyToken(token) { if (!token || !token.includes(".")) return false; const [payload, sig] = token.split("."); const expected = sign(payload); if (sig.length !== expected.length) return false; if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return false; try { const data = JSON.parse(Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64")); return data.exp && data.exp > Date.now(); } catch { return false; } } function passwordOk(input) { const a = Buffer.from(String(input ?? "")); const b = Buffer.from(PASSWORD); if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b); } function readCookie(req, name) { const raw = req.headers.cookie; if (!raw) return null; for (const part of raw.split(";")) { const i = part.indexOf("="); if (i > -1 && part.slice(0, i).trim() === name) return decodeURIComponent(part.slice(i + 1).trim()); } return null; } function setAuthCookie(req, res) { const secure = req.secure || req.headers["x-forwarded-proto"] === "https"; res.setHeader("Set-Cookie", `${COOKIE}=${makeToken()}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${MAX_AGE_DAYS * 86400}${secure ? "; Secure" : ""}`); } export function clearAuthCookie(res) { res.setHeader("Set-Cookie", `${COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`); } export function isAuthed(req) { return verifyToken(readCookie(req, COOKIE)); } // Express middleware: protects /api routes (returns 401 JSON when unauthed) export function requireApiAuth(req, res, next) { if (isAuthed(req)) return next(); res.status(401).json({ error: "Yetkisiz", auth: false }); } export function loginHandler(req, res) { if (!passwordOk(req.body?.password)) { return res.status(401).json({ error: "Şifre hatalı" }); } setAuthCookie(req, res); res.json({ ok: true }); } export function logoutHandler(req, res) { clearAuthCookie(res); res.json({ ok: true }); }