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}
${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 `
| Snapshot |
Created |
Action |
${rows}
`;
}
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",
``
)
);
}
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", ""));
}
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", ""));
}
});
app.post("/rollback/:id", (req, res) => {
if (!req.session.user || !hasAccess(req.session.user, "admin")) {
return res.status(403).send(renderPage("Safe Mode", ""));
}
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}`);
});