Switch to SQLite-backed session storage, cleanup third-party dependencies, and refine CSP for restricted external assets.
This commit is contained in:
@@ -73,11 +73,13 @@ export function createSqliteRateLimitStore(db) {
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS rate_limit_hits_expiry ON rate_limit_hits(hit_at);`);
|
||||
db.pragma("busy_timeout = 5000");
|
||||
const prune = db.prepare("DELETE FROM rate_limit_hits WHERE bucket_key = ? AND hit_at <= ?");
|
||||
// Prune globally on every write. Pruning by bucket only leaves expired rows
|
||||
// for inactive keys in the database forever.
|
||||
const prune = db.prepare("DELETE FROM rate_limit_hits WHERE hit_at <= ?");
|
||||
const count = db.prepare("SELECT COUNT(*) AS count FROM rate_limit_hits WHERE bucket_key = ?");
|
||||
const insert = db.prepare("INSERT INTO rate_limit_hits (bucket_key, hit_at) VALUES (?, ?)");
|
||||
const consume = db.transaction((key, now, windowMs, max) => {
|
||||
prune.run(key, now - windowMs);
|
||||
prune.run(now - windowMs);
|
||||
if (count.get(key).count >= max) return false;
|
||||
insert.run(key, now);
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import session from "express-session";
|
||||
|
||||
// Small shared session store backed by the same SQLite database as the app.
|
||||
// Expired rows are pruned on access/write, so no process-local timer is needed.
|
||||
export function createSqliteSessionStore(db) {
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS sessions (
|
||||
sid TEXT PRIMARY KEY,
|
||||
sess TEXT NOT NULL,
|
||||
expires_at INTEGER
|
||||
); CREATE INDEX IF NOT EXISTS sessions_expiry ON sessions(expires_at);`);
|
||||
const prune = db.prepare("DELETE FROM sessions WHERE expires_at IS NOT NULL AND expires_at <= ?");
|
||||
const get = db.prepare("SELECT sess, expires_at FROM sessions WHERE sid = ?");
|
||||
const put = db.prepare("INSERT INTO sessions(sid, sess, expires_at) VALUES(?, ?, ?) ON CONFLICT(sid) DO UPDATE SET sess=excluded.sess, expires_at=excluded.expires_at");
|
||||
const remove = db.prepare("DELETE FROM sessions WHERE sid = ?");
|
||||
const store = new session.Store();
|
||||
store.get = (sid, callback) => {
|
||||
try {
|
||||
const now = Date.now();
|
||||
prune.run(now);
|
||||
const row = get.get(sid);
|
||||
if (!row) return callback(null, null);
|
||||
if (row.expires_at != null && row.expires_at <= now) {
|
||||
remove.run(sid);
|
||||
return callback(null, null);
|
||||
}
|
||||
return callback(null, JSON.parse(row.sess));
|
||||
} catch (error) { return callback(error); }
|
||||
};
|
||||
store.set = (sid, sess, callback) => {
|
||||
try {
|
||||
const expiresAt = sess.cookie?.expires ? new Date(sess.cookie.expires).getTime() : null;
|
||||
prune.run(Date.now());
|
||||
put.run(sid, JSON.stringify(sess), Number.isFinite(expiresAt) ? expiresAt : null);
|
||||
return callback?.(null);
|
||||
} catch (error) { return callback?.(error); }
|
||||
};
|
||||
store.destroy = (sid, callback) => {
|
||||
try { remove.run(sid); return callback?.(null); } catch (error) { return callback?.(error); }
|
||||
};
|
||||
store.touch = (sid, sess, callback) => {
|
||||
try {
|
||||
const expiresAt = sess.cookie?.expires ? new Date(sess.cookie.expires).getTime() : null;
|
||||
put.run(sid, JSON.stringify(sess), Number.isFinite(expiresAt) ? expiresAt : null);
|
||||
return callback?.(null);
|
||||
} catch (error) { return callback?.(error); }
|
||||
};
|
||||
return store;
|
||||
}
|
||||
Reference in New Issue
Block a user