Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into feat/growi-bot

Yuki Takei 4 лет назад
Родитель
Сommit
c6030e96a4
45 измененных файлов с 702 добавлено и 465 удалено
  1. 31 2
      CHANGES.md
  2. 2 0
      config/env.dev.js
  3. 1 1
      package.json
  4. 10 8
      packages/app-for-hoisting/package.json
  5. 10 8
      packages/app/package.json
  6. 30 11
      resource/certs/localhost/cert.pem
  7. 0 11
      resource/certs/localhost/csr.pem
  8. 52 15
      resource/certs/localhost/key.pem
  9. 2 1
      resource/locales/en_US/meta.json
  10. 7 3
      resource/locales/en_US/translation.json
  11. 2 1
      resource/locales/ja_JP/meta.json
  12. 7 2
      resource/locales/ja_JP/translation.json
  13. 3 2
      resource/locales/zh_CN/meta.json
  14. 7 3
      resource/locales/zh_CN/translation.json
  15. 21 3
      src/client/js/components/Admin/Common/AdminNavigation.jsx
  16. 4 4
      src/client/js/components/Admin/ImportData/ImportDataPageContents.jsx
  17. 32 9
      src/client/js/components/Admin/Security/SecuritySetting.jsx
  18. 5 1
      src/client/js/components/InstallerForm.jsx
  19. 4 4
      src/client/js/components/PageCreateModal.jsx
  20. 10 0
      src/client/js/services/AdminGeneralSecurityContainer.js
  21. 24 4
      src/client/js/util/i18n.js
  22. 20 1
      src/lib/service/xss/index.js
  23. 33 0
      src/lib/util/path-utils.js
  24. 2 0
      src/server/crowi/express-init.js
  25. 5 5
      src/server/crowi/index.js
  26. 1 1
      src/server/form/invited.js
  27. 1 1
      src/server/form/register.js
  28. 1 1
      src/server/middlewares/access-token-parser.js
  29. 2 0
      src/server/models/config.js
  30. 0 27
      src/server/models/page.js
  31. 5 0
      src/server/routes/admin.js
  32. 2 1
      src/server/routes/apiv3/pages.js
  33. 7 3
      src/server/routes/apiv3/security-setting.js
  34. 61 9
      src/server/routes/apiv3/share-links.js
  35. 2 0
      src/server/routes/index.js
  36. 2 2
      src/server/routes/installer.js
  37. 2 121
      src/server/routes/page.js
  38. 10 0
      src/server/service/config-loader.js
  39. 3 1
      src/server/util/createGrowiPagesFromImports.js
  40. 7 0
      src/server/views/admin/not_found.html
  41. 1 1
      src/server/views/widget/alert_siteurl_undefined.html
  42. 1 1
      src/server/views/widget/page_content.html
  43. 0 50
      src/test/models/page.test.js
  44. 51 1
      src/test/util/path-utils.test.js
  45. 219 146
      yarn.lock

+ 31 - 2
CHANGES.md

@@ -1,8 +1,37 @@
 # CHANGES
 # CHANGES
 
 
-## v4.2.18-RC
+## v4.2.21-RC
 
 
-* Feature: Cobvertible page contents width.
+* Improvement: Upgrade mongodb driver to fix [NODE-2784](https://jira.mongodb.org/browse/NODE-2784)
+* Support: Upgrade libs
+    * connect-mongo
+    * migrate-mongo
+    * mongoose
+    * stream-to-promise
+
+
+## v4.2.20
+
+* Improvement: Error message when the password is too short
+* Improvement: Repeat XSS processing as a countermeasure against nesting 
+* Fix: NoSQL injection of access-token-parser
+* Fix: Checking permission when operating share links
+* Fix: Invalid NaN label is shown when deletedAt of the page is undefined
+    * Introduced by v4.2.8
+
+## v4.2.19
+
+* Feature: Set max-age of the user's cookie with the env var `SESSION_MAX_AGE`
+* Feature: Set max-age of the user's cookie in admin page
+* Improvement: Change the first accessing page after installation to the top page
+* Support: Upgrade libs
+    * string-width
+    * diff
+    * archiver
+
+## v4.2.18
+
+* Feature: Convertible page contents width
 * Fix: Group selector of User Group Delete Modal does not show all groups
 * Fix: Group selector of User Group Delete Modal does not show all groups
 * Fix: Global notification to Slack does not encode spaces of page path
 * Fix: Global notification to Slack does not encode spaces of page path
 * Support: Upgrade libs
 * Support: Upgrade libs

+ 2 - 0
config/env.dev.js

@@ -26,4 +26,6 @@ module.exports = {
   // SLACK_BOT_TOKEN: '',
   // SLACK_BOT_TOKEN: '',
   SALT_FOR_GTOP_TOKEN: 'proxy',
   SALT_FOR_GTOP_TOKEN: 'proxy',
   SALT_FOR_PTOG_TOKEN: 'growi',
   SALT_FOR_PTOG_TOKEN: 'growi',
+  // GROWI_CLOUD_URI: 'http://growi.cloud',
+  // GROWI_APP_ID_FOR_GROWI_CLOUD: '012345',
 };
 };

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "4.2.18-RC",
+  "version": "4.2.21-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

+ 10 - 8
packages/app-for-hoisting/package.json

