diff --git a/CHANGELOG.md b/CHANGELOG.md index 19201163..635777d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Jump directly to a version: | 4.x | |--------------------------------------| -| [**4.10.3 (latest release)**](#4103) | +| [**4.10.4 (latest release)**](#4104) | +| [4.10.3](#4103) | | [4.10.2](#4102) | | [4.10.1](#4101) | | [4.10.0](#4100) | @@ -94,7 +95,7 @@ Jump directly to a version: ___ ## Unreleased (Master Branch) -[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.3...master) +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.4...master) ### Breaking Changes - Improved schema caching through database real-time hooks. Reduces DB queries, decreases Parse Query execution time and fixes a potential schema memory leak. If multiple Parse Server instances connect to the same DB (for example behind a load balancer), set the [Parse Server Option](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `databaseOptions.enableSchemaHooks: true` to enable this feature and keep the schema in sync across all instances. Failing to do so will cause a schema change to not propagate to other instances and re-syncing will only happen when these instances restart. The options `enableSingleSchemaCache` and `schemaCacheTTL` have been removed. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required. (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214) @@ -156,6 +157,12 @@ ___ - Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560) - docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562) +## 4.10.4 +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.3...4.10.4) + +### Security Fixes +- Strip out sessionToken when LiveQuery is used on Parse.User (Daniel Blyth) [GHSA-7pr3-p5fm-8r9x](https://github.com/parse-community/parse-server/security/advisories/GHSA-7pr3-p5fm-8r9x) + ## 4.10.3 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.2...4.10.3) diff --git a/package-lock.json b/package-lock.json index 3edb41d9..cc4bab10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "4.10.3", + "version": "4.10.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 85b98bb1..6835f838 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "4.10.3", + "version": "4.10.4", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 65d1836c..ae1fb8d0 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -840,6 +840,52 @@ describe('ParseLiveQuery', function () { done(); }); + it('should strip out session token in LiveQuery', async () => { + await reconfigureServer({ + liveQuery: { classNames: ['_User'] }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const user = new Parse.User(); + user.setUsername('username'); + user.setPassword('password'); + user.set('foo', 'bar'); + + const query = new Parse.Query(Parse.User); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + + const events = ['create', 'update', 'enter', 'leave', 'delete']; + const response = (obj, prev) => { + expect(obj.get('sessionToken')).toBeUndefined(); + expect(obj.sessionToken).toBeUndefined(); + expect(prev?.sessionToken).toBeUndefined(); + if (prev && prev.get) { + expect(prev.get('sessionToken')).toBeUndefined(); + } + }; + const calls = {}; + for (const key of events) { + calls[key] = response; + spyOn(calls, key).and.callThrough(); + subscription.on(key, calls[key]); + } + await user.signUp(); + user.unset('foo'); + await user.save(); + user.set('foo', 'bar'); + await user.save(); + user.set('yolo', 'bar'); + await user.save(); + await user.destroy(); + await new Promise(resolve => process.nextTick(resolve)); + for (const key of events) { + expect(calls[key]).toHaveBeenCalled(); + } + }); + afterEach(async function (done) { const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); client.close(); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 2751cd46..e54f1c2c 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -3966,6 +3966,51 @@ describe('Parse.User testing', () => { ok(model._isLinked('facebook'), 'User should be linked to facebook'); }); }); + + it('should strip out authdata in LiveQuery', async () => { + const provider = getMockFacebookProvider(); + Parse.User._registerAuthenticationProvider(provider); + + await reconfigureServer({ + liveQuery: { classNames: ['_User'] }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const query = new Parse.Query(Parse.User); + query.doesNotExist('foo'); + const subscription = await query.subscribe(); + + const events = ['create', 'update', 'enter', 'leave', 'delete']; + const response = (obj, prev) => { + expect(obj.get('authData')).toBeUndefined(); + expect(obj.authData).toBeUndefined(); + expect(prev?.authData).toBeUndefined(); + if (prev && prev.get) { + expect(prev.get('authData')).toBeUndefined(); + } + }; + const calls = {}; + for (const key of events) { + calls[key] = response; + spyOn(calls, key).and.callThrough(); + subscription.on(key, calls[key]); + } + const user = await Parse.User._logInWith('facebook'); + + user.set('foo', 'bar'); + await user.save(); + user.unset('foo'); + await user.save(); + user.set('yolo', 'bar'); + await user.save(); + await user.destroy(); + await new Promise(resolve => process.nextTick(resolve)); + for (const key of events) { + expect(calls[key]).toHaveBeenCalled(); + } + }); }); describe('Security Advisory GHSA-8w3j-g983-8jh5', function () { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 5a44ae5c..f7a065b7 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -186,6 +186,14 @@ class ParseLiveQueryServer { deletedParseObject = res.object.toJSON(); deletedParseObject.className = className; } + if ( + (deletedParseObject.className === '_User' || + deletedParseObject.className === '_Session') && + !client.hasMasterKey + ) { + delete deletedParseObject.sessionToken; + delete deletedParseObject.authData; + } client.pushDelete(requestId, deletedParseObject); } catch (error) { Client.pushError( @@ -337,6 +345,16 @@ class ParseLiveQueryServer { originalParseObject = res.original.toJSON(); originalParseObject.className = res.original.className || className; } + if ( + (currentParseObject.className === '_User' || + currentParseObject.className === '_Session') && + !client.hasMasterKey + ) { + delete currentParseObject.sessionToken; + delete originalParseObject?.sessionToken; + delete currentParseObject.authData; + delete originalParseObject?.authData; + } const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1); if (client[functionName]) { client[functionName](requestId, currentParseObject, originalParseObject);