mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 09:35:11 +00:00
Full overhaul of the repo please see changelog
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Path for auth users file
|
||||
const AUTH_FILE = path.join(process.cwd(), 'utils/auth-users.json');
|
||||
|
||||
// In-memory cache
|
||||
let usersCache = null;
|
||||
let lastLoaded = 0;
|
||||
const LOAD_INTERVAL_MS = 30 * 1000; // reload at most every 30s
|
||||
|
||||
function ensureAuthFile() {
|
||||
if (fs.existsSync(AUTH_FILE)) return;
|
||||
const defaultPassword = 'change-me';
|
||||
const { hash } = hashPassword(defaultPassword);
|
||||
const data = {
|
||||
info: 'Initial default credentials. CHANGE THE PASSWORD IMMEDIATELY using /auth/change-password after logging in.',
|
||||
users: [ { username: 'admin', hash } ]
|
||||
};
|
||||
fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
|
||||
console.warn('[auth] Created default auth-users.json with username "admin" and password "change-me". CHANGE IT NOW.');
|
||||
}
|
||||
|
||||
function loadUsers(force=false){
|
||||
if (!force && usersCache && (Date.now() - lastLoaded) < LOAD_INTERVAL_MS) return usersCache;
|
||||
ensureAuthFile();
|
||||
try {
|
||||
const raw = fs.readFileSync(AUTH_FILE, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
usersCache = Array.isArray(parsed.users) ? parsed.users : [];
|
||||
lastLoaded = Date.now();
|
||||
} catch (e) {
|
||||
console.error('[auth] Failed loading users file:', e.message);
|
||||
usersCache = [];
|
||||
}
|
||||
return usersCache;
|
||||
}
|
||||
|
||||
function hashPassword(password, salt){
|
||||
salt = salt || crypto.randomBytes(16).toString('hex');
|
||||
const derived = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
|
||||
return { hash: `${salt}:${derived}`, salt };
|
||||
}
|
||||
|
||||
function verifyPassword(password, stored){
|
||||
if (!stored || !stored.includes(':')) return false;
|
||||
const [salt, derived] = stored.split(':');
|
||||
const test = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
|
||||
return crypto.timingSafeEqual(Buffer.from(derived,'hex'), Buffer.from(test,'hex'));
|
||||
}
|
||||
|
||||
function findUser(username){
|
||||
return loadUsers().find(u => u.username === username);
|
||||
}
|
||||
|
||||
function updatePassword(username, newPassword){
|
||||
const users = loadUsers(true);
|
||||
const u = users.find(x => x.username === username);
|
||||
if (!u) return false;
|
||||
u.hash = hashPassword(newPassword).hash;
|
||||
try {
|
||||
fs.writeFileSync(AUTH_FILE, JSON.stringify({ users }, null, 2));
|
||||
usersCache = users;
|
||||
lastLoaded = Date.now();
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('[auth] Failed writing updated password:', e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Session issuance & validation
|
||||
const SESSION_SECRET = crypto.randomBytes(32); // ephemeral; restart invalidates sessions
|
||||
const SESSION_TTL_MS = 12 * 60 * 60 * 1000; // 12h
|
||||
|
||||
function base64url(input){
|
||||
return Buffer.from(input).toString('base64').replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_');
|
||||
}
|
||||
|
||||
function sign(payloadB64){
|
||||
return base64url(crypto.createHmac('sha256', SESSION_SECRET).update(payloadB64).digest());
|
||||
}
|
||||
|
||||
function issueSession(username){
|
||||
const payload = { u: username, exp: Date.now() + SESSION_TTL_MS };
|
||||
const payloadB64 = base64url(JSON.stringify(payload));
|
||||
const sig = sign(payloadB64);
|
||||
return `${payloadB64}.${sig}`;
|
||||
}
|
||||
|
||||
function parseCookies(req){
|
||||
const h = req.headers.cookie; if (!h) return {};
|
||||
return h.split(';').map(v=>v.trim()).reduce((acc, part)=>{ const eq = part.indexOf('='); if(eq>0){ acc[part.slice(0,eq)] = decodeURIComponent(part.slice(eq+1)); } return acc; },{});
|
||||
}
|
||||
|
||||
function validateSessionToken(token){
|
||||
if (!token || !token.includes('.')) return null;
|
||||
const [payloadB64, sig] = token.split('.');
|
||||
if (sign(payloadB64) !== sig) return null;
|
||||
try {
|
||||
const payload = JSON.parse(Buffer.from(payloadB64.replace(/-/g,'+').replace(/_/g,'/'),'base64').toString('utf8'));
|
||||
if (typeof payload.exp !== 'number' || Date.now() > payload.exp) return null;
|
||||
return payload;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function authenticate(username, password){
|
||||
const user = findUser(username);
|
||||
if (!user) return false;
|
||||
return verifyPassword(password, user.hash);
|
||||
}
|
||||
|
||||
function getSession(req){
|
||||
const cookies = parseCookies(req);
|
||||
if (!cookies.session) return null;
|
||||
return validateSessionToken(cookies.session);
|
||||
}
|
||||
|
||||
function requireAuth(req, res, next){
|
||||
const sess = getSession(req);
|
||||
if (!sess) return res.status(401).json({ success:false, error:'AUTH_REQUIRED' });
|
||||
req.session = sess;
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadUsers,
|
||||
authenticate,
|
||||
issueSession,
|
||||
requireAuth,
|
||||
getSession,
|
||||
updatePassword,
|
||||
AUTH_FILE
|
||||
};
|
||||
Reference in New Issue
Block a user