don't allow users to just mount the prime db i guess for privacy reasons (may undo this someday)

This commit is contained in:
Your Name 2026-08-21 15:25:19 -04:00
parent 464774ef4b
commit 5d7bcddcd1
4 changed files with 95 additions and 7 deletions

25
db.js
View file

@ -7,6 +7,7 @@ const { Eta } = require("eta");
const cheerio = require("cheerio"); const cheerio = require("cheerio");
const db = betterSqlite3("./dbs/0.sqlite"); const db = betterSqlite3("./dbs/0.sqlite");
const PRIME_DB_ID = 0;
db.pragma("journal_mode = WAL"); db.pragma("journal_mode = WAL");
@ -587,6 +588,7 @@ function getDbsForStructure(structureId) {
FROM structure_dbs FROM structure_dbs
INNER JOIN dbs ON structure_dbs.db_id = dbs.id INNER JOIN dbs ON structure_dbs.db_id = dbs.id
WHERE structure_dbs.structure_id = ? WHERE structure_dbs.structure_id = ?
AND NOT (structure_dbs.db_id = ${PRIME_DB_ID} AND structure_dbs.structure_id != ${PRIME_DB_ID})
ORDER BY structure_dbs.created_at, is_aliased ASC; ORDER BY structure_dbs.created_at, is_aliased ASC;
`, `,
) )
@ -607,11 +609,16 @@ function getDb(dbId) {
function getDbForStructure(structureId, dbId) { function getDbForStructure(structureId, dbId) {
return db return db
.prepare( .prepare(
`SELECT * `SELECT
dbs.*,
structure_dbs.alias,
structure_dbs.structure_id AS alias_struct_id,
dbs.structure_id AS db_struct_id
FROM structure_dbs FROM structure_dbs
INNER JOIN dbs ON structure_dbs.db_id = dbs.id INNER JOIN dbs ON structure_dbs.db_id = dbs.id
WHERE structure_dbs.structure_id = ? WHERE structure_dbs.structure_id = ?
AND structure_dbs.db_id = ?; AND structure_dbs.db_id = ?
AND NOT (structure_dbs.db_id = ${PRIME_DB_ID} AND structure_dbs.structure_id != ${PRIME_DB_ID});
`, `,
) )
.get(structureId, dbId); .get(structureId, dbId);
@ -644,6 +651,9 @@ function createDb(structId, name) {
} }
function attachDb(structId, dbId, alias) { function attachDb(structId, dbId, alias) {
if (Number(dbId) === PRIME_DB_ID && Number(structId) !== PRIME_DB_ID) {
throw new Error("prime db cannot be attached to other structures");
}
const insertStructureDbStmt = db.prepare(` const insertStructureDbStmt = db.prepare(`
INSERT INTO structure_dbs (db_id, structure_id, alias) INSERT INTO structure_dbs (db_id, structure_id, alias)
VALUES (?, ?, ?) VALUES (?, ?, ?)
@ -698,10 +708,17 @@ function cloneStructure(
).lastInsertRowid; ).lastInsertRowid;
const dbIds = db const dbIds = db
.prepare(`SELECT db_id FROM structure_dbs WHERE structure_id = ?;`) .prepare(`SELECT db_id FROM structure_dbs WHERE structure_id = ? AND db_id != ${PRIME_DB_ID};`)
.all(structId); .all(structId);
const toClone = new Set(cloneDbs); const requestedCloneDbs = Array.isArray(cloneDbs)
? cloneDbs
: cloneDbs
? [cloneDbs]
: [];
const toClone = new Set(
requestedCloneDbs.filter((dbId) => Number(dbId) !== PRIME_DB_ID),
);
const toAlias = new Set(); const toAlias = new Set();
for (let { db_id } of dbIds) { for (let { db_id } of dbIds) {

View file

@ -12,11 +12,18 @@ const { match } = require("path-to-regexp");
const bcrypt = require("bcrypt"); const bcrypt = require("bcrypt");
const cheerio = require("cheerio"); const cheerio = require("cheerio");
const webPush = require("web-push"); const webPush = require("web-push");
const { randomUUID } = require("node:crypto");
const app = express(); const app = express();
const _expressWs = require("express-ws")(app); const _expressWs = require("express-ws")(app);
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
const model = require("./db"); const model = require("./db");
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
const SESSION_SECRET = process.env.SESSION_SECRET || randomUUID();
if (!process.env.SESSION_SECRET) {
console.warn(
"SESSION_SECRET is not set; using an ephemeral secret and invalidating sessions on restart.",
);
}
const db = model.db; const db = model.db;
const wsRouter = express.Router(); const wsRouter = express.Router();
@ -62,7 +69,7 @@ app.use(fileUpload());
app.use( app.use(
session({ session({
store: new SQLiteStore({ client: db, expired: { clear: true } }), store: new SQLiteStore({ client: db, expired: { clear: true } }),
secret: "your secret key", secret: SESSION_SECRET,
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { secure: false }, cookie: { secure: false },
@ -229,6 +236,26 @@ function currentUserFor(req) {
} }
} }
function dbForWorkshopRequest(req, res) {
const appDb = model.getDbForStructure(
req.params.structure_id,
req.params.db_id,
);
if (!appDb) {
res.status(404).send("Not found");
return null;
}
const ownDb = String(appDb.db_struct_id) === String(req.params.structure_id);
if (
!ownDb &&
!model.canAccessStructure(req.session.userId, appDb.db_struct_id)
) {
res.status(404).send("Not found");
return null;
}
return appDb;
}
function userIdFrom(value) { function userIdFrom(value) {
if (value == null) return null; if (value == null) return null;
if (typeof value === "object" && value.id != null) return Number(value.id); if (typeof value === "object" && value.id != null) return Number(value.id);
@ -925,6 +952,9 @@ app.post("/workshop/:structure_id/db", (req, res) => {
}); });
app.post("/workshop/:structure_id/db/attach", (req, res) => { app.post("/workshop/:structure_id/db/attach", (req, res) => {
if (Number(req.body.db_id) === 0 && Number(req.params.structure_id) !== 0) {
return res.status(400).send("prime db cannot be attached");
}
model.attachDb(req.params.structure_id, req.body.db_id, req.body.alias); model.attachDb(req.params.structure_id, req.body.db_id, req.body.alias);
const redirectUrl = `/workshop/${req.params.structure_id}/db/${req.body.db_id}`; const redirectUrl = `/workshop/${req.params.structure_id}/db/${req.body.db_id}`;
return smartRedirect(req, res, redirectUrl); return smartRedirect(req, res, redirectUrl);
@ -993,7 +1023,8 @@ app.put("/workshop/:structure_id/route/:route_id", (req, res) => {
app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => { app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
try { try {
let dbId = req.params.db_id; let dbId = req.params.db_id;
let appDb = model.getDb(dbId); let appDb = dbForWorkshopRequest(req, res);
if (!appDb) return;
model.updateDb({ ...appDb, library: req.body.library }); model.updateDb({ ...appDb, library: req.body.library });
appDb = model.getDb(dbId); appDb = model.getDb(dbId);
@ -1018,7 +1049,8 @@ app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => { app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => {
try { try {
let dbId = req.params.db_id; let dbId = req.params.db_id;
let appDb = model.getDb(dbId); let appDb = dbForWorkshopRequest(req, res);
if (!appDb) return;
let dbInstance = model.getDbInstance(dbId); let dbInstance = model.getDbInstance(dbId);
let capturedOutput = []; let capturedOutput = [];
let context = vm.createContext({ let context = vm.createContext({

View file

@ -0,0 +1,3 @@
DELETE FROM structure_dbs
WHERE db_id = 0
AND structure_id != 0;

View file

@ -70,6 +70,42 @@ else
echo "Dependencies unchanged; skipping npm install." echo "Dependencies unchanged; skipping npm install."
fi fi
if [[ ! -f .env ]]; then
install -m 600 /dev/null .env
else
chmod 600 .env || true
fi
if ! grep -Eq '^SESSION_SECRET=.+$' .env; then
SESSION_SECRET_VALUE="$(
node -e 'process.stdout.write(require("crypto").randomBytes(48).toString("base64url"))'
)"
if grep -Eq '^SESSION_SECRET=' .env; then
ENV_TMP="$(mktemp)"
awk -v secret="$SESSION_SECRET_VALUE" '
BEGIN { replaced = 0 }
/^SESSION_SECRET=/ {
if (!replaced) {
print "SESSION_SECRET=" secret
replaced = 1
}
next
}
{ print }
END {
if (!replaced) print "SESSION_SECRET=" secret
}
' .env >"$ENV_TMP"
cat "$ENV_TMP" >.env
rm -f "$ENV_TMP"
else
printf '\nSESSION_SECRET=%s\n' "$SESSION_SECRET_VALUE" >>.env
fi
echo "Generated SESSION_SECRET in $APP_DIR/.env"
else
echo "SESSION_SECRET already present in $APP_DIR/.env"
fi
PM2_TARGET="" PM2_TARGET=""
if command -v pm2 >/dev/null 2>&1; then if command -v pm2 >/dev/null 2>&1; then
PM2_TARGET="$(pm2 jlist 2>/dev/null | APP_DIR="$APP_DIR" node -e ' PM2_TARGET="$(pm2 jlist 2>/dev/null | APP_DIR="$APP_DIR" node -e '