mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
harden demo sessions
This commit is contained in:
@@ -80,6 +80,14 @@ async function provisionVault(sessionId, userVaultName) {
|
||||
const storageName = makeStorageName(sessionId, userVaultName);
|
||||
const vaultPath = path.join(config.vaultRoot, storageName);
|
||||
|
||||
// keep the resolved path inside the vault root.
|
||||
const root = path.resolve(config.vaultRoot);
|
||||
const resolved = path.resolve(vaultPath);
|
||||
|
||||
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
||||
return { error: "invalid-vault-name" };
|
||||
}
|
||||
|
||||
await fsp.mkdir(config.vaultRoot, { recursive: true });
|
||||
|
||||
try {
|
||||
|
||||
@@ -16,6 +16,13 @@ function newSessionId() {
|
||||
return crypto.randomBytes(12).toString("hex");
|
||||
}
|
||||
|
||||
// accept only the format we issue.
|
||||
const SESSION_ID_RE = /^[a-f0-9]{24}$/;
|
||||
|
||||
function isValidSessionId(id) {
|
||||
return typeof id === "string" && SESSION_ID_RE.test(id);
|
||||
}
|
||||
|
||||
function prefixFor(sessionId) {
|
||||
return "demo-" + sessionId + PREFIX_SEPARATOR;
|
||||
}
|
||||
@@ -61,20 +68,25 @@ function setSessionCookie(res, sessionId) {
|
||||
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
`${COOKIE_NAME}=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAgeSeconds}`,
|
||||
`${COOKIE_NAME}=${sessionId}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAgeSeconds}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Resolve the session for a request. If none exists, create one (unless options.peek is true).
|
||||
function getOrCreateSession(req, res, options = {}) {
|
||||
const cookies = parseCookies(req);
|
||||
const existing = cookies[COOKIE_NAME];
|
||||
const raw = cookies[COOKIE_NAME];
|
||||
const existing = isValidSessionId(raw) ? raw : null;
|
||||
|
||||
if (existing && sessions.has(existing)) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing && !sessions.has(existing)) {
|
||||
if (sessions.size >= config.demoMaxSessions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cookie outlived in-memory session. reuse the id to keep the prefix.
|
||||
sessions.set(existing, {
|
||||
lastActivity: Date.now(),
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
sessions,
|
||||
parseCookies,
|
||||
makeStorageName,
|
||||
tryParseUserVaultName,
|
||||
touchSession,
|
||||
} = require("./demo-sessions");
|
||||
|
||||
@@ -28,6 +29,20 @@ function wireWebSocket(server) {
|
||||
if (userVault && !userVault.startsWith("demo-")) {
|
||||
u.searchParams.set("vault", makeStorageName(sessionId, userVault));
|
||||
req.url = u.pathname + u.search;
|
||||
} else if (
|
||||
userVault &&
|
||||
userVault.startsWith("demo-") &&
|
||||
tryParseUserVaultName(sessionId, userVault) === null
|
||||
) {
|
||||
// An already-prefixed vault that isn't this session's: refuse the upgrade.
|
||||
const socket = rest[0];
|
||||
|
||||
if (socket && socket.writable) {
|
||||
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
touchSession(sessionId);
|
||||
|
||||
@@ -70,6 +70,11 @@ function setupDemo(app) {
|
||||
// Hide server-side plugins (headless-sync) from the demo UI
|
||||
app.use("/api/plugins", pluginsBlocker);
|
||||
|
||||
// Plugin routes are not exposed in demo mode.
|
||||
app.use("/api/ext", (req, res) => {
|
||||
res.status(403).json({ error: "Plugin routes are disabled in demo mode" });
|
||||
});
|
||||
|
||||
// Server settings are-fixed in demo mode.
|
||||
app.use("/api/settings", (req, res) => {
|
||||
res.status(403).json({ error: "Settings are disabled in demo mode" });
|
||||
|
||||
Reference in New Issue
Block a user