Beta: admin assets, resource download forms, and resource page redesign
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import { parseCookies } from './helpers.js'
|
||||
|
||||
const ADMIN_SESSION_COOKIE = 'vbn_admin_session'
|
||||
const ADMIN_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD
|
||||
const adminSessions = new Map()
|
||||
|
||||
function cookieFlags() {
|
||||
return process.env.NODE_ENV === 'production' ? '; Secure' : ''
|
||||
}
|
||||
|
||||
export function sha256(value) {
|
||||
return createHash('sha256').update(String(value)).digest('hex')
|
||||
}
|
||||
|
||||
export function isAdminPasswordConfigured() {
|
||||
return Boolean(ADMIN_PASSWORD)
|
||||
}
|
||||
|
||||
export function validateAdminPasswordSetup() {
|
||||
if (!isAdminPasswordConfigured() && process.env.NODE_ENV === 'production') {
|
||||
throw new Error('ADMIN_PASSWORD is required in production.')
|
||||
}
|
||||
|
||||
if (!isAdminPasswordConfigured()) {
|
||||
console.warn('ADMIN_PASSWORD is not configured; admin routes will remain disabled until the environment is configured.')
|
||||
}
|
||||
}
|
||||
|
||||
export function isAdminPasswordValid(password) {
|
||||
if (!isAdminPasswordConfigured()) return false
|
||||
return sha256(password) === sha256(ADMIN_PASSWORD)
|
||||
}
|
||||
|
||||
export function createAdminSession() {
|
||||
const token = randomUUID()
|
||||
adminSessions.set(token, Date.now() + ADMIN_SESSION_TTL_MS)
|
||||
return token
|
||||
}
|
||||
|
||||
export function deleteAdminSession(token) {
|
||||
if (token) {
|
||||
adminSessions.delete(token)
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidAdminSession(req) {
|
||||
if (!isAdminPasswordConfigured()) return false
|
||||
|
||||
const cookies = parseCookies(req.headers.cookie)
|
||||
const sessionToken = cookies[ADMIN_SESSION_COOKIE]
|
||||
if (!sessionToken) return false
|
||||
|
||||
const expiresAt = adminSessions.get(sessionToken)
|
||||
if (!expiresAt || expiresAt <= Date.now()) {
|
||||
adminSessions.delete(sessionToken)
|
||||
return false
|
||||
}
|
||||
|
||||
adminSessions.set(sessionToken, Date.now() + ADMIN_SESSION_TTL_MS)
|
||||
return true
|
||||
}
|
||||
|
||||
export function setAdminSessionCookie(res, token) {
|
||||
res.append(
|
||||
'Set-Cookie',
|
||||
`${ADMIN_SESSION_COOKIE}=${encodeURIComponent(token)}; Max-Age=${Math.floor(ADMIN_SESSION_TTL_MS / 1000)}; Path=/; HttpOnly; SameSite=Lax${cookieFlags()}`,
|
||||
)
|
||||
}
|
||||
|
||||
export function clearAdminSessionCookie(res) {
|
||||
res.append(
|
||||
'Set-Cookie',
|
||||
`${ADMIN_SESSION_COOKIE}=; Max-Age=0; Path=/; HttpOnly; SameSite=Lax${cookieFlags()}`,
|
||||
)
|
||||
}
|
||||
|
||||
export function requireAdminAuth(req, res, next) {
|
||||
if (!isValidAdminSession(req)) {
|
||||
res.status(401).json({ message: 'Unauthorized' })
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
Reference in New Issue
Block a user