build: Release (#8749)

This commit is contained in:
Manuel
2023-09-16 04:51:10 +02:00
committed by GitHub
26 changed files with 857 additions and 182 deletions

View File

@@ -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."

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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();
});
});

View File

@@ -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());
});
});
});

View File

@@ -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();

View File

@@ -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' }] },
});

View File

@@ -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 () => {

View File

@@ -0,0 +1,11 @@
{
"appName": "test",
"appId": "test",
"masterKey": "test",
"logLevel": "error",
"auth": {
"facebook": {
"appIds": "test"
}
}
}

View File

@@ -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

View File

@@ -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
View 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();

View File

@@ -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() {

View File

@@ -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: {} };

View File

@@ -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;
}

View File

@@ -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 => {

View File

@@ -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',

View File

@@ -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)

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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';