From 8b3e7801fc4351d3647f04f5680311f488d57de4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 22 May 2023 19:34:12 +1000 Subject: [PATCH 1/8] perf: use $graphLookup for auth --- package-lock.json | 6 +- src/Auth.js | 99 ++++++++++++++++++++++++--- src/Controllers/DatabaseController.js | 2 +- 3 files changed, 93 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f2b1c4917..2d06ae0cac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/eslint-parser": "7.19.1", - "@graphql-tools/merge": "^8.4.1", + "@graphql-tools/merge": "8.4.1", "@graphql-tools/schema": "9.0.4", "@graphql-tools/utils": "8.12.0", "@graphql-yoga/node": "2.6.0", @@ -45,14 +45,14 @@ "pg-promise": "11.3.0", "pluralize": "8.0.0", "rate-limit-redis": "3.0.1", - "redis": "^4.6.6", + "redis": "4.6.6", "semver": "7.3.8", "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.0", "winston": "3.8.1", "winston-daily-rotate-file": "4.7.1", - "ws": "^8.13.0" + "ws": "8.13.0" }, "bin": { "parse-server": "bin/parse-server" diff --git a/src/Auth.js b/src/Auth.js index 0617301d69..47cf9c2edc 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -222,17 +222,96 @@ Auth.prototype.getRolesForUser = async function () { //Stack all Parse.Role const results = []; if (this.config) { - const restWhere = { - users: { - __type: 'Pointer', - className: '_User', - objectId: this.user.id, - }, - }; const RestQuery = require('./RestQuery'); - await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result => - results.push(result) - ); + const prefix = this.config.databaseAdapter._collectionPrefix; + const result = await new RestQuery( + this.config, + master(this.config), + '_Join:users:_Role', + {}, + { + pipeline: [ + { + $match: { + relatedId: this.user.id, + }, + }, + { + $graphLookup: { + from: `${prefix}_Join:roles:_Role`, + startWith: '$owningId', + connectFromField: 'owningId', + connectToField: 'relatedId', + as: 'childRolePath', + }, + }, + { + $facet: { + directRoles: [ + { + $lookup: { + from: `${prefix}_Role`, + localField: 'owningId', + foreignField: '_id', + as: 'Roles', + }, + }, + { + $unwind: { + path: '$Roles', + }, + }, + { + $replaceRoot: { + newRoot: { + $ifNull: ['$Roles', { $literal: {} }], + }, + }, + }, + { + $project: { + name: 1, + }, + }, + ], + childRoles: [ + { + $lookup: { + from: `${prefix}_Role`, + localField: 'childRolePath.owningId', + foreignField: '_id', + as: 'Roles', + }, + }, + { + $unwind: { + path: '$Roles', + }, + }, + { + $replaceRoot: { + newRoot: { + $ifNull: ['$Roles', { $literal: {} }], + }, + }, + }, + { + $project: { + name: 1, + }, + }, + ], + }, + }, + ], + } + ).execute(); + const { directRoles, childRoles } = result.results[0] || { + directRoles: [], + childRoles: [], + }; + results.push(...directRoles); + results.push(...childRoles); } else { await new Parse.Query(Parse.Role) .equalTo('users', this.user) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index e3ac5723ab..4685c800cd 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1283,7 +1283,7 @@ class DatabaseController { return this.adapter.distinct(className, schema, query, distinct); } } else if (pipeline) { - if (!classExists) { + if (!classExists && className.slice(0, 5) !== '_Join') { return []; } else { return this.adapter.aggregate( From 0aa8f7448adccc480164b57a685530791037eefd Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 22 May 2023 19:34:56 +1000 Subject: [PATCH 2/8] Update Auth.js --- src/Auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth.js b/src/Auth.js index 47cf9c2edc..a79d3ce037 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -223,7 +223,7 @@ Auth.prototype.getRolesForUser = async function () { const results = []; if (this.config) { const RestQuery = require('./RestQuery'); - const prefix = this.config.databaseAdapter._collectionPrefix; + const prefix = this.config.databaseAdapter._collectionPrefix || ''; const result = await new RestQuery( this.config, master(this.config), From 072442be4abb9f054b3c2d75fd3887a6b3b167fc Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 22 May 2023 20:32:14 +1000 Subject: [PATCH 3/8] wip --- spec/ParseRole.spec.js | 4 ++-- src/Auth.js | 15 +-------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 47fed865fb..da8d73961e 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -184,11 +184,11 @@ describe('Parse Role testing', () => { // 1 Query for the initial setup // 1 query for the parent roles - expect(restExecute.calls.count()).toEqual(2); + expect(restExecute.calls.count()).toEqual(1); // 1 call for the 1st layer of roles // 1 call for the 2nd layer - expect(getAllRolesSpy.calls.count()).toEqual(2); + expect(getAllRolesSpy.calls.count()).toEqual(0); done(); }) .catch(() => { diff --git a/src/Auth.js b/src/Auth.js index a79d3ce037..1568eeac5d 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -342,20 +342,7 @@ Auth.prototype._loadRoles = async function () { return this.userRoles; } - const rolesMap = results.reduce( - (m, r) => { - m.names.push(r.name); - m.ids.push(r.objectId); - return m; - }, - { ids: [], names: [] } - ); - - // run the recursive finding - const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names); - this.userRoles = roleNames.map(r => { - return 'role:' + r; - }); + this.userRoles = [...new Set(results.map(({ name }) => `role:${name}`))]; this.fetchedRoles = true; this.rolePromise = null; this.cacheRoles(); From 84f8833435e93e1d321b9bc6e4fd18c5778accba Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 22 May 2023 20:47:05 +1000 Subject: [PATCH 4/8] Update Auth.js --- src/Auth.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Auth.js b/src/Auth.js index 1568eeac5d..392724d849 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -310,8 +310,13 @@ Auth.prototype.getRolesForUser = async function () { directRoles: [], childRoles: [], }; - results.push(...directRoles); - results.push(...childRoles); + const roles = [...directRoles, ...childRoles]; + for (const role of roles) { + const roleName = `role:${role.name}`; + if (!results.includes(roleName)) { + results.push(role); + } + } } else { await new Parse.Query(Parse.Role) .equalTo('users', this.user) @@ -337,12 +342,29 @@ Auth.prototype._loadRoles = async function () { this.userRoles = []; this.fetchedRoles = true; this.rolePromise = null; - this.cacheRoles(); return this.userRoles; } - this.userRoles = [...new Set(results.map(({ name }) => `role:${name}`))]; + if (typeof results[0] === 'object') { + const rolesMap = results.reduce( + (m, r) => { + m.names.push(r.name); + m.ids.push(r.objectId); + return m; + }, + { ids: [], names: [] } + ); + + // run the recursive finding + const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names); + this.userRoles = roleNames.map(r => { + return 'role:' + r; + }); + } else { + this.userRoles = results; + } + this.fetchedRoles = true; this.rolePromise = null; this.cacheRoles(); From 4b741e96e39e1b390e192c8bc43acd35a4e7824c Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 22 May 2023 20:57:33 +1000 Subject: [PATCH 5/8] Update ParseRole.spec.js --- spec/ParseRole.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index da8d73961e..4243c328d9 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -184,11 +184,11 @@ describe('Parse Role testing', () => { // 1 Query for the initial setup // 1 query for the parent roles - expect(restExecute.calls.count()).toEqual(1); + expect(restExecute.calls.count()).toEqual(2); // 1 call for the 1st layer of roles // 1 call for the 2nd layer - expect(getAllRolesSpy.calls.count()).toEqual(0); + expect(getAllRolesSpy.calls.count()).toEqual(1); done(); }) .catch(() => { From d6239287acd5298e48923c713f116e79c2dd4ad9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 28 Jul 2023 02:26:51 +1000 Subject: [PATCH 6/8] Update Auth.js --- src/Auth.js | 189 +++++++++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 83 deletions(-) diff --git a/src/Auth.js b/src/Auth.js index 58d61b7b66..781bc4b937 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -5,6 +5,7 @@ import Deprecator from './Deprecator/Deprecator'; import { logger } from './logger'; import RestQuery from './RestQuery'; import RestWrite from './RestWrite'; +import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter'; // An Auth object tells you who is requesting something and whether // the master key was used. @@ -139,7 +140,6 @@ const getAuthForSessionToken = async function ({ limit: 1, include: 'user', }; - const RestQuery = require('./RestQuery'); const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions); results = (await query.execute()).results; } else { @@ -183,7 +183,6 @@ var getAuthForLegacySessionToken = function ({ config, sessionToken, installatio var restOptions = { limit: 1, }; - const RestQuery = require('./RestQuery'); var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions); return query.execute().then(response => { var results = response.results; @@ -221,100 +220,112 @@ Auth.prototype.getRolesForUser = async function () { //Stack all Parse.Role const results = []; if (this.config) { - const RestQuery = require('./RestQuery'); - const prefix = this.config.databaseAdapter._collectionPrefix || ''; - const result = await new RestQuery( - this.config, - master(this.config), - '_Join:users:_Role', - {}, - { - pipeline: [ - { - $match: { - relatedId: this.user.id, + if (this.config.database.adapter instanceof MongoStorageAdapter) { + const prefix = this.config.databaseAdapter._collectionPrefix || ''; + const result = await new RestQuery( + this.config, + master(this.config), + '_Join:users:_Role', + {}, + { + pipeline: [ + { + $match: { + relatedId: this.user.id, + }, }, - }, - { - $graphLookup: { - from: `${prefix}_Join:roles:_Role`, - startWith: '$owningId', - connectFromField: 'owningId', - connectToField: 'relatedId', - as: 'childRolePath', + { + $graphLookup: { + from: `${prefix}_Join:roles:_Role`, + startWith: '$owningId', + connectFromField: 'owningId', + connectToField: 'relatedId', + as: 'childRolePath', + }, }, - }, - { - $facet: { - directRoles: [ - { - $lookup: { - from: `${prefix}_Role`, - localField: 'owningId', - foreignField: '_id', - as: 'Roles', + { + $facet: { + directRoles: [ + { + $lookup: { + from: `${prefix}_Role`, + localField: 'owningId', + foreignField: '_id', + as: 'Roles', + }, }, - }, - { - $unwind: { - path: '$Roles', + { + $unwind: { + path: '$Roles', + }, }, - }, - { - $replaceRoot: { - newRoot: { - $ifNull: ['$Roles', { $literal: {} }], + { + $replaceRoot: { + newRoot: { + $ifNull: ['$Roles', { $literal: {} }], + }, }, }, - }, - { - $project: { - name: 1, + { + $project: { + name: 1, + }, }, - }, - ], - childRoles: [ - { - $lookup: { - from: `${prefix}_Role`, - localField: 'childRolePath.owningId', - foreignField: '_id', - as: 'Roles', + ], + childRoles: [ + { + $lookup: { + from: `${prefix}_Role`, + localField: 'childRolePath.owningId', + foreignField: '_id', + as: 'Roles', + }, }, - }, - { - $unwind: { - path: '$Roles', + { + $unwind: { + path: '$Roles', + }, }, - }, - { - $replaceRoot: { - newRoot: { - $ifNull: ['$Roles', { $literal: {} }], + { + $replaceRoot: { + newRoot: { + $ifNull: ['$Roles', { $literal: {} }], + }, }, }, - }, - { - $project: { - name: 1, + { + $project: { + name: 1, + }, }, - }, - ], + ], + }, }, - }, - ], - } - ).execute(); - const { directRoles, childRoles } = result.results[0] || { - directRoles: [], - childRoles: [], - }; - const roles = [...directRoles, ...childRoles]; - for (const role of roles) { - const roleName = `role:${role.name}`; - if (!results.includes(roleName)) { - results.push(role); + ], + } + ).execute(); + const { directRoles, childRoles } = result.results[0] || { + directRoles: [], + childRoles: [], + }; + const roles = [...directRoles, ...childRoles]; + for (const role of roles) { + const roleName = `role:${role.name}`; + if (!results.includes(roleName)) { + results.push(role); + } } + } else { + const restWhere = { + users: { + __type: 'Pointer', + className: '_User', + objectId: this.user.id, + }, + }; + await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result => + results.push(result) + ); } } else { await new Parse.Query(Parse.Role) @@ -345,6 +356,19 @@ Auth.prototype._loadRoles = async function () { return this.userRoles; } + if (!(this.config?.database?.adapter instanceof MongoStorageAdapter)) { + const rolesMap = results.reduce( + (m, r) => { + m.names.push(r.name); + m.ids.push(r.objectId); + return m; + }, + { ids: [], names: [] } + ); + const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names); + this.userRoles = roleNames.map(r => `role:${r}`); + } + if (typeof results[0] === 'object') { const rolesMap = results.reduce( (m, r) => { @@ -410,7 +434,6 @@ Auth.prototype.getRolesByIds = async function (ins) { }; }); const restWhere = { roles: { $in: roles } }; - const RestQuery = require('./RestQuery'); await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result => results.push(result) ); From 59b8e851800fa86e7769e2be7fc6834f53b5b594 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 28 Jul 2023 02:27:25 +1000 Subject: [PATCH 7/8] Update Auth.js --- src/Auth.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Auth.js b/src/Auth.js index 781bc4b937..1c52c4a424 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -356,19 +356,6 @@ Auth.prototype._loadRoles = async function () { return this.userRoles; } - if (!(this.config?.database?.adapter instanceof MongoStorageAdapter)) { - const rolesMap = results.reduce( - (m, r) => { - m.names.push(r.name); - m.ids.push(r.objectId); - return m; - }, - { ids: [], names: [] } - ); - const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names); - this.userRoles = roleNames.map(r => `role:${r}`); - } - if (typeof results[0] === 'object') { const rolesMap = results.reduce( (m, r) => { From 28c340a4e374bde09b4751216ccfbc77406d8cb9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 28 Jul 2023 02:40:12 +1000 Subject: [PATCH 8/8] Update ParseRole.spec.js --- spec/ParseRole.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 4243c328d9..eef91ba8d7 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -188,7 +188,9 @@ describe('Parse Role testing', () => { // 1 call for the 1st layer of roles // 1 call for the 2nd layer - expect(getAllRolesSpy.calls.count()).toEqual(1); + expect(getAllRolesSpy.calls.count()).toEqual( + process.env.PARSE_SERVER_TEST_DB === 'postgres' ? 2 : 1 + ); done(); }) .catch(() => {