mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b9c3a4214 | ||
|
|
3d6d50e0af | ||
|
|
b70c2d9027 | ||
|
|
71dfd8a7ec | ||
|
|
39a91d0d77 | ||
|
|
4945ab7520 | ||
|
|
a9c34ef1e2 | ||
|
|
05939858af | ||
|
|
f5e20f9121 | ||
|
|
9c6cdf4904 | ||
|
|
4baeae4f4f | ||
|
|
3602ecb169 | ||
|
|
7f89399300 | ||
|
|
88a9106956 | ||
|
|
391c7a02aa | ||
|
|
6ea65f255f | ||
|
|
45a3ed0fcf | ||
|
|
977edeaf28 | ||
|
|
739ffbed86 | ||
|
|
5954f0ffa0 | ||
|
|
d141b822ad | ||
|
|
be4c7e23c6 | ||
|
|
877eede075 | ||
|
|
2b3d4e5d3c | ||
|
|
42929e0e5e | ||
|
|
b9bdca4520 | ||
|
|
ae68f0c31b | ||
|
|
7a198f4eed | ||
|
|
d6b17baa32 | ||
|
|
328918178f | ||
|
|
3dd99dd80e | ||
|
|
e212eb5195 |
@@ -1,3 +1,44 @@
|
||||
# [6.4.0-alpha.3](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.2...6.4.0-alpha.3) (2023-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Parse Server option `fileUpload.fileExtensions` fails to determine file extension if filename contains multiple dots ([#8754](https://github.com/parse-community/parse-server/issues/8754)) ([3d6d50e](https://github.com/parse-community/parse-server/commit/3d6d50e0afff18b95fb906914e2cebd3839b517a))
|
||||
|
||||
# [6.4.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.1...6.4.0-alpha.2) (2023-09-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade graphql from 16.6.0 to 16.8.1 ([#8758](https://github.com/parse-community/parse-server/issues/8758)) ([71dfd8a](https://github.com/parse-community/parse-server/commit/71dfd8a7ece8c0dd1a66d03bb9420cfd39f4f9b1))
|
||||
|
||||
# [6.4.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.3.0...6.4.0-alpha.1) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* Add context to Cloud Code Triggers `beforeLogin` and `afterLogin` ([#8724](https://github.com/parse-community/parse-server/issues/8724)) ([a9c34ef](https://github.com/parse-community/parse-server/commit/a9c34ef1e2c78a42fb8b5fa8d569b7677c74919d))
|
||||
|
||||
# [6.3.0-alpha.9](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.8...6.3.0-alpha.9) (2023-09-13)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Improve performance of recursive pointer iterations ([#8741](https://github.com/parse-community/parse-server/issues/8741)) ([45a3ed0](https://github.com/parse-community/parse-server/commit/45a3ed0fcf2c0170607505a1550fb15896e705fd))
|
||||
|
||||
# [6.3.0-alpha.8](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.7...6.3.0-alpha.8) (2023-08-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Redis 4 does not reconnect after unhandled error ([#8706](https://github.com/parse-community/parse-server/issues/8706)) ([2b3d4e5](https://github.com/parse-community/parse-server/commit/2b3d4e5d3c85cd142f85af68dec51a8523548d49))
|
||||
|
||||
# [6.3.0-alpha.7](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.6...6.3.0-alpha.7) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove config logging when launching Parse Server via CLI ([#8710](https://github.com/parse-community/parse-server/issues/8710)) ([ae68f0c](https://github.com/parse-community/parse-server/commit/ae68f0c31b741eeb83379c905c7ddfaa124436ec))
|
||||
|
||||
# [6.3.0-alpha.6](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.5...6.3.0-alpha.6) (2023-07-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
# [6.4.0-beta.1](https://github.com/parse-community/parse-server/compare/6.3.0...6.4.0-beta.1) (2023-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Parse Server option `fileUpload.fileExtensions` does not work with an array of extensions ([#8688](https://github.com/parse-community/parse-server/issues/8688)) ([6a4a00c](https://github.com/parse-community/parse-server/commit/6a4a00ca7af1163ea74b047b85cd6817366b824b))
|
||||
* Redis 4 does not reconnect after unhandled error ([#8706](https://github.com/parse-community/parse-server/issues/8706)) ([2b3d4e5](https://github.com/parse-community/parse-server/commit/2b3d4e5d3c85cd142f85af68dec51a8523548d49))
|
||||
* Remove config logging when launching Parse Server via CLI ([#8710](https://github.com/parse-community/parse-server/issues/8710)) ([ae68f0c](https://github.com/parse-community/parse-server/commit/ae68f0c31b741eeb83379c905c7ddfaa124436ec))
|
||||
* Server does not start via CLI when `auth` option is set ([#8666](https://github.com/parse-community/parse-server/issues/8666)) ([4e2000b](https://github.com/parse-community/parse-server/commit/4e2000bc563324389584ace3c090a5c1a7796a64))
|
||||
|
||||
### Features
|
||||
|
||||
* Add conditional email verification via dynamic Parse Server options `verifyUserEmails`, `sendUserEmailVerification` that now accept functions ([#8425](https://github.com/parse-community/parse-server/issues/8425)) ([44acd6d](https://github.com/parse-community/parse-server/commit/44acd6d9ed157ad4842200c9d01f9c77a05fec3a))
|
||||
* Add property `Parse.Server.version` to determine current version of Parse Server in Cloud Code ([#8670](https://github.com/parse-community/parse-server/issues/8670)) ([a9d376b](https://github.com/parse-community/parse-server/commit/a9d376b61f5b07806eafbda91c4e36c322f09298))
|
||||
* Add TOTP authentication adapter ([#8457](https://github.com/parse-community/parse-server/issues/8457)) ([cc079a4](https://github.com/parse-community/parse-server/commit/cc079a40f6849a0e9bc6fdc811e8649ecb67b589))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Improve performance of recursive pointer iterations ([#8741](https://github.com/parse-community/parse-server/issues/8741)) ([45a3ed0](https://github.com/parse-community/parse-server/commit/45a3ed0fcf2c0170607505a1550fb15896e705fd))
|
||||
|
||||
# [6.3.0-beta.1](https://github.com/parse-community/parse-server/compare/6.2.0...6.3.0-beta.1) (2023-06-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,42 @@
|
||||
# [6.3.0](https://github.com/parse-community/parse-server/compare/6.2.2...6.3.0) (2023-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Cloud Code Trigger `afterSave` executes even if not set ([#8520](https://github.com/parse-community/parse-server/issues/8520)) ([afd0515](https://github.com/parse-community/parse-server/commit/afd0515e207bd947840579d3f245980dffa6f804))
|
||||
* GridFS file storage doesn't work with certain `enableSchemaHooks` settings ([#8467](https://github.com/parse-community/parse-server/issues/8467)) ([d4cda4b](https://github.com/parse-community/parse-server/commit/d4cda4b26c9bde8c812549b8780bea1cfabdb394))
|
||||
* Inaccurate table total row count for PostgreSQL ([#8511](https://github.com/parse-community/parse-server/issues/8511)) ([0823a02](https://github.com/parse-community/parse-server/commit/0823a02fbf80bc88dc403bc47e9f5c6597ea78b4))
|
||||
* LiveQuery server is not shut down properly when `handleShutdown` is called ([#8491](https://github.com/parse-community/parse-server/issues/8491)) ([967700b](https://github.com/parse-community/parse-server/commit/967700bdbc94c74f75ba84d2b3f4b9f3fd2dca0b))
|
||||
* Rate limit feature is incompatible with Node 14 ([#8578](https://github.com/parse-community/parse-server/issues/8578)) ([f911f2c](https://github.com/parse-community/parse-server/commit/f911f2cd3a8c45cd326272dcd681532764a3761e))
|
||||
* Unnecessary log entries by `extendSessionOnUse` ([#8562](https://github.com/parse-community/parse-server/issues/8562)) ([fd6a007](https://github.com/parse-community/parse-server/commit/fd6a0077f2e5cf83d65e52172ae5a950ab0f1eae))
|
||||
|
||||
### Features
|
||||
|
||||
* `extendSessionOnUse` to automatically renew Parse Sessions ([#8505](https://github.com/parse-community/parse-server/issues/8505)) ([6f885d3](https://github.com/parse-community/parse-server/commit/6f885d36b94902fdfea873fc554dee83589e6029))
|
||||
* Add new Parse Server option `preventSignupWithUnverifiedEmail` to prevent returning a user without session token on sign-up with unverified email address ([#8451](https://github.com/parse-community/parse-server/issues/8451)) ([82da308](https://github.com/parse-community/parse-server/commit/82da30842a55980aa90cb7680fbf6db37ee16dab))
|
||||
* Add option to change the log level of logs emitted by Cloud Functions ([#8530](https://github.com/parse-community/parse-server/issues/8530)) ([2caea31](https://github.com/parse-community/parse-server/commit/2caea310be412d82b04a85716bc769ccc410316d))
|
||||
* Add support for `$eq` query constraint in LiveQuery ([#8614](https://github.com/parse-community/parse-server/issues/8614)) ([656d673](https://github.com/parse-community/parse-server/commit/656d673cf5dea354e4f2b3d4dc2b29a41d311b3e))
|
||||
* Add zones for rate limiting by `ip`, `user`, `session`, `global` ([#8508](https://github.com/parse-community/parse-server/issues/8508)) ([03fba97](https://github.com/parse-community/parse-server/commit/03fba97e0549bfcaeee9f2fa4c9905dbcc91840e))
|
||||
* Allow `Parse.Object` pointers in Cloud Code arguments ([#8490](https://github.com/parse-community/parse-server/issues/8490)) ([28aeda3](https://github.com/parse-community/parse-server/commit/28aeda3f160efcbbcf85a85484a8d26567fa9761))
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix: Inaccurate table total row count for PostgreSQL ([6722110](https://github.com/parse-community/parse-server/commit/6722110f203bc5fdcaa68cdf091cf9e7b48d1cff))
|
||||
|
||||
## [6.2.2](https://github.com/parse-community/parse-server/compare/6.2.1...6.2.2) (2023-09-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Parse Pointer allows to access internal Parse Server classes and circumvent `beforeFind` query trigger; fixes security vulnerability [GHSA-fcv6-fg5r-jm9q](https://github.com/parse-community/parse-server/security/advisories/GHSA-fcv6-fg5r-jm9q) ([be4c7e2](https://github.com/parse-community/parse-server/commit/be4c7e23c63a2fb690685665cebed0de26be05c5))
|
||||
|
||||
## [6.2.1](https://github.com/parse-community/parse-server/compare/6.2.0...6.2.1) (2023-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remote code execution via MongoDB BSON parser through prototype pollution; fixes security vulnerability [GHSA-462x-c3jw-7vr6](https://github.com/parse-community/parse-server/security/advisories/GHSA-462x-c3jw-7vr6) ([#8674](https://github.com/parse-community/parse-server/issues/8674)) ([3dd99dd](https://github.com/parse-community/parse-server/commit/3dd99dd80e27e5e1d99b42844180546d90c7aa90))
|
||||
|
||||
# [6.2.0](https://github.com/parse-community/parse-server/compare/6.1.0...6.2.0) (2023-05-20)
|
||||
|
||||
|
||||
|
||||
Generated
+49
-146
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0-alpha.6",
|
||||
"version": "6.4.0-alpha.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0-alpha.6",
|
||||
"version": "6.4.0-alpha.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"@graphql-tools/utils": "8.12.0",
|
||||
"@graphql-yoga/node": "2.6.0",
|
||||
"@parse/fs-files-adapter": "1.2.2",
|
||||
"@parse/push-adapter": "4.1.3",
|
||||
"@parse/push-adapter": "4.2.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"commander": "10.0.1",
|
||||
@@ -25,7 +25,7 @@
|
||||
"express": "4.18.2",
|
||||
"express-rate-limit": "6.7.0",
|
||||
"follow-redirects": "1.15.2",
|
||||
"graphql": "16.6.0",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-relay": "0.10.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
@@ -39,7 +39,7 @@
|
||||
"mime": "3.0.0",
|
||||
"mongodb": "4.10.0",
|
||||
"mustache": "4.2.0",
|
||||
"otpauth": "^9.1.2",
|
||||
"otpauth": "9.1.2",
|
||||
"parse": "4.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"pg-monitor": "2.0.0",
|
||||
@@ -2731,13 +2731,13 @@
|
||||
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
||||
},
|
||||
"node_modules/@parse/node-apn": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz",
|
||||
"integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.2.1.tgz",
|
||||
"integrity": "sha512-dwVCDv+G9YV01Ad1XslWQImnmfFDSnaNwxI4l+vuCjL+DbjsCl6DuV4nMqZpEZOpViAY0pGCRHBKUygsf+aAGg==",
|
||||
"dependencies": {
|
||||
"debug": "4.3.3",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.3.0",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"node-forge": "1.3.1",
|
||||
"verror": "1.10.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2760,35 +2760,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/jsonwebtoken": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
|
||||
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4",
|
||||
"npm": ">=1.4.28"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-gcm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@parse/node-gcm/-/node-gcm-1.0.2.tgz",
|
||||
@@ -2811,11 +2782,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/push-adapter": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.3.tgz",
|
||||
"integrity": "sha512-Oy53ag7DpUva5dUWwP6tNEsrxv2xU9QIk+rb84q1DIm1qVgo2yl4oXcZ3FPG2Ks/NYURbv4w+z9oaSgVfyBRfQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.2.0.tgz",
|
||||
"integrity": "sha512-M6D9qk4KE9bJ2lMufTvgGmKOvsbj20lFhzg0kQRmHU10ootKt4XcL+QJRSTu/BmlRbIVZMGQEZ61UyUumWTOiQ==",
|
||||
"dependencies": {
|
||||
"@parse/node-apn": "5.1.3",
|
||||
"@parse/node-apn": "5.2.1",
|
||||
"@parse/node-gcm": "1.0.2",
|
||||
"npmlog": "4.1.2",
|
||||
"parse": "3.4.0"
|
||||
@@ -8622,9 +8593,9 @@
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
|
||||
},
|
||||
"node_modules/graphql": {
|
||||
"version": "16.6.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz",
|
||||
"integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==",
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
||||
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||
}
|
||||
@@ -10800,41 +10771,23 @@
|
||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"node_modules/lodash.ismatch": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.map": {
|
||||
"version": "4.6.0",
|
||||
@@ -10847,11 +10800,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/lodash.pad": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
||||
@@ -12493,9 +12441,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
@@ -20437,9 +20385,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -22604,13 +22552,13 @@
|
||||
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
||||
},
|
||||
"@parse/node-apn": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz",
|
||||
"integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.2.1.tgz",
|
||||
"integrity": "sha512-dwVCDv+G9YV01Ad1XslWQImnmfFDSnaNwxI4l+vuCjL+DbjsCl6DuV4nMqZpEZOpViAY0pGCRHBKUygsf+aAGg==",
|
||||
"requires": {
|
||||
"debug": "4.3.3",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.3.0",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"node-forge": "1.3.1",
|
||||
"verror": "1.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -22621,28 +22569,6 @@
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"jsonwebtoken": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
|
||||
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
|
||||
"requires": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -22667,11 +22593,11 @@
|
||||
}
|
||||
},
|
||||
"@parse/push-adapter": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.3.tgz",
|
||||
"integrity": "sha512-Oy53ag7DpUva5dUWwP6tNEsrxv2xU9QIk+rb84q1DIm1qVgo2yl4oXcZ3FPG2Ks/NYURbv4w+z9oaSgVfyBRfQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.2.0.tgz",
|
||||
"integrity": "sha512-M6D9qk4KE9bJ2lMufTvgGmKOvsbj20lFhzg0kQRmHU10ootKt4XcL+QJRSTu/BmlRbIVZMGQEZ61UyUumWTOiQ==",
|
||||
"requires": {
|
||||
"@parse/node-apn": "5.1.3",
|
||||
"@parse/node-apn": "5.2.1",
|
||||
"@parse/node-gcm": "1.0.2",
|
||||
"npmlog": "4.1.2",
|
||||
"parse": "3.4.0"
|
||||
@@ -27204,9 +27130,9 @@
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
|
||||
},
|
||||
"graphql": {
|
||||
"version": "16.6.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz",
|
||||
"integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw=="
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
||||
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw=="
|
||||
},
|
||||
"graphql-list-fields": {
|
||||
"version": "2.0.2",
|
||||
@@ -28885,41 +28811,23 @@
|
||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"lodash.ismatch": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.map": {
|
||||
"version": "4.6.0",
|
||||
@@ -28932,11 +28840,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"lodash.pad": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
||||
@@ -30231,9 +30134,9 @@
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA=="
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
|
||||
},
|
||||
"node-netstat": {
|
||||
"version": "1.8.0",
|
||||
@@ -36200,9 +36103,9 @@
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0-alpha.6",
|
||||
"version": "6.4.0-alpha.3",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -25,7 +25,7 @@
|
||||
"@graphql-tools/utils": "8.12.0",
|
||||
"@graphql-yoga/node": "2.6.0",
|
||||
"@parse/fs-files-adapter": "1.2.2",
|
||||
"@parse/push-adapter": "4.1.3",
|
||||
"@parse/push-adapter": "4.2.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"commander": "10.0.1",
|
||||
@@ -34,7 +34,7 @@
|
||||
"express": "4.18.2",
|
||||
"express-rate-limit": "6.7.0",
|
||||
"follow-redirects": "1.15.2",
|
||||
"graphql": "16.6.0",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-relay": "0.10.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
|
||||
+81
-2
@@ -2398,6 +2398,56 @@ describe('beforeFind hooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets correct beforeFind trigger isGet parameter for Parse.Object.fetch request', async () => {
|
||||
const hook = {
|
||||
method: req => {
|
||||
expect(req.isGet).toEqual(true);
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
spyOn(hook, 'method').and.callThrough();
|
||||
Parse.Cloud.beforeFind('MyObject', hook.method);
|
||||
const obj = new Parse.Object('MyObject');
|
||||
await obj.save();
|
||||
const getObj = await obj.fetch();
|
||||
expect(getObj).toBeInstanceOf(Parse.Object);
|
||||
expect(hook.method).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('sets correct beforeFind trigger isGet parameter for Parse.Query.get request', async () => {
|
||||
const hook = {
|
||||
method: req => {
|
||||
expect(req.isGet).toEqual(false);
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
spyOn(hook, 'method').and.callThrough();
|
||||
Parse.Cloud.beforeFind('MyObject', hook.method);
|
||||
const obj = new Parse.Object('MyObject');
|
||||
await obj.save();
|
||||
const query = new Parse.Query('MyObject');
|
||||
const getObj = await query.get(obj.id);
|
||||
expect(getObj).toBeInstanceOf(Parse.Object);
|
||||
expect(hook.method).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('sets correct beforeFind trigger isGet parameter for Parse.Query.find request', async () => {
|
||||
const hook = {
|
||||
method: req => {
|
||||
expect(req.isGet).toEqual(false);
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
spyOn(hook, 'method').and.callThrough();
|
||||
Parse.Cloud.beforeFind('MyObject', hook.method);
|
||||
const obj = new Parse.Object('MyObject');
|
||||
await obj.save();
|
||||
const query = new Parse.Query('MyObject');
|
||||
const findObjs = await query.find();
|
||||
expect(findObjs?.[0]).toBeInstanceOf(Parse.Object);
|
||||
expect(hook.method).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should have request headers', done => {
|
||||
Parse.Cloud.beforeFind('MyObject', req => {
|
||||
expect(req.headers).toBeDefined();
|
||||
@@ -2431,6 +2481,35 @@ describe('beforeFind hooks', () => {
|
||||
})
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should run beforeFind on pointers and array of pointers from an object', async () => {
|
||||
const obj1 = new Parse.Object('TestObject');
|
||||
const obj2 = new Parse.Object('TestObject2');
|
||||
const obj3 = new Parse.Object('TestObject');
|
||||
obj2.set('aField', 'aFieldValue');
|
||||
await obj2.save();
|
||||
obj1.set('pointerField', obj2);
|
||||
obj3.set('pointerFieldArray', [obj2]);
|
||||
await obj1.save();
|
||||
await obj3.save();
|
||||
const spy = jasmine.createSpy('beforeFindSpy');
|
||||
Parse.Cloud.beforeFind('TestObject2', spy);
|
||||
const query = new Parse.Query('TestObject');
|
||||
await query.get(obj1.id);
|
||||
// Pointer not included in query so we don't expect beforeFind to be called
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
const query2 = new Parse.Query('TestObject');
|
||||
query2.include('pointerField');
|
||||
const res = await query2.get(obj1.id);
|
||||
expect(res.get('pointerField').get('aField')).toBe('aFieldValue');
|
||||
// Pointer included in query so we expect beforeFind to be called
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
const query3 = new Parse.Query('TestObject');
|
||||
query3.include('pointerFieldArray');
|
||||
const res2 = await query3.get(obj3.id);
|
||||
expect(res2.get('pointerFieldArray')[0].get('aField')).toBe('aFieldValue');
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterFind hooks', () => {
|
||||
@@ -3248,7 +3327,7 @@ describe('beforeLogin hook', () => {
|
||||
expect(req.headers).toBeDefined();
|
||||
expect(req.ip).toBeDefined();
|
||||
expect(req.installationId).toBeDefined();
|
||||
expect(req.context).toBeUndefined();
|
||||
expect(req.context).toBeDefined();
|
||||
});
|
||||
|
||||
await Parse.User.signUp('tupac', 'shakur');
|
||||
@@ -3365,7 +3444,7 @@ describe('afterLogin hook', () => {
|
||||
expect(req.headers).toBeDefined();
|
||||
expect(req.ip).toBeDefined();
|
||||
expect(req.installationId).toBeDefined();
|
||||
expect(req.context).toBeUndefined();
|
||||
expect(req.context).toBeDefined();
|
||||
});
|
||||
|
||||
await Parse.User.signUp('testuser', 'p@ssword');
|
||||
|
||||
@@ -1364,6 +1364,74 @@ describe('Parse.File testing', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('works with a period in the file name', async () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
enableForPublic: true,
|
||||
fileExtensions: ['^[^hH][^tT][^mM][^lL]?$'],
|
||||
},
|
||||
});
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
|
||||
const values = ['file.png.html', 'file.txt.png.html', 'file.png.txt.html'];
|
||||
|
||||
for (const value of values) {
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: `http://localhost:8378/1/files/${value}`,
|
||||
body: '<html></html>\n',
|
||||
}).catch(e => {
|
||||
throw new Error(e.data.error);
|
||||
})
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `File upload of extension html is disabled.`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('works to stop invalid filenames', async () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
enableForPublic: true,
|
||||
fileExtensions: ['^[^hH][^tT][^mM][^lL]?$'],
|
||||
},
|
||||
});
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
|
||||
const values = [
|
||||
'!invalid.png',
|
||||
'.png',
|
||||
'.html',
|
||||
' .html',
|
||||
'.png.html',
|
||||
'~invalid.png',
|
||||
'-invalid.png',
|
||||
];
|
||||
|
||||
for (const value of values) {
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: `http://localhost:8378/1/files/${value}`,
|
||||
body: '<html></html>\n',
|
||||
}).catch(e => {
|
||||
throw new Error(e.data.error);
|
||||
})
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.INVALID_FILE_NAME, `Filename contains invalid characters.`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('works with array', async () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
|
||||
@@ -5275,7 +5275,6 @@ describe('ParseGraphQLServer', () => {
|
||||
|
||||
it('should only count', async () => {
|
||||
await prepareData();
|
||||
|
||||
await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear();
|
||||
|
||||
const where = {
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('Parse Role testing', () => {
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
const restExecute = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
||||
const restExecute = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||
|
||||
let user, auth, getAllRolesSpy;
|
||||
createTestUser()
|
||||
|
||||
@@ -107,6 +107,36 @@ describe('Parse.User testing', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('user login with context', async () => {
|
||||
let hit = 0;
|
||||
const context = { foo: 'bar' };
|
||||
Parse.Cloud.beforeLogin(req => {
|
||||
expect(req.context).toEqual(context);
|
||||
hit++;
|
||||
});
|
||||
Parse.Cloud.afterLogin(req => {
|
||||
expect(req.context).toEqual(context);
|
||||
hit++;
|
||||
});
|
||||
await Parse.User.signUp('asdf', 'zxcv');
|
||||
await request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Cloud-Context': JSON.stringify(context),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
_method: 'GET',
|
||||
username: 'asdf',
|
||||
password: 'zxcv',
|
||||
},
|
||||
});
|
||||
expect(hit).toBe(2);
|
||||
});
|
||||
|
||||
it('user login with non-string username with REST API', async done => {
|
||||
await Parse.User.signUp('asdf', 'zxcv');
|
||||
request({
|
||||
|
||||
+24
-20
@@ -399,15 +399,16 @@ describe('RestQuery.each', () => {
|
||||
}
|
||||
const config = Config.get('test');
|
||||
await Parse.Object.saveAll(objects);
|
||||
const query = new RestQuery(
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config,
|
||||
auth.master(config),
|
||||
'Object',
|
||||
{ value: { $gt: 2 } },
|
||||
{ limit: 2 }
|
||||
);
|
||||
auth: auth.master(config),
|
||||
className: 'Object',
|
||||
restWhere: { value: { $gt: 2 } },
|
||||
restOptions: { limit: 2 },
|
||||
});
|
||||
const spy = spyOn(query, 'execute').and.callThrough();
|
||||
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
||||
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||
const results = [];
|
||||
await query.each(result => {
|
||||
expect(result.value).toBeGreaterThan(2);
|
||||
@@ -438,34 +439,37 @@ describe('RestQuery.each', () => {
|
||||
* Two queries needed since objectId are sorted and we can't know which one
|
||||
* going to be the first and then skip by the $gt added by each
|
||||
*/
|
||||
const queryOne = new RestQuery(
|
||||
const queryOne = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth.master(config),
|
||||
'Letter',
|
||||
{
|
||||
auth: auth.master(config),
|
||||
className: 'Letter',
|
||||
restWhere: {
|
||||
numbers: {
|
||||
__type: 'Pointer',
|
||||
className: 'Number',
|
||||
objectId: object1.id,
|
||||
},
|
||||
},
|
||||
{ limit: 1 }
|
||||
);
|
||||
const queryTwo = new RestQuery(
|
||||
restOptions: { limit: 1 },
|
||||
});
|
||||
|
||||
const queryTwo = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth.master(config),
|
||||
'Letter',
|
||||
{
|
||||
auth: auth.master(config),
|
||||
className: 'Letter',
|
||||
restWhere: {
|
||||
numbers: {
|
||||
__type: 'Pointer',
|
||||
className: 'Number',
|
||||
objectId: object2.id,
|
||||
},
|
||||
},
|
||||
{ limit: 1 }
|
||||
);
|
||||
restOptions: { limit: 1 },
|
||||
});
|
||||
|
||||
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
||||
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||
const resultsOne = [];
|
||||
const resultsTwo = [];
|
||||
await queryOne.each(result => {
|
||||
|
||||
@@ -660,6 +660,38 @@ describe('rest create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot get object in volatileClasses if not masterKey through pointer', async () => {
|
||||
const masterKeyOnlyClassObject = new Parse.Object('_PushStatus');
|
||||
await masterKeyOnlyClassObject.save(null, { useMasterKey: true });
|
||||
const obj2 = new Parse.Object('TestObject');
|
||||
// Anyone is can basically create a pointer to any object
|
||||
// or some developers can use master key in some hook to link
|
||||
// private objects to standard objects
|
||||
obj2.set('pointer', masterKeyOnlyClassObject);
|
||||
await obj2.save();
|
||||
const query = new Parse.Query('TestObject');
|
||||
query.include('pointer');
|
||||
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||
"Clients aren't allowed to perform the get operation on the _PushStatus collection."
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot get object in _GlobalConfig if not masterKey through pointer', async () => {
|
||||
await Parse.Config.save({ privateData: 'secret' }, { privateData: true });
|
||||
const obj2 = new Parse.Object('TestObject');
|
||||
obj2.set('globalConfigPointer', {
|
||||
__type: 'Pointer',
|
||||
className: '_GlobalConfig',
|
||||
objectId: 1,
|
||||
});
|
||||
await obj2.save();
|
||||
const query = new Parse.Query('TestObject');
|
||||
query.include('globalConfigPointer');
|
||||
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||
"Clients aren't allowed to perform the get operation on the _GlobalConfig collection."
|
||||
);
|
||||
});
|
||||
|
||||
it('locks down session', done => {
|
||||
let currentUser;
|
||||
Parse.User.signUp('foo', 'bar')
|
||||
|
||||
@@ -17,6 +17,10 @@ export class RedisCacheAdapter {
|
||||
this.ttl = isValidTTL(ttl) ? ttl : DEFAULT_REDIS_TTL;
|
||||
this.client = createClient(redisCtx);
|
||||
this.queue = new KeyPromiseQueue();
|
||||
this.client.on('error', err => { logger.error('RedisCacheAdapter client error', { error: err }) });
|
||||
this.client.on('connect', () => {});
|
||||
this.client.on('reconnecting', () => {});
|
||||
this.client.on('ready', () => {});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
|
||||
+46
-15
@@ -77,13 +77,16 @@ const renewSessionIfNeeded = async ({ config, session, sessionToken }) => {
|
||||
throttle[sessionToken] = setTimeout(async () => {
|
||||
try {
|
||||
if (!session) {
|
||||
const { results } = await new RestQuery(
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
master(config),
|
||||
'_Session',
|
||||
{ sessionToken },
|
||||
{ limit: 1 }
|
||||
).execute();
|
||||
auth: master(config),
|
||||
runBeforeFind: false,
|
||||
className: '_Session',
|
||||
restWhere: { sessionToken },
|
||||
restOptions: { limit: 1 },
|
||||
});
|
||||
const { results } = await query.execute();
|
||||
session = results[0];
|
||||
}
|
||||
const lastUpdated = new Date(session?.updatedAt);
|
||||
@@ -140,7 +143,15 @@ const getAuthForSessionToken = async function ({
|
||||
include: 'user',
|
||||
};
|
||||
const RestQuery = require('./RestQuery');
|
||||
const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
runBeforeFind: false,
|
||||
auth: master(config),
|
||||
className: '_Session',
|
||||
restWhere: { sessionToken },
|
||||
restOptions,
|
||||
});
|
||||
results = (await query.execute()).results;
|
||||
} else {
|
||||
results = (
|
||||
@@ -179,12 +190,20 @@ const getAuthForSessionToken = async function ({
|
||||
});
|
||||
};
|
||||
|
||||
var getAuthForLegacySessionToken = function ({ config, sessionToken, installationId }) {
|
||||
var getAuthForLegacySessionToken = async function ({ config, sessionToken, installationId }) {
|
||||
var restOptions = {
|
||||
limit: 1,
|
||||
};
|
||||
const RestQuery = require('./RestQuery');
|
||||
var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
|
||||
var query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
runBeforeFind: false,
|
||||
auth: master(config),
|
||||
className: '_User',
|
||||
restWhere: { _session_token: sessionToken },
|
||||
restOptions,
|
||||
});
|
||||
return query.execute().then(response => {
|
||||
var results = response.results;
|
||||
if (results.length !== 1) {
|
||||
@@ -229,9 +248,15 @@ Auth.prototype.getRolesForUser = async function () {
|
||||
},
|
||||
};
|
||||
const RestQuery = require('./RestQuery');
|
||||
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
|
||||
results.push(result)
|
||||
);
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
runBeforeFind: false,
|
||||
config: this.config,
|
||||
auth: master(this.config),
|
||||
className: '_Role',
|
||||
restWhere,
|
||||
});
|
||||
await query.each(result => results.push(result));
|
||||
} else {
|
||||
await new Parse.Query(Parse.Role)
|
||||
.equalTo('users', this.user)
|
||||
@@ -323,9 +348,15 @@ 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)
|
||||
);
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
runBeforeFind: false,
|
||||
auth: master(this.config),
|
||||
className: '_Role',
|
||||
restWhere,
|
||||
});
|
||||
await query.each(result => results.push(result));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -58,9 +58,16 @@ export class PushController {
|
||||
|
||||
// Force filtering on only valid device tokens
|
||||
const updateWhere = applyDeviceTokenExists(where);
|
||||
badgeUpdate = () => {
|
||||
badgeUpdate = async () => {
|
||||
// Build a real RestQuery so we can use it in RestWrite
|
||||
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
|
||||
const restQuery = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config,
|
||||
runBeforeFind: false,
|
||||
auth: master(config),
|
||||
className: '_Installation',
|
||||
restWhere: updateWhere,
|
||||
});
|
||||
return restQuery.buildRestWhere().then(() => {
|
||||
const write = new RestWrite(
|
||||
config,
|
||||
|
||||
@@ -61,7 +61,7 @@ export class UserController extends AdaptableController {
|
||||
return true;
|
||||
}
|
||||
|
||||
verifyEmail(username, token) {
|
||||
async verifyEmail(username, token) {
|
||||
if (!this.shouldVerifyEmails) {
|
||||
// Trying to verify email when not enabled
|
||||
// TODO: Better error here.
|
||||
@@ -83,8 +83,14 @@ export class UserController extends AdaptableController {
|
||||
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
||||
}
|
||||
const maintenanceAuth = Auth.maintenance(this.config);
|
||||
var findUserForEmailVerification = new RestQuery(this.config, maintenanceAuth, '_User', {
|
||||
username,
|
||||
var findUserForEmailVerification = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config: this.config,
|
||||
auth: maintenanceAuth,
|
||||
className: '_User',
|
||||
restWhere: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
return findUserForEmailVerification.execute().then(result => {
|
||||
if (result.results.length && result.results[0].emailVerified) {
|
||||
@@ -123,7 +129,7 @@ export class UserController extends AdaptableController {
|
||||
});
|
||||
}
|
||||
|
||||
getUserIfNeeded(user) {
|
||||
async getUserIfNeeded(user) {
|
||||
if (user.username && user.email) {
|
||||
return Promise.resolve(user);
|
||||
}
|
||||
@@ -135,7 +141,14 @@ export class UserController extends AdaptableController {
|
||||
where.email = user.email;
|
||||
}
|
||||
|
||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||
var query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config: this.config,
|
||||
runBeforeFind: false,
|
||||
auth: Auth.master(this.config),
|
||||
className: '_User',
|
||||
restWhere: where,
|
||||
});
|
||||
return query.execute().then(function (result) {
|
||||
if (result.results.length != 1) {
|
||||
throw undefined;
|
||||
|
||||
+146
-57
@@ -6,6 +6,8 @@ var Parse = require('parse/node').Parse;
|
||||
const triggers = require('./triggers');
|
||||
const { continueWhile } = require('parse/lib/node/promiseUtils');
|
||||
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
|
||||
// restOptions can include:
|
||||
// skip
|
||||
// limit
|
||||
@@ -18,7 +20,80 @@ const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
||||
// readPreference
|
||||
// includeReadPreference
|
||||
// subqueryReadPreference
|
||||
function RestQuery(
|
||||
/**
|
||||
* Use to perform a query on a class. It will run security checks and triggers.
|
||||
* @param options
|
||||
* @param options.method {RestQuery.Method} The type of query to perform
|
||||
* @param options.config {ParseServerConfiguration} The server configuration
|
||||
* @param options.auth {Auth} The auth object for the request
|
||||
* @param options.className {string} The name of the class to query
|
||||
* @param options.restWhere {object} The where object for the query
|
||||
* @param options.restOptions {object} The options object for the query
|
||||
* @param options.clientSDK {string} The client SDK that is performing the query
|
||||
* @param options.runAfterFind {boolean} Whether to run the afterFind trigger
|
||||
* @param options.runBeforeFind {boolean} Whether to run the beforeFind trigger
|
||||
* @param options.context {object} The context object for the query
|
||||
* @returns {Promise<_UnsafeRestQuery>} A promise that is resolved with the _UnsafeRestQuery object
|
||||
*/
|
||||
async function RestQuery({
|
||||
method,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere = {},
|
||||
restOptions = {},
|
||||
clientSDK,
|
||||
runAfterFind = true,
|
||||
runBeforeFind = true,
|
||||
context,
|
||||
}) {
|
||||
if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type');
|
||||
}
|
||||
enforceRoleSecurity(method, className, auth);
|
||||
const result = runBeforeFind
|
||||
? await triggers.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
config,
|
||||
auth,
|
||||
context,
|
||||
method === RestQuery.Method.get
|
||||
)
|
||||
: Promise.resolve({ restWhere, restOptions });
|
||||
|
||||
return new _UnsafeRestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
result.restWhere || restWhere,
|
||||
result.restOptions || restOptions,
|
||||
clientSDK,
|
||||
runAfterFind,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
RestQuery.Method = Object.freeze({
|
||||
get: 'get',
|
||||
find: 'find',
|
||||
});
|
||||
|
||||
/**
|
||||
* _UnsafeRestQuery is meant for specific internal usage only. When you need to skip security checks or some triggers.
|
||||
* Don't use it if you don't know what you are doing.
|
||||
* @param config
|
||||
* @param auth
|
||||
* @param className
|
||||
* @param restWhere
|
||||
* @param restOptions
|
||||
* @param clientSDK
|
||||
* @param runAfterFind
|
||||
* @param context
|
||||
*/
|
||||
function _UnsafeRestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
@@ -197,7 +272,7 @@ function RestQuery(
|
||||
// Returns a promise for the response - an object with optional keys
|
||||
// 'results' and 'count'.
|
||||
// TODO: consolidate the replaceX functions
|
||||
RestQuery.prototype.execute = function (executeOptions) {
|
||||
_UnsafeRestQuery.prototype.execute = function (executeOptions) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return this.buildRestWhere();
|
||||
@@ -231,7 +306,7 @@ RestQuery.prototype.execute = function (executeOptions) {
|
||||
});
|
||||
};
|
||||
|
||||
RestQuery.prototype.each = function (callback) {
|
||||
_UnsafeRestQuery.prototype.each = function (callback) {
|
||||
const { config, auth, className, restWhere, restOptions, clientSDK } = this;
|
||||
// if the limit is set, use it
|
||||
restOptions.limit = restOptions.limit || 100;
|
||||
@@ -243,7 +318,9 @@ RestQuery.prototype.each = function (callback) {
|
||||
return !finished;
|
||||
},
|
||||
async () => {
|
||||
const query = new RestQuery(
|
||||
// Safe here to use _UnsafeRestQuery because the security was already
|
||||
// checked during "await RestQuery()"
|
||||
const query = new _UnsafeRestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
@@ -265,7 +342,7 @@ RestQuery.prototype.each = function (callback) {
|
||||
);
|
||||
};
|
||||
|
||||
RestQuery.prototype.buildRestWhere = function () {
|
||||
_UnsafeRestQuery.prototype.buildRestWhere = function () {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return this.getUserAndRoleACL();
|
||||
@@ -294,7 +371,7 @@ RestQuery.prototype.buildRestWhere = function () {
|
||||
};
|
||||
|
||||
// Uses the Auth object to get the list of roles, adds the user id
|
||||
RestQuery.prototype.getUserAndRoleACL = function () {
|
||||
_UnsafeRestQuery.prototype.getUserAndRoleACL = function () {
|
||||
if (this.auth.isMaster) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -313,7 +390,7 @@ RestQuery.prototype.getUserAndRoleACL = function () {
|
||||
|
||||
// Changes the className if redirectClassNameForKey is set.
|
||||
// Returns a promise.
|
||||
RestQuery.prototype.redirectClassNameForKey = function () {
|
||||
_UnsafeRestQuery.prototype.redirectClassNameForKey = function () {
|
||||
if (!this.redirectKey) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -328,7 +405,7 @@ RestQuery.prototype.redirectClassNameForKey = function () {
|
||||
};
|
||||
|
||||
// Validates this operation against the allowClientClassCreation config.
|
||||
RestQuery.prototype.validateClientClassCreation = function () {
|
||||
_UnsafeRestQuery.prototype.validateClientClassCreation = function () {
|
||||
if (
|
||||
this.config.allowClientClassCreation === false &&
|
||||
!this.auth.isMaster &&
|
||||
@@ -371,7 +448,7 @@ function transformInQuery(inQueryObject, className, results) {
|
||||
// $inQuery clause.
|
||||
// The $inQuery clause turns into an $in with values that are just
|
||||
// pointers to the objects returned in the subquery.
|
||||
RestQuery.prototype.replaceInQuery = function () {
|
||||
_UnsafeRestQuery.prototype.replaceInQuery = async function () {
|
||||
var inQueryObject = findObjectWithKey(this.restWhere, '$inQuery');
|
||||
if (!inQueryObject) {
|
||||
return;
|
||||
@@ -394,13 +471,14 @@ RestQuery.prototype.replaceInQuery = function () {
|
||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||
}
|
||||
|
||||
var subquery = new RestQuery(
|
||||
this.config,
|
||||
this.auth,
|
||||
inQueryValue.className,
|
||||
inQueryValue.where,
|
||||
additionalOptions
|
||||
);
|
||||
const subquery = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
auth: this.auth,
|
||||
className: inQueryValue.className,
|
||||
restWhere: inQueryValue.where,
|
||||
restOptions: additionalOptions,
|
||||
});
|
||||
return subquery.execute().then(response => {
|
||||
transformInQuery(inQueryObject, subquery.className, response.results);
|
||||
// Recurse to repeat
|
||||
@@ -429,7 +507,7 @@ function transformNotInQuery(notInQueryObject, className, results) {
|
||||
// $notInQuery clause.
|
||||
// The $notInQuery clause turns into a $nin with values that are just
|
||||
// pointers to the objects returned in the subquery.
|
||||
RestQuery.prototype.replaceNotInQuery = function () {
|
||||
_UnsafeRestQuery.prototype.replaceNotInQuery = async function () {
|
||||
var notInQueryObject = findObjectWithKey(this.restWhere, '$notInQuery');
|
||||
if (!notInQueryObject) {
|
||||
return;
|
||||
@@ -452,13 +530,15 @@ RestQuery.prototype.replaceNotInQuery = function () {
|
||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||
}
|
||||
|
||||
var subquery = new RestQuery(
|
||||
this.config,
|
||||
this.auth,
|
||||
notInQueryValue.className,
|
||||
notInQueryValue.where,
|
||||
additionalOptions
|
||||
);
|
||||
const subquery = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
auth: this.auth,
|
||||
className: notInQueryValue.className,
|
||||
restWhere: notInQueryValue.where,
|
||||
restOptions: additionalOptions,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
transformNotInQuery(notInQueryObject, subquery.className, response.results);
|
||||
// Recurse to repeat
|
||||
@@ -492,7 +572,7 @@ const transformSelect = (selectObject, key, objects) => {
|
||||
// The $select clause turns into an $in with values selected out of
|
||||
// the subquery.
|
||||
// Returns a possible-promise.
|
||||
RestQuery.prototype.replaceSelect = function () {
|
||||
_UnsafeRestQuery.prototype.replaceSelect = async function () {
|
||||
var selectObject = findObjectWithKey(this.restWhere, '$select');
|
||||
if (!selectObject) {
|
||||
return;
|
||||
@@ -522,13 +602,15 @@ RestQuery.prototype.replaceSelect = function () {
|
||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||
}
|
||||
|
||||
var subquery = new RestQuery(
|
||||
this.config,
|
||||
this.auth,
|
||||
selectValue.query.className,
|
||||
selectValue.query.where,
|
||||
additionalOptions
|
||||
);
|
||||
const subquery = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
auth: this.auth,
|
||||
className: selectValue.query.className,
|
||||
restWhere: selectValue.query.where,
|
||||
restOptions: additionalOptions,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
transformSelect(selectObject, selectValue.key, response.results);
|
||||
// Keep replacing $select clauses
|
||||
@@ -554,7 +636,7 @@ const transformDontSelect = (dontSelectObject, key, objects) => {
|
||||
// The $dontSelect clause turns into an $nin with values selected out of
|
||||
// the subquery.
|
||||
// Returns a possible-promise.
|
||||
RestQuery.prototype.replaceDontSelect = function () {
|
||||
_UnsafeRestQuery.prototype.replaceDontSelect = async function () {
|
||||
var dontSelectObject = findObjectWithKey(this.restWhere, '$dontSelect');
|
||||
if (!dontSelectObject) {
|
||||
return;
|
||||
@@ -582,13 +664,15 @@ RestQuery.prototype.replaceDontSelect = function () {
|
||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||
}
|
||||
|
||||
var subquery = new RestQuery(
|
||||
this.config,
|
||||
this.auth,
|
||||
dontSelectValue.query.className,
|
||||
dontSelectValue.query.where,
|
||||
additionalOptions
|
||||
);
|
||||
const subquery = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
auth: this.auth,
|
||||
className: dontSelectValue.query.className,
|
||||
restWhere: dontSelectValue.query.where,
|
||||
restOptions: additionalOptions,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
transformDontSelect(dontSelectObject, dontSelectValue.key, response.results);
|
||||
// Keep replacing $dontSelect clauses
|
||||
@@ -596,7 +680,7 @@ RestQuery.prototype.replaceDontSelect = function () {
|
||||
});
|
||||
};
|
||||
|
||||
RestQuery.prototype.cleanResultAuthData = function (result) {
|
||||
_UnsafeRestQuery.prototype.cleanResultAuthData = function (result) {
|
||||
delete result.password;
|
||||
if (result.authData) {
|
||||
Object.keys(result.authData).forEach(provider => {
|
||||
@@ -635,7 +719,7 @@ const replaceEqualityConstraint = constraint => {
|
||||
return constraint;
|
||||
};
|
||||
|
||||
RestQuery.prototype.replaceEquality = function () {
|
||||
_UnsafeRestQuery.prototype.replaceEquality = function () {
|
||||
if (typeof this.restWhere !== 'object') {
|
||||
return;
|
||||
}
|
||||
@@ -646,7 +730,7 @@ RestQuery.prototype.replaceEquality = function () {
|
||||
|
||||
// Returns a promise for whether it was successful.
|
||||
// Populates this.response with an object that only has 'results'.
|
||||
RestQuery.prototype.runFind = function (options = {}) {
|
||||
_UnsafeRestQuery.prototype.runFind = function (options = {}) {
|
||||
if (this.findOptions.limit === 0) {
|
||||
this.response = { results: [] };
|
||||
return Promise.resolve();
|
||||
@@ -682,7 +766,7 @@ RestQuery.prototype.runFind = function (options = {}) {
|
||||
|
||||
// Returns a promise for whether it was successful.
|
||||
// Populates this.response.count with the count
|
||||
RestQuery.prototype.runCount = function () {
|
||||
_UnsafeRestQuery.prototype.runCount = function () {
|
||||
if (!this.doCount) {
|
||||
return;
|
||||
}
|
||||
@@ -694,7 +778,7 @@ RestQuery.prototype.runCount = function () {
|
||||
});
|
||||
};
|
||||
|
||||
RestQuery.prototype.denyProtectedFields = async function () {
|
||||
_UnsafeRestQuery.prototype.denyProtectedFields = async function () {
|
||||
if (this.auth.isMaster) {
|
||||
return;
|
||||
}
|
||||
@@ -719,7 +803,7 @@ RestQuery.prototype.denyProtectedFields = async function () {
|
||||
};
|
||||
|
||||
// Augments this.response with all pointers on an object
|
||||
RestQuery.prototype.handleIncludeAll = function () {
|
||||
_UnsafeRestQuery.prototype.handleIncludeAll = function () {
|
||||
if (!this.includeAll) {
|
||||
return;
|
||||
}
|
||||
@@ -748,7 +832,7 @@ RestQuery.prototype.handleIncludeAll = function () {
|
||||
};
|
||||
|
||||
// Updates property `this.keys` to contain all keys but the ones unselected.
|
||||
RestQuery.prototype.handleExcludeKeys = function () {
|
||||
_UnsafeRestQuery.prototype.handleExcludeKeys = function () {
|
||||
if (!this.excludeKeys) {
|
||||
return;
|
||||
}
|
||||
@@ -766,7 +850,7 @@ RestQuery.prototype.handleExcludeKeys = function () {
|
||||
};
|
||||
|
||||
// Augments this.response with data at the paths provided in this.include.
|
||||
RestQuery.prototype.handleInclude = function () {
|
||||
_UnsafeRestQuery.prototype.handleInclude = function () {
|
||||
if (this.include.length == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -793,7 +877,7 @@ RestQuery.prototype.handleInclude = function () {
|
||||
};
|
||||
|
||||
//Returns a promise of a processed set of results
|
||||
RestQuery.prototype.runAfterFindTrigger = function () {
|
||||
_UnsafeRestQuery.prototype.runAfterFindTrigger = function () {
|
||||
if (!this.response) {
|
||||
return;
|
||||
}
|
||||
@@ -845,7 +929,7 @@ RestQuery.prototype.runAfterFindTrigger = function () {
|
||||
});
|
||||
};
|
||||
|
||||
RestQuery.prototype.handleAuthAdapters = async function () {
|
||||
_UnsafeRestQuery.prototype.handleAuthAdapters = async function () {
|
||||
if (this.className !== '_User' || this.findOptions.explain) {
|
||||
return;
|
||||
}
|
||||
@@ -927,7 +1011,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
includeRestOptions.readPreference = restOptions.readPreference;
|
||||
}
|
||||
|
||||
const queryPromises = Object.keys(pointersHash).map(className => {
|
||||
const queryPromises = Object.keys(pointersHash).map(async className => {
|
||||
const objectIds = Array.from(pointersHash[className]);
|
||||
let where;
|
||||
if (objectIds.length === 1) {
|
||||
@@ -935,7 +1019,14 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
} else {
|
||||
where = { objectId: { $in: objectIds } };
|
||||
}
|
||||
var query = new RestQuery(config, auth, className, where, includeRestOptions);
|
||||
const query = await RestQuery({
|
||||
method: objectIds.length === 1 ? RestQuery.Method.get : RestQuery.Method.find,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere: where,
|
||||
restOptions: includeRestOptions,
|
||||
});
|
||||
return query.execute({ op: 'get' }).then(results => {
|
||||
results.className = className;
|
||||
return Promise.resolve(results);
|
||||
@@ -975,11 +1066,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
// Returns a list of pointers in REST format.
|
||||
function findPointers(object, path) {
|
||||
if (object instanceof Array) {
|
||||
var answer = [];
|
||||
for (var x of object) {
|
||||
answer = answer.concat(findPointers(x, path));
|
||||
}
|
||||
return answer;
|
||||
return object.map(x => findPointers(x, path)).flat();
|
||||
}
|
||||
|
||||
if (typeof object !== 'object' || !object) {
|
||||
@@ -1066,3 +1153,5 @@ function findObjectWithKey(root, key) {
|
||||
}
|
||||
|
||||
module.exports = RestQuery;
|
||||
// For tests
|
||||
module.exports._UnsafeRestQuery = _UnsafeRestQuery;
|
||||
|
||||
+19
-13
@@ -621,7 +621,7 @@ RestWrite.prototype.checkRestrictedFields = async function () {
|
||||
};
|
||||
|
||||
// The non-third-party parts of User transformation
|
||||
RestWrite.prototype.transformUser = function () {
|
||||
RestWrite.prototype.transformUser = async function () {
|
||||
var promise = Promise.resolve();
|
||||
if (this.className !== '_User') {
|
||||
return promise;
|
||||
@@ -631,19 +631,25 @@ RestWrite.prototype.transformUser = function () {
|
||||
if (this.query && this.objectId()) {
|
||||
// If we're updating a _User object, we need to clear out the cache for that user. Find all their
|
||||
// session tokens, and remove them from the cache.
|
||||
promise = new RestQuery(this.config, Auth.master(this.config), '_Session', {
|
||||
user: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: this.objectId(),
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config: this.config,
|
||||
auth: Auth.master(this.config),
|
||||
className: '_Session',
|
||||
runBeforeFind: false,
|
||||
restWhere: {
|
||||
user: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: this.objectId(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.execute()
|
||||
.then(results => {
|
||||
results.results.forEach(session =>
|
||||
this.config.cacheController.user.del(session.sessionToken)
|
||||
);
|
||||
});
|
||||
});
|
||||
promise = query.execute().then(results => {
|
||||
results.results.forEach(session =>
|
||||
this.config.cacheController.user.del(session.sessionToken)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return promise
|
||||
|
||||
@@ -155,7 +155,7 @@ export class FilesRouter {
|
||||
};
|
||||
let extension = contentType;
|
||||
if (filename && filename.includes('.')) {
|
||||
extension = filename.split('.')[1];
|
||||
extension = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
} else if (contentType && contentType.includes('/')) {
|
||||
extension = contentType.split('/')[1];
|
||||
}
|
||||
|
||||
@@ -259,7 +259,8 @@ export class UsersRouter extends ClassesRouter {
|
||||
req.auth,
|
||||
Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
|
||||
null,
|
||||
req.config
|
||||
req.config,
|
||||
req.info.context
|
||||
);
|
||||
|
||||
// If we have some new validated authData update directly
|
||||
@@ -291,7 +292,8 @@ export class UsersRouter extends ClassesRouter {
|
||||
{ ...req.auth, user: afterLoginUser },
|
||||
afterLoginUser,
|
||||
null,
|
||||
req.config
|
||||
req.config,
|
||||
req.info.context
|
||||
);
|
||||
|
||||
if (authDataResponse) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
const classesWithMasterOnlyAccess = [
|
||||
'_JobStatus',
|
||||
'_PushStatus',
|
||||
'_Hooks',
|
||||
'_GlobalConfig',
|
||||
'_JobSchedule',
|
||||
'_Idempotency',
|
||||
];
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
//all volatileClasses are masterKey only
|
||||
if (
|
||||
classesWithMasterOnlyAccess.indexOf(className) >= 0 &&
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
enforceRoleSecurity,
|
||||
};
|
||||
+12
-5
@@ -1,13 +1,20 @@
|
||||
import program from './commander';
|
||||
|
||||
function logStartupOptions(options) {
|
||||
if (!options.verbose) {
|
||||
return;
|
||||
}
|
||||
// Keys that may include sensitive information that will be redacted in logs
|
||||
const keysToRedact = [
|
||||
'databaseURI',
|
||||
'masterKey',
|
||||
'maintenanceKey',
|
||||
'push',
|
||||
];
|
||||
for (const key in options) {
|
||||
let value = options[key];
|
||||
if (key == 'masterKey') {
|
||||
value = '***REDACTED***';
|
||||
}
|
||||
if (key == 'push' && options.verbose != true) {
|
||||
value = '***REDACTED***';
|
||||
if (keysToRedact.includes(key)) {
|
||||
value = '<REDACTED>';
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
try {
|
||||
|
||||
+63
-121
@@ -12,6 +12,7 @@ var Parse = require('parse/node').Parse;
|
||||
var RestQuery = require('./RestQuery');
|
||||
var RestWrite = require('./RestWrite');
|
||||
var triggers = require('./triggers');
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some(triggerType => {
|
||||
@@ -24,65 +25,34 @@ function checkLiveQuery(className, config) {
|
||||
}
|
||||
|
||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
|
||||
enforceRoleSecurity('find', className, auth);
|
||||
return triggers
|
||||
.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
config,
|
||||
auth,
|
||||
context
|
||||
)
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
true,
|
||||
context
|
||||
);
|
||||
return query.execute();
|
||||
});
|
||||
}
|
||||
const find = async (config, auth, className, restWhere, restOptions, clientSDK, context) => {
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context,
|
||||
});
|
||||
return query.execute();
|
||||
};
|
||||
|
||||
// get is just like find but only queries an objectId.
|
||||
const get = (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
||||
const get = async (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
||||
var restWhere = { objectId };
|
||||
enforceRoleSecurity('get', className, auth);
|
||||
return triggers
|
||||
.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
config,
|
||||
auth,
|
||||
context,
|
||||
true
|
||||
)
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
true,
|
||||
context
|
||||
);
|
||||
return query.execute();
|
||||
});
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context,
|
||||
});
|
||||
return query.execute();
|
||||
};
|
||||
|
||||
// Returns a promise that doesn't resolve to any useful value.
|
||||
@@ -101,35 +71,40 @@ function del(config, auth, className, objectId, context) {
|
||||
let schemaController;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||
return new RestQuery(config, auth, className, { objectId })
|
||||
.execute({ op: 'delete' })
|
||||
.then(response => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere: { objectId },
|
||||
});
|
||||
return query.execute({ op: 'delete' }).then(response => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.beforeDelete,
|
||||
auth,
|
||||
inflatedObject,
|
||||
null,
|
||||
config,
|
||||
context
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.');
|
||||
});
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.beforeDelete,
|
||||
auth,
|
||||
inflatedObject,
|
||||
null,
|
||||
config,
|
||||
context
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.');
|
||||
});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
})
|
||||
@@ -193,21 +168,22 @@ function update(config, auth, className, restWhere, restObject, clientSDK, conte
|
||||
enforceRoleSecurity('update', className, auth);
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery) {
|
||||
// Do not use find, as it runs the before finds
|
||||
return new RestQuery(
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
context
|
||||
).execute({
|
||||
runAfterFind: false,
|
||||
runBeforeFind: false,
|
||||
context,
|
||||
});
|
||||
return query.execute({
|
||||
op: 'update',
|
||||
});
|
||||
}
|
||||
@@ -248,40 +224,6 @@ function handleSessionMissingError(error, className, auth) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const classesWithMasterOnlyAccess = [
|
||||
'_JobStatus',
|
||||
'_PushStatus',
|
||||
'_Hooks',
|
||||
'_GlobalConfig',
|
||||
'_JobSchedule',
|
||||
'_Idempotency',
|
||||
];
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
//all volatileClasses are masterKey only
|
||||
if (
|
||||
classesWithMasterOnlyAccess.indexOf(className) >= 0 &&
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
del,
|
||||
|
||||
@@ -270,6 +270,8 @@ export function getRequestObject(
|
||||
triggerType === Types.afterSave ||
|
||||
triggerType === Types.beforeDelete ||
|
||||
triggerType === Types.afterDelete ||
|
||||
triggerType === Types.beforeLogin ||
|
||||
triggerType === Types.afterLogin ||
|
||||
triggerType === Types.afterFind
|
||||
) {
|
||||
// Set a copy of the context on the request object.
|
||||
|
||||
Reference in New Issue
Block a user