to-do / auth.js
rodopsin
Tek yöneticili login ekle, e-posta hatırlatmasını kaldır
6ed2a0a
Raw
History Blame Contribute Delete
2.98 kB
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 });
}