@@ -4,7 +4,8 @@
   "license": "MIT",
   "license": "MIT",
   "scripts": {},
   "scripts": {},
   "// comments for dependencies": {
   "// comments for dependencies": {
-    "openid-client": "Node.js 12 or higher is required for openid-client@3 and above."
+    "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
+    "string-width": "5.0.0 or above uses ESM."
   },
   },
   "dependencies": {
   "dependencies": {
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
@@ -15,7 +16,7 @@
     "@slack/web-api": "^6.2.3",
     "@slack/web-api": "^6.2.3",
     "@slack/webhook": "^6.0.0",
     "@slack/webhook": "^6.0.0",
     "JSONStream": "^1.3.5",
     "JSONStream": "^1.3.5",
-    "archiver": "^3.1.1",
+    "archiver": "^5.3.0",
     "array.prototype.flatmap": "^1.2.2",
     "array.prototype.flatmap": "^1.2.2",
     "async-canvas-to-blob": "^1.0.3",
     "async-canvas-to-blob": "^1.0.3",
     "aws-sdk": "^2.88.0",
     "aws-sdk": "^2.88.0",
@@ -26,14 +27,14 @@
     "bunyan-format": "^0.2.1",
     "bunyan-format": "^0.2.1",
     "check-node-version": "^4.1.0",
     "check-node-version": "^4.1.0",
     "connect-flash": "~0.1.1",
     "connect-flash": "~0.1.1",
-    "connect-mongo": "^3.2.0",
+    "connect-mongo": "^4.4.1",
     "connect-redis": "^4.0.4",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
     "cookie-parser": "^1.4.5",
     "cross-env": "^7.0.0",
     "cross-env": "^7.0.0",
     "csrf": "^3.1.0",
     "csrf": "^3.1.0",
     "date-fns": "^2.0.0",
     "date-fns": "^2.0.0",
     "detect-indent": "^6.0.0",
     "detect-indent": "^6.0.0",
-    "diff": "^4.0.1",
+    "diff": "^5.0.0",
     "elasticsearch": "^16.0.0",
     "elasticsearch": "^16.0.0",
     "entities": "^2.0.0",
     "entities": "^2.0.0",
     "env-cmd": "^10.0.1",
     "env-cmd": "^10.0.1",
@@ -42,6 +43,7 @@
     "express": "^4.16.1",
     "express": "^4.16.1",
     "express-bunyan-logger": "^1.3.3",
     "express-bunyan-logger": "^1.3.3",
     "express-form": "~0.12.0",
     "express-form": "~0.12.0",
+    "express-mongo-sanitize": "^2.1.0",
     "express-session": "^1.16.1",
     "express-session": "^1.16.1",
     "express-validator": "^6.1.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",
@@ -56,10 +58,10 @@
     "lucene-query-parser": "^1.2.0",
     "lucene-query-parser": "^1.2.0",
     "md5": "^2.2.1",
     "md5": "^2.2.1",
     "method-override": "^3.0.0",
     "method-override": "^3.0.0",
-    "migrate-mongo": "^8.1.4",
+    "migrate-mongo": "^8.2.2",
     "mkdirp": "^1.0.3",
     "mkdirp": "^1.0.3",
     "module-alias": "^2.0.6",
     "module-alias": "^2.0.6",
-    "mongoose": "5.10.11",
+    "mongoose": "5.12.13",
     "mongoose-gridfs": "^1.2.42",
     "mongoose-gridfs": "^1.2.42",
     "mongoose-paginate-v2": "^1.3.9",
     "mongoose-paginate-v2": "^1.3.9",
     "mongoose-unique-validator": "^2.0.3",
     "mongoose-unique-validator": "^2.0.3",
@@ -85,8 +87,8 @@
     "redis": "^3.0.2",
     "redis": "^3.0.2",
     "rimraf": "^3.0.0",
     "rimraf": "^3.0.0",
     "socket.io": "^2.3.0",
     "socket.io": "^2.3.0",
-    "stream-to-promise": "^2.2.0",
-    "string-width": "^4.1.0",
+    "stream-to-promise": "^3.0.0",
+    "string-width": "=4.2.2",
     "swig-templates": "^2.0.2",
     "swig-templates": "^2.0.2",
     "uglifycss": "^0.0.29",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
     "universal-bunyan": "^0.9.2",

+ 10 - 8
packages/app/package.json

@@ -4,7 +4,8 @@
   "license": "MIT",
   "license": "MIT",
   "scripts": {},
   "scripts": {},
   "// comments for dependencies": {
   "// comments for dependencies": {
-    "openid-client": "Node.js 12 or higher is required for openid-client@3 and above."
+    "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
+    "string-width": "5.0.0 or above uses ESM."
   },
   },
   "dependencies": {
   "dependencies": {
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
@@ -15,7 +16,7 @@
     "@slack/web-api": "^6.2.3",
     "@slack/web-api": "^6.2.3",
     "@slack/webhook": "^6.0.0",
     "@slack/webhook": "^6.0.0",
     "JSONStream": "^1.3.5",
     "JSONStream": "^1.3.5",
-    "archiver": "^3.1.1",
+    "archiver": "^5.3.0",
     "array.prototype.flatmap": "^1.2.2",
     "array.prototype.flatmap": "^1.2.2",
     "async-canvas-to-blob": "^1.0.3",
     "async-canvas-to-blob": "^1.0.3",
     "aws-sdk": "^2.88.0",
     "aws-sdk": "^2.88.0",
@@ -26,14 +27,14 @@
     "bunyan-format": "^0.2.1",
     "bunyan-format": "^0.2.1",
     "check-node-version": "^4.1.0",
     "check-node-version": "^4.1.0",
     "connect-flash": "~0.1.1",
     "connect-flash": "~0.1.1",
-    "connect-mongo": "^3.2.0",
+    "connect-mongo": "^4.4.1",
     "connect-redis": "^4.0.4",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
     "cookie-parser": "^1.4.5",
     "cross-env": "^7.0.0",
     "cross-env": "^7.0.0",
     "csrf": "^3.1.0",
     "csrf": "^3.1.0",
     "date-fns": "^2.0.0",
     "date-fns": "^2.0.0",
     "detect-indent": "^6.0.0",
     "detect-indent": "^6.0.0",
-    "diff": "^4.0.1",
+    "diff": "^5.0.0",
     "elasticsearch": "^16.0.0",
     "elasticsearch": "^16.0.0",
     "entities": "^2.0.0",
     "entities": "^2.0.0",
     "env-cmd": "^10.0.1",
     "env-cmd": "^10.0.1",
@@ -42,6 +43,7 @@
     "express": "^4.16.1",
     "express": "^4.16.1",
     "express-bunyan-logger": "^1.3.3",
     "express-bunyan-logger": "^1.3.3",
     "express-form": "~0.12.0",
     "express-form": "~0.12.0",
+    "express-mongo-sanitize": "^2.1.0",
     "express-session": "^1.16.1",
     "express-session": "^1.16.1",
     "express-validator": "^6.1.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",
@@ -56,10 +58,10 @@
     "lucene-query-parser": "^1.2.0",
     "lucene-query-parser": "^1.2.0",
     "md5": "^2.2.1",
     "md5": "^2.2.1",
     "method-override": "^3.0.0",
     "method-override": "^3.0.0",
-    "migrate-mongo": "^8.1.4",
+    "migrate-mongo": "^8.2.2",
     "mkdirp": "^1.0.3",
     "mkdirp": "^1.0.3",
     "module-alias": "^2.0.6",
     "module-alias": "^2.0.6",
-    "mongoose": "5.10.11",
+    "mongoose": "5.12.13",
     "mongoose-gridfs": "^1.2.42",
     "mongoose-gridfs": "^1.2.42",
     "mongoose-paginate-v2": "^1.3.9",
     "mongoose-paginate-v2": "^1.3.9",
     "mongoose-unique-validator": "^2.0.3",
     "mongoose-unique-validator": "^2.0.3",
@@ -85,8 +87,8 @@
     "redis": "^3.0.2",
     "redis": "^3.0.2",
     "rimraf": "^3.0.0",
     "rimraf": "^3.0.0",
     "socket.io": "^2.3.0",
     "socket.io": "^2.3.0",
-    "stream-to-promise": "^2.2.0",
-    "string-width": "^4.1.0",
+    "stream-to-promise": "^3.0.0",
+    "string-width": "=4.2.2",
     "swig-templates": "^2.0.2",
     "swig-templates": "^2.0.2",
     "uglifycss": "^0.0.29",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
     "universal-bunyan": "^0.9.2",

+ 30 - 11
resource/certs/localhost/cert.pem

@@ -1,13 +1,32 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
-UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
-AwwJbG9jYWxob3N0MB4XDTE4MDkxMjEwMjIzNFoXDTE4MTAxMjEwMjIzNFowSDEL
-MAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRUwEwYDVQQKDAxXRVNFRUssIElu
-Yy4xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
-gYEAy0FKOlCQynbo5wYyMCL5LW27Re9dB14wXPDT+zd7wDdYrCbAKKZ1r+7Sj1e7
-638lnn7n4WkhkgsQi/mTxF7W9PHYF00Dh2X0qGf9t+LocNeLVQBHMGNi7HXh8X3j
-iM7w9FffdlfBvuYxPIdDXP12x9JmRhr59Tpv1aaMcRxAY1cCAwEAATANBgkqhkiG
-9w0BAQsFAAOBgQBa/PwnEeFCQ5G4SS6IcL6QVh3KLfeVMCfYVk1o0iJVmJTvfdrq
-crmVwBzbloUO2l6k1ibwD2WVwpdxMKIF5z58HfKAvxZAzCHE7kMEZr1ge30WRXQA
-pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
+MIIFcTCCA1mgAwIBAgIUPCyJHVo85uXE84ITTVrxFDflhv4wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRUwEwYDVQQKDAxXRVNF
+RUssIEluYy4xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMTA2MDcxNTIzMDFaFw0z
+MTA2MDUxNTIzMDFaMEgxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIDAVUb2t5bzEVMBMG
+A1UECgwMV0VTRUVLLCBJbmMuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDJwKo3/75jQ7gFO6ejC/3hTptDvZpCnKpI
+TFJ5We2jZpbxULS2lQqFiVj2ezGvXRq+IHO2lUYFI/kLLOmSruM2u/Wrot3F/iZ8
+M31kZQiOoOwIZKrD1TZiEa848q0Gk3yU20d2mZmUB1ddIWl37o7MDeLaqrr825qc
+2Nf8jNm7AdEBPogXcwxI3MNphkEjICm8xOBhZh4IYlmRicweogebOFpUK4oQNdUA
+HejX5tyZboadoBRhXeZxnbQchkMv2HpV0JY31rgpJWKcuK7kTz3BZouOCUgI18Zi
+4nNzRr589IigpDYqDJWcnuz22XeimQJe+T/eEshrhyCFHIFeGi6UEH7CIcZYjigH
+Ha7nommeApjqWultgPZuNjUoQsNW8c8Y/e8rIyQg8WwevcmUbL/PDSKj/kx8In02
+7lR2NETJvRnHwpPuj9/MggfA8jsmnqVaNu9zShUjri03KqLsdkW0mVr3q4jtazsA
+xukAWXGlA+VqRBGwvpwaY2NEG1t9s/NX5FsUQpWmw75caQndxrx9oAr/59NtUeg2
+mLyI9avyZFMhMIa3lnabp0efcII3HeiouPRNJGsR2PrD418dUSwlAXftu4yEPENs
+nlKf8pMP0PCfGCQ2NWxDe0Yp1YvtIwXX9ZoFLrkxEZ4tzVpvSSxOC3YX+IY9E4Q2
+GI5ofJoO4wIDAQABo1MwUTAdBgNVHQ4EFgQUscss6LD5V0Bla8YKtb/mBiVXBR0w
+HwYDVR0jBBgwFoAUscss6LD5V0Bla8YKtb/mBiVXBR0wDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAiLpFrI7aGlJsPSuarroTPCvSz6vaFFg+QlIR
+MSwITiZ/kNj6c4bh+rzADdsiYU0wOC1ufq841IdHgxnMw7l33hyL1HMc/wrUVCHO
+yE/pz6w2ybxipIOfw21fgwYwYlrdvvlLtqTC/zf92nsuEmMhIjqm8oIhUfRlzX0b
+A1hzMXzvPwxUxNyTX/TSeTvAViDgXTGwstBfN/iiJr8RnX+4cGcyibu+80wa/8As
+T8ryOqdJWC1VoOTupfs5TCbQ8Cc/UDO580ytSHUb8r5RqpB1wl35ui7d6S/6TwwB
+VSKigcbq080oFk+8J0AFAXxYkdAInuDXxvmdSULttQR6H7++kNBiHFObbFOyK4gf
+YDl64bp+q4xAXnKCCmafdpfvMhDlsM91IBy+1utCQd5PeQ6rE5Tv7cz9DPec9X/v
+3tVch3rYYHUHxdPv2ZkXEPTKaMiTFEuO5daV/j5qSvZIwaDofHOEpEojyRGpopxt
+FA9A1oPA/yBFjGpOOQummAKiVooYJUdDX2gSRODNGfXyfYOFh2x1Czdl9VJuK4BW
+qeKTMKJHSVAquOJdk879fF9/Cx4C7iBIaNgFwSCkKnhYIRvYVEarX0QEHBJmiSgG
++0P2AR5mOiUhJqGlRLOWfjEQd+pYfqTi1KOkRMP4xEJdBNCp07zBWmOMXGlRwzQ2
+giuw184=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 0 - 11
resource/certs/localhost/csr.pem

@@ -1,11 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIBhzCB8QIBADBIMQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8xFTATBgNV
-BAoMDFdFU0VFSywgSW5jLjESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3
-DQEBAQUAA4GNADCBiQKBgQDLQUo6UJDKdujnBjIwIvktbbtF710HXjBc8NP7N3vA
-N1isJsAopnWv7tKPV7vrfyWefufhaSGSCxCL+ZPEXtb08dgXTQOHZfSoZ/234uhw
-14tVAEcwY2LsdeHxfeOIzvD0V992V8G+5jE8h0Nc/XbH0mZGGvn1Om/VpoxxHEBj
-VwIDAQABoAAwDQYJKoZIhvcNAQELBQADgYEAd49hz4IoQO55tr62OFlZr254ZPBX
-SXxCtSWawBWLFij8QLl1B8JkHARrMKdM1jBCy5UXcH05DrxGwIOXIcRW7mfrIQDH
-pGs+BQCHMHuYnssg/z2aDhafkmPaLBwh0KWPypVIStxUwLcKxA1xk5VBoP/q+Lgk
-h/mCVJ7JY40BlLA=
------END CERTIFICATE REQUEST-----

+ 52 - 15
resource/certs/localhost/key.pem

@@ -1,15 +1,52 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDLQUo6UJDKdujnBjIwIvktbbtF710HXjBc8NP7N3vAN1isJsAo
-pnWv7tKPV7vrfyWefufhaSGSCxCL+ZPEXtb08dgXTQOHZfSoZ/234uhw14tVAEcw
-Y2LsdeHxfeOIzvD0V992V8G+5jE8h0Nc/XbH0mZGGvn1Om/VpoxxHEBjVwIDAQAB
-AoGALsf8OafJa5Aq0uGOM54ZE+eprtME6mk3YGzdnXiLtxYGBrl0iOanN7MUK4HZ
-8r30/qHe5Pa5j0+Uo2LyK8RYXOcT77CeSSQiSGlBgGj7US7ZmyTqsOwaUKVnbFcy
-Bf/bTJl4EjZREy7kdfCWVO1yY98tV4XrZ2CBDSEDyI8UiiECQQDw47YBMy2duuS6
-z5Ui0xRr8MYwRlCNOt6xKQxlKEhnnRA0vdNB6VMiSzvZ9Bt5nPWI/B3ugUupo6Hn
-FXi1tOgTAkEA2AE7RTICcGPpogOtq/5g7pPNofH524hN26qtUT2kKjUy316JYjrU
-t+N6Ck867w4juVbDcVOTB2Nbj+2+t2EILQJAfo1CyvKWHm1XSQVRNlBqRCLkG+x0
-2R16bNxB1MsK7tRG9U5ctB3ePQAFW4WxAX0CSYsaNnjaxS5gGkTfe6ak3QJAWVlh
-EAVYtu7NRKQq4btOk0F2TOfQB7xBIH1gRfuufXsV+Qmc4JIfTZV99OfDJAGAS3kV
-TTpZ1jOGO2oHeslbXQJBAM4xX8hUueQMIllpBNjlAx1xTqptOHa4elaaPZi7HcDj
-olRU0OP/wPOoEJRvHGP8+LAerx5CEYbadnukQAnNPLA=
------END RSA PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDJwKo3/75jQ7gF
+O6ejC/3hTptDvZpCnKpITFJ5We2jZpbxULS2lQqFiVj2ezGvXRq+IHO2lUYFI/kL
+LOmSruM2u/Wrot3F/iZ8M31kZQiOoOwIZKrD1TZiEa848q0Gk3yU20d2mZmUB1dd
+IWl37o7MDeLaqrr825qc2Nf8jNm7AdEBPogXcwxI3MNphkEjICm8xOBhZh4IYlmR
+icweogebOFpUK4oQNdUAHejX5tyZboadoBRhXeZxnbQchkMv2HpV0JY31rgpJWKc
+uK7kTz3BZouOCUgI18Zi4nNzRr589IigpDYqDJWcnuz22XeimQJe+T/eEshrhyCF
+HIFeGi6UEH7CIcZYjigHHa7nommeApjqWultgPZuNjUoQsNW8c8Y/e8rIyQg8Wwe
+vcmUbL/PDSKj/kx8In027lR2NETJvRnHwpPuj9/MggfA8jsmnqVaNu9zShUjri03
+KqLsdkW0mVr3q4jtazsAxukAWXGlA+VqRBGwvpwaY2NEG1t9s/NX5FsUQpWmw75c
+aQndxrx9oAr/59NtUeg2mLyI9avyZFMhMIa3lnabp0efcII3HeiouPRNJGsR2PrD
+418dUSwlAXftu4yEPENsnlKf8pMP0PCfGCQ2NWxDe0Yp1YvtIwXX9ZoFLrkxEZ4t
+zVpvSSxOC3YX+IY9E4Q2GI5ofJoO4wIDAQABAoICAEfK6Iee2QgHu/8YuFGb3c8E
+ItobKT6U8z9g8WQ/bQHdas8acgQayzCzrFWhhw+pKPAJKkQJoG4J3ABvDfrlgvMi
+NYzLGvHuu5Es3W0RStc+Sl++OsoPIBgLxPeV255gc0+hLC/2/qCfcCYy0o4b9PKF
+6nj3kiySlNVFkK5NDNmwDsjLkLAPf4qaDGuLm7g9JL0IrfPa28sN/GN+zoUEEYLZ
+rcMP70RPc6lj+0yA2XU70Glc4oO3X16EyOfxmwz2kqng3OOn48a4h/vO+GVEEWy1
+6ek0Qk7rQaxEeuLFhNmARvIrHR8fdUnTf+1je1//5MkZFvIWav1gi2DywNJUrMff
+QzNrGTzQba4W/9T4u1Qi49unmvV1TGzKgzLGPGwdqpWB2PJepOheYKKOrIr8yf+Q
+0S6f2djIn3bUvrNOCzKacxG5WobVbUjDQXs0/ws9jB//MFTb99q6kIgdhuWUSv9w
+5PCdPPdWEOWUPcln6xWLYErM86+i1RhJEkDgF4FPbwT3qdYrF8cTjVK09znUC2Uh
+W0ODnUTJS6jp3L2cYUaNl9MsehKAbRFjhPykLg080QJvoqmUJ+S1U8+6htM6Gpsl
+JWo5gC0qnXkINpRQPKSrd7IwoiCOFOmU7I0RfErsfx+ZpaD/Nq1crbYxlGKvFHac
+BckEbh9y9wIJWD0zFpZZAoIBAQD2iYrgU+qR1m7aB/cxwmKh6bM4ZYu6LWE1nn6U
+Oz7sMGs+R6dX+ZyvPEj2HIHnFLlGZI8fKbqC4s81ah9uHZXKZ10CBAjwc7jSoWj/
+jLhnU0ifmxBRyiuyFkghWbf5DGx8/EX2xU1GjAB5aAEgCbPJJMEwZbJ1p0rzCQv1
+4E0DG4DlSsIur/WJAWjC4IPG+CCbKAnHDvWseU9Zfr4MoSwezyorzmb9+DksRrGj
+ekfwLkU+CMCKRBJEq8/GsyAecGiW7x5FxQUrCT9ocd8KSgndSag6byrEaVhXKTFO
+NDMwI+MSXn73eEQUKaXBb30DxN7wBXkULoFrwl48JxJol6CFAoIBAQDRfxJH8wST
+LzKKspYc+0ObsrYMaUiNsa3lcSO1+6W0P7lH7oNeuA6Rio03k6y81DwcYO9UQfUo
+CVM5vQNwDZ5uWRq2apgijgyhwJ1+lVsexX/SL5nzmQUnHhKMBk8CEFm6/2f99waP
+fU+n161TjGSB4DfSc2YzY5SLUUugkJHGKxNlhDK3b8XlXSDwHam+5m9MhQfbiVQg
+8bHZGh9Vupj4Tvgq8KOk2Qlk0IR+l1B11bVJUBv6TsU2Ah+RMGYsmVHUeDKhGh1Z
+q4Jof4tcyX2lckcB1zyYXuDj33n4xwUfhsolEklnWTN4kGSerU5re4D5CG9yLY9R
+OspSeMMZtYJHAoIBAQCoNYIOutLB/GeX1XWAettnE5fXc9lZBttbhS9iHeY5qnOR
+g7wW11VjbqNtYFf1fXtIHxZTsdnysaOlEyosrHzmI44e8PfUb3B8LesA6VdsEDPt
+yhPhaipUEMXYE2nlv+dJg6qMy2OFLcKpEyApZtVG+Q/i4JBZm/IUTmjdstN4ukxA
+pbhFuIKznsWGqT7gA63jjvbU3U0cB4zvR/2Nim4a+0gPqX017SW9IuLS4nyw0DEo
+OTp9XCKGvh8+uDrC6flCehXSjrGPAnlD3uXKFGgUlsv0SqMIbcP7fDyK2izOvL2S
+a+z/FgyFIUFZ8KKAbtOZOnK5JV3iUzSOoC38NPAtAoIBAQDPYIROKKf8spnLZiDm
+q2svcuZ2vrmNWbcUCr0Y5fQPy4yzQc1VM8loFMjZnRs/CLBAP0xqEaiGa0SxQQNo
+JnMQNLAWpsnWWtryO2Mr7Nj4SvTOsUQqOO063a/qYExRdLH+Q9lDMwaRf7AcK4lt
+zOmpDrTzZDxk3kwq5NyCItnKWtWmhghWQRSaiWeTiSmAyUafbohqrzRSMC6nBA7E
+pCPMmQ9cIj07b4S3ARpG3a5OFrCP0/b9/n+FiPCKnmhl+ZdhRWKIyvsGypoaqzbf
+MK1p2wejMNbQ1gq0R4HL2+acYKc1rzZuUYR78iNIV7Hq/6SZ39nCIoteE1436Ubn
+6h79AoIBAGndPh3t/xqlVRDexW6XNJvVjCDblsk62WkWuIPECCLL6prdAjjfBCEn
+5J1OY9VcdqFDz5Xit3D05VV3noZAIGZz37+l4v0pI2m7FP+WpSem0woHmKceTqUl
+MaEaqBSg/tBF6A1ueVaR1Ot5KDzvwiPyguCNjOxw1m7xxfNdkhADs6CJTThNNkwN
+RSuYYueYmD6NKJVBVLLwym+oY59fUeYMXvfDVYf0MI2UJNWYOvjm0HeYD/nRjed3
+9NXEIRfM9siZhzwXo4z+vX/FrAAz7uw3UI/r9awt1PMcWLjgj5fwvKw2GYnvjv8Q
++13WXbYYm6udwoFKrodRDCRDcDAlCoI=
+-----END PRIVATE KEY-----

+ 2 - 1
resource/locales/en_US/meta.json

@@ -1,4 +1,5 @@
 {
 {
   "id": "en_US",
   "id": "en_US",
-  "displayName": "English"
+  "displayName": "English",
+  "aliases": ["en"]
 }
 }

+ 7 - 3
resource/locales/en_US/translation.json

@@ -128,13 +128,11 @@
   "Only me": "Only me",
   "Only me": "Only me",
   "Only inside the group": "Only inside the group",
   "Only inside the group": "Only inside the group",
   "page_list": "Page List",
   "page_list": "Page List",
-  "page_list_and_search_results": "Page list / Search results",
   "scope_of_page_disclosure": "Scope of page disclosure",
   "scope_of_page_disclosure": "Scope of page disclosure",
   "set_point": "Set point",
   "set_point": "Set point",
   "always_displayed": "Always displayed",
   "always_displayed": "Always displayed",
   "always_hidden": "Always hidden",
   "always_hidden": "Always hidden",
   "displayed_or_hidden": "Displayed / Hidden",
   "displayed_or_hidden": "Displayed / Hidden",
-  "page_access_and_delete_rights": "Page access / Delete rights",
   "Reselect the group": "Reselect the group",
   "Reselect the group": "Reselect the group",
   "Shareable link": "Shareable link",
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
@@ -532,7 +530,6 @@
     "delete_completely": "Delete completely"
     "delete_completely": "Delete completely"
   },
   },
   "security_setting": {
   "security_setting": {
-    "Security settings": "Security settings",
     "Guest Users Access": "Guest users access",
     "Guest Users Access": "Guest users access",
     "Fixed by env var": "This is fixed by the env var <code>%s=%s</code>.",
     "Fixed by env var": "This is fixed by the env var <code>%s=%s</code>.",
     "Register limitation": "Register limitation",
     "Register limitation": "Register limitation",
@@ -544,15 +541,21 @@
     "for_example": " For example, if you would like to restrict registration to users within the growi.org domain, you can write ",
     "for_example": " For example, if you would like to restrict registration to users within the growi.org domain, you can write ",
     "in_this_case": "; in this case, only users within the growi.org domain would be able to register, and all other users would be rejected.",
     "in_this_case": "; in this case, only users within the growi.org domain would be able to register, and all other users would be rejected.",
     "insert_single": "Please insert single e-mail address per line.",
     "insert_single": "Please insert single e-mail address per line.",
+    "page_list_and_search_results": "Page list / Search results",
     "page_listing_1": "Page listing/searching<br>restricted by 'Only me'",
     "page_listing_1": "Page listing/searching<br>restricted by 'Only me'",
     "page_listing_1_desc": "Show pages that are restricted by 'Only me' option when listing/searching",
     "page_listing_1_desc": "Show pages that are restricted by 'Only me' option when listing/searching",
     "page_listing_2": "Page listing/searching<br>restricted by User group",
     "page_listing_2": "Page listing/searching<br>restricted by User group",
     "page_listing_2_desc": "Show pages that are restricted by User group when listing/searching",
     "page_listing_2_desc": "Show pages that are restricted by User group when listing/searching",
+    "page_access_and_delete_rights": "Page access / Delete rights",
     "complete_deletion": "Restrict complete deletion of pages",
     "complete_deletion": "Restrict complete deletion of pages",
     "complete_deletion_explain": "Restricts users who can completely delete pages.",
     "complete_deletion_explain": "Restricts users who can completely delete pages.",
     "admin_only": "Admin only",
     "admin_only": "Admin only",
     "admin_and_author": "Admin and author",
     "admin_and_author": "Admin and author",
     "anyone": "Anyone",
     "anyone": "Anyone",
+    "session": "Session",
+    "max_age": "Max age (msec)",
+    "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
+    "max_age_caution": "Restarting the server is required after you modify this value.",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "setup_is_not_yet_complete": "Setup is not yet complete",
     "setup_is_not_yet_complete": "Setup is not yet complete",
     "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
     "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
@@ -789,6 +792,7 @@
     "rebuild_description_1": "Click the button to rebuild index and add all page datas.",
     "rebuild_description_1": "Click the button to rebuild index and add all page datas.",
     "rebuild_description_2": "This may take a while."
     "rebuild_description_2": "This may take a while."
   },
   },
+  "to_cloud_settings": "Open GROWI.cloud Settings",
   "login": {
   "login": {
     "Sign in error": "Login error",
     "Sign in error": "Login error",
     "Registration successful": "Registration successful",
     "Registration successful": "Registration successful",

+ 2 - 1
resource/locales/ja_JP/meta.json

@@ -1,4 +1,5 @@
 {
 {
   "id": "ja_JP",
   "id": "ja_JP",
-  "displayName": "日本語"
+  "displayName": "日本語",
+  "aliases": ["ja"]
 }
 }

+ 7 - 2
resource/locales/ja_JP/translation.json

@@ -128,13 +128,11 @@
   "Only me": "自分のみ",
   "Only me": "自分のみ",
   "Only inside the group": "特定グループのみ",
   "Only inside the group": "特定グループのみ",
   "page_list": "ページリスト",
   "page_list": "ページリスト",
-  "page_list_and_search_results": "ページリスト・検索結果",
   "scope_of_page_disclosure": "ページの公開範囲",
   "scope_of_page_disclosure": "ページの公開範囲",
   "set_point": "設定値",
   "set_point": "設定値",
   "always_displayed": "表示 (固定)",
   "always_displayed": "表示 (固定)",
   "always_hidden": "非表示 (固定)",
   "always_hidden": "非表示 (固定)",
   "displayed_or_hidden": "表示 / 非表示",
   "displayed_or_hidden": "表示 / 非表示",
-  "page_access_and_delete_rights": "ページの閲覧・削除権限",
   "Reselect the group": "グループの再選択",
   "Reselect the group": "グループの再選択",
   "Shareable link": "このページの共有用URL",
   "Shareable link": "このページの共有用URL",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
@@ -545,15 +543,21 @@
     "for_example": "例えば、",
     "for_example": "例えば、",
     "in_this_case": "と記載することで、そのドメインのメールアドレスを持っている人のみ登録可能になります。",
     "in_this_case": "と記載することで、そのドメインのメールアドレスを持っている人のみ登録可能になります。",
     "insert_single": "1行に1メールアドレス入力してください。",
     "insert_single": "1行に1メールアドレス入力してください。",
+    "page_list_and_search_results": "ページリスト・検索結果",
     "page_listing_1": "ページのリスト表示と検索<br>'自分のみ'に閲覧制限しているページ",
     "page_listing_1": "ページのリスト表示と検索<br>'自分のみ'に閲覧制限しているページ",
     "page_listing_1_desc": "ページのリスト表示や検索結果において、'自分のみ'に閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
     "page_listing_1_desc": "ページのリスト表示や検索結果において、'自分のみ'に閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
     "page_listing_2": "ページのリスト表示と検索<br>特定グループに閲覧制限しているページ",
     "page_listing_2": "ページのリスト表示と検索<br>特定グループに閲覧制限しているページ",
     "page_listing_2_desc": "ページのリスト表示や検索結果において、特定グループにのみ閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
     "page_listing_2_desc": "ページのリスト表示や検索結果において、特定グループにのみ閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
+    "page_access_and_delete_rights": "ページの閲覧・削除権限",
     "complete_deletion": "ページの完全削除",
     "complete_deletion": "ページの完全削除",
     "complete_deletion_explain": "ページを完全に削除できるユーザーを制限します。",
     "complete_deletion_explain": "ページを完全に削除できるユーザーを制限します。",
     "admin_only": "管理者のみ可能",
     "admin_only": "管理者のみ可能",
     "admin_and_author": "管理者とページ作者が可能",
     "admin_and_author": "管理者とページ作者が可能",
     "anyone": "誰でも可能",
     "anyone": "誰でも可能",
+    "session": "セッション",
+    "max_age": "有効期間 (ミリ秒)",
+    "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
+    "max_age_caution": "この値を変更した後は、サーバーを再起動する必要があります。",
     "Authentication mechanism settings": "認証機構設定",
     "Authentication mechanism settings": "認証機構設定",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
     "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
     "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
@@ -782,6 +786,7 @@
     "rebuild_description_1": "全てのページのインデックスを削除し、作り直します。",
     "rebuild_description_1": "全てのページのインデックスを削除し、作り直します。",
     "rebuild_description_2": "この作業には数秒かかります。"
     "rebuild_description_2": "この作業には数秒かかります。"
   },
   },
+  "to_cloud_settings": "GROWI.cloud の管理画面へ",
   "login": {
   "login": {
     "Sign in error": "ログインエラー",
     "Sign in error": "ログインエラー",
     "Registration successful": "登録完了",
     "Registration successful": "登録完了",

+ 3 - 2
resource/locales/zh_CN/meta.json

@@ -1,4 +1,5 @@
 {
 {
 	"id": "zh_CN",
 	"id": "zh_CN",
-	"displayName": "简体中文"
-}
+	"displayName": "简体中文",
+  "aliases": ["zh","zh-HK","zh-CN","zh-TW","zh-hk","zh-cn","zh-tw"]
+}

+ 7 - 3
resource/locales/zh_CN/translation.json

@@ -137,13 +137,11 @@
 	"Only me": "只有我",
 	"Only me": "只有我",
   "Only inside the group": "仅组内",
   "Only inside the group": "仅组内",
   "page_list": "Page List",
   "page_list": "Page List",
-	"page_list_and_search_results": "页面列表/搜索结果",
 	"scope_of_page_disclosure": "页面公开范围",
 	"scope_of_page_disclosure": "页面公开范围",
 	"set_point": "设定值",
 	"set_point": "设定值",
 	"always_displayed": "始终显示",
 	"always_displayed": "始终显示",
 	"always_hidden": "总是隐藏",
 	"always_hidden": "总是隐藏",
 	"displayed_or_hidden": "显示/隐藏",
 	"displayed_or_hidden": "显示/隐藏",
-	"page_access_and_delete_rights": "页面访问/删除权限",
 	"Reselect the group": "重新选择组",
 	"Reselect the group": "重新选择组",
 	"Shareable link": "可分享链接",
 	"Shareable link": "可分享链接",
 	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
 	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
@@ -520,7 +518,6 @@
     "Invalid_Number_of_Date" : "You entered invalid value"
     "Invalid_Number_of_Date" : "You entered invalid value"
   },
   },
 	"security_setting": {
 	"security_setting": {
-		"Security settings": "安全设置",
 		"Guest Users Access": "来宾用户访问",
 		"Guest Users Access": "来宾用户访问",
 		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
 		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
 		"Register limitation": "注册限制",
 		"Register limitation": "注册限制",
@@ -532,15 +529,21 @@
 		"for_example": " 例如,如果要将注册限制为growi.org网站域,你可以写",
 		"for_example": " 例如,如果要将注册限制为growi.org网站域,你可以写",
 		"in_this_case": ";在这种情况下,只有growi.org网站域将能够注册,所有其他用户将被拒绝。",
 		"in_this_case": ";在这种情况下,只有growi.org网站域将能够注册,所有其他用户将被拒绝。",
 		"insert_single": "请每行插入一个电子邮件地址。",
 		"insert_single": "请每行插入一个电子邮件地址。",
+    "page_list_and_search_results": "页面列表/搜索结果",
 		"page_listing_1": "页面列表/搜索<br>受“仅限我”限制",
 		"page_listing_1": "页面列表/搜索<br>受“仅限我”限制",
 		"page_listing_1_desc": "列出/搜索时显示受“仅限我”选项限制的页面",
 		"page_listing_1_desc": "列出/搜索时显示受“仅限我”选项限制的页面",
 		"page_listing_2": "页面列表/搜索<br>受用户组限制",
 		"page_listing_2": "页面列表/搜索<br>受用户组限制",
 		"page_listing_2_desc": "显示列出/搜索时受用户组限制的页面",
 		"page_listing_2_desc": "显示列出/搜索时受用户组限制的页面",
+    "page_access_and_delete_rights": "页面访问/删除权限",
 		"complete_deletion": "限制完全删除页面",
 		"complete_deletion": "限制完全删除页面",
 		"complete_deletion_explain": "限制可以完全删除页面的用户。",
 		"complete_deletion_explain": "限制可以完全删除页面的用户。",
 		"admin_only": "仅管理员",
 		"admin_only": "仅管理员",
 		"admin_and_author": "管理员|作者",
 		"admin_and_author": "管理员|作者",
 		"anyone": "任何人",
 		"anyone": "任何人",
+    "session": "会议",
+    "max_age": "有效期间  (msec)",
+    "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
+    "max_age_caution": "修改该值后需要重启服务器。",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"setup_is_not_yet_complete": "安装尚未完成",
 		"setup_is_not_yet_complete": "安装尚未完成",
 		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
 		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
@@ -794,6 +797,7 @@
 		"deletion_modal_header": "删除页",
 		"deletion_modal_header": "删除页",
 		"delete_completely": "完全删除"
 		"delete_completely": "完全删除"
 	},
 	},
+	"to_cloud_settings": "進入 GROWI.cloud 的管理界面",
 	"login": {
 	"login": {
 		"Sign in error": "登录错误",
 		"Sign in error": "登录错误",
 		"Registration successful": "注册成功",
 		"Registration successful": "注册成功",

+ 21 - 3
src/client/js/components/Admin/Common/AdminNavigation.jsx

@@ -8,10 +8,16 @@ import urljoin from 'url-join';
 
 
 import { pathUtils } from 'growi-commons';
 import { pathUtils } from 'growi-commons';
 
 
+import AppContainer from '../../../services/AppContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
 const AdminNavigation = (props) => {
 const AdminNavigation = (props) => {
-  const { t } = props;
+  const { t, appContainer } = props;
   const pathname = window.location.pathname;
   const pathname = window.location.pathname;
 
 
+  const growiCloudUri = appContainer.config.env.GROWI_CLOUD_URI;
+  const growiAppIdForGrowiCloud = appContainer.config.env.GROWI_APP_ID_FOR_GROWI_CLOUD;
+
   // eslint-disable-next-line react/prop-types
   // eslint-disable-next-line react/prop-types
   const MenuLabel = ({ menu }) => {
   const MenuLabel = ({ menu }) => {
     switch (menu) {
     switch (menu) {
@@ -27,6 +33,7 @@ const AdminNavigation = (props) => {
       case 'users':                    return <><i className="icon-fw icon-user"></i>            { t('User_Management') }</>;
       case 'users':                    return <><i className="icon-fw icon-user"></i>            { t('User_Management') }</>;
       case 'user-groups':              return <><i className="icon-fw icon-people"></i>          { t('UserGroup Management') }</>;
       case 'user-groups':              return <><i className="icon-fw icon-people"></i>          { t('UserGroup Management') }</>;
       case 'search':                   return <><i className="icon-fw icon-magnifier"></i>       { t('Full Text Search Management') }</>;
       case 'search':                   return <><i className="icon-fw icon-magnifier"></i>       { t('Full Text Search Management') }</>;
+      case 'cloud':                    return <><i className="icon-fw icon-share-alt"></i>       { t('to_cloud_settings')} </>;
       default:                         return <><i className="icon-fw icon-home"></i>            { t('Wiki Management Home Page') }</>;
       default:                         return <><i className="icon-fw icon-home"></i>            { t('Wiki Management Home Page') }</>;
     }
     }
   };
   };
@@ -75,6 +82,16 @@ const AdminNavigation = (props) => {
         <MenuLink menu="users"        isListGroupItems isActive={isActiveMenu('/users')} />
         <MenuLink menu="users"        isListGroupItems isActive={isActiveMenu('/users')} />
         <MenuLink menu="user-groups"  isListGroupItems isActive={isActiveMenu('/user-groups')} />
         <MenuLink menu="user-groups"  isListGroupItems isActive={isActiveMenu('/user-groups')} />
         <MenuLink menu="search"       isListGroupItems isActive={isActiveMenu('/search')} />
         <MenuLink menu="search"       isListGroupItems isActive={isActiveMenu('/search')} />
+        {growiCloudUri != null && growiAppIdForGrowiCloud != null
+          && (
+          <a
+            href={`${growiCloudUri}/my/apps/${growiAppIdForGrowiCloud}`}
+            className="list-group-item list-group-item-action border-0 round-corner"
+          >
+            <MenuLabel menu="cloud" />
+          </a>
+          )
+        }
       </>
       </>
     );
     );
   };
   };
@@ -121,10 +138,11 @@ const AdminNavigation = (props) => {
   );
   );
 };
 };
 
 
+const AdminNavigationWrapper = withUnstatedContainers(AdminNavigation, [AppContainer]);
 
 
 AdminNavigation.propTypes = {
 AdminNavigation.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
-
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 };
 };
 
 
