19 Commits

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

### Bug Fixes

* AuthData validation incorrectly triggered on unchanged providers ([#10025](https://github.com/parse-community/parse-server/issues/10025)) ([d3d6e9e](d3d6e9e22a))
2026-02-06 02:04:22 +00:00
Copilot
d3d6e9e22a fix: AuthData validation incorrectly triggered on unchanged providers (#10025) 2026-02-06 02:03:34 +00:00
semantic-release-bot
a4909792bd chore(release): 9.2.1-alpha.1 [skip ci]
## [9.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.2.0...9.2.1-alpha.1) (2026-02-06)

### Bug Fixes

* Default HTML pages for password reset, email verification not found ([#10034](https://github.com/parse-community/parse-server/issues/10034)) ([e299107](e29910764d))
2026-02-06 01:43:56 +00:00
Manuel
e29910764d fix: Default HTML pages for password reset, email verification not found (#10034) 2026-02-06 01:42:54 +00:00
dependabot[bot]
8cc71cf9e4 refactor: Bump @babel/core from 7.28.6 to 7.29.0 (#10032) 2026-02-05 13:44:33 +00:00
semantic-release-bot
84959c69e5 chore(release): 9.2.0 [skip ci]
# [9.2.0](https://github.com/parse-community/parse-server/compare/9.1.1...9.2.0) (2026-02-05)

### Bug Fixes

* MongoDB timeout errors unhandled and potentially revealing internal data ([#10020](https://github.com/parse-community/parse-server/issues/10020)) ([1d3336d](1d3336d128))
* Security upgrade @apollo/server from 5.0.0 to 5.4.0 ([#10035](https://github.com/parse-community/parse-server/issues/10035)) ([9f368ff](9f368ff9ca))

### Features

* Add option `databaseOptions.clientMetadata` to send custom metadata to database server for logging and debugging ([#10017](https://github.com/parse-community/parse-server/issues/10017)) ([756c204](756c204220))
* Upgrade mongodb from 6.20.0 to 7.0.0 ([#10027](https://github.com/parse-community/parse-server/issues/10027)) ([14b3fce](14b3fce203))
* Upgrade to parse 8.0.3 and @parse/push-adapter 8.2.0 ([#10021](https://github.com/parse-community/parse-server/issues/10021)) ([9833fdb](9833fdb111))
2026-02-05 13:10:33 +00:00
Manuel
88b6977333 build: Release (#10036) 2026-02-05 13:09:31 +00:00
GitHub Actions
88fa87aa28 empty commit to trigger CI 2026-02-05 12:45:46 +00:00
semantic-release-bot
e70303d5c3 chore(release): 9.2.0-alpha.5 [skip ci]
# [9.2.0-alpha.5](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.4...9.2.0-alpha.5) (2026-02-05)

### Bug Fixes

* Security upgrade @apollo/server from 5.0.0 to 5.4.0 ([#10035](https://github.com/parse-community/parse-server/issues/10035)) ([9f368ff](9f368ff9ca))
2026-02-05 12:43:01 +00:00
dependabot[bot]
9f368ff9ca fix: Security upgrade @apollo/server from 5.0.0 to 5.4.0 (#10035) 2026-02-05 12:42:13 +00:00
dependabot[bot]
b87eaea12f refactor: Bump cors from 2.8.5 to 2.8.6 (#10033) 2026-02-04 17:55:30 +00:00
Corey
6cfbcfd139 refactor: Upgrade pg-promise from 12.2.0 to 12.6.0 (#10031) 2026-02-04 00:55:44 +00:00
dependabot[bot]
c21e8952ae refactor: Bump m from 1.9.1 to 1.10.0 (#10029) 2026-01-31 02:22:17 +01:00
dependabot[bot]
f6d78005d4 refactor: Bump @babel/eslint-parser from 7.28.0 to 7.28.6 (#10028) 2026-01-30 19:16:58 +01:00
dependabot[bot]
b42a0ee61d refactor: Bump winston from 3.17.0 to 3.19.0 (#10023) 2026-01-29 20:29:19 +01:00
semantic-release-bot
2457da9e15 chore(release): 9.2.0-alpha.4 [skip ci]
# [9.2.0-alpha.4](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.3...9.2.0-alpha.4) (2026-01-29)

### Features

* Upgrade mongodb from 6.20.0 to 7.0.0 ([#10027](https://github.com/parse-community/parse-server/issues/10027)) ([14b3fce](14b3fce203))
2026-01-29 12:52:21 +00:00
dependabot[bot]
14b3fce203 feat: Upgrade mongodb from 6.20.0 to 7.0.0 (#10027) 2026-01-29 13:51:32 +01:00
dependabot[bot]
73e21e77c7 refactor: Bump bcryptjs from 3.0.2 to 3.0.3 (#10022) 2026-01-27 20:09:50 +01:00
Manuel
c015864293 docs: Simplify PR template (#10026) 2026-01-27 18:07:02 +01:00
9 changed files with 705 additions and 533 deletions

View File

@@ -2,10 +2,9 @@
- Report security issues [confidentially](https://github.com/parse-community/parse-server/security/policy).
- Any contribution is under this [license](https://github.com/parse-community/parse-server/blob/alpha/LICENSE).
- Link this pull request to an [issue](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).
## Issue
<!-- Add the link to the issue that this PR closes. -->
<!-- Describe the issue. -->
Closes: FILL_THIS_OUT
@@ -13,7 +12,7 @@ Closes: FILL_THIS_OUT
<!-- Describe the changes in this PR. -->
## Tasks
<!-- Delete tasks that don't apply. -->
<!-- Check completed tasks and delete tasks that don't apply. -->
- [ ] Add tests
- [ ] Add changes to documentation (guides, repository pages, code comments)

View File

@@ -1,3 +1,31 @@
## [9.2.1-alpha.2](https://github.com/parse-community/parse-server/compare/9.2.1-alpha.1...9.2.1-alpha.2) (2026-02-06)
### Bug Fixes
* AuthData validation incorrectly triggered on unchanged providers ([#10025](https://github.com/parse-community/parse-server/issues/10025)) ([d3d6e9e](https://github.com/parse-community/parse-server/commit/d3d6e9e22a212885690853cbbb84bb8c53da5646))
## [9.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.2.0...9.2.1-alpha.1) (2026-02-06)
### Bug Fixes
* Default HTML pages for password reset, email verification not found ([#10034](https://github.com/parse-community/parse-server/issues/10034)) ([e299107](https://github.com/parse-community/parse-server/commit/e29910764daef3c03ed1b09eee19cedc3b12a86a))
# [9.2.0-alpha.5](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.4...9.2.0-alpha.5) (2026-02-05)
### Bug Fixes
* Security upgrade @apollo/server from 5.0.0 to 5.4.0 ([#10035](https://github.com/parse-community/parse-server/issues/10035)) ([9f368ff](https://github.com/parse-community/parse-server/commit/9f368ff9ca322c61cdcfab735e5b5240d1c8f917))
# [9.2.0-alpha.4](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.3...9.2.0-alpha.4) (2026-01-29)
### Features
* Upgrade mongodb from 6.20.0 to 7.0.0 ([#10027](https://github.com/parse-community/parse-server/issues/10027)) ([14b3fce](https://github.com/parse-community/parse-server/commit/14b3fce203be0abaf29c27c123cba47f35d09c68))
# [9.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.2...9.2.0-alpha.3) (2026-01-27)

View File

@@ -1,3 +1,17 @@
# [9.2.0](https://github.com/parse-community/parse-server/compare/9.1.1...9.2.0) (2026-02-05)
### Bug Fixes
* MongoDB timeout errors unhandled and potentially revealing internal data ([#10020](https://github.com/parse-community/parse-server/issues/10020)) ([1d3336d](https://github.com/parse-community/parse-server/commit/1d3336d128671c974b419b9b34db35ada7d1a44d))
* Security upgrade @apollo/server from 5.0.0 to 5.4.0 ([#10035](https://github.com/parse-community/parse-server/issues/10035)) ([9f368ff](https://github.com/parse-community/parse-server/commit/9f368ff9ca322c61cdcfab735e5b5240d1c8f917))
### Features
* Add option `databaseOptions.clientMetadata` to send custom metadata to database server for logging and debugging ([#10017](https://github.com/parse-community/parse-server/issues/10017)) ([756c204](https://github.com/parse-community/parse-server/commit/756c204220a2c7be3770b7d4a49f11e8903323db))
* Upgrade mongodb from 6.20.0 to 7.0.0 ([#10027](https://github.com/parse-community/parse-server/issues/10027)) ([14b3fce](https://github.com/parse-community/parse-server/commit/14b3fce203be0abaf29c27c123cba47f35d09c68))
* Upgrade to parse 8.0.3 and @parse/push-adapter 8.2.0 ([#10021](https://github.com/parse-community/parse-server/issues/10021)) ([9833fdb](https://github.com/parse-community/parse-server/commit/9833fdb111c373dc75fc74ea5f9209408186a475))
## [9.1.1](https://github.com/parse-community/parse-server/compare/9.1.0...9.1.1) (2025-12-16)

994
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "9.2.0-alpha.3",
"version": "9.2.1-alpha.2",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -10,7 +10,7 @@
"files": [
"bin/",
"lib/",
"public_html/",
"public/",
"views/",
"LICENSE",
"NOTICE",
@@ -20,16 +20,16 @@
],
"license": "Apache-2.0",
"dependencies": {
"@apollo/server": "5.0.0",
"@apollo/server": "5.4.0",
"@as-integrations/express5": "1.1.2",
"@graphql-tools/merge": "9.0.24",
"@graphql-tools/schema": "10.0.23",
"@graphql-tools/utils": "10.8.6",
"@parse/fs-files-adapter": "3.0.0",
"@parse/push-adapter": "8.2.0",
"bcryptjs": "3.0.2",
"bcryptjs": "3.0.3",
"commander": "14.0.2",
"cors": "2.8.5",
"cors": "2.8.6",
"deepcopy": "2.1.0",
"express": "5.2.1",
"express-rate-limit": "7.5.1",
@@ -45,13 +45,13 @@
"lodash": "4.17.23",
"lru-cache": "10.4.0",
"mime": "4.0.7",
"mongodb": "6.20.0",
"mongodb": "7.0.0",
"mustache": "4.2.0",
"otpauth": "9.4.0",
"parse": "8.0.3",
"path-to-regexp": "8.3.0",
"pg-monitor": "3.0.0",
"pg-promise": "12.2.0",
"pg-promise": "12.6.0",
"pluralize": "8.0.0",
"punycode": "2.3.1",
"rate-limit-redis": "4.2.0",
@@ -60,7 +60,7 @@
"subscriptions-transport-ws": "0.11.0",
"tv4": "1.3.0",
"uuid": "11.1.0",
"winston": "3.17.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.18.2"
},
@@ -68,8 +68,8 @@
"@actions/core": "1.11.1",
"@apollo/client": "3.13.8",
"@babel/cli": "7.27.0",
"@babel/core": "7.28.6",
"@babel/eslint-parser": "7.28.0",
"@babel/core": "7.29.0",
"@babel/eslint-parser": "7.28.6",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-transform-flow-strip-types": "7.26.5",
"@babel/preset-env": "7.27.2",
@@ -97,7 +97,7 @@
"jsdoc": "4.0.4",
"jsdoc-babel": "0.5.0",
"lint-staged": "16.1.0",
"m": "1.9.1",
"m": "1.10.0",
"madge": "8.0.0",
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",

View File

@@ -76,6 +76,41 @@ describe('Auth Adapter features', () => {
validateAppId: () => Promise.resolve(),
};
// Code-based adapter that requires 'code' field (like gpgames)
const codeBasedAdapter = {
validateAppId: () => Promise.resolve(),
validateSetUp: authData => {
if (!authData.code) {
throw new Error('code is required.');
}
return Promise.resolve({ save: { id: authData.id } });
},
validateUpdate: authData => {
if (!authData.code) {
throw new Error('code is required.');
}
return Promise.resolve({ save: { id: authData.id } });
},
validateLogin: authData => {
if (!authData.code) {
throw new Error('code is required.');
}
return Promise.resolve({ save: { id: authData.id } });
},
afterFind: authData => {
// Strip sensitive 'code' field when returning to client
return { id: authData.id };
},
};
// Simple adapter that doesn't require code
const simpleAdapter = {
validateAppId: () => Promise.resolve(),
validateSetUp: () => Promise.resolve(),
validateUpdate: () => Promise.resolve(),
validateLogin: () => Promise.resolve(),
};
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
@@ -1302,4 +1337,42 @@ describe('Auth Adapter features', () => {
await user.fetch({ useMasterKey: true });
expect(user.get('authData')).toEqual({ adapterB: { id: 'test' } });
});
it('should handle multiple providers: add one while another remains unchanged (code-based)', async () => {
await reconfigureServer({
auth: {
codeBasedAdapter,
simpleAdapter,
},
});
// Login with code-based provider
const user = new Parse.User();
await user.save({ authData: { codeBasedAdapter: { id: 'user1', code: 'code1' } } });
const sessionToken = user.getSessionToken();
await user.fetch({ sessionToken });
// At this point, authData.codeBasedAdapter only has {id: 'user1'} due to afterFind
const current = user.get('authData') || {};
expect(current.codeBasedAdapter).toEqual({ id: 'user1' });
// Add a second provider while keeping the first unchanged
user.set('authData', {
...current,
simpleAdapter: { id: 'simple1' },
// codeBasedAdapter is NOT modified (no new code provided)
});
// This should succeed without requiring 'code' for codeBasedAdapter
await user.save(null, { sessionToken });
// Verify both providers are present
const reloaded = await new Parse.Query(Parse.User).get(user.id, {
useMasterKey: true,
});
const authData = reloaded.get('authData') || {};
expect(authData.simpleAdapter && authData.simpleAdapter.id).toBe('simple1');
expect(authData.codeBasedAdapter && authData.codeBasedAdapter.id).toBe('user1');
});
});

View File

@@ -1181,6 +1181,61 @@ describe('Pages Router', () => {
});
});
describe('async publicServerURL', () => {
it('resolves async publicServerURL for password reset page', async () => {
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
appId: 'test',
appName: 'exampleAppname',
verifyUserEmails: true,
emailAdapter,
publicServerURL: () => 'http://localhost:8378/1',
pages: { enableRouter: true },
});
const user = new Parse.User();
user.setUsername('asyncUrlUser');
user.setPassword('examplePassword');
user.set('email', 'async-url@example.com');
await user.signUp();
await Parse.User.requestPasswordReset('async-url@example.com');
const response = await request({
url: 'http://localhost:8378/1/apps/test/request_password_reset?token=invalidToken',
followRedirects: false,
}).catch(e => e);
expect(response.status).toBe(200);
expect(response.text).toContain('Invalid password reset link!');
});
it('resolves async publicServerURL for email verification page', async () => {
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
appId: 'test',
appName: 'exampleAppname',
verifyUserEmails: true,
emailAdapter,
publicServerURL: () => 'http://localhost:8378/1',
pages: { enableRouter: true },
});
const response = await request({
url: 'http://localhost:8378/1/apps/test/verify_email?token=invalidToken',
followRedirects: false,
}).catch(e => e);
expect(response.status).toBe(200);
expect(response.text).toContain('Invalid verification link!');
});
});
describe('XSS Protection', () => {
beforeEach(async () => {
await reconfigureServer({

View File

@@ -456,7 +456,32 @@ const hasMutatedAuthData = (authData, userAuthData) => {
if (provider === 'anonymous') { return; }
const providerData = authData[provider];
const userProviderAuthData = userAuthData[provider];
if (!isDeepStrictEqual(providerData, userProviderAuthData)) {
// If unlinking (setting to null), consider it mutated
if (providerData === null) {
mutatedAuthData[provider] = providerData;
return;
}
// If provider doesn't exist in stored data, it's new
if (!userProviderAuthData) {
mutatedAuthData[provider] = providerData;
return;
}
// Check if incoming data represents actual changes vs just echoing back
// what afterFind returned. If incoming data is a subset of stored data
// (all incoming fields match stored values), it's not mutated.
// If incoming data has different values or fields not in stored data, it's mutated.
// This handles the case where afterFind strips sensitive fields like 'code':
// - Incoming: { id: 'x' }, Stored: { id: 'x', code: 'secret' } -> NOT mutated (subset)
// - Incoming: { id: 'x', token: 'new' }, Stored: { id: 'x', token: 'old' } -> MUTATED
const incomingKeys = Object.keys(providerData || {});
const hasChanges = incomingKeys.some(key => {
return !isDeepStrictEqual(providerData[key], userProviderAuthData[key]);
});
if (hasChanges) {
mutatedAuthData[provider] = providerData;
}
});

View File

@@ -624,12 +624,14 @@ export class PagesRouter extends PromiseRouter {
* @param {Boolean} failGracefully Is true if failing to set the config should
* not result in an invalid request response. Default is `false`.
*/
setConfig(req, failGracefully = false) {
async setConfig(req, failGracefully = false) {
req.config = Config.get(req.params.appId || req.query.appId);
if (!req.config && !failGracefully) {
this.invalidRequest();
}
return Promise.resolve();
if (req.config) {
await req.config.loadKeys();
}
}
mountPagesRoutes() {
@@ -637,7 +639,7 @@ export class PagesRouter extends PromiseRouter {
'GET',
`/${this.pagesEndpoint}/:appId/verify_email`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
req => {
return this.verifyEmail(req);
@@ -648,7 +650,7 @@ export class PagesRouter extends PromiseRouter {
'POST',
`/${this.pagesEndpoint}/:appId/resend_verification_email`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
req => {
return this.resendVerificationEmail(req);
@@ -659,7 +661,7 @@ export class PagesRouter extends PromiseRouter {
'GET',
`/${this.pagesEndpoint}/choose_password`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
req => {
return this.passwordReset(req);
@@ -670,7 +672,7 @@ export class PagesRouter extends PromiseRouter {
'POST',
`/${this.pagesEndpoint}/:appId/request_password_reset`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
req => {
return this.resetPassword(req);
@@ -681,7 +683,7 @@ export class PagesRouter extends PromiseRouter {
'GET',
`/${this.pagesEndpoint}/:appId/request_password_reset`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
req => {
return this.requestResetPassword(req);
@@ -695,7 +697,7 @@ export class PagesRouter extends PromiseRouter {
route.method,
`/${this.pagesEndpoint}/:appId/${route.path}`,
req => {
this.setConfig(req);
return this.setConfig(req);
},
async req => {
const { file, query = {} } = (await route.handler(req)) || {};
@@ -718,7 +720,7 @@ export class PagesRouter extends PromiseRouter {
'GET',
`/${this.pagesEndpoint}/*resource`,
req => {
this.setConfig(req, true);
return this.setConfig(req, true);
},
req => {
return this.staticRoute(req);