fix: remove JWT_SECRET env var — server manages it exclusively
Setting JWT_SECRET via environment variable was broken by design: the admin panel rotation updates the in-memory binding and persists the new value to data/.jwt_secret, but an env var would silently override it on the next restart, reverting the rotation. The server now always loads JWT_SECRET from data/.jwt_secret (auto-generating it on first start), making the file the single source of truth. Rotation is handled exclusively through the admin panel. - config.ts: drop process.env.JWT_SECRET fallback and JWT_SECRET_IS_GENERATED export; always read from / write to data/.jwt_secret - index.ts: remove the now-obsolete JWT_SECRET startup warning - .env.example, docker-compose.yml, README: remove JWT_SECRET entries - Helm chart: remove JWT_SECRET from secretEnv, secret.yaml, and deployment.yaml; rename generateJwtSecret → generateEncryptionKey and update NOTES.txt and README accordingly
This commit is contained in:
@@ -2,29 +2,28 @@ import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
let _jwtSecret: string = process.env.JWT_SECRET || '';
|
||||
const dataDir = path.resolve(__dirname, '../data');
|
||||
|
||||
if (!_jwtSecret) {
|
||||
const dataDir = path.resolve(__dirname, '../data');
|
||||
const secretFile = path.join(dataDir, '.jwt_secret');
|
||||
// JWT_SECRET is always managed by the server — auto-generated on first start and
|
||||
// persisted to data/.jwt_secret. Use the admin panel to rotate it; do not set it
|
||||
// via environment variable (env var would override a rotation on next restart).
|
||||
const jwtSecretFile = path.join(dataDir, '.jwt_secret');
|
||||
let _jwtSecret: string;
|
||||
|
||||
try {
|
||||
_jwtSecret = fs.readFileSync(jwtSecretFile, 'utf8').trim();
|
||||
} catch {
|
||||
_jwtSecret = crypto.randomBytes(32).toString('hex');
|
||||
try {
|
||||
_jwtSecret = fs.readFileSync(secretFile, 'utf8').trim();
|
||||
} catch {
|
||||
_jwtSecret = crypto.randomBytes(32).toString('hex');
|
||||
try {
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
||||
fs.writeFileSync(secretFile, _jwtSecret, { mode: 0o600 });
|
||||
console.log('Generated and saved JWT secret to', secretFile);
|
||||
} catch (writeErr: unknown) {
|
||||
console.warn('WARNING: Could not persist JWT secret to disk:', writeErr instanceof Error ? writeErr.message : writeErr);
|
||||
console.warn('Sessions will reset on server restart. Set JWT_SECRET env var for persistent sessions.');
|
||||
}
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
||||
fs.writeFileSync(jwtSecretFile, _jwtSecret, { mode: 0o600 });
|
||||
console.log('Generated and saved JWT secret to', jwtSecretFile);
|
||||
} catch (writeErr: unknown) {
|
||||
console.warn('WARNING: Could not persist JWT secret to disk:', writeErr instanceof Error ? writeErr.message : writeErr);
|
||||
console.warn('Sessions will reset on server restart.');
|
||||
}
|
||||
}
|
||||
|
||||
const JWT_SECRET_IS_GENERATED = !process.env.JWT_SECRET;
|
||||
|
||||
// export let so TypeScript's CJS output keeps exports.JWT_SECRET live
|
||||
// (generates `exports.JWT_SECRET = JWT_SECRET = newVal` inside updateJwtSecret)
|
||||
export let JWT_SECRET = _jwtSecret;
|
||||
@@ -48,7 +47,6 @@ export function updateJwtSecret(newSecret: string): void {
|
||||
let ENCRYPTION_KEY: string = process.env.ENCRYPTION_KEY || '';
|
||||
|
||||
if (!ENCRYPTION_KEY) {
|
||||
const dataDir = path.resolve(__dirname, '../data');
|
||||
const keyFile = path.join(dataDir, '.encryption_key');
|
||||
|
||||
try {
|
||||
@@ -66,4 +64,4 @@ if (!ENCRYPTION_KEY) {
|
||||
}
|
||||
}
|
||||
|
||||
export { JWT_SECRET_IS_GENERATED, ENCRYPTION_KEY };
|
||||
export { ENCRYPTION_KEY };
|
||||
|
||||
Reference in New Issue
Block a user