Generate a session secret instead of refusing to start
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
setup.sh starts the production server without SESSION_SECRET, so the new "refuse to start" check stopped the app after an update. When the variable is unset in production the server now makes a random secret once and keeps it in DATA_DIR/.session-secret (mode 600, git-ignored). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f274bc9c8a
commit
31fb3f958d
3 files changed
+24
-7
No files matched your search
+21
-6
@@ -2,11 +2,12 @@ import express from 'express';
|
||||
import compression from 'compression';
|
||||
import session from 'express-session';
|
||||
import QRCode from 'qrcode';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { randomUUID, randomBytes } from 'crypto';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import {
|
||||
initDb, getAllProjects, getProject, getProjectEdited, upsertProject, deleteProject,
|
||||
initDb, dataDir, getAllProjects, getProject, getProjectEdited, upsertProject, deleteProject,
|
||||
countUsers, createUser, getUserByEmail, getUserById, claimOrphanProjects,
|
||||
enableTotp, disableTotp, setBackupCodeHashes, setPodcastName,
|
||||
getShareToken, setShareToken, clearShareToken, getProjectByShareToken,
|
||||
@@ -50,9 +51,23 @@ const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
if (isProd && !process.env.SESSION_SECRET) {
|
||||
console.error('SESSION_SECRET is not set. Set it to a long random string before running in production.');
|
||||
process.exit(1);
|
||||
// Session cookies are signed with SESSION_SECRET. When it isn't set (setup.sh doesn't set one), a random secret
|
||||
// is made once and kept in DATA_DIR, so it survives restarts and nobody has to configure anything.
|
||||
function sessionSecret() {
|
||||
if (process.env.SESSION_SECRET) return process.env.SESSION_SECRET;
|
||||
if (!isProd) return 'dev-only-secret-change-me';
|
||||
const file = join(dataDir(), '.session-secret');
|
||||
try {
|
||||
if (existsSync(file)) return readFileSync(file, 'utf8').trim();
|
||||
mkdirSync(dataDir(), { recursive: true });
|
||||
const made = randomBytes(48).toString('hex');
|
||||
writeFileSync(file, made, { mode: 0o600 });
|
||||
console.log(`SESSION_SECRET is not set; generated one and saved it to ${file}.`);
|
||||
return made;
|
||||
} catch (err) {
|
||||
console.error('Could not read or save a session secret; set SESSION_SECRET instead.', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Trust the reverse proxy (needed for secure cookies to work behind nginx/etc).
|
||||
@@ -98,7 +113,7 @@ app.use(compression());
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(session({
|
||||
store: new SqliteSessionStore(),
|
||||
secret: process.env.SESSION_SECRET || 'dev-only-secret-change-me',
|
||||
secret: sessionSecret(),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
rolling: true,
|
||||
|
||||
Reference in new issue
Block a user