-export default withTranslation()(AdminNavigation);
+export default withTranslation()(AdminNavigationWrapper);

+ 4 - 4
src/client/js/components/Admin/ImportData/ImportDataPageContents.jsx

@@ -69,7 +69,7 @@ class ImportDataPageContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="esaTeamName"
                   name="esaTeamName"
-                  value={adminImportContainer.state.esaTeamName}
+                  value={adminImportContainer.state.esaTeamName || ''}
                   onChange={adminImportContainer.handleInputValue}
                   onChange={adminImportContainer.handleInputValue}
                 />
                 />
               </div>
               </div>
@@ -85,7 +85,7 @@ class ImportDataPageContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="password"
                   type="password"
                   name="esaAccessToken"
                   name="esaAccessToken"
-                  value={adminImportContainer.state.esaAccessToken}
+                  value={adminImportContainer.state.esaAccessToken || ''}
                   onChange={adminImportContainer.handleInputValue}
                   onChange={adminImportContainer.handleInputValue}
                 />
                 />
               </div>
               </div>
@@ -174,7 +174,7 @@ class ImportDataPageContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="qiitaTeamName"
                   name="qiitaTeamName"
-                  value={adminImportContainer.state.qiitaTeamName}
+                  value={adminImportContainer.state.qiitaTeamName || ''}
                   onChange={adminImportContainer.handleInputValue}
                   onChange={adminImportContainer.handleInputValue}
                 />
                 />
               </div>
               </div>
