build: Release (#8749)
This commit is contained in:
@@ -13,7 +13,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
|
||||
| DEPPS7 | Remove file trigger syntax `Parse.Cloud.beforeSaveFile((request) => {})` | [#7966](https://github.com/parse-community/parse-server/pull/7966) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS8 | Login with expired 3rd party authentication token defaults to `false` | [#7079](https://github.com/parse-community/parse-server/pull/7079) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS9 | Rename LiveQuery `fields` option to `keys` | [#8389](https://github.com/parse-community/parse-server/issues/8389) | 6.0.0 (2023) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS10 | Config option `encodeParseObjectInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS10 | Config option `encodeParseObjectInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 8.0.0 (2025) | deprecated | - |
|
||||
|
||||
[i_deprecation]: ## "The version and date of the deprecation."
|
||||
[i_removal]: ## "The version and date of the planned removal."
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
# [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)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
# [6.3.0-alpha.5](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.4...6.3.0-alpha.5) (2023-07-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
# [6.3.0-alpha.4](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.3...6.3.0-alpha.4) (2023-07-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
# [6.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.2...6.3.0-alpha.3) (2023-06-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add TOTP authentication adapter ([#8457](https://github.com/parse-community/parse-server/issues/8457)) ([cc079a4](https://github.com/parse-community/parse-server/commit/cc079a40f6849a0e9bc6fdc811e8649ecb67b589))
|
||||
|
||||
# [6.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.1...6.3.0-alpha.2) (2023-06-20)
|
||||
|
||||
|
||||
|
||||
214
package-lock.json
generated
214
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0",
|
||||
"version": "6.3.0-alpha.9",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0",
|
||||
"version": "6.3.0-alpha.9",
|
||||
"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",
|
||||
@@ -39,6 +39,7 @@
|
||||
"mime": "3.0.0",
|
||||
"mongodb": "4.10.0",
|
||||
"mustache": "4.2.0",
|
||||
"otpauth": "9.1.2",
|
||||
"parse": "4.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"pg-monitor": "2.0.0",
|
||||
@@ -46,7 +47,7 @@
|
||||
"pluralize": "8.0.0",
|
||||
"rate-limit-redis": "3.0.2",
|
||||
"redis": "4.6.6",
|
||||
"semver": "^7.5.2",
|
||||
"semver": "7.5.2",
|
||||
"subscriptions-transport-ws": "0.11.0",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "9.0.0",
|
||||
@@ -2730,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": {
|
||||
@@ -2759,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",
|
||||
@@ -2810,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"
|
||||
@@ -10220,6 +10192,14 @@
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jssha": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.0.tgz",
|
||||
"integrity": "sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
@@ -10791,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",
|
||||
@@ -10838,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",
|
||||
@@ -12484,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"
|
||||
}
|
||||
@@ -15940,6 +15897,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/otpauth": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.2.tgz",
|
||||
"integrity": "sha512-iI5nlVvMFP3aTPdjG/fnC4mhVJ/KZOSnBrvo/VnYHUwlTp9jVLjAe2B3i3pyCH+3/E5jYQRSvuHk/8oas3870g==",
|
||||
"dependencies": {
|
||||
"jssha": "~3.3.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/hectorm/otpauth?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-cancelable": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||
@@ -20417,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"
|
||||
}
|
||||
@@ -22584,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": {
|
||||
@@ -22601,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=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -22647,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"
|
||||
@@ -28397,6 +28343,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jssha": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.0.tgz",
|
||||
"integrity": "sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w=="
|
||||
},
|
||||
"jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
@@ -28860,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",
|
||||
@@ -28907,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",
|
||||
@@ -30206,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",
|
||||
@@ -32712,6 +32640,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"otpauth": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.2.tgz",
|
||||
"integrity": "sha512-iI5nlVvMFP3aTPdjG/fnC4mhVJ/KZOSnBrvo/VnYHUwlTp9jVLjAe2B3i3pyCH+3/E5jYQRSvuHk/8oas3870g==",
|
||||
"requires": {
|
||||
"jssha": "~3.3.0"
|
||||
}
|
||||
},
|
||||
"p-cancelable": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||
@@ -36167,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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "6.3.0",
|
||||
"version": "6.3.0-alpha.9",
|
||||
"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",
|
||||
@@ -48,6 +48,7 @@
|
||||
"mime": "3.0.0",
|
||||
"mongodb": "4.10.0",
|
||||
"mustache": "4.2.0",
|
||||
"otpauth": "9.1.2",
|
||||
"parse": "4.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"pg-monitor": "2.0.0",
|
||||
|
||||
@@ -2406,3 +2406,342 @@ describe('facebook limited auth adapter', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('OTP TOTP auth adatper', () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await reconfigureServer({
|
||||
auth: {
|
||||
mfa: {
|
||||
enabled: true,
|
||||
options: ['TOTP'],
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can enroll', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
const response = user.get('authDataResponse');
|
||||
expect(response.mfa).toBeDefined();
|
||||
expect(response.mfa.recovery).toBeDefined();
|
||||
expect(response.mfa.recovery.split(',').length).toEqual(2);
|
||||
await user.fetch();
|
||||
expect(user.get('authData').mfa).toEqual({ status: 'enabled' });
|
||||
});
|
||||
|
||||
it('can login with valid token', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
const response = await request({
|
||||
headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
body: JSON.stringify({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
authData: {
|
||||
mfa: {
|
||||
token: totp.generate(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).then(res => res.data);
|
||||
expect(response.objectId).toEqual(user.id);
|
||||
expect(response.sessionToken).toBeDefined();
|
||||
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
|
||||
expect(Object.keys(response).sort()).toEqual(
|
||||
[
|
||||
'objectId',
|
||||
'username',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'authData',
|
||||
'ACL',
|
||||
'sessionToken',
|
||||
'authDataResponse',
|
||||
].sort()
|
||||
);
|
||||
});
|
||||
|
||||
it('can change OTP with valid token', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
|
||||
const new_secret = new OTPAuth.Secret();
|
||||
const new_totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret: new_secret,
|
||||
});
|
||||
const new_token = new_totp.generate();
|
||||
await user.save(
|
||||
{
|
||||
authData: { mfa: { secret: new_secret.base32, token: new_token, old: totp.generate() } },
|
||||
},
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
await user.fetch({ useMasterKey: true });
|
||||
expect(user.get('authData').mfa.secret).toEqual(new_secret.base32);
|
||||
});
|
||||
|
||||
it('cannot change OTP with invalid token', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
|
||||
const new_secret = new OTPAuth.Secret();
|
||||
const new_totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret: new_secret,
|
||||
});
|
||||
const new_token = new_totp.generate();
|
||||
await expectAsync(
|
||||
user.save(
|
||||
{
|
||||
authData: { mfa: { secret: new_secret.base32, token: new_token, old: '123' } },
|
||||
},
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
)
|
||||
).toBeRejectedWith(new Parse.Error(Parse.Error.OTHER_CAUSE, 'Invalid MFA token'));
|
||||
await user.fetch({ useMasterKey: true });
|
||||
expect(user.get('authData').mfa.secret).toEqual(secret.base32);
|
||||
});
|
||||
|
||||
it('future logins require TOTP token', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
await expectAsync(Parse.User.logIn('username', 'password')).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing additional authData mfa')
|
||||
);
|
||||
});
|
||||
|
||||
it('future logins reject incorrect TOTP token', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const OTPAuth = require('otpauth');
|
||||
const secret = new OTPAuth.Secret();
|
||||
const totp = new OTPAuth.TOTP({
|
||||
algorithm: 'SHA1',
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret,
|
||||
});
|
||||
const token = totp.generate();
|
||||
await user.save(
|
||||
{ authData: { mfa: { secret: secret.base32, token } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
await expectAsync(
|
||||
request({
|
||||
headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
body: JSON.stringify({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
authData: {
|
||||
mfa: {
|
||||
token: 'abcd',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).catch(e => {
|
||||
throw e.data;
|
||||
})
|
||||
).toBeRejectedWith({ code: Parse.Error.SCRIPT_FAILED, error: 'Invalid MFA token' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('OTP SMS auth adatper', () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
let code;
|
||||
let mobile;
|
||||
const mfa = {
|
||||
enabled: true,
|
||||
options: ['SMS'],
|
||||
sendSMS(smsCode, number) {
|
||||
expect(smsCode).toBeDefined();
|
||||
expect(number).toBeDefined();
|
||||
expect(smsCode.length).toEqual(6);
|
||||
code = smsCode;
|
||||
mobile = number;
|
||||
},
|
||||
digits: 6,
|
||||
period: 30,
|
||||
};
|
||||
beforeEach(async () => {
|
||||
code = '';
|
||||
mobile = '';
|
||||
await reconfigureServer({
|
||||
auth: {
|
||||
mfa,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can enroll', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const sessionToken = user.getSessionToken();
|
||||
const spy = spyOn(mfa, 'sendSMS').and.callThrough();
|
||||
await user.save({ authData: { mfa: { mobile: '+11111111111' } } }, { sessionToken });
|
||||
await user.fetch({ sessionToken });
|
||||
expect(user.get('authData')).toEqual({ mfa: { status: 'disabled' } });
|
||||
expect(spy).toHaveBeenCalledWith(code, '+11111111111');
|
||||
await user.fetch({ useMasterKey: true });
|
||||
const authData = user.get('authData').mfa?.pending;
|
||||
expect(authData).toBeDefined();
|
||||
expect(authData['+11111111111']).toBeDefined();
|
||||
expect(Object.keys(authData['+11111111111'])).toEqual(['token', 'expiry']);
|
||||
|
||||
await user.save({ authData: { mfa: { mobile, token: code } } }, { sessionToken });
|
||||
await user.fetch({ sessionToken });
|
||||
expect(user.get('authData')).toEqual({ mfa: { status: 'enabled' } });
|
||||
});
|
||||
|
||||
it('future logins require SMS code', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
const spy = spyOn(mfa, 'sendSMS').and.callThrough();
|
||||
await user.save(
|
||||
{ authData: { mfa: { mobile: '+11111111111' } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
|
||||
await user.save(
|
||||
{ authData: { mfa: { mobile, token: code } } },
|
||||
{ sessionToken: user.getSessionToken() }
|
||||
);
|
||||
|
||||
spy.calls.reset();
|
||||
|
||||
await expectAsync(Parse.User.logIn('username', 'password')).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing additional authData mfa')
|
||||
);
|
||||
const res = await request({
|
||||
headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
body: JSON.stringify({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
authData: {
|
||||
mfa: {
|
||||
token: 'request',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).catch(e => e.data);
|
||||
expect(res).toEqual({ code: Parse.Error.SCRIPT_FAILED, error: 'Please enter the token' });
|
||||
expect(spy).toHaveBeenCalledWith(code, '+11111111111');
|
||||
const response = await request({
|
||||
headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
body: JSON.stringify({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
authData: {
|
||||
mfa: {
|
||||
token: code,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).then(res => res.data);
|
||||
expect(response.objectId).toEqual(user.id);
|
||||
expect(response.sessionToken).toBeDefined();
|
||||
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
|
||||
expect(Object.keys(response).sort()).toEqual(
|
||||
[
|
||||
'objectId',
|
||||
'username',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'authData',
|
||||
'ACL',
|
||||
'sessionToken',
|
||||
'authDataResponse',
|
||||
].sort()
|
||||
);
|
||||
});
|
||||
|
||||
it('partially enrolled users can still login', async () => {
|
||||
const user = await Parse.User.signUp('username', 'password');
|
||||
await user.save({ authData: { mfa: { mobile: '+11111111111' } } });
|
||||
const spy = spyOn(mfa, 'sendSMS').and.callThrough();
|
||||
await Parse.User.logIn('username', 'password');
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,4 +302,25 @@ describe('execution', () => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('can start Parse Server with auth via CLI', done => {
|
||||
const env = { ...process.env };
|
||||
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
|
||||
childProcess = spawn(
|
||||
binPath,
|
||||
['--databaseURI', databaseURI, './spec/configs/CLIConfigAuth.json'],
|
||||
{ env }
|
||||
);
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
console.log(data);
|
||||
if (data.includes('parse-server running on')) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
data = data.toString();
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,6 +103,14 @@ describe('Cloud Code', () => {
|
||||
expect(currentConfig.silent).toBeFalse();
|
||||
});
|
||||
|
||||
it('can get curent version', () => {
|
||||
const version = require('../package.json').version;
|
||||
const currentConfig = Config.get('test');
|
||||
expect(Parse.Server.version).toBeDefined();
|
||||
expect(currentConfig.version).toBeDefined();
|
||||
expect(Parse.Server.version).toEqual(version);
|
||||
});
|
||||
|
||||
it('show warning on duplicate cloud functions', done => {
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'warn').and.callFake(() => {});
|
||||
@@ -2390,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();
|
||||
|
||||
@@ -554,7 +554,7 @@ describe('DefinedSchemas', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not delete automatically classes', async () => {
|
||||
it('should not delete classes automatically', async () => {
|
||||
await reconfigureServer({
|
||||
schema: { definitions: [{ className: '_User' }, { className: 'Test' }] },
|
||||
});
|
||||
|
||||
@@ -1368,7 +1368,7 @@ describe('Parse.File testing', () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
enableForPublic: true,
|
||||
fileExtensions: ['jpg'],
|
||||
fileExtensions: ['jpg', 'wav'],
|
||||
},
|
||||
});
|
||||
await expectAsync(
|
||||
@@ -1387,6 +1387,30 @@ describe('Parse.File testing', () => {
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `File upload of extension html is disabled.`)
|
||||
);
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/files/file',
|
||||
body: JSON.stringify({
|
||||
_ApplicationId: 'test',
|
||||
_JavaScriptKey: 'test',
|
||||
_ContentType: 'image/jpg',
|
||||
base64: 'PGh0bWw+PC9odG1sPgo=',
|
||||
}),
|
||||
})
|
||||
).toBeResolved();
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/files/file',
|
||||
body: JSON.stringify({
|
||||
_ApplicationId: 'test',
|
||||
_JavaScriptKey: 'test',
|
||||
_ContentType: 'audio/wav',
|
||||
base64: 'UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA',
|
||||
}),
|
||||
})
|
||||
).toBeResolved();
|
||||
});
|
||||
|
||||
it('works with array without Content-Type', async () => {
|
||||
|
||||
11
spec/configs/CLIConfigAuth.json
Normal file
11
spec/configs/CLIConfigAuth.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"appName": "test",
|
||||
"appId": "test",
|
||||
"masterKey": "test",
|
||||
"logLevel": "error",
|
||||
"auth": {
|
||||
"facebook": {
|
||||
"appIds": "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ export class AuthAdapter {
|
||||
* Usage policy
|
||||
* @type {AuthPolicy}
|
||||
*/
|
||||
this.policy = 'default';
|
||||
if (!this.policy) {
|
||||
this.policy = 'default';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param appIds The specified app IDs in the configuration
|
||||
|
||||
@@ -9,6 +9,7 @@ const facebook = require('./facebook');
|
||||
const instagram = require('./instagram');
|
||||
const linkedin = require('./linkedin');
|
||||
const meetup = require('./meetup');
|
||||
import mfa from './mfa';
|
||||
const google = require('./google');
|
||||
const github = require('./github');
|
||||
const twitter = require('./twitter');
|
||||
@@ -44,6 +45,7 @@ const providers = {
|
||||
instagram,
|
||||
linkedin,
|
||||
meetup,
|
||||
mfa,
|
||||
google,
|
||||
github,
|
||||
twitter,
|
||||
@@ -75,7 +77,11 @@ function authDataValidator(provider, adapter, appIds, options) {
|
||||
if (appIds && typeof adapter.validateAppId === 'function') {
|
||||
await Promise.resolve(adapter.validateAppId(appIds, authData, options, requestObject));
|
||||
}
|
||||
if (adapter.policy && !authAdapterPolicies[adapter.policy]) {
|
||||
if (
|
||||
adapter.policy &&
|
||||
!authAdapterPolicies[adapter.policy] &&
|
||||
typeof adapter.policy !== 'function'
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OTHER_CAUSE,
|
||||
'AuthAdapter policy is not configured correctly. The value must be either "solo", "additional", "default" or undefined (will be handled as "default")'
|
||||
@@ -225,17 +231,20 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) {
|
||||
if (!authAdapter) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
adapter: { afterFind },
|
||||
providerOptions,
|
||||
} = authAdapter;
|
||||
const { adapter, providerOptions } = authAdapter;
|
||||
const afterFind = adapter.afterFind;
|
||||
if (afterFind && typeof afterFind === 'function') {
|
||||
const requestObject = {
|
||||
ip: req.config.ip,
|
||||
user: req.auth.user,
|
||||
master: req.auth.isMaster,
|
||||
};
|
||||
const result = afterFind(requestObject, authData[provider], providerOptions);
|
||||
const result = afterFind.call(
|
||||
adapter,
|
||||
requestObject,
|
||||
authData[provider],
|
||||
providerOptions
|
||||
);
|
||||
if (result) {
|
||||
authData[provider] = result;
|
||||
}
|
||||
|
||||
213
src/Adapters/Auth/mfa.js
Normal file
213
src/Adapters/Auth/mfa.js
Normal file
@@ -0,0 +1,213 @@
|
||||
import { TOTP, Secret } from 'otpauth';
|
||||
import { randomString } from '../../cryptoUtils';
|
||||
import AuthAdapter from './AuthAdapter';
|
||||
class MFAAdapter extends AuthAdapter {
|
||||
validateOptions(opts) {
|
||||
const validOptions = opts.options;
|
||||
if (!Array.isArray(validOptions)) {
|
||||
throw 'mfa.options must be an array';
|
||||
}
|
||||
this.sms = validOptions.includes('SMS');
|
||||
this.totp = validOptions.includes('TOTP');
|
||||
if (!this.sms && !this.totp) {
|
||||
throw 'mfa.options must include SMS or TOTP';
|
||||
}
|
||||
const digits = opts.digits || 6;
|
||||
const period = opts.period || 30;
|
||||
if (typeof digits !== 'number') {
|
||||
throw 'mfa.digits must be a number';
|
||||
}
|
||||
if (typeof period !== 'number') {
|
||||
throw 'mfa.period must be a number';
|
||||
}
|
||||
if (digits < 4 || digits > 10) {
|
||||
throw 'mfa.digits must be between 4 and 10';
|
||||
}
|
||||
if (period < 10) {
|
||||
throw 'mfa.period must be greater than 10';
|
||||
}
|
||||
const sendSMS = opts.sendSMS;
|
||||
if (this.sms && typeof sendSMS !== 'function') {
|
||||
throw 'mfa.sendSMS callback must be defined when using SMS OTPs';
|
||||
}
|
||||
this.smsCallback = sendSMS;
|
||||
this.digits = digits;
|
||||
this.period = period;
|
||||
this.algorithm = opts.algorithm || 'SHA1';
|
||||
}
|
||||
validateSetUp(mfaData) {
|
||||
if (mfaData.mobile && this.sms) {
|
||||
return this.setupMobileOTP(mfaData.mobile);
|
||||
}
|
||||
if (this.totp) {
|
||||
return this.setupTOTP(mfaData);
|
||||
}
|
||||
throw 'Invalid MFA data';
|
||||
}
|
||||
async validateLogin(loginData, _, req) {
|
||||
const saveResponse = {
|
||||
doNotSave: true,
|
||||
};
|
||||
const token = loginData.token;
|
||||
const auth = req.original.get('authData') || {};
|
||||
const { secret, recovery, mobile, token: saved, expiry } = auth.mfa || {};
|
||||
if (this.sms && mobile) {
|
||||
if (token === 'request') {
|
||||
const { token: sendToken, expiry } = await this.sendSMS(mobile);
|
||||
auth.mfa.token = sendToken;
|
||||
auth.mfa.expiry = expiry;
|
||||
req.object.set('authData', auth);
|
||||
await req.object.save(null, { useMasterKey: true });
|
||||
throw 'Please enter the token';
|
||||
}
|
||||
if (!saved || token !== saved) {
|
||||
throw 'Invalid MFA token 1';
|
||||
}
|
||||
if (new Date() > expiry) {
|
||||
throw 'Invalid MFA token 2';
|
||||
}
|
||||
delete auth.mfa.token;
|
||||
delete auth.mfa.expiry;
|
||||
return {
|
||||
save: auth.mfa,
|
||||
};
|
||||
}
|
||||
if (this.totp) {
|
||||
if (typeof token !== 'string') {
|
||||
throw 'Invalid MFA token';
|
||||
}
|
||||
if (!secret) {
|
||||
return saveResponse;
|
||||
}
|
||||
if (recovery[0] === token || recovery[1] === token) {
|
||||
return saveResponse;
|
||||
}
|
||||
const totp = new TOTP({
|
||||
algorithm: this.algorithm,
|
||||
digits: this.digits,
|
||||
period: this.period,
|
||||
secret: Secret.fromBase32(secret),
|
||||
});
|
||||
const valid = totp.validate({
|
||||
token,
|
||||
});
|
||||
if (valid === null) {
|
||||
throw 'Invalid MFA token';
|
||||
}
|
||||
}
|
||||
return saveResponse;
|
||||
}
|
||||
async validateUpdate(authData, _, req) {
|
||||
if (req.master) {
|
||||
return;
|
||||
}
|
||||
if (authData.mobile && this.sms) {
|
||||
if (!authData.token) {
|
||||
throw 'MFA is already set up on this account';
|
||||
}
|
||||
return this.confirmSMSOTP(authData, req.original.get('authData')?.mfa || {});
|
||||
}
|
||||
if (this.totp) {
|
||||
await this.validateLogin({ token: authData.old }, null, req);
|
||||
return this.validateSetUp(authData);
|
||||
}
|
||||
throw 'Invalid MFA data';
|
||||
}
|
||||
afterFind(req, authData) {
|
||||
if (req.master) {
|
||||
return;
|
||||
}
|
||||
if (this.totp && authData.secret) {
|
||||
return {
|
||||
status: 'enabled',
|
||||
};
|
||||
}
|
||||
if (this.sms && authData.mobile) {
|
||||
return {
|
||||
status: 'enabled',
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 'disabled',
|
||||
};
|
||||
}
|
||||
|
||||
policy(req, auth) {
|
||||
if (this.sms && auth?.pending && Object.keys(auth).length === 1) {
|
||||
return 'default';
|
||||
}
|
||||
return 'additional';
|
||||
}
|
||||
|
||||
async setupMobileOTP(mobile) {
|
||||
const { token, expiry } = await this.sendSMS(mobile);
|
||||
return {
|
||||
save: {
|
||||
pending: {
|
||||
[mobile]: {
|
||||
token,
|
||||
expiry,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async sendSMS(mobile) {
|
||||
if (!/^[+]*[(]{0,1}[0-9]{1,3}[)]{0,1}[-\s\./0-9]*$/g.test(mobile)) {
|
||||
throw 'Invalid mobile number.';
|
||||
}
|
||||
let token = '';
|
||||
while (token.length < this.digits) {
|
||||
token += randomString(10).replace(/\D/g, '');
|
||||
}
|
||||
token = token.substring(0, this.digits);
|
||||
await Promise.resolve(this.smsCallback(token, mobile));
|
||||
const expiry = new Date(new Date().getTime() + this.period * 1000);
|
||||
return { token, expiry };
|
||||
}
|
||||
|
||||
async confirmSMSOTP(inputData, authData) {
|
||||
const { mobile, token } = inputData;
|
||||
if (!authData.pending?.[mobile]) {
|
||||
throw 'This number is not pending';
|
||||
}
|
||||
const pendingData = authData.pending[mobile];
|
||||
if (token !== pendingData.token) {
|
||||
throw 'Invalid MFA token';
|
||||
}
|
||||
if (new Date() > pendingData.expiry) {
|
||||
throw 'Invalid MFA token';
|
||||
}
|
||||
delete authData.pending[mobile];
|
||||
authData.mobile = mobile;
|
||||
return {
|
||||
save: authData,
|
||||
};
|
||||
}
|
||||
|
||||
setupTOTP(mfaData) {
|
||||
const { secret, token } = mfaData;
|
||||
if (!secret || !token || secret.length < 20) {
|
||||
throw 'Invalid MFA data';
|
||||
}
|
||||
const totp = new TOTP({
|
||||
algorithm: this.algorithm,
|
||||
digits: this.digits,
|
||||
period: this.period,
|
||||
secret: Secret.fromBase32(secret),
|
||||
});
|
||||
const valid = totp.validate({
|
||||
token,
|
||||
});
|
||||
if (valid === null) {
|
||||
throw 'Invalid MFA token';
|
||||
}
|
||||
const recovery = [randomString(30), randomString(30)];
|
||||
return {
|
||||
response: { recovery: recovery.join(', ') },
|
||||
save: { secret, recovery },
|
||||
};
|
||||
}
|
||||
}
|
||||
export default new MFAAdapter();
|
||||
@@ -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() {
|
||||
|
||||
22
src/Auth.js
22
src/Auth.js
@@ -438,6 +438,7 @@ const hasMutatedAuthData = (authData, userAuthData) => {
|
||||
};
|
||||
|
||||
const checkIfUserHasProvidedConfiguredProvidersForLogin = (
|
||||
req = {},
|
||||
authData = {},
|
||||
userAuthData = {},
|
||||
config
|
||||
@@ -461,7 +462,16 @@ const checkIfUserHasProvidedConfiguredProvidersForLogin = (
|
||||
|
||||
const additionProvidersNotFound = [];
|
||||
const hasProvidedAtLeastOneAdditionalProvider = savedUserProviders.some(provider => {
|
||||
if (provider && provider.adapter && provider.adapter.policy === 'additional') {
|
||||
let policy = provider.adapter.policy;
|
||||
if (typeof policy === 'function') {
|
||||
const requestObject = {
|
||||
ip: req.config.ip,
|
||||
user: req.auth.user,
|
||||
master: req.auth.isMaster,
|
||||
};
|
||||
policy = policy.call(provider.adapter, requestObject, userAuthData[provider.name]);
|
||||
}
|
||||
if (policy === 'additional') {
|
||||
if (authData[provider.name]) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -498,14 +508,8 @@ const handleAuthDataValidation = async (authData, req, foundUser) => {
|
||||
await user.fetch({ useMasterKey: true });
|
||||
}
|
||||
|
||||
const { originalObject, updatedObject } = req.buildParseObjects();
|
||||
const requestObject = getRequestObject(
|
||||
undefined,
|
||||
req.auth,
|
||||
updatedObject,
|
||||
originalObject || user,
|
||||
req.config
|
||||
);
|
||||
const { updatedObject } = req.buildParseObjects();
|
||||
const requestObject = getRequestObject(undefined, req.auth, updatedObject, user, req.config);
|
||||
// Perform validation as step-by-step pipeline for better error consistency
|
||||
// and also to avoid to trigger a provider (like OTP SMS) if another one fails
|
||||
const acc = { authData: {}, authDataResponse: {} };
|
||||
|
||||
@@ -7,6 +7,7 @@ import net from 'net';
|
||||
import AppCache from './cache';
|
||||
import DatabaseController from './Controllers/DatabaseController';
|
||||
import { logLevels as validLogLevels } from './Controllers/LoggerController';
|
||||
import { version } from '../package.json';
|
||||
import {
|
||||
AccountLockoutOptions,
|
||||
DatabaseOptions,
|
||||
@@ -50,6 +51,7 @@ export class Config {
|
||||
config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind(
|
||||
config
|
||||
);
|
||||
config.version = version;
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -1716,12 +1716,6 @@ class DatabaseController {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
||||
.catch(error => {
|
||||
logger.warn('Unable to create case insensitive username index: ', error);
|
||||
throw error;
|
||||
});
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
||||
.catch(error => {
|
||||
|
||||
@@ -103,7 +103,6 @@ module.exports.ParseServerOptions = {
|
||||
env: 'PARSE_SERVER_AUTH_PROVIDERS',
|
||||
help:
|
||||
'Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication',
|
||||
action: parsers.arrayParser,
|
||||
},
|
||||
cacheAdapter: {
|
||||
env: 'PARSE_SERVER_CACHE_ADAPTER',
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* @property {Adapter<AnalyticsAdapter>} analyticsAdapter Adapter module for the analytics
|
||||
* @property {String} appId Your Parse Application ID
|
||||
* @property {String} appName Sets the app name
|
||||
* @property {AuthAdapter[]} auth Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
|
||||
* @property {Object} auth Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
|
||||
* @property {Adapter<CacheAdapter>} cacheAdapter Adapter module for the cache
|
||||
* @property {Number} cacheMaxSize Sets the maximum size for the in memory cache, defaults to 10000
|
||||
* @property {Number} cacheTTL Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)
|
||||
|
||||
@@ -149,7 +149,7 @@ export interface ParseServerOptions {
|
||||
allowCustomObjectId: ?boolean;
|
||||
/* Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
|
||||
:ENV: PARSE_SERVER_AUTH_PROVIDERS */
|
||||
auth: ?(AuthAdapter[]);
|
||||
auth: ?{ [string]: AuthAdapter };
|
||||
/* Max file size for uploads, defaults to 20mb
|
||||
:DEFAULT: 20mb */
|
||||
maxUploadSize: ?string;
|
||||
|
||||
@@ -1066,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) {
|
||||
|
||||
@@ -558,6 +558,7 @@ RestWrite.prototype.handleAuthData = async function (authData) {
|
||||
// we need to be sure that the user has provided
|
||||
// required authData
|
||||
Auth.checkIfUserHasProvidedConfiguredProvidersForLogin(
|
||||
{ config: this.config, auth: this.auth },
|
||||
authData,
|
||||
userResult.authData,
|
||||
this.config
|
||||
|
||||
@@ -147,7 +147,7 @@ export class FilesRouter {
|
||||
if (ext === '*') {
|
||||
return true;
|
||||
}
|
||||
const regex = new RegExp(fileExtensions);
|
||||
const regex = new RegExp(ext);
|
||||
if (regex.test(extension)) {
|
||||
return true;
|
||||
}
|
||||
@@ -176,6 +176,7 @@ export class FilesRouter {
|
||||
const file = new Parse.File(filename, { base64 }, contentType);
|
||||
const { metadata = {}, tags = {} } = req.fileData || {};
|
||||
try {
|
||||
// Scan request data for denied keywords
|
||||
Utils.checkProhibitedKeywords(config, metadata);
|
||||
Utils.checkProhibitedKeywords(config, tags);
|
||||
} catch (error) {
|
||||
|
||||
@@ -189,7 +189,12 @@ export class UsersRouter extends ClassesRouter {
|
||||
const user = await this._authenticateUserFromRequest(req);
|
||||
const authData = req.body && req.body.authData;
|
||||
// Check if user has provided their required auth providers
|
||||
Auth.checkIfUserHasProvidedConfiguredProvidersForLogin(authData, user.authData, req.config);
|
||||
Auth.checkIfUserHasProvidedConfiguredProvidersForLogin(
|
||||
req,
|
||||
authData,
|
||||
user.authData,
|
||||
req.config
|
||||
);
|
||||
|
||||
let authDataResponse;
|
||||
let validatedAuthData;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -7,7 +7,6 @@ import LRUCacheAdapter from './Adapters/Cache/LRUCache.js';
|
||||
import * as TestUtils from './TestUtils';
|
||||
import * as SchemaMigrations from './SchemaMigrations/Migrations';
|
||||
import AuthAdapter from './Adapters/Auth/AuthAdapter';
|
||||
|
||||
import { useExternal } from './deprecated';
|
||||
import { getLogger } from './logger';
|
||||
import { PushWorker } from './Push/PushWorker';
|
||||
|
||||
Reference in New Issue
Block a user