Skip to content

Commit 7200e53

Browse files
committed
merge upstream/alpha and fix CI failures
- Resolve merge conflicts in triggers.js and Definitions.js - Fix ESLint curly rule in InProcessAdapter spec - Fix TypeScript: import * as http, import Parse default - Fix Options/index.js: use ?Object instead of inline types (buildConfigDefinitions.js cannot parse Array<Object> or inline object type annotations)
2 parents 5b9f644 + 206e8b6 commit 7200e53

52 files changed

Lines changed: 878 additions & 154 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DEPRECATIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
2222
| DEPPS16 | Remove config option `mountPlayground` | [#10110](https://github.com/parse-community/parse-server/issues/10110) | 9.5.0 (2026) | 10.0.0 (2027) | deprecated | - |
2323
| DEPPS17 | Remove config option `playgroundPath` | [#10110](https://github.com/parse-community/parse-server/issues/10110) | 9.5.0 (2026) | 10.0.0 (2027) | deprecated | - |
2424
| DEPPS18 | Config option `requestComplexity` limits enabled by default | [#10207](https://github.com/parse-community/parse-server/pull/10207) | 9.6.0 (2026) | 10.0.0 (2027) | deprecated | - |
25+
| DEPPS19 | Remove config option `enableProductPurchaseLegacyApi` | [#10228](https://github.com/parse-community/parse-server/pull/10228) | 9.6.0 (2026) | 10.0.0 (2027) | deprecated | - |
2526

2627
[i_deprecation]: ## "The version and date of the deprecation."
2728
[i_change]: ## "The version and date of the planned change."

changelogs/CHANGELOG_alpha.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1+
# [9.6.0-alpha.33](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.32...9.6.0-alpha.33) (2026-03-17)
2+
3+
4+
### Features
5+
6+
* Add `enableProductPurchaseLegacyApi` option to disable legacy IAP validation ([#10228](https://github.com/parse-community/parse-server/issues/10228)) ([622ee85](https://github.com/parse-community/parse-server/commit/622ee85dc27a4ef721c1d4f61d3ed881a064da0b))
7+
8+
# [9.6.0-alpha.32](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.31...9.6.0-alpha.32) (2026-03-16)
9+
10+
11+
### Bug Fixes
12+
13+
* Instance comparison with `instanceof` is not realm-safe ([#10225](https://github.com/parse-community/parse-server/issues/10225)) ([51efb1e](https://github.com/parse-community/parse-server/commit/51efb1efb9fa3f2d578de63f61b20c6a4fbcbd9a))
14+
15+
# [9.6.0-alpha.31](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.30...9.6.0-alpha.31) (2026-03-16)
16+
17+
18+
### Bug Fixes
19+
20+
* Validate authData provider values in challenge endpoint ([#10224](https://github.com/parse-community/parse-server/issues/10224)) ([e5e1f5b](https://github.com/parse-community/parse-server/commit/e5e1f5bbc008c869614a13ab540f72af57adda8f))
21+
22+
# [9.6.0-alpha.30](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.29...9.6.0-alpha.30) (2026-03-16)
23+
24+
25+
### Bug Fixes
26+
27+
* Block dot-notation updates to authData sub-fields and harden login provider checks ([#10223](https://github.com/parse-community/parse-server/issues/10223)) ([12c24c6](https://github.com/parse-community/parse-server/commit/12c24c6c6c578219703aaea186625f8f36c0d020))
28+
29+
# [9.6.0-alpha.29](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.28...9.6.0-alpha.29) (2026-03-16)
30+
31+
32+
### Bug Fixes
33+
34+
* Empty authData bypasses credential requirement on signup ([GHSA-wjqw-r9x4-j59v](https://github.com/parse-community/parse-server/security/advisories/GHSA-wjqw-r9x4-j59v)) ([#10219](https://github.com/parse-community/parse-server/issues/10219)) ([5dcbf41](https://github.com/parse-community/parse-server/commit/5dcbf41249f1b67c72296934bc4f8538f3b1d821))
35+
136
# [9.6.0-alpha.28](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.27...9.6.0-alpha.28) (2026-03-16)
237

338

eslint.config.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,46 @@ module.exports = [
4141
curly: ["error", "all"],
4242
"block-spacing": ["error", "always"],
4343
"no-unused-vars": "off",
44-
"no-console": "warn"
44+
"no-console": "warn",
45+
"no-restricted-syntax": [
46+
"error",
47+
{
48+
selector: "BinaryExpression[operator='instanceof'][right.name='Date']",
49+
message: "Use Utils.isDate() instead of instanceof Date (cross-realm safe).",
50+
},
51+
{
52+
selector: "BinaryExpression[operator='instanceof'][right.name='RegExp']",
53+
message: "Use Utils.isRegExp() instead of instanceof RegExp (cross-realm safe).",
54+
},
55+
{
56+
selector: "BinaryExpression[operator='instanceof'][right.name='Error']",
57+
message: "Use Utils.isNativeError() instead of instanceof Error (cross-realm safe).",
58+
},
59+
{
60+
selector: "BinaryExpression[operator='instanceof'][right.name='Promise']",
61+
message: "Use Utils.isPromise() instead of instanceof Promise (cross-realm safe).",
62+
},
63+
{
64+
selector: "BinaryExpression[operator='instanceof'][right.name='Map']",
65+
message: "Use Utils.isMap() instead of instanceof Map (cross-realm safe).",
66+
},
67+
{
68+
selector: "BinaryExpression[operator='instanceof'][right.name='Object']",
69+
message: "Use Utils.isObject() instead of instanceof Object (cross-realm safe).",
70+
},
71+
{
72+
selector: "BinaryExpression[operator='instanceof'][right.name='Set']",
73+
message: "Use Utils.isSet() instead of instanceof Set (cross-realm safe).",
74+
},
75+
{
76+
selector: "BinaryExpression[operator='instanceof'][right.name='Buffer']",
77+
message: "Use Buffer.isBuffer() instead of instanceof Buffer (cross-realm safe).",
78+
},
79+
{
80+
selector: "BinaryExpression[operator='instanceof'][right.name='Array']",
81+
message: "Use Array.isArray() instead of instanceof Array (cross-realm safe).",
82+
},
83+
]
4584
},
4685
},
4786
];

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "9.6.0-alpha.28",
3+
"version": "9.6.0-alpha.33",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/AdapterLoader.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { loadAdapter, loadModule } = require('../lib/Adapters/AdapterLoader');
22
const FilesAdapter = require('@parse/fs-files-adapter').default;
33
const MockFilesAdapter = require('mock-files-adapter');
44
const Config = require('../lib/Config');
5+
const Utils = require('../lib/Utils');
56

67
describe('AdapterLoader', () => {
78
it('should instantiate an adapter from string in object', done => {
@@ -15,7 +16,7 @@ describe('AdapterLoader', () => {
1516
},
1617
});
1718

18-
expect(adapter instanceof Object).toBe(true);
19+
expect(Utils.isObject(adapter)).toBe(true);
1920
expect(adapter.options.key).toBe('value');
2021
expect(adapter.options.foo).toBe('bar');
2122
done();
@@ -25,7 +26,7 @@ describe('AdapterLoader', () => {
2526
const adapterPath = require('path').resolve('./spec/support/MockAdapter');
2627
const adapter = loadAdapter(adapterPath);
2728

28-
expect(adapter instanceof Object).toBe(true);
29+
expect(Utils.isObject(adapter)).toBe(true);
2930
done();
3031
});
3132

spec/AuthenticationAdaptersV2.spec.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const request = require('../lib/request');
22
const Auth = require('../lib/Auth');
3+
const Config = require('../lib/Config');
34
const requestWithExpectedError = async params => {
45
try {
56
return await request(params);
@@ -1613,4 +1614,92 @@ describe('Auth Adapter features', () => {
16131614
expect(authData.simpleAdapter && authData.simpleAdapter.id).toBe('simple1');
16141615
expect(authData.codeBasedAdapter && authData.codeBasedAdapter.id).toBe('user1');
16151616
});
1617+
1618+
describe('authData dot-notation injection and login crash', () => {
1619+
it('rejects dotted update key that targets authData sub-field', async () => {
1620+
const user = new Parse.User();
1621+
user.setUsername('dotuser');
1622+
user.setPassword('pass1234');
1623+
await user.signUp();
1624+
1625+
const res = await request({
1626+
method: 'PUT',
1627+
url: `http://localhost:8378/1/users/${user.id}`,
1628+
headers: {
1629+
'Content-Type': 'application/json',
1630+
'X-Parse-Application-Id': 'test',
1631+
'X-Parse-REST-API-Key': 'rest',
1632+
'X-Parse-Session-Token': user.getSessionToken(),
1633+
},
1634+
body: JSON.stringify({ 'authData.anonymous".id': 'injected' }),
1635+
}).catch(e => e);
1636+
expect(res.status).toBe(400);
1637+
});
1638+
1639+
it('login does not crash when stored authData has unknown provider', async () => {
1640+
const user = new Parse.User();
1641+
user.setUsername('dotuser2');
1642+
user.setPassword('pass1234');
1643+
await user.signUp();
1644+
await Parse.User.logOut();
1645+
1646+
// Inject unknown provider directly in database to simulate corrupted data
1647+
const config = Config.get('test');
1648+
await config.database.update(
1649+
'_User',
1650+
{ objectId: user.id },
1651+
{ authData: { unknown_provider: { id: 'bad' } } }
1652+
);
1653+
1654+
// Login should not crash with 500
1655+
const login = await request({
1656+
method: 'GET',
1657+
url: `http://localhost:8378/1/login?username=dotuser2&password=pass1234`,
1658+
headers: {
1659+
'X-Parse-Application-Id': 'test',
1660+
'X-Parse-REST-API-Key': 'rest',
1661+
},
1662+
}).catch(e => e);
1663+
expect(login.status).toBe(200);
1664+
expect(login.data.sessionToken).toBeDefined();
1665+
});
1666+
});
1667+
1668+
describe('challenge endpoint authData provider value validation', () => {
1669+
it('rejects challenge request with null provider value without 500', async () => {
1670+
const res = await request({
1671+
method: 'POST',
1672+
url: 'http://localhost:8378/1/challenge',
1673+
headers: {
1674+
'Content-Type': 'application/json',
1675+
'X-Parse-Application-Id': 'test',
1676+
'X-Parse-REST-API-Key': 'rest',
1677+
},
1678+
body: JSON.stringify({
1679+
authData: { anonymous: null },
1680+
challengeData: { anonymous: { token: '123456' } },
1681+
}),
1682+
}).catch(e => e);
1683+
expect(res.status).toBeGreaterThanOrEqual(400);
1684+
expect(res.status).toBeLessThan(500);
1685+
});
1686+
1687+
it('rejects challenge request with non-object provider value without 500', async () => {
1688+
const res = await request({
1689+
method: 'POST',
1690+
url: 'http://localhost:8378/1/challenge',
1691+
headers: {
1692+
'Content-Type': 'application/json',
1693+
'X-Parse-Application-Id': 'test',
1694+
'X-Parse-REST-API-Key': 'rest',
1695+
},
1696+
body: JSON.stringify({
1697+
authData: { anonymous: 'string_value' },
1698+
challengeData: { anonymous: { token: '123456' } },
1699+
}),
1700+
}).catch(e => e);
1701+
expect(res.status).toBeGreaterThanOrEqual(400);
1702+
expect(res.status).toBeLessThan(500);
1703+
});
1704+
});
16161705
});

spec/CloudCode.spec.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const ParseServer = require('../lib/index').ParseServer;
55
const request = require('../lib/request');
66
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
77
.InMemoryCacheAdapter;
8+
const Utils = require('../lib/Utils');
89

910
const mockAdapter = {
1011
createFile: async filename => ({
@@ -1272,15 +1273,15 @@ describe('Cloud Code', () => {
12721273

12731274
it('test cloud function request params types', function (done) {
12741275
Parse.Cloud.define('params', function (req) {
1275-
expect(req.params.date instanceof Date).toBe(true);
1276+
expect(Utils.isDate(req.params.date)).toBe(true);
12761277
expect(req.params.date.getTime()).toBe(1463907600000);
1277-
expect(req.params.dateList[0] instanceof Date).toBe(true);
1278+
expect(Utils.isDate(req.params.dateList[0])).toBe(true);
12781279
expect(req.params.dateList[0].getTime()).toBe(1463907600000);
1279-
expect(req.params.complexStructure.date[0] instanceof Date).toBe(true);
1280+
expect(Utils.isDate(req.params.complexStructure.date[0])).toBe(true);
12801281
expect(req.params.complexStructure.date[0].getTime()).toBe(1463907600000);
1281-
expect(req.params.complexStructure.deepDate.date[0] instanceof Date).toBe(true);
1282+
expect(Utils.isDate(req.params.complexStructure.deepDate.date[0])).toBe(true);
12821283
expect(req.params.complexStructure.deepDate.date[0].getTime()).toBe(1463907600000);
1283-
expect(req.params.complexStructure.deepDate2[0].date instanceof Date).toBe(true);
1284+
expect(Utils.isDate(req.params.complexStructure.deepDate2[0].date)).toBe(true);
12841285
expect(req.params.complexStructure.deepDate2[0].date.getTime()).toBe(1463907600000);
12851286
// Regression for #2294
12861287
expect(req.params.file instanceof Parse.File).toBe(true);

spec/Deprecator.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,29 @@ describe('Deprecator', () => {
171171
}
172172
});
173173

174+
it('logs deprecation for enableProductPurchaseLegacyApi when set', async () => {
175+
const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {});
176+
177+
await reconfigureServer({ enableProductPurchaseLegacyApi: true });
178+
expect(logSpy).toHaveBeenCalledWith(
179+
jasmine.objectContaining({
180+
optionKey: 'enableProductPurchaseLegacyApi',
181+
changeNewKey: '',
182+
})
183+
);
184+
});
185+
186+
it('does not log deprecation for enableProductPurchaseLegacyApi when not set', async () => {
187+
const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {});
188+
189+
await reconfigureServer();
190+
expect(logSpy).not.toHaveBeenCalledWith(
191+
jasmine.objectContaining({
192+
optionKey: 'enableProductPurchaseLegacyApi',
193+
})
194+
);
195+
});
196+
174197
it('does not log deprecation for requestComplexity limits when explicitly set', async () => {
175198
const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {});
176199

spec/GridFSBucketStorageAdapter.spec.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ describe_only_db('mongo')('GridFSBucket', () => {
103103
).toEqual(1);
104104
expect(notRotated.length).toEqual(0);
105105
let result = await encryptedAdapter.getFileData(fileName1);
106-
expect(result instanceof Buffer).toBe(true);
106+
expect(Buffer.isBuffer(result)).toBe(true);
107107
expect(result.toString('utf-8')).toEqual(data1);
108108
const encryptedData1 = await unencryptedAdapter.getFileData(fileName1);
109109
expect(encryptedData1.toString('utf-8')).not.toEqual(unencryptedResult1);
110110
result = await encryptedAdapter.getFileData(fileName2);
111-
expect(result instanceof Buffer).toBe(true);
111+
expect(Buffer.isBuffer(result)).toBe(true);
112112
expect(result.toString('utf-8')).toEqual(data2);
113113
const encryptedData2 = await unencryptedAdapter.getFileData(fileName2);
114114
expect(encryptedData2.toString('utf-8')).not.toEqual(unencryptedResult2);
@@ -146,7 +146,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
146146
).toEqual(1);
147147
expect(notRotated.length).toEqual(0);
148148
let result = await encryptedAdapter.getFileData(fileName1);
149-
expect(result instanceof Buffer).toBe(true);
149+
expect(Buffer.isBuffer(result)).toBe(true);
150150
expect(result.toString('utf-8')).toEqual(data1);
151151
let decryptionError1;
152152
let encryptedData1;
@@ -158,7 +158,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
158158
expect(decryptionError1).toMatch('Error');
159159
expect(encryptedData1).toBeUndefined();
160160
result = await encryptedAdapter.getFileData(fileName2);
161-
expect(result instanceof Buffer).toBe(true);
161+
expect(Buffer.isBuffer(result)).toBe(true);
162162
expect(result.toString('utf-8')).toEqual(data2);
163163
let decryptionError2;
164164
let encryptedData2;
@@ -203,7 +203,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
203203
).toEqual(1);
204204
expect(notRotated.length).toEqual(0);
205205
let result = await unEncryptedAdapter.getFileData(fileName1);
206-
expect(result instanceof Buffer).toBe(true);
206+
expect(Buffer.isBuffer(result)).toBe(true);
207207
expect(result.toString('utf-8')).toEqual(data1);
208208
let decryptionError1;
209209
let encryptedData1;
@@ -215,7 +215,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
215215
expect(decryptionError1).toMatch('Error');
216216
expect(encryptedData1).toBeUndefined();
217217
result = await unEncryptedAdapter.getFileData(fileName2);
218-
expect(result instanceof Buffer).toBe(true);
218+
expect(Buffer.isBuffer(result)).toBe(true);
219219
expect(result.toString('utf-8')).toEqual(data2);
220220
let decryptionError2;
221221
let encryptedData2;
@@ -271,7 +271,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
271271
}).length
272272
).toEqual(0);
273273
let result = await encryptedAdapter.getFileData(fileName1);
274-
expect(result instanceof Buffer).toBe(true);
274+
expect(Buffer.isBuffer(result)).toBe(true);
275275
expect(result.toString('utf-8')).toEqual(data1);
276276
let decryptionError1;
277277
let encryptedData1;
@@ -283,7 +283,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
283283
expect(decryptionError1).toMatch('Error');
284284
expect(encryptedData1).toBeUndefined();
285285
result = await encryptedAdapter.getFileData(fileName2);
286-
expect(result instanceof Buffer).toBe(true);
286+
expect(Buffer.isBuffer(result)).toBe(true);
287287
expect(result.toString('utf-8')).toEqual(data2);
288288
let decryptionError2;
289289
let encryptedData2;
@@ -338,7 +338,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
338338
}).length
339339
).toEqual(1);
340340
let result = await encryptedAdapter.getFileData(fileName1);
341-
expect(result instanceof Buffer).toBe(true);
341+
expect(Buffer.isBuffer(result)).toBe(true);
342342
expect(result.toString('utf-8')).toEqual(data1);
343343
let decryptionError1;
344344
let encryptedData1;
@@ -350,7 +350,7 @@ describe_only_db('mongo')('GridFSBucket', () => {
350350
expect(decryptionError1).toMatch('Error');
351351
expect(encryptedData1).toBeUndefined();
352352
result = await encryptedAdapter.getFileData(fileName2);
353-
expect(result instanceof Buffer).toBe(true);
353+
expect(Buffer.isBuffer(result)).toBe(true);
354354
expect(result.toString('utf-8')).toEqual(data2);
355355
let decryptionError2;
356356
let encryptedData2;

0 commit comments

Comments
 (0)