@@ -189,7 +189,7 @@ class ImportDataPageContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="password"
                   type="password"
                   name="qiitaAccessToken"
                   name="qiitaAccessToken"
-                  value={adminImportContainer.state.qiitaAccessToken}
+                  value={adminImportContainer.state.qiitaAccessToken || ''}
                   onChange={adminImportContainer.handleInputValue}
                   onChange={adminImportContainer.handleInputValue}
                 />
                 />
               </div>
               </div>

+ 32 - 9
src/client/js/components/Admin/Security/SecuritySetting.jsx

@@ -37,15 +37,14 @@ class SecuritySetting extends React.Component {
         <h2 className="alert-anchor border-bottom">
         <h2 className="alert-anchor border-bottom">
           {t('security_settings')}
           {t('security_settings')}
         </h2>
         </h2>
+
         {adminGeneralSecurityContainer.retrieveError != null && (
         {adminGeneralSecurityContainer.retrieveError != null && (
-        <div className="alert alert-danger">
-          <p>{t('Error occurred')} : {adminGeneralSecurityContainer.retrieveError}</p>
-        </div>
-          )}
+          <div className="alert alert-danger">
+            <p>{t('Error occurred')} : {adminGeneralSecurityContainer.retrieveError}</p>
+          </div>
+        )}
 
 
-        <h4 className="mt-4">
-          { t('page_list_and_search_results') }
-        </h4>
+        <h4 className="mt-4">{ t('security_setting.page_list_and_search_results') }</h4>
         <table className="table table-bordered col-lg-9 mb-5">
         <table className="table table-bordered col-lg-9 mb-5">
           <thead>
           <thead>
             <tr>
             <tr>
@@ -98,7 +97,8 @@ class SecuritySetting extends React.Component {
             </tr>
             </tr>
           </tbody>
           </tbody>
         </table>
         </table>
-        <h4>{t('page_access_and_delete_rights')}</h4>
+
+        <h4>{t('security_setting.page_access_and_delete_rights')}</h4>
         <div className="row mb-4">
         <div className="row mb-4">
           <div className="col-md-3 text-md-right py-2">
           <div className="col-md-3 text-md-right py-2">
             <strong>{t('security_setting.Guest Users Access')}</strong>
             <strong>{t('security_setting.Guest Users Access')}</strong>
@@ -142,7 +142,6 @@ class SecuritySetting extends React.Component {
             )}
             )}
           </div>
           </div>
         </div>
         </div>
-
         <div className="row mb-4">
         <div className="row mb-4">
           <div className="col-md-3 text-md-right mb-2">
           <div className="col-md-3 text-md-right mb-2">
             <strong>{t('security_setting.complete_deletion')}</strong>
             <strong>{t('security_setting.complete_deletion')}</strong>
@@ -189,6 +188,30 @@ class SecuritySetting extends React.Component {
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
+
+        <h4>{t('security_setting.session')}</h4>
+        <div className="form-group row">
+          <label className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.max_age')}</label>
+          <div className="col-md-6">
+            <input
+              className="form-control col-md-3"
+              type="text"
+              defaultValue={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
+              onChange={(e) => {
+                adminGeneralSecurityContainer.setSessionMaxAge(e.target.value);
+              }}
+              placeholder="2592000000"
+            />
+            {/* eslint-disable-next-line react/no-danger */}
+            <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('security_setting.max_age_desc') }} />
+            <p className="card well">
+              <span className="text-warning">
+                <i className="icon-info"></i> {t('security_setting.max_age_caution')}
+              </span>
+            </p>
+          </div>
+        </div>
+
         <div className="row my-3">
         <div className="row my-3">
           <div className="text-center text-md-left offset-md-3 col-md-5">
           <div className="text-center text-md-left offset-md-3 col-md-5">
             <button type="button" className="btn btn-primary" disabled={adminGeneralSecurityContainer.retrieveError != null} onClick={this.putSecuritySetting}>
             <button type="button" className="btn btn-primary" disabled={adminGeneralSecurityContainer.retrieveError != null} onClick={this.putSecuritySetting}>

+ 5 - 1
src/client/js/components/InstallerForm.jsx

@@ -22,7 +22,11 @@ class InstallerForm extends React.Component {
   }
   }
 
 
   componentWillMount() {
   componentWillMount() {
-    this.changeLanguage(localeMetadatas[0]);
+    const meta = localeMetadatas.find(v => v.id === i18next.language);
+    if (meta == null) {
+      return this.setState({ selectedLang: localeMetadatas[0] });
+    }
+    this.setState({ selectedLang: meta });
   }
   }
 
 
   // checkUserName(event) {
   // checkUserName(event) {

+ 4 - 4
src/client/js/components/PageCreateModal.jsx

@@ -8,7 +8,7 @@ import { withTranslation } from 'react-i18next';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
-import { userPageRoot } from '@commons/util/path-utils';
+import { userPageRoot, isCreatablePage } from '@commons/util/path-utils';
 import { pathUtils } from 'growi-commons';
 import { pathUtils } from 'growi-commons';
 
 
 import AppContainer from '../services/AppContainer';
 import AppContainer from '../services/AppContainer';
