Move cookie secret to data root. Make config.yaml immutable

7ea2c5f8cff0648be26f14eb6d68e9cd85a91678

Cohee <18619528+Cohee1207@users.noreply.github.com>

5 files changed, +45 -31Ignore whitespace
default/config.yaml+0 -2
@@ -79,8 +79,6 @@ minLogLevel: 0
7979## Set to 0 to expire session when the browser is closed
8080## Set to a negative number to disable session expiration
8181sessionTimeout: -1
82-# Used to sign session cookies. Will be auto-generated if not set
83-cookieSecret: ''
8482# Disable CSRF protection - NOT RECOMMENDED
8583disableCsrfProtection: false
8684# Disable startup security checks - NOT RECOMMENDED
post-install.js+19 -1
@@ -104,6 +104,15 @@ const keyMigrationMap = [
104104 newKey: 'extensions.models.textToSpeech',
105105 migrate: (value) => value,
106106 },
107+ // uncommend one release after 1.12.13
108+ /*
109+ {
110+ oldKey: 'cookieSecret',
111+ newKey: 'cookieSecret',
112+ migrate: () => void 0,
113+ remove: true,
114+ },
115+ */
107116];
108117
109118/**
@@ -163,8 +172,17 @@ function addMissingConfigValues() {
163172
164173 // Migrate old keys to new keys
165174 const migratedKeys = [];
166175 for (const { oldKey, newKey, migrate, remove } of keyMigrationMap) {
167176 if (_.has(config, oldKey)) {
177+ if (remove) {
178+ _.unset(config, oldKey);
179+ migratedKeys.push({
180+ oldKey,
181+ newValue: void 0,
182+ });
183+ continue;
184+ }
185+
168186 const oldValue = _.get(config, oldKey);
169187 const newValue = migrate(oldValue);
170188 _.set(config, newKey, newValue);
server.js+4 -4
@@ -274,7 +274,7 @@ const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('list
274274const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
275275const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
276276/** @type {string} */
277277const dataRootglobalThis.DATA_ROOT = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
278278/** @type {boolean} */
279279const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
280280const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
@@ -282,7 +282,7 @@ const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BAS
282282/** @type {boolean} */
283283const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
284284
285285const uploadsPath = path.join(dataRootglobalThis.DATA_ROOT, UPLOADS_DIRECTORY);
286286
287287
288288/** @type {boolean | "auto"} */
@@ -466,7 +466,7 @@ app.use(cookieSession({
466466 sameSite: 'strict',
467467 httpOnly: true,
468468 maxAge: getSessionCookieAge(),
469469 secret: getCookieSecret(globalThis.DATA_ROOT),
470470}));
471471
472472app.use(setUserDataMiddleware);
@@ -1137,7 +1137,7 @@ function apply404Middleware() {
11371137}
11381138
11391139// User storage module needs to be initialized before starting the server
11401140initUserStorage(dataRootglobalThis.DATA_ROOT)
11411141 .then(ensurePublicDirectoriesExist)
11421142 .then(migrateUserData)
11431143 .then(migrateSystemPrompts)
src/users.js+22 -10
@@ -15,7 +15,7 @@ import _ from 'lodash';
1515import { sync as writeFileAtomicSync } from 'write-file-atomic';
1616
1717import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } from './constants.js';
1818import { getConfigValue, color, delay, setConfigValue, generateTimestamp } from './util.js';
1919import { readSecret, writeSecret } from './endpoints/secrets.js';
2020import { getContentOfType } from './endpoints/content-manager.js';
2121
@@ -32,6 +32,7 @@ const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
3232 */
3333const DIRECTORIES_CACHE = new Map();
3434const PUBLIC_USER_AVATAR = '/img/default-user.png';
35+const COOKIE_SECRET_PATH = 'cookie-secret.txt';
3536
3637const STORAGE_KEYS = {
3738 csrfSecret: 'csrfSecret',
@@ -412,11 +413,10 @@ export function toAvatarKey(handle) {
412413 * @returns {Promise<void>}
413414 */
414415export async function initUserStorage(dataRoot) {
415- globalThis.DATA_ROOT = dataRoot;
416+ console.log('Using data root:', color.green(dataRoot));
416- console.log('Using data root:', color.green(globalThis.DATA_ROOT));
417417 console.log();
418418 await storage.init({
419419 dir: path.join(globalThis.DATA_ROOTdataRoot, '_storage'),
420420 ttl: false, // Never expire
421421 });
422422
@@ -430,17 +430,29 @@ export async function initUserStorage(dataRoot) {
430430
431431/**
432432 * Get the cookie secret from the config. If it doesn't exist, generate a new one.
433+ * @param {string} dataRoot The root directory for user data
433434 * @returns {string} The cookie secret
434435 */
435436export function getCookieSecret(dataRoot) {
436437 letconst secretcookieSecretPath = getConfigValue(STORAGE_KEYSpath.cookieSecretjoin(dataRoot, COOKIE_SECRET_PATH);
438+
439+ if (fs.existsSync(cookieSecretPath)) {
440+ const stat = fs.statSync(cookieSecretPath);
441+ if (stat.size > 0) {
442+ return fs.readFileSync(cookieSecretPath, 'utf8');
443+ }
444+ }
437445
438- if (!secret) {
446+ const oldSecret = getConfigValue(STORAGE_KEYS.cookieSecret);
439- console.warn(color.yellow('Cookie secret is missing from config.yaml. Generating a new one...'));
447+ if (oldSecret) {
440- secret = crypto.randomBytes(64).toString('base64');
448+ console.log('Migrating cookie secret from config.yaml...');
441- setConfigValue(STORAGE_KEYS.cookieSecret, secret);
449+ writeFileAtomicSync(cookieSecretPath, oldSecret, { encoding: 'utf8' });
450+ return oldSecret;
442451 }
443452
453+ console.warn(color.yellow('Cookie secret is missing from data root. Generating a new one...'));
454+ const secret = crypto.randomBytes(64).toString('base64');
455+ writeFileAtomicSync(cookieSecretPath, secret, { encoding: 'utf8' });
444456 return secret;
445457}
446458
src/util.js+0 -14
@@ -9,7 +9,6 @@ import { promises as dnsPromise } from 'node:dns';
99
1010import yaml from 'yaml';
1111import { sync as commandExistsSync } from 'command-exists';
12-import { sync as writeFileAtomicSync } from 'write-file-atomic';
1312import _ from 'lodash';
1413import yauzl from 'yauzl';
1514import mime from 'mime-types';
@@ -59,19 +58,6 @@ export function getConfigValue(key, defaultValue = null) {
5958}
6059
6160/**
62- * Sets a value for the given key in the config object and writes it to the config.yaml file.
63- * @param {string} key Key to set
64- * @param {any} value Value to set
65- */
66-export function setConfigValue(key, value) {
67- // Reset cache so that the next getConfig call will read the updated config file
68- CACHED_CONFIG = null;
69- const config = getConfig();
70- _.set(config, key, value);
71- writeFileAtomicSync('./config.yaml', yaml.stringify(config));
72-}
73-
74-/**
7561 * Encodes the Basic Auth header value for the given user and password.
7662 * @param {string} auth username:password
7763 * @returns {string} Basic Auth header value