From d35351f6b5250698250c682320f4a64dcbcb99f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:51:56 +0000 Subject: [PATCH 1/4] Initial plan From 953ab4d8a7985300de711dac504770fce50f7b16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:58:01 +0000 Subject: [PATCH 2/4] Initial analysis: Identified CSRF token regression issue Co-authored-by: mtrezza <5673677+mtrezza@users.noreply.github.com> --- package-lock.json | 52 +++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9bae464e..3f08d0cea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -211,7 +211,6 @@ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2195,12 +2194,14 @@ "node_modules/@codemirror/state": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz", - "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==" + "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==", + "peer": true }, "node_modules/@codemirror/view": { "version": "0.20.7", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz", "integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==", + "peer": true, "dependencies": { "@codemirror/state": "^0.20.0", "style-mod": "^4.0.0", @@ -2305,7 +2306,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2329,7 +2329,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3937,12 +3936,14 @@ "node_modules/@lezer/common": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz", - "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==" + "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==", + "peer": true }, "node_modules/@lezer/highlight": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz", "integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==", + "peer": true, "dependencies": { "@lezer/common": "^0.16.0" } @@ -3951,6 +3952,7 @@ "version": "0.16.3", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz", "integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==", + "peer": true, "dependencies": { "@lezer/common": "^0.16.0" } @@ -4035,7 +4037,6 @@ "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -4692,7 +4693,6 @@ "version": "2.11.5", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4994,7 +4994,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", "dev": true, - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5675,7 +5674,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", "dev": true, - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -5825,7 +5823,6 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", "dev": true, - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -6943,7 +6940,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6963,7 +6959,6 @@ "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.24.tgz", "integrity": "sha512-eIpyco99gTH+FTI3J7Oi/OH8MZoFMJuztNRimDOJwH4iGIsKV2qkGnk4M9VzlaVWeEEWLWSQRy0FEA0Kz218cg==", - "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -7788,7 +7783,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7847,7 +7841,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8862,7 +8855,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9366,8 +9358,7 @@ "node_modules/codemirror": { "version": "5.65.9", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", - "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==", - "peer": true + "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==" }, "node_modules/codemirror-graphql": { "version": "2.0.0", @@ -10361,8 +10352,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff-match-patch": { "version": "1.0.5", @@ -10989,7 +10979,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -13057,7 +13046,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -14416,7 +14404,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "30.0.4", "@jest/types": "30.0.1", @@ -15667,7 +15654,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -16318,7 +16304,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -19314,7 +19299,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -20443,7 +20427,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -21133,7 +21116,6 @@ "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -21233,7 +21215,6 @@ "version": "16.14.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -21302,8 +21283,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "peer": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view": { "version": "1.21.3", @@ -22132,7 +22112,6 @@ "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -22266,7 +22245,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -22308,7 +22286,6 @@ "integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -23622,7 +23599,8 @@ "node_modules/style-mod": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "peer": true }, "node_modules/stylus-lookup": { "version": "6.1.0", @@ -24356,7 +24334,6 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -24797,7 +24774,8 @@ "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "peer": true }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", @@ -24880,7 +24858,6 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -24930,7 +24907,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", From 5ec933e09d6bff8662683286430f4076926ffccb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 04:00:44 +0000 Subject: [PATCH 3/4] Fix: Support cookieSessionSecret in config file for multi-replica deployments Co-authored-by: mtrezza <5673677+mtrezza@users.noreply.github.com> --- Parse-Dashboard/app.js | 4 +- Parse-Dashboard/server.js | 2 +- README.md | 17 +++++++- src/lib/tests/SessionStore.test.js | 64 ++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index cc8e034fe..f9c73354a 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -83,9 +83,9 @@ module.exports = function(config, options) { const useEncryptedPasswords = config.useEncryptedPasswords ? true : false; const authInstance = new Authentication(users, useEncryptedPasswords, mountPath); authInstance.initialize(app, { - cookieSessionSecret: options.cookieSessionSecret, + cookieSessionSecret: options.cookieSessionSecret || config.cookieSessionSecret, cookieSessionMaxAge: options.cookieSessionMaxAge, - cookieSessionStore: options.cookieSessionStore + cookieSessionStore: options.cookieSessionStore || config.cookieSessionStore }); // CSRF error handler diff --git a/Parse-Dashboard/server.js b/Parse-Dashboard/server.js index a04fa79ac..9a8efc8bb 100644 --- a/Parse-Dashboard/server.js +++ b/Parse-Dashboard/server.js @@ -164,7 +164,7 @@ module.exports = (options) => { config.data.trustProxy = trustProxy; const dashboardOptions = { allowInsecureHTTP, - cookieSessionSecret, + cookieSessionSecret: cookieSessionSecret || config.data.cookieSessionSecret, dev, cookieSessionMaxAge, cookieSessionStore: config.data.cookieSessionStore diff --git a/README.md b/README.md index 2fb91054e..5ff466988 100644 --- a/README.md +++ b/README.md @@ -819,7 +819,7 @@ When deploying Parse Dashboard with multiple replicas behind a load balancer, yo #### Using a Custom Session Store -Parse Dashboard supports using any session store compatible with [express-session](https://github.com/expressjs/session). The `sessionStore` option must be configured programmatically when initializing the dashboard. +Parse Dashboard supports using any session store compatible with [express-session](https://github.com/expressjs/session). Both `cookieSessionSecret` and `cookieSessionStore` can be configured either programmatically when initializing the dashboard or via a configuration file (note: `cookieSessionStore` can only be configured programmatically as it requires an object instance). **Suggested Session Stores:** @@ -851,6 +851,21 @@ const dashboard = new ParseDashboard({ cookieSessionStore, cookieSessionSecret: 'your-secret-key', }); +``` + +**Example using a configuration file:** + +When starting the dashboard with a config file, you can specify `cookieSessionSecret` directly in the JSON configuration: + +```json +{ + "apps": [...], + "users": [...], + "cookieSessionSecret": "your-secret-key" +} +``` + +Note: `cookieSessionStore` must still be configured programmatically as it requires an object instance, but `cookieSessionSecret` can now be specified in the config file for convenience. **Important Notes:** diff --git a/src/lib/tests/SessionStore.test.js b/src/lib/tests/SessionStore.test.js index 813b6daa9..d4132b188 100644 --- a/src/lib/tests/SessionStore.test.js +++ b/src/lib/tests/SessionStore.test.js @@ -158,4 +158,68 @@ describe('SessionStore Integration', () => { expect(dashboardApp).toBeDefined(); expect(typeof dashboardApp).toBe('function'); }); + + it('reads cookieSessionSecret from config when not in options', () => { + const parseDashboard = require('../../../Parse-Dashboard/app.js'); + + const config = { + apps: [ + { + serverURL: 'http://localhost:1337/parse', + appId: 'testAppId', + masterKey: 'testMasterKey', + appName: 'TestApp', + }, + ], + users: [ + { + user: 'testuser', + pass: 'testpass', + }, + ], + cookieSessionSecret: 'secret-from-config', + }; + + const options = {}; + + // Create dashboard app with cookieSessionSecret in config + const dashboardApp = parseDashboard(config, options); + + // The app should be created successfully with config-provided secret + expect(dashboardApp).toBeDefined(); + expect(typeof dashboardApp).toBe('function'); + }); + + it('prefers cookieSessionSecret from options over config', () => { + const parseDashboard = require('../../../Parse-Dashboard/app.js'); + + const config = { + apps: [ + { + serverURL: 'http://localhost:1337/parse', + appId: 'testAppId', + masterKey: 'testMasterKey', + appName: 'TestApp', + }, + ], + users: [ + { + user: 'testuser', + pass: 'testpass', + }, + ], + cookieSessionSecret: 'secret-from-config', + }; + + const options = { + cookieSessionSecret: 'secret-from-options', + }; + + // Create dashboard app with both config and options secret + const dashboardApp = parseDashboard(config, options); + + // The app should be created successfully, preferring options secret + expect(dashboardApp).toBeDefined(); + expect(typeof dashboardApp).toBe('function'); + }); }); From 70fd86700f567ad6e20fdcaf4805e3e21421be99 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:09:54 +0100 Subject: [PATCH 4/4] Apply suggestion from @mtrezza Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5ff466988..ef53c2293 100644 --- a/README.md +++ b/README.md @@ -865,8 +865,6 @@ When starting the dashboard with a config file, you can specify `cookieSessionSe } ``` -Note: `cookieSessionStore` must still be configured programmatically as it requires an object instance, but `cookieSessionSecret` can now be specified in the config file for convenience. - **Important Notes:** - The `cookieSessionSecret` option must be set to the same value across all replicas to ensure session cookies work correctly.