@@ -24,12 +24,12 @@ const PageCreateModal = (props) => {
   const isReachable = config.isSearchServiceReachable;
   const isReachable = config.isSearchServiceReachable;
   const pathname = decodeURI(window.location.pathname);
   const pathname = decodeURI(window.location.pathname);
   const userPageRootPath = userPageRoot(appContainer.currentUser);
   const userPageRootPath = userPageRoot(appContainer.currentUser);
-  const parentPath = pathUtils.addTrailingSlash(pathname);
+  const pageNameInputInitialValue = isCreatablePage(pathname) ? pathUtils.addTrailingSlash(pathname) : '/';
   const now = format(new Date(), 'yyyy/MM/dd');
   const now = format(new Date(), 'yyyy/MM/dd');
 
 
   const [todayInput1, setTodayInput1] = useState(t('Memo'));
   const [todayInput1, setTodayInput1] = useState(t('Memo'));
   const [todayInput2, setTodayInput2] = useState('');
   const [todayInput2, setTodayInput2] = useState('');
-  const [pageNameInput, setPageNameInput] = useState(parentPath);
+  const [pageNameInput, setPageNameInput] = useState(pageNameInputInitialValue);
   const [template, setTemplate] = useState(null);
   const [template, setTemplate] = useState(null);
 
 
   function transitBySubmitEvent(e, transitHandler) {
   function transitBySubmitEvent(e, transitHandler) {
@@ -162,7 +162,7 @@ const PageCreateModal = (props) => {
               {isReachable
               {isReachable
                 ? (
                 ? (
                   <PagePathAutoComplete
                   <PagePathAutoComplete
-                    initializedPath={pathname}
+                    initializedPath={pageNameInput}
                     addTrailingSlash
                     addTrailingSlash
                     onSubmit={ppacSubmitHandler}
                     onSubmit={ppacSubmitHandler}
                     onInputChange={ppacInputChangeHandler}
                     onInputChange={ppacInputChangeHandler}

+ 10 - 0
src/client/js/services/AdminGeneralSecurityContainer.js

@@ -18,6 +18,7 @@ export default class AdminGeneralSecurityContainer extends Container {
 
 
     this.state = {
     this.state = {
       retrieveError: null,
       retrieveError: null,
+      sessionMaxAge: null,
       wikiMode: '',
       wikiMode: '',
       // set dummy value tile for using suspense
       // set dummy value tile for using suspense
       currentRestrictGuestMode: this.dummyCurrentRestrictGuestMode,
       currentRestrictGuestMode: this.dummyCurrentRestrictGuestMode,
@@ -51,6 +52,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
+      sessionMaxAge: generalSetting.sessionMaxAge,
       wikiMode: generalSetting.wikiMode,
       wikiMode: generalSetting.wikiMode,
       isLocalEnabled: generalAuth.isLocalEnabled,
       isLocalEnabled: generalAuth.isLocalEnabled,
       isLdapEnabled: generalAuth.isLdapEnabled,
       isLdapEnabled: generalAuth.isLdapEnabled,
@@ -79,6 +81,13 @@ export default class AdminGeneralSecurityContainer extends Container {
     return this.state.wikiMode === 'public' || this.state.wikiMode === 'private';
     return this.state.wikiMode === 'public' || this.state.wikiMode === 'private';
   }
   }
 
 
+  /**
+   * setter for sessionMaxAge
+   */
+  setSessionMaxAge(sessionMaxAge) {
+    this.setState({ sessionMaxAge });
+  }
+
   /**
   /**
    * Change restrictGuestMode
    * Change restrictGuestMode
    */
    */
@@ -115,6 +124,7 @@ export default class AdminGeneralSecurityContainer extends Container {
   async updateGeneralSecuritySetting() {
   async updateGeneralSecuritySetting() {
 
 
     let requestParams = {
     let requestParams = {
+      sessionMaxAge: this.state.sessionMaxAge,
       restrictGuestMode: this.state.currentRestrictGuestMode,
       restrictGuestMode: this.state.currentRestrictGuestMode,
       pageCompleteDeletionAuthority: this.state.currentPageCompleteDeletionAuthority,
       pageCompleteDeletionAuthority: this.state.currentPageCompleteDeletionAuthority,
       hideRestrictedByGroup: !this.state.isShowRestrictedByGroup,
       hideRestrictedByGroup: !this.state.isShowRestrictedByGroup,

+ 24 - 4
src/client/js/util/i18n.js

@@ -1,13 +1,22 @@
 import i18n from 'i18next';
 import i18n from 'i18next';
 import LanguageDetector from 'i18next-browser-languagedetector';
 import LanguageDetector from 'i18next-browser-languagedetector';
 import { initReactI18next } from 'react-i18next';
 import { initReactI18next } from 'react-i18next';
-
 import locales from '@root/resource/locales';
 import locales from '@root/resource/locales';
 
 
+const aliasesMapping = {};
+Object.values(locales).forEach((locale) => {
+  if (locale.meta.aliases == null) {
+    return;
+  }
+  locale.meta.aliases.forEach((alias) => {
+    aliasesMapping[alias] = locale.meta.id;
+  });
+});
+
 // extract metadata list from 'resource/locales/${locale}/meta.json'
 // extract metadata list from 'resource/locales/${locale}/meta.json'
 export const localeMetadatas = Object.values(locales).map(locale => locale.meta);
 export const localeMetadatas = Object.values(locales).map(locale => locale.meta);
 
 
-export const i18nFactory = (userLocaleId = 'en_US') => {
+export const i18nFactory = (userLocaleId) => {
   // setup LanguageDetector
   // setup LanguageDetector
   const langDetector = new LanguageDetector();
   const langDetector = new LanguageDetector();
   langDetector.addDetector({
   langDetector.addDetector({
@@ -15,7 +24,18 @@ export const i18nFactory = (userLocaleId = 'en_US') => {
     lookup(options) {
     lookup(options) {
       return userLocaleId;
       return userLocaleId;
     },
     },
-    cacheUserlanguage(lng, options) {
+  });
+  // Wrapper to convert lang after detected from browser
+  langDetector.addDetector({
+    name: 'navigatorWrapperToConvertByAlias',
+    lookup(options) {
+      const results = langDetector.detectors.navigator.lookup(options);
+      const lang = results[0];
+      if (lang == null) {
+        return;
+      }
+
+      return aliasesMapping[lang] || lang;
     },
     },
   });
   });
 
 
@@ -29,7 +49,7 @@ export const i18nFactory = (userLocaleId = 'en_US') => {
 
 
       fallbackLng: 'en_US',
       fallbackLng: 'en_US',
       detection: {
       detection: {
-        order: ['userSettingDetector', 'querystring', 'localStorage'],
+        order: ['userSettingDetector', 'navigatorWrapperToConvertByAlias', 'querystring'],
       },
       },
 
 
       interpolation: {
       interpolation: {

+ 20 - 1
src/lib/service/xss/index.js

@@ -1,6 +1,9 @@
 const xss = require('xss');
 const xss = require('xss');
 const commonmarkSpec = require('./commonmark-spec');
 const commonmarkSpec = require('./commonmark-spec');
 
 
+
+const REPETITIONS_NUM = 50;
+
 class Xss {
 class Xss {
 
 
   constructor(xssOption) {
   constructor(xssOption) {
@@ -36,7 +39,23 @@ class Xss {
   }
   }
 
 
   process(document) {
   process(document) {
-    return this.myxss.process(document);
+    let count = 0;
+    let currDoc = document;
+    let prevDoc = document;
+
+    do {
+      count += 1;
+      // stop running infinitely
+      if (count > REPETITIONS_NUM) {
+        return '--filtered--';
+      }
+
+      prevDoc = currDoc;
+      currDoc = this.myxss.process(currDoc);
+    }
+    while (currDoc !== prevDoc);
+
+    return currDoc;
   }
   }
 
 
 }
 }

+ 33 - 0
src/lib/util/path-utils.js

@@ -37,6 +37,38 @@ const isUserPage = (path) => {
   return false;
   return false;
 };
 };
 
 
+const forbiddenPages = [
+  /\^|\$|\*|\+|#|%/,
+  /^\/-\/.*/,
+  /^\/_r\/.*/,
+  /^\/_apix?(\/.*)?/,
+  /^\/?https?:\/\/.+$/, // avoid miss in renaming
+  /\/{2,}/, // avoid miss in renaming
+  /\s+\/\s+/, // avoid miss in renaming
+  /.+\/edit$/,
+  /.+\.md$/,
+  /^(\.\.)$/, // see: https://github.com/weseek/growi/issues/3582
+  /(\/\.\.)\/?/, // see: https://github.com/weseek/growi/issues/3582
+  /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments|tags|share)(\/.*|$)/,
+];
+
+/**
+ * Whether path can be created
+ * @param {string} path
+ * @returns {boolean}
+ */
+const isCreatablePage = (path) => {
+  let isCreatable = true;
+  forbiddenPages.forEach((page) => {
+    const pageNameReg = new RegExp(page);
+    if (path.match(pageNameReg)) {
+      isCreatable = false;
+    }
+  });
+
+  return isCreatable;
+};
+
 /**
 /**
  * return user path
  * return user path
  * @param {Object} user
  * @param {Object} user
@@ -83,6 +115,7 @@ module.exports = {
   isTopPage,
   isTopPage,
   isTrashPage,
   isTrashPage,
   isUserPage,
   isUserPage,
+  isCreatablePage,
   userPageRoot,
   userPageRoot,
   convertToNewAffiliationPath,
   convertToNewAffiliationPath,
   encodeSpaces,
   encodeSpaces,

+ 2 - 0
src/server/crowi/express-init.js

@@ -11,6 +11,7 @@ module.exports = function(crowi, app) {
   const passport = require('passport');
   const passport = require('passport');
   const expressSession = require('express-session');
   const expressSession = require('express-session');
   const flash = require('connect-flash');
   const flash = require('connect-flash');
+  const mongoSanitize = require('express-mongo-sanitize');
   const swig = require('swig-templates');
   const swig = require('swig-templates');
   const webpackAssets = require('express-webpack-assets');
   const webpackAssets = require('express-webpack-assets');
   const i18next = require('i18next');
   const i18next = require('i18next');
@@ -116,6 +117,7 @@ module.exports = function(crowi, app) {
   app.use(passport.session());
   app.use(passport.session());
 
 
   app.use(flash());
   app.use(flash());
+  app.use(mongoSanitize());
 
 
   app.use(promster);
   app.use(promster);
   app.use(registerSafeRedirect);
   app.use(registerSafeRedirect);

+ 5 - 5
src/server/crowi/index.js

@@ -81,8 +81,8 @@ function Crowi(rootdir) {
 Crowi.prototype.init = async function() {
 Crowi.prototype.init = async function() {
   await this.setupDatabase();
   await this.setupDatabase();
   await this.setupModels();
   await this.setupModels();
-  await this.setupSessionConfig();
   await this.setupConfigManager();
   await this.setupConfigManager();
+  await this.setupSessionConfig();
 
 
   // setup messaging services
   // setup messaging services
   await this.setupS2sMessagingService();
   await this.setupS2sMessagingService();
@@ -220,7 +220,7 @@ Crowi.prototype.setupDatabase = function() {
 
 
 Crowi.prototype.setupSessionConfig = async function() {
 Crowi.prototype.setupSessionConfig = async function() {
   const session = require('express-session');
   const session = require('express-session');
-  const sessionAge = (1000 * 3600 * 24 * 30);
+  const sessionMaxAge = this.configManager.getConfig('crowi', 'security:sessionMaxAge') || 2592000000; // default: 30days
   const redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null;
   const redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null;
   const uid = require('uid-safe').sync;
   const uid = require('uid-safe').sync;
 
 
@@ -233,7 +233,7 @@ Crowi.prototype.setupSessionConfig = async function() {
     resave: false,
     resave: false,
     saveUninitialized: true,
     saveUninitialized: true,
     cookie: {
     cookie: {
-      maxAge: sessionAge,
+      maxAge: sessionMaxAge,
     },
     },
     genid(req) {
     genid(req) {
       // return pre-defined uid when healthcheck
       // return pre-defined uid when healthcheck
@@ -257,8 +257,8 @@ Crowi.prototype.setupSessionConfig = async function() {
   }
   }
   // use MongoDB for session store
   // use MongoDB for session store
   else {
   else {
-    const MongoStore = require('connect-mongo')(session);
-    sessionConfig.store = new MongoStore({ mongooseConnection: mongoose.connection });
+    const MongoStore = require('connect-mongo');
+    sessionConfig.store = MongoStore.create({ client: mongoose.connection.getClient() });
   }
   }
 
 
   this.sessionConfig = sessionConfig;
   this.sessionConfig = sessionConfig;

+ 1 - 1
src/server/form/invited.js

@@ -5,5 +5,5 @@ const field = form.field;
 module.exports = form(
 module.exports = form(
   field('invitedForm.username').required().is(/^[\da-zA-Z\-_.]+$/),
   field('invitedForm.username').required().is(/^[\da-zA-Z\-_.]+$/),
   field('invitedForm.name').required(),
   field('invitedForm.name').required(),
-  field('invitedForm.password').required().is(/^[\x20-\x7F]{6,}$/),
+  field('invitedForm.password').required().is(/^[\x20-\x7F]*$/).minLength(6),
 );
 );

+ 1 - 1
src/server/form/register.js

@@ -6,6 +6,6 @@ module.exports = form(
   field('registerForm.username').required().is(/^[\da-zA-Z\-_.]+$/),
   field('registerForm.username').required().is(/^[\da-zA-Z\-_.]+$/),
   field('registerForm.name').required(),
   field('registerForm.name').required(),
   field('registerForm.email').required(),
   field('registerForm.email').required(),
-  field('registerForm.password').required().is(/^[\x20-\x7F]{6,}$/),
+  field('registerForm.password').required().is(/^[\x20-\x7F]*$/).minLength(6),
   field('registerForm[app:globalLang]'),
   field('registerForm[app:globalLang]'),
 );
 );

+ 1 - 1
src/server/middlewares/access-token-parser.js

@@ -8,7 +8,7 @@ module.exports = (crowi) => {
   return async(req, res, next) => {
   return async(req, res, next) => {
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     const accessToken = req.query.access_token || req.body.access_token || null;
     const accessToken = req.query.access_token || req.body.access_token || null;
-    if (!accessToken) {
+    if (accessToken == null || typeof accessToken !== 'string') {
       return next();
       return next();
     }
     }
 
 

+ 2 - 0
src/server/models/config.js

@@ -222,6 +222,8 @@ module.exports = function(crowi) {
         HACKMD_URI: env.HACKMD_URI || null,
         HACKMD_URI: env.HACKMD_URI || null,
         MATHJAX: env.MATHJAX || null,
         MATHJAX: env.MATHJAX || null,
         NO_CDN: env.NO_CDN || null,
         NO_CDN: env.NO_CDN || null,
+        GROWI_CLOUD_URI: env.GROWI_CLOUD_URI || null,
+        GROWI_APP_ID_FOR_GROWI_CLOUD: env.GROWI_APP_ID_FOR_GROWI_CLOUD || null,
       },
       },
       isEnabledStaleNotification: crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isEnabledStaleNotification: crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isAclEnabled: crowi.aclService.isAclEnabled(),
       isAclEnabled: crowi.aclService.isAclEnabled(),

+ 0 - 27
src/server/models/page.js

@@ -522,33 +522,6 @@ module.exports = function(crowi) {
     return true;
     return true;
   };
   };
 
 
-  pageSchema.statics.isCreatableName = function(name) {
-    const forbiddenPages = [
-      /\^|\$|\*|\+|#|%/,
-      /^\/-\/.*/,
-      /^\/_r\/.*/,
-      /^\/_apix?(\/.*)?/,
-      /^\/?https?:\/\/.+$/, // avoid miss in renaming
-      /\/{2,}/, // avoid miss in renaming
-      /\s+\/\s+/, // avoid miss in renaming
-      /.+\/edit$/,
-      /.+\.md$/,
-      /^(\.\.)$/, // see: https://github.com/weseek/growi/issues/3582
-      /(\/\.\.)\/?/, // see: https://github.com/weseek/growi/issues/3582
-      /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments|tags|share)(\/.*|$)/,
-    ];
-
-    let isCreatable = true;
-    forbiddenPages.forEach((page) => {
-      const pageNameReg = new RegExp(page);
-      if (name.match(pageNameReg)) {
-        isCreatable = false;
-      }
-    });
-
-    return isCreatable;
-  };
-
   pageSchema.statics.fixToCreatableName = function(path) {
   pageSchema.statics.fixToCreatableName = function(path) {
     return path
     return path
       .replace(/\/\//g, '/');
       .replace(/\/\//g, '/');

+ 5 - 0
src/server/routes/admin.js

@@ -498,5 +498,10 @@ module.exports = function(crowi, app) {
     return res.json(ApiResponse.success());
     return res.json(ApiResponse.success());
   };
   };
 
 
+  actions.notFound = {};
+  actions.notFound.index = function(req, res) {
+    return res.render('admin/not_found');
+  };
+
   return actions;
   return actions;
 };
 };

+ 2 - 1
src/server/routes/apiv3/pages.js

@@ -6,6 +6,7 @@ const pathUtils = require('growi-commons').pathUtils;
 
 
 const { body } = require('express-validator');
 const { body } = require('express-validator');
 const { query } = require('express-validator');
 const { query } = require('express-validator');
+const { isCreatablePage } = require('@commons/util/path-utils');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 const router = express.Router();
 const router = express.Router();
@@ -378,7 +379,7 @@ module.exports = (crowi) => {
       socketClientId: +req.body.socketClientId || undefined,
       socketClientId: +req.body.socketClientId || undefined,
     };
     };
 
 
-    if (!Page.isCreatableName(newPagePath)) {
+    if (!isCreatablePage(newPagePath)) {
       return res.apiv3Err(new ErrorV3(`Could not use the path '${newPagePath})'`, 'invalid_path'), 409);
       return res.apiv3Err(new ErrorV3(`Could not use the path '${newPagePath})'`, 'invalid_path'), 409);
     }
     }
 
 

+ 7 - 3
src/server/routes/apiv3/security-setting.js

@@ -12,6 +12,7 @@ const removeNullPropertyFromObject = require('../../../lib/util/removeNullProper
 
 
 const validator = {
 const validator = {
   generalSetting: [
   generalSetting: [
+    body('sessionMaxAge').optional({ checkFalsy: true }).trim().isInt(),
     body('restrictGuestMode').if(value => value != null).isString().isIn([
     body('restrictGuestMode').if(value => value != null).isString().isIn([
       'Deny', 'Readonly',
       'Deny', 'Readonly',
     ]),
     ]),
@@ -361,6 +362,7 @@ module.exports = (crowi) => {
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
         hideRestrictedByGroup: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup'),
         hideRestrictedByGroup: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup'),
         wikiMode: await crowi.configManager.getConfig('crowi', 'security:wikiMode'),
         wikiMode: await crowi.configManager.getConfig('crowi', 'security:wikiMode'),
+        sessionMaxAge: await crowi.configManager.getConfig('crowi', 'security:sessionMaxAge'),
       },
       },
       localSetting: {
       localSetting: {
         useOnlyEnvVarsForSomeOptions: await crowi.configManager.getConfig('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions'),
         useOnlyEnvVarsForSomeOptions: await crowi.configManager.getConfig('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions'),
@@ -566,7 +568,8 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/GeneralSetting'
    *                  $ref: '#/components/schemas/GeneralSetting'
    */
    */
   router.put('/general-setting', loginRequiredStrictly, adminRequired, csrf, validator.generalSetting, apiV3FormValidator, async(req, res) => {
   router.put('/general-setting', loginRequiredStrictly, adminRequired, csrf, validator.generalSetting, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
+    const updateData = {
+      'security:sessionMaxAge': parseInt(req.body.sessionMaxAge),
       'security:restrictGuestMode': req.body.restrictGuestMode,
       'security:restrictGuestMode': req.body.restrictGuestMode,
       'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority,
       'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority,
       'security:list-policy:hideRestrictedByOwner': req.body.hideRestrictedByOwner,
       'security:list-policy:hideRestrictedByOwner': req.body.hideRestrictedByOwner,
@@ -575,11 +578,12 @@ module.exports = (crowi) => {
     const wikiMode = await crowi.configManager.getConfig('crowi', 'security:wikiMode');
     const wikiMode = await crowi.configManager.getConfig('crowi', 'security:wikiMode');
     if (wikiMode === 'private' || wikiMode === 'public') {
     if (wikiMode === 'private' || wikiMode === 'public') {
       logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
       logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
-      delete requestParams['security:restrictGuestMode'];
+      delete updateData['security:restrictGuestMode'];
     }
     }
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', updateData);
       const securitySettingParams = {
       const securitySettingParams = {
+        sessionMaxAge: await crowi.configManager.getConfig('crowi', 'security:sessionMaxAge'),
         restrictGuestMode: await crowi.configManager.getConfig('crowi', 'security:restrictGuestMode'),
         restrictGuestMode: await crowi.configManager.getConfig('crowi', 'security:restrictGuestMode'),
         pageCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
         pageCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),

+ 61 - 9
src/server/routes/apiv3/share-links.js

@@ -8,7 +8,7 @@ const express = require('express');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const { body } = require('express-validator');
+const { body, query, param } = require('express-validator');
 
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
@@ -28,14 +28,19 @@ module.exports = (crowi) => {
   const csrf = require('../../middlewares/csrf')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const ShareLink = crowi.model('ShareLink');
   const ShareLink = crowi.model('ShareLink');
+  const Page = crowi.model('Page');
 
 
+  validator.getShareLinks = [
+    // validate the page id is MongoId
+    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+  ];
 
 
   /**
   /**
    * @swagger
    * @swagger
    *
    *
    *  paths:
    *  paths:
    *    /share-links/:
    *    /share-links/:
-   *      post:
+   *      get:
    *        tags: [ShareLink]
    *        tags: [ShareLink]
    *        description: get share links
    *        description: get share links
    *        parameters:
    *        parameters:
@@ -49,10 +54,19 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: Succeeded to get share links
    *            description: Succeeded to get share links
    */
    */
-  router.get('/', loginRequired, async(req, res) => {
+  router.get('/', loginRequired, validator.getShareLinks, apiV3FormValidator, async(req, res) => {
     const { relatedPage } = req.query;
     const { relatedPage } = req.query;
+
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
+    }
+
     try {
     try {
-      const shareLinksResult = await ShareLink.find({ relatedPage: { $in: relatedPage } }).populate({ path: 'relatedPage', select: 'path' });
+      const shareLinksResult = await ShareLink.find({ relatedPage }).populate({ path: 'relatedPage', select: 'path' });
       return res.apiv3({ shareLinksResult });
       return res.apiv3({ shareLinksResult });
     }
     }
     catch (err) {
     catch (err) {
@@ -63,8 +77,8 @@ module.exports = (crowi) => {
   });
   });
 
 
   validator.shareLinkStatus = [
   validator.shareLinkStatus = [
-    // validate the page id is null
-    body('relatedPage').not().isEmpty().withMessage('Page Id is null'),
+    // validate the page id is MongoId
+    body('relatedPage').isMongoId().withMessage('Page Id is required'),
     // validate expireation date is not empty, is not before today and is date.
     // validate expireation date is not empty, is not before today and is date.
     body('expiredAt').if(value => value != null).isAfter(today.toString()).withMessage('Your Selected date is past'),
     body('expiredAt').if(value => value != null).isAfter(today.toString()).withMessage('Your Selected date is past'),
     // validate the length of description is max 100.
     // validate the length of description is max 100.
@@ -103,6 +117,15 @@ module.exports = (crowi) => {
 
 
   router.post('/', loginRequired, csrf, validator.shareLinkStatus, apiV3FormValidator, async(req, res) => {
   router.post('/', loginRequired, csrf, validator.shareLinkStatus, apiV3FormValidator, async(req, res) => {
     const { relatedPage, expiredAt, description } = req.body;
     const { relatedPage, expiredAt, description } = req.body;
+
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
+    }
+
     const ShareLink = crowi.model('ShareLink');
     const ShareLink = crowi.model('ShareLink');
 
 
     try {
     try {
@@ -116,6 +139,12 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+
+  validator.deleteShareLinks = [
+    // validate the page id is MongoId
+    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+  ];
+
   /**
   /**
   * @swagger
   * @swagger
   *
   *
@@ -135,9 +164,17 @@ module.exports = (crowi) => {
   *          200:
   *          200:
   *            description: Succeeded to delete o all share links related one page
   *            description: Succeeded to delete o all share links related one page
   */
   */
-  router.delete('/', loginRequired, csrf, async(req, res) => {
+  router.delete('/', loginRequired, csrf, validator.deleteShareLinks, apiV3FormValidator, async(req, res) => {
     const { relatedPage } = req.query;
     const { relatedPage } = req.query;
 
 
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'delete-shareLinks-for-page-failed'));
+    }
+
     try {
     try {
       const deletedShareLink = await ShareLink.remove({ relatedPage });
       const deletedShareLink = await ShareLink.remove({ relatedPage });
       return res.apiv3(deletedShareLink);
       return res.apiv3(deletedShareLink);
@@ -174,6 +211,10 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  validator.deleteShareLink = [
+    param('id').isMongoId().withMessage('ShareLink Id is required'),
+  ];
+
   /**
   /**
   * @swagger
   * @swagger
   *
   *
@@ -192,11 +233,22 @@ module.exports = (crowi) => {
   *          200:
   *          200:
   *            description: Succeeded to delete one share link
   *            description: Succeeded to delete one share link
   */
   */
-  router.delete('/:id', loginRequired, csrf, async(req, res) => {
+  router.delete('/:id', loginRequired, csrf, validator.deleteShareLink, apiV3FormValidator, async(req, res) => {
     const { id } = req.params;
     const { id } = req.params;
 
 
     try {
     try {
-      const deletedShareLink = await ShareLink.findOneAndRemove({ _id: id });
+      const deletedShareLink = await ShareLink.findOne({ _id: id });
+
+      // check permission
+      const page = await Page.findByIdAndViewer(deletedShareLink.relatedPage, req.user);
+      if (page == null) {
+        const msg = 'Page is not found or forbidden';
+        logger.error('Error', msg);
+        return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
+      }
+
+      // remove
+      await deletedShareLink.remove();
       return res.apiv3({ deletedShareLink });
       return res.apiv3({ deletedShareLink });
     }
     }
     catch (err) {
     catch (err) {

+ 2 - 0
src/server/routes/index.js

@@ -117,6 +117,8 @@ module.exports = function(crowi, app) {
   app.get('/admin/export'                       , loginRequiredStrictly , adminRequired ,admin.export.index);
   app.get('/admin/export'                       , loginRequiredStrictly , adminRequired ,admin.export.index);
   app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
   app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
 
 
+  app.get('/admin/*'                       , loginRequiredStrictly ,adminRequired, admin.notFound.index);
+
   app.get('/me'                       , loginRequiredStrictly , me.index);
   app.get('/me'                       , loginRequiredStrictly , me.index);
   // external-accounts
   // external-accounts
   app.get('/me/external-accounts'                         , loginRequiredStrictly , me.externalAccounts.list);
   app.get('/me/external-accounts'                         , loginRequiredStrictly , me.externalAccounts.list);

+ 2 - 2
src/server/routes/installer.js

@@ -92,12 +92,12 @@ module.exports = function(crowi) {
     req.logIn(adminUser, (err) => {
     req.logIn(adminUser, (err) => {
       if (err) {
       if (err) {
         req.flash('successMessage', req.t('message.complete_to_install1'));
         req.flash('successMessage', req.t('message.complete_to_install1'));
-        req.session.redirectTo = '/admin/app';
+        req.session.redirectTo = '/';
         return res.redirect('/login');
         return res.redirect('/login');
       }
       }
 
 
       req.flash('successMessage', req.t('message.complete_to_install2'));
       req.flash('successMessage', req.t('message.complete_to_install2'));
-      return res.redirect('/admin/app');
+      return res.redirect('/');
     });
     });
   };
   };
 
 

+ 2 - 121
src/server/routes/page.js

@@ -1,3 +1,4 @@
+const { isCreatablePage } = require('@commons/util/path-utils');
 const { serializePageSecurely } = require('../models/serializers/page-serializer');
 const { serializePageSecurely } = require('../models/serializers/page-serializer');
 const { serializeRevisionSecurely } = require('../models/serializers/revision-serializer');
 const { serializeRevisionSecurely } = require('../models/serializers/revision-serializer');
 const { serializeUserSecurely } = require('../models/serializers/user-serializer');
 const { serializeUserSecurely } = require('../models/serializers/user-serializer');
@@ -478,12 +479,10 @@ module.exports = function(crowi, app) {
   actions.notFound = async function(req, res) {
   actions.notFound = async function(req, res) {
     const path = getPathFromRequest(req);
     const path = getPathFromRequest(req);
 
 
-    const isCreatable = Page.isCreatableName(path);
-
     let view;
     let view;
     const renderVars = { path };
     const renderVars = { path };
 
 
-    if (!isCreatable) {
+    if (!isCreatablePage(path)) {
       view = 'layout-growi/not_creatable';
       view = 'layout-growi/not_creatable';
     }
     }
     else if (req.isForbidden) {
     else if (req.isForbidden) {
@@ -1231,124 +1230,6 @@ module.exports = function(crowi, app) {
     return res.json(ApiResponse.success(result));
     return res.json(ApiResponse.success(result));
   };
   };
 
 
-  /**
-   * @swagger
-   *
-   *    /pages.rename:
-   *      post:
-   *        tags: [Pages, CrowiCompatibles]
-   *        operationId: renamePage
-   *        summary: /pages.rename
-   *        description: Rename page
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  page_id:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  path:
-   *                    $ref: '#/components/schemas/Page/properties/path'
-   *                  revision_id:
-   *                    $ref: '#/components/schemas/Revision/properties/_id'
-   *                  new_path:
-   *                    type: string
-   *                    description: new path
-   *                    example: /user/alice/new_test
-   *                  create_redirect:
-   *                    type: boolean
-   *                    description: whether redirect page
-   *                required:
-   *                  - page_id
-   *        responses:
-   *          200:
-   *            description: Succeeded to rename page.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    page:
-   *                      $ref: '#/components/schemas/Page'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * @api {post} /pages.rename Rename page
-   * @apiName RenamePage
-   * @apiGroup Page
-   *
-   * @apiParam {String} page_id Page Id.
-   * @apiParam {String} path
-   * @apiParam {String} revision_id
-   * @apiParam {String} new_path New path name.
-   * @apiParam {Bool} create_redirect
-   */
-  api.rename = async function(req, res) {
-    const pageId = req.body.page_id;
-    const previousRevision = req.body.revision_id || null;
-    let newPagePath = pathUtils.normalizePath(req.body.new_path);
-    const options = {
-      createRedirectPage: (req.body.create_redirect != null),
-      updateMetadata: (req.body.remain_metadata == null),
-      socketClientId: +req.body.socketClientId || undefined,
-    };
-    const isRecursively = (req.body.recursively != null);
-
-    if (!Page.isCreatableName(newPagePath)) {
-      return res.json(ApiResponse.error(`Could not use the path '${newPagePath})'`, 'invalid_path'));
-    }
-
-    // check whether path starts slash
-    newPagePath = pathUtils.addHeadingSlash(newPagePath);
-
-    const isExist = await Page.count({ path: newPagePath }) > 0;
-    if (isExist) {
-      // if page found, cannot cannot rename to that path
-      return res.json(ApiResponse.error(`'new_path=${newPagePath}' already exists`, 'already_exists'));
-    }
-
-    let page;
-
-    try {
-      page = await Page.findByIdAndViewer(pageId, req.user);
-
-      if (page == null) {
-        return res.json(ApiResponse.error(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'));
-      }
-
-      if (!page.isUpdatable(previousRevision)) {
-        return res.json(ApiResponse.error('Someone could update this page, so couldn\'t delete.', 'outdated'));
-      }
-
-      page = await crowi.pageService.renamePage(page, newPagePath, req.user, options, isRecursively);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('Failed to update page.', 'unknown'));
-    }
-
-    const result = {};
-    result.page = page; // TODO consider to use serializePageSecurely method -- 2018.08.06 Yuki Takei
-
-    res.json(ApiResponse.success(result));
-
-    try {
-      // global notification
-      await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_MOVE, page, req.user, {
-        oldPath: req.body.path,
-      });
-    }
-    catch (err) {
-      logger.error('Move notification failed', err);
-    }
-
-    return page;
-  };
-
   /**
   /**
    * @swagger
    * @swagger
    *
    *

+ 10 - 0
src/server/service/config-loader.js

@@ -242,6 +242,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.STRING,
     type:    TYPES.STRING,
     default: undefined,
     default: undefined,
   },
   },
+  SESSION_MAX_AGE: {
+    ns:      'crowi',
+    key:     'security:sessionMaxAge',
+    type:    TYPES.NUMBER,
+    default: undefined,
+  },
   USER_UPPER_LIMIT: {
   USER_UPPER_LIMIT: {
     ns:      'crowi',
     ns:      'crowi',
     key:     'security:userUpperLimit',
     key:     'security:userUpperLimit',
@@ -419,6 +425,10 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
   SLACK_BOT_TYPE: {
   SLACK_BOT_TYPE: {
     ns:      'crowi',
     ns:      'crowi',
     key:     'slackbot:currentBotType', // 'officialBot' || 'customBotWithoutProxy' || 'customBotWithProxy'
     key:     'slackbot:currentBotType', // 'officialBot' || 'customBotWithoutProxy' || 'customBotWithProxy'
+  },
+  GROWI_APP_ID_FOR_GROWI_CLOUD: {
+    ns:      'crowi',
+    key:     'app:growiAppIdForCloud',
     type:    TYPES.STRING,
     type:    TYPES.STRING,
     default: null,
     default: null,
   },
   },

+ 3 - 1
src/server/util/createGrowiPagesFromImports.js

@@ -1,3 +1,5 @@
+const { isCreatablePage } = require('@commons/util/path-utils');
+
 module.exports = (crowi) => {
 module.exports = (crowi) => {
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
 
 
@@ -18,7 +20,7 @@ module.exports = (crowi) => {
       const path = page.path;
       const path = page.path;
       const user = page.user;
       const user = page.user;
       const body = page.body;
       const body = page.body;
-      const isCreatableName = await Page.isCreatableName(path);
+      const isCreatableName = isCreatablePage(path);
       const isPageNameTaken = await Page.findByPathAndViewer(path, user);
       const isPageNameTaken = await Page.findByPathAndViewer(path, user);
 
 
       if (isCreatableName && !isPageNameTaken) {
       if (isCreatableName && !isPageNameTaken) {

+ 7 - 0
src/server/views/admin/not_found.html

@@ -0,0 +1,7 @@
+{% extends '../layout/admin.html' %}
+
+{% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('not_found_page.page_not_exist')) }}{% endblock %}
+
+{% block content_main %}
+<h1 class="title">{{ t('not_found_page.page_not_exist') }}</h1>
+{% endblock content_main %}

+ 1 - 1
src/server/views/widget/alert_siteurl_undefined.html

@@ -1,6 +1,6 @@
 {% if !getConfig('crowi', 'app:siteUrl') %}
 {% if !getConfig('crowi', 'app:siteUrl') %}
 <div class="alert alert-danger d-edit-none mb-0 px-4 py-2">
 <div class="alert alert-danger d-edit-none mb-0 px-4 py-2">
   <i class="icon-exclamation"></i>
   <i class="icon-exclamation"></i>
-  {{ t("security_setting.alert_siteUrl_is_not_set", { link:t('App Settings')}) }}
+  {{ t("security_setting.alert_siteUrl_is_not_set", { link: t('App Settings')}) }} &gt;&gt; <a href="/admin/app">{{t('App Settings')}}<i class="icon-login"></i></a>
 </div>
 </div>
 {% endif %}
 {% endif %}

+ 1 - 1
src/server/views/widget/page_content.html

@@ -24,7 +24,7 @@
   data-page-last-update-username="{% if page && page.lastUpdateUser %}{{ page.lastUpdateUser.name }}{% endif %}"
   data-page-last-update-username="{% if page && page.lastUpdateUser %}{{ page.lastUpdateUser.name }}{% endif %}"
   data-page-updated-at="{% if page %}{{ page.updatedAt|datetz('Y/m/d H:i:s') }}{% endif %}"
   data-page-updated-at="{% if page %}{{ page.updatedAt|datetz('Y/m/d H:i:s') }}{% endif %}"
   data-page-delete-username="{% if page && page.deleteUser %}{{ page.deleteUser.name }}{% endif %}"
   data-page-delete-username="{% if page && page.deleteUser %}{{ page.deleteUser.name }}{% endif %}"
-  data-page-deleted-at="{% if page %}{{ page.deletedAt|datetz('Y/m/d H:i:s') }}{% endif %}"
+  data-page-deleted-at="{% if page && page.deletedAt %}{{ page.deletedAt|datetz('Y/m/d H:i:s') }}{% endif %}"
   data-page-has-children="{% if pages.length > 0 %}true{% else %}false{% endif %}"
   data-page-has-children="{% if pages.length > 0 %}true{% else %}false{% endif %}"
   data-page-user="{% if pageUser %}{{ pageUser|json }}{% else %}null{% endif %}"
   data-page-user="{% if pageUser %}{{ pageUser|json }}{% else %}null{% endif %}"
   data-page-ids-of-seen-users="{{ page.seenUsers|slice(-15)|default([])|reverse|join(',') }}"
   data-page-ids-of-seen-users="{{ page.seenUsers|slice(-15)|default([])|reverse|join(',') }}"

+ 0 - 50
src/test/models/page.test.js

@@ -168,56 +168,6 @@ describe('Page', () => {
     });
     });
   });
   });
 
 
-  describe('.isCreatableName', () => {
-    test('should decide creatable or not', () => {
-      expect(Page.isCreatableName('/hoge')).toBeTruthy();
-
-      // edge cases
-      expect(Page.isCreatableName('/me')).toBeFalsy();
-      expect(Page.isCreatableName('/me/')).toBeFalsy();
-      expect(Page.isCreatableName('/me/x')).toBeFalsy();
-      expect(Page.isCreatableName('/meeting')).toBeTruthy();
-      expect(Page.isCreatableName('/meeting/x')).toBeTruthy();
-
-      // end with "edit"
-      expect(Page.isCreatableName('/meeting/edit')).toBeFalsy();
-
-      // under score
-      expect(Page.isCreatableName('/_')).toBeTruthy();
-      expect(Page.isCreatableName('/_template')).toBeTruthy();
-      expect(Page.isCreatableName('/__template')).toBeTruthy();
-      expect(Page.isCreatableName('/_r/x')).toBeFalsy();
-      expect(Page.isCreatableName('/_api')).toBeFalsy();
-      expect(Page.isCreatableName('/_apix')).toBeFalsy();
-      expect(Page.isCreatableName('/_api/x')).toBeFalsy();
-
-      expect(Page.isCreatableName('/hoge/xx.md')).toBeFalsy();
-
-      // relative path
-      expect(Page.isCreatableName('/..')).toBeFalsy();
-      expect(Page.isCreatableName('/../page')).toBeFalsy();
-      expect(Page.isCreatableName('/page/..')).toBeFalsy();
-      expect(Page.isCreatableName('/page/../page')).toBeFalsy();
-
-      // start with https?
-      expect(Page.isCreatableName('/http://demo.growi.org/hoge')).toBeFalsy();
-      expect(Page.isCreatableName('/https://demo.growi.org/hoge')).toBeFalsy();
-      expect(Page.isCreatableName('http://demo.growi.org/hoge')).toBeFalsy();
-      expect(Page.isCreatableName('https://demo.growi.org/hoge')).toBeFalsy();
-
-      expect(Page.isCreatableName('/ the / path / with / space')).toBeFalsy();
-
-      const forbidden = ['installer', 'register', 'login', 'logout',
-                         'admin', 'files', 'trash', 'paste', 'comments'];
-      for (let i = 0; i < forbidden.length; i++) {
-        const pn = forbidden[i];
-        expect(Page.isCreatableName(`/${pn}`)).toBeFalsy();
-        expect(Page.isCreatableName(`/${pn}/`)).toBeFalsy();
-        expect(Page.isCreatableName(`/${pn}/abc`)).toBeFalsy();
-      }
-    });
-  });
-
   describe('.isAccessiblePageByViewer', () => {
   describe('.isAccessiblePageByViewer', () => {
     describe('with a granted page', () => {
     describe('with a granted page', () => {
       test('should return true with granted user', async() => {
       test('should return true with granted user', async() => {

+ 51 - 1
src/test/util/path-utils.test.js

@@ -1,4 +1,4 @@
-const { isTopPage, convertToNewAffiliationPath } = require('../../lib/util/path-utils');
+const { isTopPage, convertToNewAffiliationPath, isCreatablePage } = require('../../lib/util/path-utils');
 
 
 
 
 describe('TopPage Path test', () => {
 describe('TopPage Path test', () => {
@@ -55,3 +55,53 @@ describe('convertToNewAffiliationPath test', () => {
     }).toThrow();
     }).toThrow();
   });
   });
 });
 });
+
+describe('isCreatablePage test', () => {
+  test('should decide creatable or not', () => {
+    expect(isCreatablePage('/hoge')).toBeTruthy();
+
+    // edge cases
+    expect(isCreatablePage('/me')).toBeFalsy();
+    expect(isCreatablePage('/me/')).toBeFalsy();
+    expect(isCreatablePage('/me/x')).toBeFalsy();
+    expect(isCreatablePage('/meeting')).toBeTruthy();
+    expect(isCreatablePage('/meeting/x')).toBeTruthy();
+
+    // end with "edit"
+    expect(isCreatablePage('/meeting/edit')).toBeFalsy();
+
+    // under score
+    expect(isCreatablePage('/_')).toBeTruthy();
+    expect(isCreatablePage('/_template')).toBeTruthy();
+    expect(isCreatablePage('/__template')).toBeTruthy();
+    expect(isCreatablePage('/_r/x')).toBeFalsy();
+    expect(isCreatablePage('/_api')).toBeFalsy();
+    expect(isCreatablePage('/_apix')).toBeFalsy();
+    expect(isCreatablePage('/_api/x')).toBeFalsy();
+
+    expect(isCreatablePage('/hoge/xx.md')).toBeFalsy();
+
+    // relative path
+    expect(isCreatablePage('/..')).toBeFalsy();
+    expect(isCreatablePage('/../page')).toBeFalsy();
+    expect(isCreatablePage('/page/..')).toBeFalsy();
+    expect(isCreatablePage('/page/../page')).toBeFalsy();
+
+    // start with https?
+    expect(isCreatablePage('/http://demo.growi.org/hoge')).toBeFalsy();
+    expect(isCreatablePage('/https://demo.growi.org/hoge')).toBeFalsy();
+    expect(isCreatablePage('http://demo.growi.org/hoge')).toBeFalsy();
+    expect(isCreatablePage('https://demo.growi.org/hoge')).toBeFalsy();
+
+    expect(isCreatablePage('/ the / path / with / space')).toBeFalsy();
+
+    const forbidden = ['installer', 'register', 'login', 'logout',
+                       'admin', 'files', 'trash', 'paste', 'comments'];
+    for (let i = 0; i < forbidden.length; i++) {
+      const pn = forbidden[i];
+      expect(isCreatablePage(`/${pn}`)).toBeFalsy();
+      expect(isCreatablePage(`/${pn}/`)).toBeFalsy();
+      expect(isCreatablePage(`/${pn}/abc`)).toBeFalsy();
+    }
+  });
+});

+ 219 - 146
yarn.lock

@@ -2955,6 +2955,13 @@
     "@types/express" "*"
     "@types/express" "*"
     "@types/node" "*"
     "@types/node" "*"
 
 
+"@types/bson@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.3.tgz#30889d2ffde6262abbe38659364c631454999fbf"
+  integrity sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/cache-manager@^3.4.0":
 "@types/cache-manager@^3.4.0":
   version "3.4.0"
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/@types/cache-manager/-/cache-manager-3.4.0.tgz#414136ea3807a8cd071b8f20370c5df5dbffd382"
   resolved "https://registry.yarnpkg.com/@types/cache-manager/-/cache-manager-3.4.0.tgz#414136ea3807a8cd071b8f20370c5df5dbffd382"
@@ -3098,6 +3105,14 @@
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
 
+"@types/mongodb@^3.5.27":
+  version "3.6.17"
+  resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.17.tgz#a8893654989cb11e9a241858bc530060b6fd126d"
+  integrity sha512-9hhgvYPdC5iHyyksPcKCu45gfaAIPQHKHGdvNXu4582DmOZX3wrUJIJPT40o4G1oTKPgpMMFqZglOTjhnYoF+A==
+  dependencies:
+    "@types/bson" "*"
+    "@types/node" "*"
+
 "@types/multer@^1.4.5":
 "@types/multer@^1.4.5":
   version "1.4.5"
   version "1.4.5"
   resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.5.tgz#db0557562307e9adb6661a9500c334cd7ddd0cd9"
   resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.5.tgz#db0557562307e9adb6661a9500c334cd7ddd0cd9"
@@ -3856,18 +3871,18 @@ archiver-utils@^2.1.0:
     normalize-path "^3.0.0"
     normalize-path "^3.0.0"
     readable-stream "^2.0.0"
     readable-stream "^2.0.0"
 
 
-archiver@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0"
-  integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==
+archiver@^5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba"
+  integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==
   dependencies:
   dependencies:
     archiver-utils "^2.1.0"
     archiver-utils "^2.1.0"
-    async "^2.6.3"
+    async "^3.2.0"
     buffer-crc32 "^0.2.1"
     buffer-crc32 "^0.2.1"
-    glob "^7.1.4"
-    readable-stream "^3.4.0"
-    tar-stream "^2.1.0"
-    zip-stream "^2.1.2"
+    readable-stream "^3.6.0"
+    readdir-glob "^1.0.0"
+    tar-stream "^2.2.0"
+    zip-stream "^4.1.0"
 
 
 are-we-there-yet@~1.1.2:
 are-we-there-yet@~1.1.2:
   version "1.1.5"
   version "1.1.5"
@@ -4008,6 +4023,16 @@ asn1.js@^4.0.0:
     inherits "^2.0.1"
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
     minimalistic-assert "^1.0.0"
 
 
+asn1.js@^5.4.1:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
+  integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
+  dependencies:
+    bn.js "^4.0.0"
+    inherits "^2.0.1"
+    minimalistic-assert "^1.0.0"
+    safer-buffer "^2.1.0"
+
 asn1@0.2.3:
 asn1@0.2.3:
   version "0.2.3"
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
@@ -4079,12 +4104,12 @@ async@1.5.2, async@^1.4.0:
   version "1.5.2"
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
 
-async@3.2.0, async@>=3.2.0:
+async@3.2.0, async@>=3.2.0, async@^3.2.0:
   version "3.2.0"
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
   integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
   integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
 
 
-async@^2.6.2, async@^2.6.3:
+async@^2.6.2:
   version "2.6.3"
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
   integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
   integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
@@ -4504,12 +4529,14 @@ bl@^2.2.1:
     readable-stream "^2.3.5"
     readable-stream "^2.3.5"
     safe-buffer "^5.1.1"
     safe-buffer "^5.1.1"
 
 
-bl@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
-  integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
+bl@^4.0.3:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
   dependencies:
   dependencies:
-    readable-stream "^3.0.1"
+    buffer "^5.5.0"
+    inherits "^2.0.4"
+    readable-stream "^3.4.0"
 
 
 blob@0.0.4:
 blob@0.0.4:
   version "0.0.4"
   version "0.0.4"
@@ -4849,13 +4876,13 @@ buffer@4.9.1, buffer@^4.3.0:
     ieee754 "^1.1.4"
     ieee754 "^1.1.4"
     isarray "^1.0.0"
     isarray "^1.0.0"
 
 
-buffer@^5.1.0:
-  version "5.4.2"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.2.tgz#2012872776206182480eccb2c0fba5f672a2efef"
-  integrity sha512-iy9koArjAFCzGnx3ZvNA6Z0clIbbFgbdWQ0mKD3hO0krOrZh8UgA6qMKcZvwLJxS+D6iVR76+5/pV56yMNYTag==
+buffer@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
   dependencies:
   dependencies:
-    base64-js "^1.0.2"
-    ieee754 "^1.1.4"
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
 
 
 buffer@^6.0.3:
 buffer@^6.0.3:
   version "6.0.3"
   version "6.0.3"
@@ -5694,16 +5721,16 @@ commander@^5.1.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
   resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
   integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
   integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
 
 
-commander@^6.1.0:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
-  integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
-
 commander@^6.2.1:
 commander@^6.2.1:
   version "6.2.1"
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
   integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
   integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
 
 
+commander@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+  integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
 comment-json@^4.1.0:
 comment-json@^4.1.0:
   version "4.1.0"
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.1.0.tgz#09d08f0fbc4ad5eeccbac20f469adbb967dcbd2c"
   resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.1.0.tgz#09d08f0fbc4ad5eeccbac20f469adbb967dcbd2c"
@@ -5748,15 +5775,15 @@ component-inherit@0.0.3:
   version "0.0.3"
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
   resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
 
 
-compress-commons@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610"
-  integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==
+compress-commons@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d"
+  integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==
   dependencies:
   dependencies:
     buffer-crc32 "^0.2.13"
     buffer-crc32 "^0.2.13"
-    crc32-stream "^3.0.1"
+    crc32-stream "^4.0.2"
     normalize-path "^3.0.0"
     normalize-path "^3.0.0"
-    readable-stream "^2.3.6"
+    readable-stream "^3.6.0"
 
 
 compressible@^2.0.12:
 compressible@^2.0.12:
   version "2.0.17"
   version "2.0.17"
@@ -5860,12 +5887,14 @@ connect-injector@^0.4.2:
     stream-buffers "^0.2.3"
     stream-buffers "^0.2.3"
     uberproto "^1.1.0"
     uberproto "^1.1.0"
 
 
-connect-mongo@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-3.2.0.tgz#20f776c7f2a9d8144fc76cfdcbf33edb05eb4d52"
-  integrity sha512-0Mx88079Z20CG909wCFlR3UxhMYGg6Ibn1hkIje1hwsqOLWtL9HJV+XD0DAjUvQScK6WqY/FA8tSVQM9rR64Rw==
+connect-mongo@^4.4.1:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-4.4.1.tgz#b817f97940539b46c9116e92cf2f344c120fae7d"
+  integrity sha512-I1QUE2tSGPtIBDAL2sFqUEPspDeJOR0u4g+N41ARJZk958pncu2PBG48Ev++fnldljobpIfdafak7hSlPYarvA==
   dependencies:
   dependencies:
-    mongodb "^3.1.0"
+    debug "^4.3.1"
+    kruptein "^3.0.0"
+    mongodb "3.6.5"
 
 
 connect-redis@^4.0.4:
 connect-redis@^4.0.4:
   version "4.0.4"
   version "4.0.4"
@@ -6164,20 +6193,21 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     path-type "^4.0.0"
     yaml "^1.10.0"
     yaml "^1.10.0"
 
 
-crc32-stream@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85"
-  integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==
+crc-32@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
+  integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==
   dependencies:
   dependencies:
-    crc "^3.4.4"
-    readable-stream "^3.4.0"
+    exit-on-epipe "~1.0.1"
+    printj "~1.1.0"
 
 
-crc@^3.4.4:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
-  integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
+crc32-stream@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007"
+  integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==
   dependencies:
   dependencies:
-    buffer "^5.1.0"
+    crc-32 "^1.2.0"
+    readable-stream "^3.4.0"
 
 
 create-ecdh@^4.0.0:
 create-ecdh@^4.0.0:
   version "4.0.0"
   version "4.0.0"
@@ -6554,10 +6584,10 @@ date-fns@^2.0.0:
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
   integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
   integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
 
 
-date-fns@^2.16.1:
-  version "2.16.1"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
-  integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
+date-fns@^2.19.0:
+  version "2.22.1"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
+  integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
 
 
 date-format@^2.0.0, date-format@^2.1.0:
 date-format@^2.0.0, date-format@^2.1.0:
   version "2.1.0"
   version "2.1.0"
@@ -6835,6 +6865,11 @@ diff@^4.0.1:
   version "4.0.1"
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
 
 
+diff@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
 diffie-hellman@^5.0.0:
 diffie-hellman@^5.0.0:
   version "5.0.2"
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -7160,11 +7195,12 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   dependencies:
   dependencies:
     once "^1.4.0"
     once "^1.4.0"
 
 
-end-of-stream@~1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
+end-of-stream@~1.4.1:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+  integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
   dependencies:
   dependencies:
-    once "~1.3.0"
+    once "^1.4.0"
 
 
 engine.io-client@~3.2.0:
 engine.io-client@~3.2.0:
   version "3.2.1"
   version "3.2.1"
@@ -7810,6 +7846,11 @@ exenv@^1.2.2:
   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
   integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
   integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
 
 
+exit-on-epipe@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
+  integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
+
 exit@0.1.2, exit@^0.1.2:
 exit@0.1.2, exit@^0.1.2:
   version "0.1.2"
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -7885,6 +7926,11 @@ express-graceful-exit@=0.5.0:
   dependencies:
   dependencies:
     underscore "^1.4.4"
     underscore "^1.4.4"
 
 
+express-mongo-sanitize@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/express-mongo-sanitize/-/express-mongo-sanitize-2.1.0.tgz#a8c647787c25ded6e97b5e864d113e7687c5d471"
+  integrity sha512-ELGeH/Tx+kJGn3klCzSmOewfN1ezJQrkqzq83dl/K3xhd5PUbvLtiD5CiuYRmQfoZPL4rUEVjANf/YjE2BpTWQ==
+
 express-session@^1.16.1:
 express-session@^1.16.1:
   version "1.16.1"
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
@@ -8558,16 +8604,6 @@ fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     jsonfile "^4.0.0"
     universalify "^0.1.0"
     universalify "^0.1.0"
 
 
-fs-extra@^9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
-  integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
-  dependencies:
-    at-least-node "^1.0.0"
-    graceful-fs "^4.2.0"
-    jsonfile "^6.0.1"
-    universalify "^1.0.0"
-
 fs-extra@^9.1.0:
 fs-extra@^9.1.0:
   version "9.1.0"
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@@ -9665,15 +9701,15 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
   dependencies:
   dependencies:
     postcss "^7.0.14"
     postcss "^7.0.14"
 
 
-ieee754@^1.1.4:
-  version "1.1.8"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
-
-ieee754@^1.2.1:
+ieee754@^1.1.13, ieee754@^1.2.1:
   version "1.2.1"
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
 
+ieee754@^1.1.4:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+
 ienoopen@1.0.0:
 ienoopen@1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b"
   resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b"
@@ -9815,7 +9851,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     once "^1.3.0"
     wrappy "1"
     wrappy "1"
 
 
-inherits@2, inherits@2.0.4, inherits@~2.0.0, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -11194,10 +11230,10 @@ jws@^4.0.0:
     jwa "^2.0.0"
     jwa "^2.0.0"
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
 
 
-kareem@2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87"
-  integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==
+kareem@2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93"
+  integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==
 
 
 keygrip@~1.0.2:
 keygrip@~1.0.2:
   version "1.0.2"
   version "1.0.2"
@@ -11245,6 +11281,13 @@ known-css-properties@^0.18.0:
   resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.18.0.tgz#d6e00b56ee1d5b0d171fd86df1583cfb012c521f"
   resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.18.0.tgz#d6e00b56ee1d5b0d171fd86df1583cfb012c521f"
   integrity sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw==
   integrity sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw==
 
 
+kruptein@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/kruptein/-/kruptein-3.0.0.tgz#9a7960248c9758f1e75522d2b87b8388bd40874f"
+  integrity sha512-Fh5sIb+3XI9L12GsgeBQqXVRPLB1HVViKSUkqPPOcqTEX4NwoF8Z3pEfMSl3Psd1j+QlloV8Uxxwp4gk3aFBGA==
+  dependencies:
+    asn1.js "^5.4.1"
+
 last-call-webpack-plugin@^3.0.0:
 last-call-webpack-plugin@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555"
   resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555"
@@ -11637,11 +11680,6 @@ lodash@^4.17.2, lodash@^4.17.4:
   version "4.17.4"
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
 
-lodash@^4.17.20:
-  version "4.17.20"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
-  integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
-
 lodash@^4.17.5:
 lodash@^4.17.5:
   version "4.17.10"
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -12147,19 +12185,19 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
     snapdragon "^0.8.1"
     snapdragon "^0.8.1"
     to-regex "^3.0.2"
     to-regex "^3.0.2"
 
 
-migrate-mongo@^8.1.4:
-  version "8.1.4"
-  resolved "https://registry.yarnpkg.com/migrate-mongo/-/migrate-mongo-8.1.4.tgz#700fb921fae0aecc571c6b251486e0fd1fac93e7"
-  integrity sha512-iuJiG86Qzr1w9B5j6jB6akZO0w040DTEWtKdclISaQg7KdovtXGYDmW8hehDsULY5oFGjNtJv/ABsOPlz0c5mw==
+migrate-mongo@^8.2.2:
+  version "8.2.2"
+  resolved "https://registry.yarnpkg.com/migrate-mongo/-/migrate-mongo-8.2.2.tgz#6c4aaf9bbc6c001276320e5e406b0e21a7046d9a"
+  integrity sha512-RK8zE9QGzaDZ8xN+Cyb/mUhSIA1pkj1Q/aNYeH4QB9U2UNfKej1lmxh20Ot1xFl1C62ro3hqiaZ9QErzCN3qPw==
   dependencies:
   dependencies:
     cli-table3 "^0.6.0"
     cli-table3 "^0.6.0"
-    commander "^6.1.0"
-    date-fns "^2.16.1"
+    commander "^7.1.0"
+    date-fns "^2.19.0"
     fn-args "^5.0.0"
     fn-args "^5.0.0"
-    fs-extra "^9.0.1"
-    lodash "^4.17.20"
-    mongodb "^3.6.2"
-    p-each-series "^2.1.0"
+    fs-extra "^9.1.0"
+    lodash "^4.17.21"
+    mongodb "^3.6.4"
+    p-each-series "^2.2.0"
 
 
 miller-rabin@^4.0.0:
 miller-rabin@^4.0.0:
   version "4.0.1"
   version "4.0.1"
@@ -12512,10 +12550,10 @@ moment@^2.19.3:
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
   integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
   integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
 
 
-mongodb@3.6.2, mongodb@^3.1.0, mongodb@^3.6.2:
-  version "3.6.2"
-  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.2.tgz#1154a4ac107bf1375112d83a29c5cf97704e96b6"
-  integrity sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==
+mongodb@3.6.5:
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.5.tgz#c27d786fd4d3c83dc19302483707d12a9d2aee5f"
+  integrity sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==
   dependencies:
   dependencies:
     bl "^2.2.1"
     bl "^2.2.1"
     bson "^1.1.4"
     bson "^1.1.4"
@@ -12525,6 +12563,19 @@ mongodb@3.6.2, mongodb@^3.1.0, mongodb@^3.6.2:
   optionalDependencies:
   optionalDependencies:
     saslprep "^1.0.0"
     saslprep "^1.0.0"
 
 
+mongodb@3.6.8, mongodb@^3.6.4:
+  version "3.6.8"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.8.tgz#3e2632af81915b3ff99b7681121ca0895e8ed407"
+  integrity sha512-sDjJvI73WjON1vapcbyBD3Ao9/VN3TKYY8/QX9EPbs22KaCSrQ5rXo5ZZd44tWJ3wl3FlnrFZ+KyUtNH6+1ZPQ==
+  dependencies:
+    bl "^2.2.1"
+    bson "^1.1.4"
+    denque "^1.4.1"
+    optional-require "^1.0.3"
+    safe-buffer "^5.1.2"
+  optionalDependencies:
+    saslprep "^1.0.0"
+
 mongoose-gridfs@^1.2.42:
 mongoose-gridfs@^1.2.42:
   version "1.2.42"
   version "1.2.42"
   resolved "https://registry.yarnpkg.com/mongoose-gridfs/-/mongoose-gridfs-1.2.42.tgz#15f4ff25b9b4d7563d544cedd716fc326ad34961"
   resolved "https://registry.yarnpkg.com/mongoose-gridfs/-/mongoose-gridfs-1.2.42.tgz#15f4ff25b9b4d7563d544cedd716fc326ad34961"
@@ -12568,21 +12619,22 @@ mongoose-valid8@>=1.6.18:
     lodash ">=4.17.15"
     lodash ">=4.17.15"
     validator ">=13.0.0"
     validator ">=13.0.0"
 
 
-mongoose@5.10.11:
-  version "5.10.11"
-  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.10.11.tgz#4bef4a85d35f38eb45a0af27b276d2bfd2196b5c"
-  integrity sha512-R5BFitKW94/S/Z48w+X+qi/eto66jWBcVEVA8nYVkBoBAPFGq7JSYP/0uso+ZHs+7XjSzTuui+SUllzxIrf9yA==
+mongoose@5.12.13:
+  version "5.12.13"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.12.13.tgz#6707fb2f6284536bb6367c94ccc3ce85475a387d"
+  integrity sha512-QGn1FCzZ8Z+mMGVg8oR2kQw4NmhLloCHsw1NqKWg3Yr7WfPzkE4pe7s9P6o5pkYGsku17n9mqMHowne7EFK/zQ==
   dependencies:
   dependencies:
+    "@types/mongodb" "^3.5.27"
     bson "^1.1.4"
     bson "^1.1.4"
-    kareem "2.3.1"
-    mongodb "3.6.2"
+    kareem "2.3.2"
+    mongodb "3.6.8"
     mongoose-legacy-pluralize "1.0.2"
     mongoose-legacy-pluralize "1.0.2"
-    mpath "0.7.0"
-    mquery "3.2.2"
+    mpath "0.8.3"
+    mquery "3.2.5"
     ms "2.1.2"
     ms "2.1.2"
     regexp-clone "1.0.0"
     regexp-clone "1.0.0"
     safe-buffer "5.2.1"
     safe-buffer "5.2.1"
-    sift "7.0.1"
+    sift "13.5.2"
     sliced "1.0.1"
     sliced "1.0.1"
 
 
 morgan@^1.10.0:
 morgan@^1.10.0:
@@ -12617,15 +12669,15 @@ move-concurrently@^1.0.1:
     rimraf "^2.5.4"
     rimraf "^2.5.4"
     run-queue "^1.0.3"
     run-queue "^1.0.3"
 
 
-mpath@0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.7.0.tgz#20e8102e276b71709d6e07e9f8d4d0f641afbfb8"
-  integrity sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==
+mpath@0.8.3:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.3.tgz#828ac0d187f7f42674839d74921970979abbdd8f"
+  integrity sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==
 
 
-mquery@3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7"
-  integrity sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==
+mquery@3.2.5:
+  version "3.2.5"
+  resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.5.tgz#8f2305632e4bb197f68f60c0cffa21aaf4060c51"
+  integrity sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==
   dependencies:
   dependencies:
     bluebird "3.5.1"
     bluebird "3.5.1"
     debug "3.1.0"
     debug "3.1.0"
@@ -13559,12 +13611,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
   dependencies:
   dependencies:
     wrappy "1"
     wrappy "1"
 
 
-once@~1.3.0:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
-  dependencies:
-    wrappy "1"
-
 onetime@^2.0.0:
 onetime@^2.0.0:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
@@ -13643,6 +13689,11 @@ optimize-css-assets-webpack-plugin@^5.0.3:
     cssnano "^4.1.10"
     cssnano "^4.1.10"
     last-call-webpack-plugin "^3.0.0"
     last-call-webpack-plugin "^3.0.0"
 
 
+optional-require@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.0.3.tgz#275b8e9df1dc6a17ad155369c2422a440f89cb07"
+  integrity sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==
+
 optional@0.1.4:
 optional@0.1.4:
   version "0.1.4"
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/optional/-/optional-0.1.4.tgz#cdb1a9bedc737d2025f690ceeb50e049444fd5b3"
   resolved "https://registry.yarnpkg.com/optional/-/optional-0.1.4.tgz#cdb1a9bedc737d2025f690ceeb50e049444fd5b3"
@@ -13716,6 +13767,11 @@ p-each-series@^2.1.0:
   resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
   resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
   integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
   integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
 
 
+p-each-series@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a"
+  integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==
+
 p-finally@^1.0.0:
 p-finally@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -14913,6 +14969,11 @@ pretty-format@^26.0.0, pretty-format@^26.6.2:
     ansi-styles "^4.0.0"
     ansi-styles "^4.0.0"
     react-is "^17.0.1"
     react-is "^17.0.1"
 
 
+printj@~1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
+  integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
+
 private@^0.1.6:
 private@^0.1.6:
   version "0.1.8"
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -15700,7 +15761,7 @@ read@1, read@~1.0.1:
   dependencies:
   dependencies:
     mute-stream "~0.0.4"
     mute-stream "~0.0.4"
 
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@~2.3.6:
   version "2.3.6"
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
   dependencies:
   dependencies:
@@ -15730,7 +15791,7 @@ readable-stream@1.1.x:
     isarray "0.0.1"
     isarray "0.0.1"
     string_decoder "~0.10.x"
     string_decoder "~0.10.x"
 
 
-readable-stream@3, readable-stream@^3.0.0:
+readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0:
   version "3.6.0"
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -15764,7 +15825,7 @@ readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.2.6, readable
     string_decoder "~1.0.3"
     string_decoder "~1.0.3"
     util-deprecate "~1.0.1"
     util-deprecate "~1.0.1"
 
 
-readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.4.0:
+readable-stream@^3.0.2, readable-stream@^3.4.0:
   version "3.4.0"
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
   integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
   integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
@@ -15781,6 +15842,13 @@ readable-stream@^3.1.1:
     string_decoder "^1.1.1"
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
     util-deprecate "^1.0.1"
 
 
+readdir-glob@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4"
+  integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==
+  dependencies:
+    minimatch "^3.0.4"
+
 readdir-scoped-modules@^1.0.0:
 readdir-scoped-modules@^1.0.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
   resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
@@ -16945,10 +17013,10 @@ side-channel@^1.0.4:
     get-intrinsic "^1.0.2"
     get-intrinsic "^1.0.2"
     object-inspect "^1.9.0"
     object-inspect "^1.9.0"
 
 
-sift@7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08"
-  integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==
+sift@13.5.2:
+  version "13.5.2"
+  resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6"
+  integrity sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==
 
 
 signal-exit@^3.0.0, signal-exit@^3.0.3:
 signal-exit@^3.0.0, signal-exit@^3.0.3:
   version "3.0.3"
   version "3.0.3"
@@ -17488,12 +17556,13 @@ stream-to-array@~2.3.0:
   dependencies:
   dependencies:
     any-promise "^1.1.0"
     any-promise "^1.1.0"
 
 
-stream-to-promise@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f"
+stream-to-promise@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-3.0.0.tgz#8934d66dcbc9189394e8b33200da3bb9611db774"
+  integrity sha512-h+7wLeFiYegOdgTfTxjRsrT7/Op7grnKEIHWgaO1RTHwcwk7xRreMr3S8XpDfDMesSxzgM2V4CxNCFAGo6ssnA==
   dependencies:
   dependencies:
     any-promise "~1.3.0"
     any-promise "~1.3.0"
-    end-of-stream "~1.1.0"
+    end-of-stream "~1.4.1"
     stream-to-array "~2.3.0"
     stream-to-array "~2.3.0"
 
 
 streamroller@^1.0.3:
 streamroller@^1.0.3:
@@ -17542,6 +17611,15 @@ string-template@>=1.0.0:
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
 
 
+string-width@=4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
+  integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.0"
+
 string-width@^1.0.1, string-width@^1.0.2:
 string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -18056,12 +18134,12 @@ tapable@^1.0.0-beta.5, tapable@^1.1.3:
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
   integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
   integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
 
-tar-stream@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
-  integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
+tar-stream@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
   dependencies:
   dependencies:
-    bl "^3.0.0"
+    bl "^4.0.3"
     end-of-stream "^1.4.1"
     end-of-stream "^1.4.1"
     fs-constants "^1.0.0"
     fs-constants "^1.0.0"
     inherits "^2.0.3"
     inherits "^2.0.3"
@@ -18906,11 +18984,6 @@ universalify@^0.1.0:
   version "0.1.1"
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
 
 
-universalify@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
-  integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
-
 universalify@^2.0.0:
 universalify@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
@@ -20104,11 +20177,11 @@ zen-observable@^0.8.15:
   resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
   resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
   integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
   integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
 
 
-zip-stream@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.2.tgz#841efd23214b602ff49c497cba1a85d8b5fbc39c"
-  integrity sha512-ykebHGa2+uzth/R4HZLkZh3XFJzivhVsjJt8bN3GvBzLaqqrUdRacu+c4QtnUgjkkQfsOuNE1JgLKMCPNmkKgg==
+zip-stream@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79"
+  integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==
   dependencies:
   dependencies:
     archiver-utils "^2.1.0"
     archiver-utils "^2.1.0"
-    compress-commons "^2.1.1"
-    readable-stream "^3.4.0"
+    compress-commons "^4.1.0"
+    readable-stream "^3.6.0"