Add password authentication

This commit is contained in:
Unheard0840
2026-08-29 09:15:14 +02:00
parent 6648bf0662
commit ab1f479ffd
6 changed files with 271 additions and 27 deletions
+86
View File
@@ -0,0 +1,86 @@
const crypto = require('crypto')
const adminPassword = process.env.MUNIN_ADMIN_PASSWORD || ''
const sessions = new Map()
const SESSION_TTL_MS = 24 * 60 * 60 * 1000
function createSession() {
const token = crypto.randomBytes(32).toString('hex')
sessions.set(token, {
expiresAt: Date.now() + SESSION_TTL_MS,
})
return token
}
function parseCookies(cookieHeader) {
const cookies = {}
for (const part of (cookieHeader || '').split(';')) {
const index = part.indexOf('=')
if (index === -1) continue
const name = part.slice(0, index).trim()
const value = part.slice(index + 1).trim()
if (name) {
cookies[name] = decodeURIComponent(value)
}
}
return cookies
}
function isValidSession(req) {
const token = parseCookies(req.headers.cookie).munin_session
if (!token) return false
const session = sessions.get(token)
if (!session) return false
if (session.expiresAt <= Date.now()) {
sessions.delete(token)
return false
}
return true
}
function passwordsEqual(a, b) {
const left = Buffer.from(a)
const right = Buffer.from(b)
if (left.length !== right.length) return false
return crypto.timingSafeEqual(left, right)
}
function checkPassword(password) {
if (!adminPassword || typeof password !== 'string') {
return false
}
return passwordsEqual(password, adminPassword)
}
function cleanupSessions() {
const now = Date.now()
for (const [token, session] of sessions) {
if (session.expiresAt <= now) {
sessions.delete(token)
}
}
}
setInterval(cleanupSessions, 60 * 60 * 1000).unref()
module.exports = {
checkPassword,
createSession,
isValidSession,
}
+64 -15
View File
@@ -1,5 +1,10 @@
const http = require('http');
const net = require('net');
const {
checkPassword,
createSession,
isValidSession,
} = require('./auth');
const port = 8080;
const fulcrumTimeoutMs = 3000;
@@ -222,36 +227,80 @@ async function getStatus() {
}
const server = http.createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/api/login') {
let body = ''
req.on('data', (chunk) => {
body += chunk
})
req.on('end', () => {
try {
const input = JSON.parse(body)
if (!checkPassword(input.password)) {
res.writeHead(401, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ error: 'Invalid password' }))
return
}
const token = createSession()
res.writeHead(200, {
'Content-Type': 'application/json',
'Set-Cookie': 'munin_session=' + encodeURIComponent(token) +
'; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=86400',
})
res.end(JSON.stringify({ ok: true }))
} catch {
res.writeHead(400, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ error: 'Invalid request' }))
}
})
return
}
if (req.url === '/api/status') {
if (!isValidSession(req)) {
res.writeHead(401, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ error: 'Authentication required' }))
return
}
try {
const status = await getStatus();
const status = await getStatus()
res.writeHead(200, {
'Content-Type': 'application/json',
});
})
res.end(JSON.stringify(status));
res.end(JSON.stringify(status))
} catch (error) {
console.error('Failed to get service status:', error);
console.error('Failed to get service status:', error)
res.writeHead(500, {
'Content-Type': 'application/json',
});
})
res.end(
JSON.stringify({
service: 'running',
error: 'Failed to determine service status',
}),
);
res.end(JSON.stringify({
service: 'running',
error: 'Failed to determine service status',
}))
}
return;
return
}
res.writeHead(404);
res.end('Not found');
});
res.writeHead(404)
res.end('Not found')
})
server.listen(port, () => {
console.log(`Status API listening on port ${port}`);