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 storageName = makeStorageName(sessionId, userVaultName);
|
||||||
const vaultPath = path.join(config.vaultRoot, storageName);
|
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 });
|
await fsp.mkdir(config.vaultRoot, { recursive: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ function newSessionId() {
|
|||||||
return crypto.randomBytes(12).toString("hex");
|
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) {
|
function prefixFor(sessionId) {
|
||||||
return "demo-" + sessionId + PREFIX_SEPARATOR;
|
return "demo-" + sessionId + PREFIX_SEPARATOR;
|
||||||
}
|
}
|
||||||
@@ -61,20 +68,25 @@ function setSessionCookie(res, sessionId) {
|
|||||||
|
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"Set-Cookie",
|
"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).
|
// Resolve the session for a request. If none exists, create one (unless options.peek is true).
|
||||||
function getOrCreateSession(req, res, options = {}) {
|
function getOrCreateSession(req, res, options = {}) {
|
||||||
const cookies = parseCookies(req);
|
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)) {
|
if (existing && sessions.has(existing)) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing && !sessions.has(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.
|
// Cookie outlived in-memory session. reuse the id to keep the prefix.
|
||||||
sessions.set(existing, {
|
sessions.set(existing, {
|
||||||
lastActivity: Date.now(),
|
lastActivity: Date.now(),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
sessions,
|
sessions,
|
||||||
parseCookies,
|
parseCookies,
|
||||||
makeStorageName,
|
makeStorageName,
|
||||||
|
tryParseUserVaultName,
|
||||||
touchSession,
|
touchSession,
|
||||||
} = require("./demo-sessions");
|
} = require("./demo-sessions");
|
||||||
|
|
||||||
@@ -28,6 +29,20 @@ function wireWebSocket(server) {
|
|||||||
if (userVault && !userVault.startsWith("demo-")) {
|
if (userVault && !userVault.startsWith("demo-")) {
|
||||||
u.searchParams.set("vault", makeStorageName(sessionId, userVault));
|
u.searchParams.set("vault", makeStorageName(sessionId, userVault));
|
||||||
req.url = u.pathname + u.search;
|
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);
|
touchSession(sessionId);
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ function setupDemo(app) {
|
|||||||
// Hide server-side plugins (headless-sync) from the demo UI
|
// Hide server-side plugins (headless-sync) from the demo UI
|
||||||
app.use("/api/plugins", pluginsBlocker);
|
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.
|
// Server settings are-fixed in demo mode.
|
||||||
app.use("/api/settings", (req, res) => {
|
app.use("/api/settings", (req, res) => {
|
||||||
res.status(403).json({ error: "Settings are disabled in demo mode" });
|
res.status(403).json({ error: "Settings are disabled in demo mode" });
|
||||||
|
|||||||
Reference in New Issue
Block a user