3 Commits

Author SHA1 Message Date
semantic-release-bot
97de70a017 chore(release): 9.3.0-alpha.2 [skip ci]
# [9.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/9.3.0-alpha.1...9.3.0-alpha.2) (2026-02-06)

### Bug Fixes

* Default HTML pages for password reset, email verification not found ([#10041](https://github.com/parse-community/parse-server/issues/10041)) ([a4265bb](a4265bb124))
2026-02-06 16:31:03 +00:00
Manuel
a4265bb124 fix: Default HTML pages for password reset, email verification not found (#10041) 2026-02-06 16:30:13 +00:00
dependabot[bot]
c1f1800cad refactor: Bump commander from 14.0.2 to 14.0.3 (#10039) 2026-02-06 15:19:51 +00:00
8 changed files with 53 additions and 22 deletions

View File

@@ -1,3 +1,10 @@
# [9.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/9.3.0-alpha.1...9.3.0-alpha.2) (2026-02-06)
### Bug Fixes
* Default HTML pages for password reset, email verification not found ([#10041](https://github.com/parse-community/parse-server/issues/10041)) ([a4265bb](https://github.com/parse-community/parse-server/commit/a4265bb1241551b7147e8aee08c36e1f8ab09ba4))
# [9.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/9.2.1-alpha.2...9.3.0-alpha.1) (2026-02-06) # [9.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/9.2.1-alpha.2...9.3.0-alpha.1) (2026-02-06)

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "parse-server", "name": "parse-server",
"version": "9.3.0-alpha.1", "version": "9.3.0-alpha.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "parse-server", "name": "parse-server",
"version": "9.3.0-alpha.1", "version": "9.3.0-alpha.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -18,7 +18,7 @@
"@parse/fs-files-adapter": "3.0.0", "@parse/fs-files-adapter": "3.0.0",
"@parse/push-adapter": "8.2.0", "@parse/push-adapter": "8.2.0",
"bcryptjs": "3.0.3", "bcryptjs": "3.0.3",
"commander": "14.0.2", "commander": "14.0.3",
"cors": "2.8.6", "cors": "2.8.6",
"deepcopy": "2.1.0", "deepcopy": "2.1.0",
"express": "5.2.1", "express": "5.2.1",
@@ -8555,9 +8555,9 @@
} }
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "14.0.2", "version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
@@ -28403,9 +28403,9 @@
} }
}, },
"commander": { "commander": {
"version": "14.0.2", "version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==" "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="
}, },
"commondir": { "commondir": {
"version": "1.0.1", "version": "1.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "parse-server", "name": "parse-server",
"version": "9.3.0-alpha.1", "version": "9.3.0-alpha.2",
"description": "An express module providing a Parse-compatible API server", "description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js", "main": "lib/index.js",
"repository": { "repository": {
@@ -28,7 +28,7 @@
"@parse/fs-files-adapter": "3.0.0", "@parse/fs-files-adapter": "3.0.0",
"@parse/push-adapter": "8.2.0", "@parse/push-adapter": "8.2.0",
"bcryptjs": "3.0.3", "bcryptjs": "3.0.3",
"commander": "14.0.2", "commander": "14.0.3",
"cors": "2.8.6", "cors": "2.8.6",
"deepcopy": "2.1.0", "deepcopy": "2.1.0",
"express": "5.2.1", "express": "5.2.1",

View File

@@ -225,9 +225,7 @@ describe('Pages Router', () => {
expect(Config.get(Parse.applicationId).pages.forceRedirect).toBe( expect(Config.get(Parse.applicationId).pages.forceRedirect).toBe(
Definitions.PagesOptions.forceRedirect.default Definitions.PagesOptions.forceRedirect.default
); );
expect(Config.get(Parse.applicationId).pages.pagesPath).toBe( expect(Config.get(Parse.applicationId).pages.pagesPath).toBeUndefined();
Definitions.PagesOptions.pagesPath.default
);
expect(Config.get(Parse.applicationId).pages.pagesEndpoint).toBe( expect(Config.get(Parse.applicationId).pages.pagesEndpoint).toBe(
Definitions.PagesOptions.pagesEndpoint.default Definitions.PagesOptions.pagesEndpoint.default
); );
@@ -1236,6 +1234,36 @@ describe('Pages Router', () => {
}); });
}); });
describe('pagesPath resolution', () => {
it('should serve pages when current working directory differs from module directory', async () => {
const originalCwd = process.cwd();
const os = require('os');
process.chdir(os.tmpdir());
try {
await reconfigureServer({
appId: 'test',
appName: 'exampleAppname',
publicServerURL: 'http://localhost:8378/1',
pages: { enableRouter: true },
});
// Request the password reset page with an invalid token;
// even with an invalid token, the server should serve the
// "invalid link" page (200), not a 404. A 404 indicates the
// HTML template files could not be found because pagesPath
// resolved to the wrong directory.
const response = await request({
url: 'http://localhost:8378/1/apps/test/request_password_reset?token=invalidToken',
}).catch(e => e);
expect(response.status).toBe(200);
expect(response.text).toContain('Invalid password reset link');
} finally {
process.chdir(originalCwd);
}
});
});
describe('XSS Protection', () => { describe('XSS Protection', () => {
beforeEach(async () => { beforeEach(async () => {
await reconfigureServer({ await reconfigureServer({

View File

@@ -326,9 +326,7 @@ export class Config {
} else if (!isBoolean(pages.forceRedirect)) { } else if (!isBoolean(pages.forceRedirect)) {
throw 'Parse Server option pages.forceRedirect must be a boolean.'; throw 'Parse Server option pages.forceRedirect must be a boolean.';
} }
if (pages.pagesPath === undefined) { if (pages.pagesPath !== undefined && !isString(pages.pagesPath)) {
pages.pagesPath = PagesOptions.pagesPath.default;
} else if (!isString(pages.pagesPath)) {
throw 'Parse Server option pages.pagesPath must be a string.'; throw 'Parse Server option pages.pagesPath must be a string.';
} }
if (pages.pagesEndpoint === undefined) { if (pages.pagesEndpoint === undefined) {

View File

@@ -772,8 +772,7 @@ module.exports.PagesOptions = {
pagesPath: { pagesPath: {
env: 'PARSE_SERVER_PAGES_PAGES_PATH', env: 'PARSE_SERVER_PAGES_PAGES_PATH',
help: help:
"The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory.", "The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory of the parse-server module.",
default: './public',
}, },
placeholders: { placeholders: {
env: 'PARSE_SERVER_PAGES_PLACEHOLDERS', env: 'PARSE_SERVER_PAGES_PLACEHOLDERS',

View File

@@ -142,7 +142,7 @@
* @property {String} localizationFallbackLocale The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file. * @property {String} localizationFallbackLocale The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file.
* @property {String} localizationJsonPath The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale. * @property {String} localizationJsonPath The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale.
* @property {String} pagesEndpoint The API endpoint for the pages. Default is 'apps'. * @property {String} pagesEndpoint The API endpoint for the pages. Default is 'apps'.
* @property {String} pagesPath The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory. * @property {String} pagesPath The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory of the parse-server module.
* @property {Object} placeholders The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function. * @property {Object} placeholders The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function.
*/ */

View File

@@ -437,8 +437,7 @@ export interface PagesOptions {
/* Is true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response). /* Is true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response).
:DEFAULT: false */ :DEFAULT: false */
forceRedirect: ?boolean; forceRedirect: ?boolean;
/* The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory. /* The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory of the parse-server module. */
:DEFAULT: ./public */
pagesPath: ?string; pagesPath: ?string;
/* The API endpoint for the pages. Default is 'apps'. /* The API endpoint for the pages. Default is 'apps'.
:DEFAULT: apps */ :DEFAULT: apps */