const express = require("express"); const crypto = require("crypto"); const session = require("express-session"); const BetterSqlite3Store = require("better-sqlite3-session-store")(session); const { db } = require("./src/services/db"); const { getSetting, setSetting } = require("./src/services/settings"); const { buildDiscordAuthUrl, exchangeDiscordCode, fetchDiscordUser, fetchDiscordGuildMember } = require("./src/services/auth"); const { getRoleFlags, hasAccess } = require("./src/services/rbac"); const { listSnapshots, restoreSnapshot } = require("./src/services/update-manager"); const { requestRestart } = require("./src/services/updater"); function ensureSessionSecret() { let secret = getSetting("session_secret"); if (!secret) { secret = crypto.randomBytes(32).toString("hex"); setSetting("session_secret", secret); } return secret; } function isConfigured() { return Boolean( getSetting("discord_client_id") && getSetting("discord_client_secret") && getSetting("discord_guild_id") ); } function renderPage(title, content) { return ` ${title}
Safe Mode
${content}
`; } function buildSnapshotTable(snapshots) { if (!snapshots.length) { return "

No snapshots available.

"; } const rows = snapshots .map((snap) => { const label = snap.type === "plugin" ? `Plugin: ${snap.pluginId}` : "Bot core"; const when = new Date(snap.createdAt).toLocaleString(); return ` ${label} ${when}
`; }) .join(""); return ` ${rows}
Snapshot Created Action
`; } const app = express(); const sessionStore = new BetterSqlite3Store({ client: db }); app.use( session({ secret: ensureSessionSecret(), resave: false, saveUninitialized: false, store: sessionStore }) ); app.use(express.urlencoded({ extended: false })); app.get("/", (req, res) => { if (!isConfigured()) { return res.send( renderPage( "Safe Mode", `

Discord not configured

Discord settings are required to enter safe mode.

` ) ); } if (!req.session.user) { return res.send( renderPage( "Safe Mode", `

Login required

Authenticate with Discord to access rollback tools.

Login with Discord
` ) ); } if (!hasAccess(req.session.user, "admin")) { return res.send( renderPage( "Safe Mode", `

Access denied

You do not have administrator access.

` ) ); } const snapshots = listSnapshots(); const table = buildSnapshotTable(snapshots); res.send( renderPage( "Safe Mode", `

Rollback snapshots

Use these snapshots to roll back failed updates. The server will restart after rollback.

${table}
` ) ); }); app.get("/auth/discord", (req, res) => { if (!isConfigured()) { return res.redirect("/"); } const state = crypto.randomBytes(16).toString("hex"); req.session.discordState = state; const baseUrl = `${req.protocol}://${req.get("host")}`; const redirectUri = `${baseUrl}/auth/discord/callback`; const url = buildDiscordAuthUrl(state, redirectUri); res.redirect(url); }); app.get("/auth/discord/callback", async (req, res) => { const { code, state } = req.query; if (!code || state !== req.session.discordState) { return res.send(renderPage("Safe Mode", "
Invalid login state.
")); } try { const baseUrl = `${req.protocol}://${req.get("host")}`; const redirectUri = `${baseUrl}/auth/discord/callback`; const token = await exchangeDiscordCode(code, redirectUri); const user = await fetchDiscordUser(token.access_token); const guildId = getSetting("discord_guild_id"); const member = guildId ? await fetchDiscordGuildMember(token.access_token, guildId) : null; const roles = member?.roles || []; const flags = getRoleFlags(roles); req.session.user = { id: user.id, username: user.global_name || user.username, roles, ...flags }; res.redirect("/"); } catch (error) { console.error(error); res.send(renderPage("Safe Mode", "
Login failed.
")); } }); app.post("/rollback/:id", (req, res) => { if (!req.session.user || !hasAccess(req.session.user, "admin")) { return res.status(403).send(renderPage("Safe Mode", "
Access denied.
")); } try { restoreSnapshot(req.params.id); res.send( renderPage( "Safe Mode", "

Rollback complete

Restarting the bot now...

" ) ); requestRestart(); } catch (error) { res.send( renderPage( "Safe Mode", `

Rollback failed

${error.message}

` ) ); } }); const port = Number(process.env.SAFE_MODE_PORT || 3001); app.listen(port, () => { console.log(`Safe mode listening on http://localhost:${port}`); });