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 | - |
|
| 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 | - |
|
| 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 | - |
|
| 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_deprecation]: ## "The version and date of the deprecation."
|
||||||
[i_removal]: ## "The version and date of the planned removal."
|
[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)
|
# [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",
|
"name": "parse-server",
|
||||||
"version": "6.3.0",
|
"version": "6.3.0-alpha.9",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "6.3.0",
|
"version": "6.3.0-alpha.9",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"@graphql-tools/utils": "8.12.0",
|
"@graphql-tools/utils": "8.12.0",
|
||||||
"@graphql-yoga/node": "2.6.0",
|
"@graphql-yoga/node": "2.6.0",
|
||||||
"@parse/fs-files-adapter": "1.2.2",
|
"@parse/fs-files-adapter": "1.2.2",
|
||||||
"@parse/push-adapter": "4.1.3",
|
"@parse/push-adapter": "4.2.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"commander": "10.0.1",
|
"commander": "10.0.1",
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"mongodb": "4.10.0",
|
"mongodb": "4.10.0",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
|
"otpauth": "9.1.2",
|
||||||
"parse": "4.1.0",
|
"parse": "4.1.0",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pg-monitor": "2.0.0",
|
"pg-monitor": "2.0.0",
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
"pluralize": "8.0.0",
|
"pluralize": "8.0.0",
|
||||||
"rate-limit-redis": "3.0.2",
|
"rate-limit-redis": "3.0.2",
|
||||||
"redis": "4.6.6",
|
"redis": "4.6.6",
|
||||||
"semver": "^7.5.2",
|
"semver": "7.5.2",
|
||||||
"subscriptions-transport-ws": "0.11.0",
|
"subscriptions-transport-ws": "0.11.0",
|
||||||
"tv4": "1.3.0",
|
"tv4": "1.3.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
@@ -2730,13 +2731,13 @@
|
|||||||
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
||||||
},
|
},
|
||||||
"node_modules/@parse/node-apn": {
|
"node_modules/@parse/node-apn": {
|
||||||
"version": "5.1.3",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.2.1.tgz",
|
||||||
"integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==",
|
"integrity": "sha512-dwVCDv+G9YV01Ad1XslWQImnmfFDSnaNwxI4l+vuCjL+DbjsCl6DuV4nMqZpEZOpViAY0pGCRHBKUygsf+aAGg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "4.3.3",
|
"debug": "4.3.3",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "9.0.0",
|
||||||
"node-forge": "1.3.0",
|
"node-forge": "1.3.1",
|
||||||
"verror": "1.10.1"
|
"verror": "1.10.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"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": {
|
"node_modules/@parse/node-gcm": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@parse/node-gcm/-/node-gcm-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@parse/node-gcm/-/node-gcm-1.0.2.tgz",
|
||||||
@@ -2810,11 +2782,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@parse/push-adapter": {
|
"node_modules/@parse/push-adapter": {
|
||||||
"version": "4.1.3",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.2.0.tgz",
|
||||||
"integrity": "sha512-Oy53ag7DpUva5dUWwP6tNEsrxv2xU9QIk+rb84q1DIm1qVgo2yl4oXcZ3FPG2Ks/NYURbv4w+z9oaSgVfyBRfQ==",
|
"integrity": "sha512-M6D9qk4KE9bJ2lMufTvgGmKOvsbj20lFhzg0kQRmHU10ootKt4XcL+QJRSTu/BmlRbIVZMGQEZ61UyUumWTOiQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parse/node-apn": "5.1.3",
|
"@parse/node-apn": "5.2.1",
|
||||||
"@parse/node-gcm": "1.0.2",
|
"@parse/node-gcm": "1.0.2",
|
||||||
"npmlog": "4.1.2",
|
"npmlog": "4.1.2",
|
||||||
"parse": "3.4.0"
|
"parse": "3.4.0"
|
||||||
@@ -10220,6 +10192,14 @@
|
|||||||
"extsprintf": "^1.2.0"
|
"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": {
|
"node_modules/jwa": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
@@ -10791,41 +10771,23 @@
|
|||||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/lodash.ismatch": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||||
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"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": {
|
"node_modules/lodash.isstring": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
"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": {
|
"node_modules/lodash.map": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
@@ -10838,11 +10800,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
"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": {
|
"node_modules/lodash.pad": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
||||||
@@ -12484,9 +12441,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
|
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.13.0"
|
"node": ">= 6.13.0"
|
||||||
}
|
}
|
||||||
@@ -15940,6 +15897,17 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/p-cancelable": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||||
@@ -20417,9 +20385,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -22584,13 +22552,13 @@
|
|||||||
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
"integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g=="
|
||||||
},
|
},
|
||||||
"@parse/node-apn": {
|
"@parse/node-apn": {
|
||||||
"version": "5.1.3",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.2.1.tgz",
|
||||||
"integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==",
|
"integrity": "sha512-dwVCDv+G9YV01Ad1XslWQImnmfFDSnaNwxI4l+vuCjL+DbjsCl6DuV4nMqZpEZOpViAY0pGCRHBKUygsf+aAGg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "4.3.3",
|
"debug": "4.3.3",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "9.0.0",
|
||||||
"node-forge": "1.3.0",
|
"node-forge": "1.3.1",
|
||||||
"verror": "1.10.1"
|
"verror": "1.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -22601,28 +22569,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.1.2"
|
"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": {
|
"@parse/push-adapter": {
|
||||||
"version": "4.1.3",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.2.0.tgz",
|
||||||
"integrity": "sha512-Oy53ag7DpUva5dUWwP6tNEsrxv2xU9QIk+rb84q1DIm1qVgo2yl4oXcZ3FPG2Ks/NYURbv4w+z9oaSgVfyBRfQ==",
|
"integrity": "sha512-M6D9qk4KE9bJ2lMufTvgGmKOvsbj20lFhzg0kQRmHU10ootKt4XcL+QJRSTu/BmlRbIVZMGQEZ61UyUumWTOiQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@parse/node-apn": "5.1.3",
|
"@parse/node-apn": "5.2.1",
|
||||||
"@parse/node-gcm": "1.0.2",
|
"@parse/node-gcm": "1.0.2",
|
||||||
"npmlog": "4.1.2",
|
"npmlog": "4.1.2",
|
||||||
"parse": "3.4.0"
|
"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": {
|
"jwa": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
@@ -28860,41 +28811,23 @@
|
|||||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||||
"dev": true
|
"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": {
|
"lodash.ismatch": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||||
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
"integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
|
||||||
"dev": true
|
"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": {
|
"lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"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": {
|
"lodash.isstring": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
"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": {
|
"lodash.map": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
@@ -28907,11 +28840,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
"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": {
|
"lodash.pad": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
||||||
@@ -30206,9 +30134,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA=="
|
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
|
||||||
},
|
},
|
||||||
"node-netstat": {
|
"node-netstat": {
|
||||||
"version": "1.8.0",
|
"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": {
|
"p-cancelable": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||||
@@ -36167,9 +36103,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
|
||||||
},
|
},
|
||||||
"wordwrap": {
|
"wordwrap": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "6.3.0",
|
"version": "6.3.0-alpha.9",
|
||||||
"description": "An express module providing a Parse-compatible API server",
|
"description": "An express module providing a Parse-compatible API server",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@graphql-tools/utils": "8.12.0",
|
"@graphql-tools/utils": "8.12.0",
|
||||||
"@graphql-yoga/node": "2.6.0",
|
"@graphql-yoga/node": "2.6.0",
|
||||||
"@parse/fs-files-adapter": "1.2.2",
|
"@parse/fs-files-adapter": "1.2.2",
|
||||||
"@parse/push-adapter": "4.1.3",
|
"@parse/push-adapter": "4.2.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"commander": "10.0.1",
|
"commander": "10.0.1",
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"mongodb": "4.10.0",
|
"mongodb": "4.10.0",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
|
"otpauth": "9.1.2",
|
||||||
"parse": "4.1.0",
|
"parse": "4.1.0",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pg-monitor": "2.0.0",
|
"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());
|
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();
|
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 => {
|
it('show warning on duplicate cloud functions', done => {
|
||||||
const logger = require('../lib/logger').logger;
|
const logger = require('../lib/logger').logger;
|
||||||
spyOn(logger, 'warn').and.callFake(() => {});
|
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 => {
|
it('should have request headers', done => {
|
||||||
Parse.Cloud.beforeFind('MyObject', req => {
|
Parse.Cloud.beforeFind('MyObject', req => {
|
||||||
expect(req.headers).toBeDefined();
|
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({
|
await reconfigureServer({
|
||||||
schema: { definitions: [{ className: '_User' }, { className: 'Test' }] },
|
schema: { definitions: [{ className: '_User' }, { className: 'Test' }] },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1368,7 +1368,7 @@ describe('Parse.File testing', () => {
|
|||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
fileUpload: {
|
fileUpload: {
|
||||||
enableForPublic: true,
|
enableForPublic: true,
|
||||||
fileExtensions: ['jpg'],
|
fileExtensions: ['jpg', 'wav'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expectAsync(
|
await expectAsync(
|
||||||
@@ -1387,6 +1387,30 @@ describe('Parse.File testing', () => {
|
|||||||
).toBeRejectedWith(
|
).toBeRejectedWith(
|
||||||
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `File upload of extension html is disabled.`)
|
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 () => {
|
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
|
* Usage policy
|
||||||
* @type {AuthPolicy}
|
* @type {AuthPolicy}
|
||||||
*/
|
*/
|
||||||
this.policy = 'default';
|
if (!this.policy) {
|
||||||
|
this.policy = 'default';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param appIds The specified app IDs in the configuration
|
* @param appIds The specified app IDs in the configuration
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const facebook = require('./facebook');
|
|||||||
const instagram = require('./instagram');
|
const instagram = require('./instagram');
|
||||||
const linkedin = require('./linkedin');
|
const linkedin = require('./linkedin');
|
||||||
const meetup = require('./meetup');
|
const meetup = require('./meetup');
|
||||||
|
import mfa from './mfa';
|
||||||
const google = require('./google');
|
const google = require('./google');
|
||||||
const github = require('./github');
|
const github = require('./github');
|
||||||
const twitter = require('./twitter');
|
const twitter = require('./twitter');
|
||||||
@@ -44,6 +45,7 @@ const providers = {
|
|||||||
instagram,
|
instagram,
|
||||||
linkedin,
|
linkedin,
|
||||||
meetup,
|
meetup,
|
||||||
|
mfa,
|
||||||
google,
|
google,
|
||||||
github,
|
github,
|
||||||
twitter,
|
twitter,
|
||||||
@@ -75,7 +77,11 @@ function authDataValidator(provider, adapter, appIds, options) {
|
|||||||
if (appIds && typeof adapter.validateAppId === 'function') {
|
if (appIds && typeof adapter.validateAppId === 'function') {
|
||||||
await Promise.resolve(adapter.validateAppId(appIds, authData, options, requestObject));
|
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(
|
throw new Parse.Error(
|
||||||
Parse.Error.OTHER_CAUSE,
|
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")'
|
'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) {
|
if (!authAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {
|
const { adapter, providerOptions } = authAdapter;
|
||||||
adapter: { afterFind },
|
const afterFind = adapter.afterFind;
|
||||||
providerOptions,
|
|
||||||
} = authAdapter;
|
|
||||||
if (afterFind && typeof afterFind === 'function') {
|
if (afterFind && typeof afterFind === 'function') {
|
||||||
const requestObject = {
|
const requestObject = {
|
||||||
ip: req.config.ip,
|
ip: req.config.ip,
|
||||||
user: req.auth.user,
|
user: req.auth.user,
|
||||||
master: req.auth.isMaster,
|
master: req.auth.isMaster,
|
||||||
};
|
};
|
||||||
const result = afterFind(requestObject, authData[provider], providerOptions);
|
const result = afterFind.call(
|
||||||
|
adapter,
|
||||||
|
requestObject,
|
||||||
|
authData[provider],
|
||||||
|
providerOptions
|
||||||
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
authData[provider] = 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.ttl = isValidTTL(ttl) ? ttl : DEFAULT_REDIS_TTL;
|
||||||
this.client = createClient(redisCtx);
|
this.client = createClient(redisCtx);
|
||||||
this.queue = new KeyPromiseQueue();
|
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() {
|
async connect() {
|
||||||
|
|||||||
22
src/Auth.js
22
src/Auth.js
@@ -438,6 +438,7 @@ const hasMutatedAuthData = (authData, userAuthData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkIfUserHasProvidedConfiguredProvidersForLogin = (
|
const checkIfUserHasProvidedConfiguredProvidersForLogin = (
|
||||||
|
req = {},
|
||||||
authData = {},
|
authData = {},
|
||||||
userAuthData = {},
|
userAuthData = {},
|
||||||
config
|
config
|
||||||
@@ -461,7 +462,16 @@ const checkIfUserHasProvidedConfiguredProvidersForLogin = (
|
|||||||
|
|
||||||
const additionProvidersNotFound = [];
|
const additionProvidersNotFound = [];
|
||||||
const hasProvidedAtLeastOneAdditionalProvider = savedUserProviders.some(provider => {
|
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]) {
|
if (authData[provider.name]) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -498,14 +508,8 @@ const handleAuthDataValidation = async (authData, req, foundUser) => {
|
|||||||
await user.fetch({ useMasterKey: true });
|
await user.fetch({ useMasterKey: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { originalObject, updatedObject } = req.buildParseObjects();
|
const { updatedObject } = req.buildParseObjects();
|
||||||
const requestObject = getRequestObject(
|
const requestObject = getRequestObject(undefined, req.auth, updatedObject, user, req.config);
|
||||||
undefined,
|
|
||||||
req.auth,
|
|
||||||
updatedObject,
|
|
||||||
originalObject || user,
|
|
||||||
req.config
|
|
||||||
);
|
|
||||||
// Perform validation as step-by-step pipeline for better error consistency
|
// 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
|
// and also to avoid to trigger a provider (like OTP SMS) if another one fails
|
||||||
const acc = { authData: {}, authDataResponse: {} };
|
const acc = { authData: {}, authDataResponse: {} };
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import net from 'net';
|
|||||||
import AppCache from './cache';
|
import AppCache from './cache';
|
||||||
import DatabaseController from './Controllers/DatabaseController';
|
import DatabaseController from './Controllers/DatabaseController';
|
||||||
import { logLevels as validLogLevels } from './Controllers/LoggerController';
|
import { logLevels as validLogLevels } from './Controllers/LoggerController';
|
||||||
|
import { version } from '../package.json';
|
||||||
import {
|
import {
|
||||||
AccountLockoutOptions,
|
AccountLockoutOptions,
|
||||||
DatabaseOptions,
|
DatabaseOptions,
|
||||||
@@ -50,6 +51,7 @@ export class Config {
|
|||||||
config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind(
|
config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind(
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
|
config.version = version;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1716,12 +1716,6 @@ class DatabaseController {
|
|||||||
throw error;
|
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
|
await this.adapter
|
||||||
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ module.exports.ParseServerOptions = {
|
|||||||
env: 'PARSE_SERVER_AUTH_PROVIDERS',
|
env: 'PARSE_SERVER_AUTH_PROVIDERS',
|
||||||
help:
|
help:
|
||||||
'Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication',
|
'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: {
|
cacheAdapter: {
|
||||||
env: 'PARSE_SERVER_CACHE_ADAPTER',
|
env: 'PARSE_SERVER_CACHE_ADAPTER',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
* @property {Adapter<AnalyticsAdapter>} analyticsAdapter Adapter module for the analytics
|
* @property {Adapter<AnalyticsAdapter>} analyticsAdapter Adapter module for the analytics
|
||||||
* @property {String} appId Your Parse Application ID
|
* @property {String} appId Your Parse Application ID
|
||||||
* @property {String} appName Sets the app name
|
* @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 {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} 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)
|
* @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;
|
allowCustomObjectId: ?boolean;
|
||||||
/* Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
|
/* 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 */
|
:ENV: PARSE_SERVER_AUTH_PROVIDERS */
|
||||||
auth: ?(AuthAdapter[]);
|
auth: ?{ [string]: AuthAdapter };
|
||||||
/* Max file size for uploads, defaults to 20mb
|
/* Max file size for uploads, defaults to 20mb
|
||||||
:DEFAULT: 20mb */
|
:DEFAULT: 20mb */
|
||||||
maxUploadSize: ?string;
|
maxUploadSize: ?string;
|
||||||
|
|||||||
@@ -1066,11 +1066,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
|||||||
// Returns a list of pointers in REST format.
|
// Returns a list of pointers in REST format.
|
||||||
function findPointers(object, path) {
|
function findPointers(object, path) {
|
||||||
if (object instanceof Array) {
|
if (object instanceof Array) {
|
||||||
var answer = [];
|
return object.map(x => findPointers(x, path)).flat();
|
||||||
for (var x of object) {
|
|
||||||
answer = answer.concat(findPointers(x, path));
|
|
||||||
}
|
|
||||||
return answer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof object !== 'object' || !object) {
|
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
|
// we need to be sure that the user has provided
|
||||||
// required authData
|
// required authData
|
||||||
Auth.checkIfUserHasProvidedConfiguredProvidersForLogin(
|
Auth.checkIfUserHasProvidedConfiguredProvidersForLogin(
|
||||||
|
{ config: this.config, auth: this.auth },
|
||||||
authData,
|
authData,
|
||||||
userResult.authData,
|
userResult.authData,
|
||||||
this.config
|
this.config
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export class FilesRouter {
|
|||||||
if (ext === '*') {
|
if (ext === '*') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const regex = new RegExp(fileExtensions);
|
const regex = new RegExp(ext);
|
||||||
if (regex.test(extension)) {
|
if (regex.test(extension)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -176,6 +176,7 @@ export class FilesRouter {
|
|||||||
const file = new Parse.File(filename, { base64 }, contentType);
|
const file = new Parse.File(filename, { base64 }, contentType);
|
||||||
const { metadata = {}, tags = {} } = req.fileData || {};
|
const { metadata = {}, tags = {} } = req.fileData || {};
|
||||||
try {
|
try {
|
||||||
|
// Scan request data for denied keywords
|
||||||
Utils.checkProhibitedKeywords(config, metadata);
|
Utils.checkProhibitedKeywords(config, metadata);
|
||||||
Utils.checkProhibitedKeywords(config, tags);
|
Utils.checkProhibitedKeywords(config, tags);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -189,7 +189,12 @@ export class UsersRouter extends ClassesRouter {
|
|||||||
const user = await this._authenticateUserFromRequest(req);
|
const user = await this._authenticateUserFromRequest(req);
|
||||||
const authData = req.body && req.body.authData;
|
const authData = req.body && req.body.authData;
|
||||||
// Check if user has provided their required auth providers
|
// 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 authDataResponse;
|
||||||
let validatedAuthData;
|
let validatedAuthData;
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import program from './commander';
|
import program from './commander';
|
||||||
|
|
||||||
function logStartupOptions(options) {
|
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) {
|
for (const key in options) {
|
||||||
let value = options[key];
|
let value = options[key];
|
||||||
if (key == 'masterKey') {
|
if (keysToRedact.includes(key)) {
|
||||||
value = '***REDACTED***';
|
value = '<REDACTED>';
|
||||||
}
|
|
||||||
if (key == 'push' && options.verbose != true) {
|
|
||||||
value = '***REDACTED***';
|
|
||||||
}
|
}
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import LRUCacheAdapter from './Adapters/Cache/LRUCache.js';
|
|||||||
import * as TestUtils from './TestUtils';
|
import * as TestUtils from './TestUtils';
|
||||||
import * as SchemaMigrations from './SchemaMigrations/Migrations';
|
import * as SchemaMigrations from './SchemaMigrations/Migrations';
|
||||||
import AuthAdapter from './Adapters/Auth/AuthAdapter';
|
import AuthAdapter from './Adapters/Auth/AuthAdapter';
|
||||||
|
|
||||||
import { useExternal } from './deprecated';
|
import { useExternal } from './deprecated';
|
||||||
import { getLogger } from './logger';
|
import { getLogger } from './logger';
|
||||||
import { PushWorker } from './Push/PushWorker';
|
import { PushWorker } from './Push/PushWorker';
|
||||||
|
|||||||
Reference in New Issue
Block a user