Procházet zdrojové kódy

Merge branch 'master' into fix/GW-6897-behavior-when-moving-recursively

Shun Miyazawa před 4 roky
rodič
revize
8727b3f5d4
30 změnil soubory, kde provedl 377 přidání a 214 odebrání
  1. 4 0
      CHANGES.md
  2. 1 1
      package.json
  3. 3 2
      packages/app/package.json
  4. 22 22
      packages/app/resource/locales/en_US/sandbox.md
  5. 10 0
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  6. 7 1
      packages/app/src/components/PageEditor/Editor.jsx
  7. 7 9
      packages/app/src/components/PageEditor/EditorIcon.jsx
  8. 6 1
      packages/app/src/server/crowi/express-init.js
  9. 2 2
      packages/app/src/server/routes/apiv3/healthcheck.js
  10. 2 2
      packages/app/src/server/routes/apiv3/search.js
  11. 2 2
      packages/app/src/server/routes/apiv3/statistics.js
  12. 3 0
      packages/slack/src/index.ts
  13. 3 3
      packages/slack/src/utils/check-communicable.ts
  14. 28 0
      packages/slack/src/utils/publish-initial-home-view.ts
  15. 10 0
      packages/slack/src/utils/required-scopes.ts
  16. 4 0
      packages/slack/src/utils/slash-command-parser.ts
  17. 21 0
      packages/slack/src/utils/welcome-message.ts
  18. 11 0
      packages/slackbot-proxy/CHANGES.md
  19. 4 1
      packages/slackbot-proxy/docker/Dockerfile
  20. 2 1
      packages/slackbot-proxy/package.json
  21. 9 1
      packages/slackbot-proxy/src/Server.ts
  22. 108 61
      packages/slackbot-proxy/src/controllers/slack.ts
  23. 2 9
      packages/slackbot-proxy/src/controllers/top.ts
  24. 32 0
      packages/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts
  25. 21 0
      packages/slackbot-proxy/src/views/install-failed.ejs
  26. 18 0
      packages/slackbot-proxy/src/views/install-succeeded-but-has-problem.ejs
  27. 21 0
      packages/slackbot-proxy/src/views/install-succeeded.ejs
  28. 1 0
      packages/slackbot-proxy/src/views/top.ejs
  29. 4 0
      packages/slackbot-proxy/tsconfig.base.json
  30. 9 96
      yarn.lock

+ 4 - 0
CHANGES.md

@@ -2,6 +2,7 @@
 
 
 ## v4.3.3-RC
 ## v4.3.3-RC
 
 
+* Improvement: Add attachment button in editor navbar
 * Fix: Encode spaces in page path in LinkEditModal
 * Fix: Encode spaces in page path in LinkEditModal
 * Fix: Layout is broken when editing users page ([#4128](https://github.com/weseek/growi/issues/4128))
 * Fix: Layout is broken when editing users page ([#4128](https://github.com/weseek/growi/issues/4128))
 * Support: Create @growi/core package
 * Support: Create @growi/core package
@@ -10,8 +11,11 @@
 * Support: Include official plugins as sub packages
 * Support: Include official plugins as sub packages
 * Support: Upgrade libs
 * Support: Upgrade libs
     * @slack/web-api
     * @slack/web-api
+    * date-fns
     * escape-string-regexp
     * escape-string-regexp
+    * helmet
     * morgan
     * morgan
+    * socket.io
 
 
 ## v4.3.2
 ## v4.3.2
 
 

+ 1 - 1
package.json

@@ -31,7 +31,7 @@
   "scripts": {
   "scripts": {
     "start": "yarn app:server",
     "start": "yarn app:server",
     "prestart": "yarn app:build",
     "prestart": "yarn app:build",
-    "app:build": "yarn lerna run build --scope @growi/app --scope @growi/slack --scope @growi/plugin-pukiwiki-like-linker",
+    "app:build": "yarn lerna run build --scope @growi/app --scope @growi/slack --scope @growi/plugin-*",
     "app:server": "yarn lerna run server --scope @growi/app",
     "app:server": "yarn lerna run server --scope @growi/app",
     "slackbot-proxy:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",

+ 3 - 2
packages/app/package.json

@@ -80,7 +80,7 @@
     "connect-redis": "^4.0.4",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
     "cookie-parser": "^1.4.5",
     "csrf": "^3.1.0",
     "csrf": "^3.1.0",
-    "date-fns": "^2.0.0",
+    "date-fns": "^2.23.0",
     "detect-indent": "^6.0.0",
     "detect-indent": "^6.0.0",
     "diff": "^5.0.0",
     "diff": "^5.0.0",
     "elasticsearch": "^16.0.0",
     "elasticsearch": "^16.0.0",
@@ -96,7 +96,8 @@
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",
     "graceful-fs": "^4.1.11",
     "graceful-fs": "^4.1.11",
     "growi-commons": "^5.0.4",
     "growi-commons": "^5.0.4",
-    "helmet": "^3.13.0",
+    "helmet": "^4.6.0",
+    "nocache": "^3.0.1",
     "http-errors": "~1.6.2",
     "http-errors": "~1.6.2",
     "i18next": "^20.3.2",
     "i18next": "^20.3.2",
     "i18next-express-middleware": "^2.0.0",
     "i18next-express-middleware": "^2.0.0",

+ 22 - 22
packages/app/resource/locales/en_US/sandbox.md

@@ -37,7 +37,7 @@ Add one `#` per level at the start of the line
 
 
 ## Block paragraph
 ## Block paragraph
 
 
-Pararaphs are created by inserting a newline character
+Paragraphs are created by inserting a newline character
 A paragraph can be created by pressing Enter at the end of the previous paragraph.
 A paragraph can be created by pressing Enter at the end of the previous paragraph.
 
 
 ```
 ```
@@ -53,17 +53,17 @@ paragraph2
 ## Br new line
 ## Br new line
 
 
 Add two spaces before break.
 Add two spaces before break.
-***This behaviour can be modified in the options menu.***
+***This behavior can be modified in the options menu.***
 
 
 ```
 ```
-hoge
-fuga(two spaces)
-piyo
+foo
+bar(two spaces)
+baz
 ```
 ```
 
 
-hoge
-fuga
-piyo
+foo
+bar
+baz
 
 
 ## Blockquotes
 ## Blockquotes
 
 
@@ -84,7 +84,7 @@ Add one `>` per level at the start of the line
 Wrap code with three back quotes or tildes.
 Wrap code with three back quotes or tildes.
 
 
 ```
 ```
-print 'hoge'
+print 'foo'
 ```
 ```
 
 
 ### Syntax highlight and file name
 ### Syntax highlight and file name
@@ -131,16 +131,16 @@ This is  `Inline Code`.
 Code blocks should be preceded by four spaces or one tab.
 Code blocks should be preceded by four spaces or one tab.
 
 
 ```
 ```
-    class Hoge
-        def hoge
-            print 'hoge'
+    class Foo
+        def foo
+            print 'foo'
         end
         end
     end
     end
 ```
 ```
 
 
-    class Hoge
-        def hoge
-            print 'hoge'
+    class Foo
+        def foo
+            print 'foo'
         end
         end
     end
     end
 
 
@@ -166,7 +166,7 @@ ___
 
 
 ### Italic
 ### Italic
 
 
-To italicize text, add One asterisk or underscores before and after a word or phrase.
+To italicize text, add one asterisk or underscores before and after a word or phrase.
 
 
 ```
 ```
 This is *Italic* .
 This is *Italic* .
@@ -178,7 +178,7 @@ This is _Italic_ .
 
 
 ### Bold
 ### Bold
 
 
-To bold text, add two asterisks or underscores before and after a word or phrase.
+To make text bold, add two asterisks or underscores before and after a word or phrase.
 
 
 ```
 ```
 This is **bold**.
 This is **bold**.
@@ -263,7 +263,7 @@ Example of Bootstrap4 is[[here>./Bootstrap4]]
 
 
 ## Ul Bulleted list
 ## Ul Bulleted list
 
 
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items. 
+To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items.
 Items can be nested using indentation.
 Items can be nested using indentation.
 
 
 ```
 ```
@@ -286,7 +286,7 @@ Items can be nested using indentation.
 
 
 ## Ol Numbered List
 ## Ol Numbered List
 
 
-To create an ordered list, add line items with numbers followed by periods. 
+To create an ordered list, add line items with numbers followed by periods.
 The numbers don’t have to be in numerical order, but the list should start with the number one.
 The numbers don’t have to be in numerical order, but the list should start with the number one.
 
 
 ```
 ```
@@ -449,9 +449,9 @@ See [emojione](https://www.emojione.com/)
 
 
 # :heavy_plus_sign: More..
 # :heavy_plus_sign: More..
 
 
-- Try to attach Bootstrap4 Tags?
+- Want to attach Bootstrap4 Tags?
     - :arrow_right: [/Sandbox/Bootstrap4]
     - :arrow_right: [/Sandbox/Bootstrap4]
-- Try to draw Diagrams?
+- Want to draw Diagrams?
     - :arrow_right: [/Sandbox/Diagrams]
     - :arrow_right: [/Sandbox/Diagrams]
-- Try to write Math Formulas?
+- Want to write Math Formulas?
     - :arrow_right: [/Sandbox/Math]
     - :arrow_right: [/Sandbox/Math]

+ 10 - 0
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -801,6 +801,15 @@ export default class CodeMirrorEditor extends AbstractEditor {
       >
       >
         <EditorIcon icon="CheckList" />
         <EditorIcon icon="CheckList" />
       </Button>,
       </Button>,
+      <Button
+        key="nav-item-attachment"
+        color={null}
+        size="sm"
+        title="Attachment"
+        onClick={this.props.onAddAttachmentButtonClicked}
+      >
+        <EditorIcon icon="Attachment" />
+      </Button>,
       <Button
       <Button
         key="nav-item-link"
         key="nav-item-link"
         color={null}
         color={null}
@@ -947,6 +956,7 @@ CodeMirrorEditor.propTypes = Object.assign({
   emojiStrategy: PropTypes.object,
   emojiStrategy: PropTypes.object,
   lineNumbers: PropTypes.bool,
   lineNumbers: PropTypes.bool,
   onMarkdownHelpButtonClicked: PropTypes.func,
   onMarkdownHelpButtonClicked: PropTypes.func,
+  onAddAttachmentButtonClicked: PropTypes.func,
 }, AbstractEditor.propTypes);
 }, AbstractEditor.propTypes);
 CodeMirrorEditor.defaultProps = {
 CodeMirrorEditor.defaultProps = {
   lineNumbers: true,
   lineNumbers: true,

+ 7 - 1
packages/app/src/components/PageEditor/Editor.jsx

@@ -39,6 +39,7 @@ export default class Editor extends AbstractEditor {
     this.dropHandler = this.dropHandler.bind(this);
     this.dropHandler = this.dropHandler.bind(this);
 
 
     this.showMarkdownHelp = this.showMarkdownHelp.bind(this);
     this.showMarkdownHelp = this.showMarkdownHelp.bind(this);
+    this.addAttachmentHandler = this.addAttachmentHandler.bind(this);
 
 
     this.getAcceptableType = this.getAcceptableType.bind(this);
     this.getAcceptableType = this.getAcceptableType.bind(this);
     this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
     this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
@@ -187,6 +188,10 @@ export default class Editor extends AbstractEditor {
     this.setState({ isCheatsheetModalShown: true });
     this.setState({ isCheatsheetModalShown: true });
   }
   }
 
 
+  addAttachmentHandler() {
+    this.dropzone.open();
+  }
+
   getDropzoneClassName(isDragAccept, isDragReject) {
   getDropzoneClassName(isDragAccept, isDragReject) {
     let className = 'dropzone';
     let className = 'dropzone';
     if (!this.props.isUploadable) {
     if (!this.props.isUploadable) {
@@ -314,6 +319,7 @@ export default class Editor extends AbstractEditor {
                         onPasteFiles={this.pasteFilesHandler}
                         onPasteFiles={this.pasteFilesHandler}
                         onDragEnter={this.dragEnterHandler}
                         onDragEnter={this.dragEnterHandler}
                         onMarkdownHelpButtonClicked={this.showMarkdownHelp}
                         onMarkdownHelpButtonClicked={this.showMarkdownHelp}
+                        onAddAttachmentButtonClicked={this.addAttachmentHandler}
                         {...this.props}
                         {...this.props}
                       />
                       />
                     )}
                     )}
@@ -341,7 +347,7 @@ export default class Editor extends AbstractEditor {
             <button
             <button
               type="button"
               type="button"
               className="btn btn-outline-secondary btn-block btn-open-dropzone"
               className="btn btn-outline-secondary btn-block btn-open-dropzone"
-              onClick={() => { this.dropzone.open() }}
+              onClick={this.addAttachmentHandler}
             >
             >
               <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
               <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
               Attach files
               Attach files

+ 7 - 9
packages/app/src/components/PageEditor/EditorIcon.jsx

@@ -118,15 +118,13 @@ const EditorIcon = (props) => {
           <path d="M22.12,17H19.75l-3.12-4H18a1,1,0,0,0,1-1V8a1,1,0,0,0-1-1H12a1,1,0,0,0-1,1v4a1,1,0,0,0,1,1h1.38l-2.92,4H7.88A.94.94,0,0,0,7,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,14,22V18a.94.94,0,0,0-.88-1H11.63l3.13-4h.47l3.13,4H16.88A.94.94,0,0,0,16,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,23,22V18A.94.94,0,0,0,22.12,17ZM13,22H8V18h5ZM12,8h6v4H12ZM22,22H17V18h5Z" />
           <path d="M22.12,17H19.75l-3.12-4H18a1,1,0,0,0,1-1V8a1,1,0,0,0-1-1H12a1,1,0,0,0-1,1v4a1,1,0,0,0,1,1h1.38l-2.92,4H7.88A.94.94,0,0,0,7,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,14,22V18a.94.94,0,0,0-.88-1H11.63l3.13-4h.47l3.13,4H16.88A.94.94,0,0,0,16,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,23,22V18A.94.94,0,0,0,22.12,17ZM13,22H8V18h5ZM12,8h6v4H12ZM22,22H17V18h5Z" />
         </svg>
         </svg>
       );
       );
-    // Unused icon
-    // case 'attachment':
-    //   return (
-    //     <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-    //       <rect fillOpacity="0" width="30" height="30" />
-    //       <path d="M9.71,22.5a2.57,2.57,0,0,1-1.85-.79,2.79,2.79,0,0,1,0-4l9-9.23a3.21,3.21,0,0,1,1.59-.87,3.39,3.39,0,0,1,1.81.1,4.38,4.38,0,0,1,1.7,1.05,4.15,4.15,0,0,1,.46.56,3.73,3.73,0,0,1,.35.65,4.25,4.25,0,0,1,.2.72,3.91,3.91,0,0,1,.07.76,3.71,3.71,0,0,1-1.12,2.67l-6.79,7a.48.48,0,0,1-.34.16.51.51,0,0,1-.35-.13.48.48,0,0,1,0-.7l6.78-7a2.8,2.8,0,0,0,.84-2,2.58,2.58,0,0,0-.79-2,3.63,3.63,0,0,0-1.11-.75,2.41,2.41,0,0,0-1.31-.17,2.19,2.19,0,0,0-1.25.62l-9,9.22A1.8,1.8,0,0,0,8,19.69,1.78,1.78,0,0,0,8.58,21a1.81,1.81,0,0,0,.57.39,1.48,1.48,0,0,0,.66.1,2,2,0,0,0,1.28-.62l7.12-7.35.15-.16a1.15,1.15,0,0,0,.15-.2.9.9,0,0,0,.12-.24,1.17,1.17,0,0,0,.07-.25.52.52,0,0,0-.05-.27.75.75,0,0,0-.19-.26.73.73,0,0,0-.58-.27,1.29,1.29,0,0,0-.67.38l-5.36,5.53a.5.5,0,0,1-.22.13.46.46,0,0,1-.26,0,.48.48,0,0,1-.22-.12A.41.41,0,0,1,11,17.5a.5.5,0,0,1,.14-.35L16.5,11.6a2.19,2.19,0,0,1,1.29-.67,1.69,1.69,0,0,1,1.37.55,1.54,1.54,0,0,1,.53,1.31,2.26,2.26,0,0,1-.76,1.42L11.8,21.58a3.06,3.06,0,0,1-2,.91H9.71Z" />
-    //     </svg>
-    // );
-
+    case 'Attachment':
+      return (
+        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
+          <rect fillOpacity="0" width="30" height="30" />
+          <path d="M9.71,22.5a2.57,2.57,0,0,1-1.85-.79,2.79,2.79,0,0,1,0-4l9-9.23a3.21,3.21,0,0,1,1.59-.87,3.39,3.39,0,0,1,1.81.1,4.38,4.38,0,0,1,1.7,1.05,4.15,4.15,0,0,1,.46.56,3.73,3.73,0,0,1,.35.65,4.25,4.25,0,0,1,.2.72,3.91,3.91,0,0,1,.07.76,3.71,3.71,0,0,1-1.12,2.67l-6.79,7a.48.48,0,0,1-.34.16.51.51,0,0,1-.35-.13.48.48,0,0,1,0-.7l6.78-7a2.8,2.8,0,0,0,.84-2,2.58,2.58,0,0,0-.79-2,3.63,3.63,0,0,0-1.11-.75,2.41,2.41,0,0,0-1.31-.17,2.19,2.19,0,0,0-1.25.62l-9,9.22A1.8,1.8,0,0,0,8,19.69,1.78,1.78,0,0,0,8.58,21a1.81,1.81,0,0,0,.57.39,1.48,1.48,0,0,0,.66.1,2,2,0,0,0,1.28-.62l7.12-7.35.15-.16a1.15,1.15,0,0,0,.15-.2.9.9,0,0,0,.12-.24,1.17,1.17,0,0,0,.07-.25.52.52,0,0,0-.05-.27.75.75,0,0,0-.19-.26.73.73,0,0,0-.58-.27,1.29,1.29,0,0,0-.67.38l-5.36,5.53a.5.5,0,0,1-.22.13.46.46,0,0,1-.26,0,.48.48,0,0,1-.22-.12A.41.41,0,0,1,11,17.5a.5.5,0,0,1,.14-.35L16.5,11.6a2.19,2.19,0,0,1,1.29-.67,1.69,1.69,0,0,1,1.37.55,1.54,1.54,0,0,1,.53,1.31,2.26,2.26,0,0,1-.76,1.42L11.8,21.58a3.06,3.06,0,0,1-2,.91H9.71Z" />
+        </svg>
+      );
   }
   }
 
 
 
 

+ 6 - 1
packages/app/src/server/crowi/express-init.js

@@ -53,7 +53,12 @@ module.exports = function(crowi, app) {
       nsSeparator: '::',
       nsSeparator: '::',
     });
     });
 
 
-  app.use(helmet());
+  app.use(helmet({
+    contentSecurityPolicy: false,
+    expectCt: false,
+    referrerPolicy: false,
+    permittedCrossDomainPolicies: false,
+  }));
 
 
   app.use((req, res, next) => {
   app.use((req, res, next) => {
     const now = new Date();
     const now = new Date();

+ 2 - 2
packages/app/src/server/routes/apiv3/healthcheck.js

@@ -6,7 +6,7 @@ const express = require('express');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const helmet = require('helmet');
+const noCache = require('nocache');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 /**
 /**
@@ -122,7 +122,7 @@ module.exports = (crowi) => {
    *                  info:
    *                  info:
    *                    $ref: '#/components/schemas/HealthcheckInfo'
    *                    $ref: '#/components/schemas/HealthcheckInfo'
    */
    */
-  router.get('/', helmet.noCache(), async(req, res) => {
+  router.get('/', noCache(), async(req, res) => {
     let checkServices = req.query.checkServices || [];
     let checkServices = req.query.checkServices || [];
     let isStrictly = req.query.strictly != null;
     let isStrictly = req.query.strictly != null;
 
 

+ 2 - 2
packages/app/src/server/routes/apiv3/search.js

@@ -7,7 +7,7 @@ const { body } = require('express-validator');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const helmet = require('helmet');
+const noCache = require('nocache');
 
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
@@ -41,7 +41,7 @@ module.exports = (crowi) => {
    *                  info:
    *                  info:
    *                    type: object
    *                    type: object
    */
    */
-  router.get('/indices', helmet.noCache(), accessTokenParser, loginRequired, adminRequired, async(req, res) => {
+  router.get('/indices', noCache(), accessTokenParser, loginRequired, adminRequired, async(req, res) => {
     const { searchService } = crowi;
     const { searchService } = crowi;
 
 
     if (!searchService.isConfigured) {
     if (!searchService.isConfigured) {

+ 2 - 2
packages/app/src/server/routes/apiv3/statistics.js

@@ -6,7 +6,7 @@ const express = require('express');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const helmet = require('helmet');
+const noCache = require('nocache');
 
 
 const USER_STATUS_MASTER = {
 const USER_STATUS_MASTER = {
   1: 'registered',
   1: 'registered',
@@ -97,7 +97,7 @@ module.exports = (crowi) => {
    *                    type: object
    *                    type: object
    *                    description: Statistics for all user
    *                    description: Statistics for all user
    */
    */
-  router.get('/user', helmet.noCache(), async(req, res) => {
+  router.get('/user', noCache(), async(req, res) => {
     const data = req.user == null ? await getUserStatisticsForNotLoggedIn() : await getUserStatistics();
     const data = req.user == null ? await getUserStatisticsForNotLoggedIn() : await getUserStatistics();
     res.status(200).send({ data });
     res.status(200).send({ data });
   });
   });

+ 3 - 0
packages/slack/src/index.ts

@@ -26,6 +26,9 @@ export * from './utils/block-kit-builder';
 export * from './utils/check-communicable';
 export * from './utils/check-communicable';
 export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/post-ephemeral-errors';
 export * from './utils/post-ephemeral-errors';
+export * from './utils/publish-initial-home-view';
 export * from './utils/reshape-contents-body';
 export * from './utils/reshape-contents-body';
 export * from './utils/slash-command-parser';
 export * from './utils/slash-command-parser';
 export * from './utils/webclient-factory';
 export * from './utils/webclient-factory';
+export * from './utils/welcome-message';
+export * from './utils/required-scopes';

+ 3 - 3
packages/slack/src/utils/check-communicable.ts

@@ -4,6 +4,7 @@ import { WebClient } from '@slack/web-api';
 
 
 import { generateWebClient } from './webclient-factory';
 import { generateWebClient } from './webclient-factory';
 import { ConnectionStatus } from '../interfaces/connection-status';
 import { ConnectionStatus } from '../interfaces/connection-status';
+import { requiredScopes } from './required-scopes';
 
 
 /**
 /**
  * Check whether the HTTP server responds or not.
  * Check whether the HTTP server responds or not.
@@ -45,11 +46,10 @@ const testSlackApiServer = async(client: WebClient): Promise<any> => {
 
 
 const checkSlackScopes = (resultTestSlackApiServer: any) => {
 const checkSlackScopes = (resultTestSlackApiServer: any) => {
   const slackScopes = resultTestSlackApiServer.response_metadata.scopes;
   const slackScopes = resultTestSlackApiServer.response_metadata.scopes;
-  const correctScopes = ['commands', 'team:read', 'chat:write'];
-  const isPassedScopeCheck = correctScopes.every(e => slackScopes.includes(e));
+  const isPassedScopeCheck = requiredScopes.every(e => slackScopes.includes(e));
 
 
   if (!isPassedScopeCheck) {
   if (!isPassedScopeCheck) {
-    throw new Error('The scopes is not appropriate. Required scopes is [\'commands\', \'team:read\', \'chat:write\']');
+    throw new Error(`The scopes you registered are not appropriate. Required scopes are ${requiredScopes}`);
   }
   }
 };
 };
 
 

+ 28 - 0
packages/slack/src/utils/publish-initial-home-view.ts

@@ -0,0 +1,28 @@
+import { ViewsPublishResponse, WebClient } from '@slack/web-api';
+
+export const publishInitialHomeView = (client: WebClient, userId: string): Promise<ViewsPublishResponse> => {
+  return client.views.publish({
+    user_id: userId,
+    view: {
+      type: 'home',
+      blocks: [
+        {
+          type: 'section',
+          text: {
+            type: 'mrkdwn',
+            text: 'Welcome GROWI Official Bot Home',
+          },
+        },
+        {
+          type: 'section',
+          text: {
+            type: 'mrkdwn',
+            text: 'Learn how to use GROWI Official bot.'
+            // eslint-disable-next-line max-len
+              + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+          },
+        },
+      ],
+    },
+  });
+};

+ 10 - 0
packages/slack/src/utils/required-scopes.ts

@@ -0,0 +1,10 @@
+export const requiredScopes: string[] = [
+  'commands',
+  'team:read',
+  'chat:write',
+  'channels:join',
+  'channels:history',
+  'groups:history',
+  'im:history',
+  'mpim:history',
+];

+ 4 - 0
packages/slack/src/utils/slash-command-parser.ts

@@ -2,6 +2,10 @@ import { GrowiCommand } from '../interfaces/growi-command';
 import { InvalidGrowiCommandError } from '../models/errors';
 import { InvalidGrowiCommandError } from '../models/errors';
 
 
 export const parseSlashCommand = (slashCommand:{[key:string]:string}): GrowiCommand => {
 export const parseSlashCommand = (slashCommand:{[key:string]:string}): GrowiCommand => {
+  if (slashCommand.text == null) {
+    throw new InvalidGrowiCommandError('The SlashCommand.text is null');
+  }
+
   const trimmedText = slashCommand.text.trim();
   const trimmedText = slashCommand.text.trim();
   const splitted = trimmedText.split(' ');
   const splitted = trimmedText.split(' ');
 
 

+ 21 - 0
packages/slack/src/utils/welcome-message.ts

@@ -0,0 +1,21 @@
+import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
+
+export const postWelcomeMessage = (client: WebClient, userId: string): Promise<ChatPostMessageResponse> => {
+  return client.chat.postMessage({
+    channel: userId,
+    user: userId,
+    blocks: [
+      {
+        type: 'section',
+        text: {
+          type: 'mrkdwn',
+          text: ':tada: You have successfully installed GROWI Official bot on this Slack workspace.\n'
+            + 'At first you do `/growi register` in the channel that you want to use.\n'
+            + 'Looking for additional help?'
+            // eslint-disable-next-line max-len
+            + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+        },
+      },
+    ],
+  });
+};

+ 11 - 0
packages/slackbot-proxy/CHANGES.md

@@ -0,0 +1,11 @@
+# CHANGES
+
+## v1.0.1-RC
+
+* Improvement: Automatically add GROWI bot user to the channel where the command was executed
+* Improvement: Post a welcome message when the installation is successful
+* Improvement: Post a warning message when none of GROWI permit the command
+* Improvement: Permission scopes
+* Improvement: Top page information
+* Improvement: Add a link to docs
+

+ 4 - 1
packages/slackbot-proxy/docker/Dockerfile

@@ -37,7 +37,10 @@ RUN tar cf node_modules.tar node_modules \
 FROM deps-resolver-base AS deps-resolver-prod
 FROM deps-resolver-base AS deps-resolver-prod
 RUN npx lerna bootstrap -- --production
 RUN npx lerna bootstrap -- --production
 # make artifacts
 # make artifacts
-RUN tar cf dependencies.tar node_modules packages/slackbot-proxy/node_modules
+RUN tar cf dependencies.tar
+  node_modules \
+  packages/slack/node_modules \
+  packages/slackbot-proxy/node_modules
 
 
 
 
 ##
 ##

+ 2 - 1
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/slackbot-proxy",
   "name": "@growi/slackbot-proxy",
-  "version": "1.0.0-RC",
+  "version": "1.0.1-RC",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -37,6 +37,7 @@
     "bunyan": "^1.8.15",
     "bunyan": "^1.8.15",
     "compression": "^1.7.4",
     "compression": "^1.7.4",
     "cookie-parser": "^1.4.5",
     "cookie-parser": "^1.4.5",
+    "date-fns": "^2.23.0",
     "express-bunyan-logger": "^1.3.3",
     "express-bunyan-logger": "^1.3.3",
     "extensible-custom-error": "^0.0.7",
     "extensible-custom-error": "^0.0.7",
     "helmet": "^4.6.0",
     "helmet": "^4.6.0",

+ 9 - 1
packages/slackbot-proxy/src/Server.ts

@@ -42,7 +42,12 @@ const connectionOptions: ConnectionOptions = {
 } as ConnectionOptions;
 } as ConnectionOptions;
 
 
 const swaggerSettings = isProduction ? swaggerSettingsForProd : swaggerSettingsForDev;
 const swaggerSettings = isProduction ? swaggerSettingsForProd : swaggerSettingsForDev;
-const helmetOptions = isProduction ? {} : {
+const helmetOptions = isProduction ? {
+  contentSecurityPolicy: false,
+  expectCt: false,
+  referrerPolicy: false,
+  permittedCrossDomainPolicies: false,
+} : {
   contentSecurityPolicy: {
   contentSecurityPolicy: {
     directives: {
     directives: {
       defaultSrc: ['\'self\''],
       defaultSrc: ['\'self\''],
@@ -51,6 +56,9 @@ const helmetOptions = isProduction ? {} : {
       scriptSrc: ['\'self\'', 'https: \'unsafe-inline\''],
       scriptSrc: ['\'self\'', 'https: \'unsafe-inline\''],
     },
     },
   },
   },
+  expectCt: false,
+  referrerPolicy: false,
+  permittedCrossDomainPolicies: false,
 };
 };
 
 
 @Configuration({
 @Configuration({

+ 108 - 61
packages/slackbot-proxy/src/controllers/slack.ts

@@ -1,13 +1,16 @@
 import {
 import {
-  BodyParams, Controller, Get, Inject, Post, Req, Res, UseBefore,
+  BodyParams, Controller, Get, Inject, PlatformResponse, Post, Req, Res, UseBefore,
 } from '@tsed/common';
 } from '@tsed/common';
 
 
 import axios from 'axios';
 import axios from 'axios';
 
 
 import { WebAPICallResult } from '@slack/web-api';
 import { WebAPICallResult } from '@slack/web-api';
+import { Installation } from '@slack/oauth';
+
 
 
 import {
 import {
   markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
   markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
+  InvalidGrowiCommandError, requiredScopes, postWelcomeMessage, publishInitialHomeView,
 } from '@growi/slack';
 } from '@growi/slack';
 
 
 import { Relation } from '~/entities/relation';
 import { Relation } from '~/entities/relation';
@@ -25,6 +28,7 @@ import { RelationsService } from '~/services/RelationsService';
 import { UnregisterService } from '~/services/UnregisterService';
 import { UnregisterService } from '~/services/UnregisterService';
 import { InvalidUrlError } from '../models/errors';
 import { InvalidUrlError } from '../models/errors';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
+import { JoinToConversationMiddleware } from '~/middlewares/slack-to-growi/join-to-conversation';
 
 
 
 
 const logger = loggerFactory('slackbot-proxy:controllers:slack');
 const logger = loggerFactory('slackbot-proxy:controllers:slack');
@@ -97,15 +101,27 @@ export class SlackCtrl {
   }
   }
 
 
   @Post('/commands')
   @Post('/commands')
-  @UseBefore(AddSigningSecretToReq, verifySlackRequest, AuthorizeCommandMiddleware)
+  @UseBefore(AddSigningSecretToReq, verifySlackRequest, AuthorizeCommandMiddleware, JoinToConversationMiddleware)
   async handleCommand(@Req() req: SlackOauthReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
   async handleCommand(@Req() req: SlackOauthReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
     const { body, authorizeResult } = req;
     const { body, authorizeResult } = req;
 
 
-    if (body.text == null) {
-      return 'No text.';
-    }
+    let growiCommand;
 
 
-    const growiCommand = parseSlashCommand(body);
+    try {
+      growiCommand = parseSlashCommand(body);
+    }
+    catch (err) {
+      if (err instanceof InvalidGrowiCommandError) {
+        res.json({
+          blocks: [
+            markdownSectionBlock('*Command type is not specified.*'),
+            markdownSectionBlock('Run `/growi help` to check the commands you can use.'),
+          ],
+        });
+      }
+      logger.error(err.message);
+      return;
+    }
 
 
     // register
     // register
     if (growiCommand.growiCommandType === 'register') {
     if (growiCommand.growiCommandType === 'register') {
@@ -165,50 +181,63 @@ export class SlackCtrl {
 
 
     const baseDate = new Date();
     const baseDate = new Date();
 
 
-    const relationsForSingleUse:Relation[] = [];
+    const allowedRelationsForSingleUse:Relation[] = [];
+    const disallowedGrowiUrls: Set<string> = new Set();
+
+    // check permission for single use
     await Promise.all(relations.map(async(relation) => {
     await Promise.all(relations.map(async(relation) => {
       const isSupported = await this.relationsService.isSupportedGrowiCommandForSingleUse(relation, growiCommand.growiCommandType, baseDate);
       const isSupported = await this.relationsService.isSupportedGrowiCommandForSingleUse(relation, growiCommand.growiCommandType, baseDate);
       if (isSupported) {
       if (isSupported) {
-        relationsForSingleUse.push(relation);
+        allowedRelationsForSingleUse.push(relation);
+      }
+      else {
+        disallowedGrowiUrls.add(relation.growiUri);
       }
       }
     }));
     }));
 
 
-    let isCommandPermitted = false;
-
-    if (relationsForSingleUse.length > 0) {
-      isCommandPermitted = true;
-      body.growiUrisForSingleUse = relationsForSingleUse.map(v => v.growiUri);
+    // select GROWI
+    if (allowedRelationsForSingleUse.length > 0) {
+      body.growiUrisForSingleUse = allowedRelationsForSingleUse.map(v => v.growiUri);
       return this.selectGrowiService.process(growiCommand, authorizeResult, body);
       return this.selectGrowiService.process(growiCommand, authorizeResult, body);
     }
     }
 
 
+    // check permission for broadcast use
     const relationsForBroadcastUse:Relation[] = [];
     const relationsForBroadcastUse:Relation[] = [];
     await Promise.all(relations.map(async(relation) => {
     await Promise.all(relations.map(async(relation) => {
       const isSupported = await this.relationsService.isSupportedGrowiCommandForBroadcastUse(relation, growiCommand.growiCommandType, baseDate);
       const isSupported = await this.relationsService.isSupportedGrowiCommandForBroadcastUse(relation, growiCommand.growiCommandType, baseDate);
       if (isSupported) {
       if (isSupported) {
         relationsForBroadcastUse.push(relation);
         relationsForBroadcastUse.push(relation);
       }
       }
+      else {
+        disallowedGrowiUrls.add(relation.growiUri);
+      }
     }));
     }));
 
 
-    /*
-     * forward to GROWI server
-     */
+    // forward to GROWI server
     if (relationsForBroadcastUse.length > 0) {
     if (relationsForBroadcastUse.length > 0) {
-      isCommandPermitted = true;
       this.sendCommand(growiCommand, relationsForBroadcastUse, body);
       this.sendCommand(growiCommand, relationsForBroadcastUse, body);
     }
     }
 
 
-    if (!isCommandPermitted) {
-      const botToken = relations[0].installation?.data.bot?.token;
-
+    // when all of GROWI disallowed
+    if (relations.length === disallowedGrowiUrls.size) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const client = generateWebClient(botToken!);
+      const client = generateWebClient(authorizeResult.botToken!);
+
+      const linkUrlList = Array.from(disallowedGrowiUrls).map((growiUrl) => {
+        return '\n'
+          + `• ${new URL('/admin/slack-integration', growiUrl).toString()}`;
+      });
 
 
       return client.chat.postEphemeral({
       return client.chat.postEphemeral({
         text: 'Error occured.',
         text: 'Error occured.',
         channel: body.channel_id,
         channel: body.channel_id,
         user: body.user_id,
         user: body.user_id,
         blocks: [
         blocks: [
-          markdownSectionBlock(`It is not allowed to run *'${growiCommand.growiCommandType}'* command to this GROWI.`),
+          markdownSectionBlock('*None of GROWI permitted the command.*'),
+          markdownSectionBlock(`*'${growiCommand.growiCommandType}'* command was not allowed.`),
+          markdownSectionBlock(
+            `To use this command, modify settings from following pages: ${linkUrlList}`,
+          ),
         ],
         ],
       });
       });
     }
     }
@@ -307,50 +336,68 @@ export class SlackCtrl {
   }
   }
 
 
   @Get('/oauth_redirect')
   @Get('/oauth_redirect')
-  async handleOauthRedirect(@Req() req: Req, @Res() res: Res): Promise<void> {
-
-    if (req.query.state === '') {
-      res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
-      res.end('<html>'
-      + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
-      + '<body style="text-align:center; padding-top:20%;">'
-      + '<h1>Illegal state, try it again.</h1>'
-      + '<a href="/">'
-      + 'Go to install page'
-      + '</a>'
-      + '</body></html>');
+  async handleOauthRedirect(@Req() req: Req, @Res() serverRes: Res, @Res() platformRes: PlatformResponse): Promise<void|string> {
+
+    // create 'Add to Slack' url
+    const addToSlackUrl = await this.installerService.installer.generateInstallUrl({
+      scopes: requiredScopes,
+    });
+
+    const state = req.query.state;
+    if (state == null || state === '') {
+      return platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
     }
     }
 
 
-    await this.installerService.installer.handleCallback(req, res, {
-      success: (installation, metadata, req, res) => {
-        logger.info('Success to install', { installation, metadata });
+    // promisify
+    const installPromise = new Promise<Installation>((resolve, reject) => {
+      this.installerService.installer.handleCallback(req, serverRes, {
+        success: async(installation, metadata) => {
+          logger.info('Success to install', { installation, metadata });
+          resolve(installation);
+        },
+        failure: async(error) => {
+          reject(error); // go to catch block
+        },
+      });
+    });
+
+    let httpStatus = 200;
+    let httpBody;
+    try {
+      const installation = await installPromise;
 
 
+      // check whether bot is not null
+      if (installation.bot == null) {
+        logger.warn('Success to install but something wrong. `installation.bot` is null.');
+        httpStatus = 500;
+        httpBody = await platformRes.render('install-succeeded-but-has-problem.ejs', { reason: '`installation.bot` is null' });
+      }
+      // MAIN PATH: everything is fine
+      else {
         const appPageUrl = `https://slack.com/apps/${installation.appId}`;
         const appPageUrl = `https://slack.com/apps/${installation.appId}`;
+        httpBody = await platformRes.render('install-succeeded.ejs', { appPageUrl });
 
 
-        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end('<html>'
-        + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
-        + '<body style="text-align:center; padding-top:20%;">'
-        + '<h1>Congratulations!</h1>'
-        + '<p>GROWI Bot installation has succeeded.</p>'
-        + `<a href="${appPageUrl}">`
-        + 'Access to Slack App detail page.'
-        + '</a>'
-        + '</body></html>');
-      },
-      failure: (error, installOptions, req, res) => {
-        res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end('<html>'
-        + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
-        + '<body style="text-align:center; padding-top:20%;">'
-        + '<h1>GROWI Bot installation failed</h1>'
-        + '<p>Please contact administrators of your workspace</p>'
-        + 'Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">'
-        + 'Manage app installation settings for your workspace'
-        + '</a>'
-        + '</body></html>');
-      },
-    });
+        // generate client
+        const client = generateWebClient(installation.bot.token);
+
+        const userId = installation.user.id;
+
+        await Promise.all([
+          // post message
+          postWelcomeMessage(client, userId),
+          // publish home
+          publishInitialHomeView(client, userId),
+        ]);
+      }
+    }
+    catch (error) {
+      logger.error(error);
+      httpStatus = 500;
+      httpBody = await platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
+    }
+
+    platformRes.status(httpStatus);
+    return httpBody;
   }
   }
 
 
 }
 }

+ 2 - 9
packages/slackbot-proxy/src/controllers/top.ts

@@ -2,6 +2,7 @@ import {
   Controller, Get, Inject, View,
   Controller, Get, Inject, View,
 } from '@tsed/common';
 } from '@tsed/common';
 
 
+import { requiredScopes } from '@growi/slack';
 import { InstallerService } from '~/services/InstallerService';
 import { InstallerService } from '~/services/InstallerService';
 
 
 const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
 const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
@@ -18,15 +19,7 @@ export class TopCtrl {
   async getTopPage(): Promise<any> {
   async getTopPage(): Promise<any> {
     const url = await this.installerService.installer.generateInstallUrl({
     const url = await this.installerService.installer.generateInstallUrl({
       // Add the scopes your app needs
       // Add the scopes your app needs
-      scopes: [
-        'channels:history',
-        'commands',
-        'groups:history',
-        'im:history',
-        'mpim:history',
-        'chat:write',
-        'team:read',
-      ],
+      scopes: requiredScopes,
     });
     });
 
 
     return { url, isOfficialMode };
     return { url, isOfficialMode };

+ 32 - 0
packages/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts

@@ -0,0 +1,32 @@
+import { generateWebClient } from '@growi/slack';
+import {
+  IMiddleware, Middleware, Req,
+} from '@tsed/common';
+
+import Logger from 'bunyan';
+import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
+
+import loggerFactory from '~/utils/logger';
+
+const logger: Logger = loggerFactory('slackbot-proxy:middlewares:JoinToConversationsMiddleware');
+
+
+/**
+ * This middleware should be processed after AuthorizeCommandMiddleware or AuthorizeInteractionMiddleware
+ */
+@Middleware()
+export class JoinToConversationMiddleware implements IMiddleware {
+
+  async use(@Req() req: SlackOauthReq): Promise<void> {
+    const { body, authorizeResult } = req;
+
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const client = generateWebClient(authorizeResult.botToken!);
+
+    const joinResult = await client.conversations.join({ channel: body.channel_id });
+    if (!joinResult.ok) {
+      logger.error(joinResult.error, joinResult);
+    }
+  }
+
+}

+ 21 - 0
packages/slackbot-proxy/src/views/install-failed.ejs

@@ -0,0 +1,21 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">GROWI Bot installation failed..</h1>
+        <p>
+          Retry from
+          <a href=<%- url %>>
+            <img alt="Add to Slack" height="40" width="139" src="/images/add-to-slack.png"/>
+          </a>
+        </p>
+        <p>
+          Or, please contact administrators of your workspace<br>
+          Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">Manage app installation settings for your workspace</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>

+ 18 - 0
packages/slackbot-proxy/src/views/install-succeeded-but-has-problem.ejs

@@ -0,0 +1,18 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">GROWI Bot installation has succeeded, but something went wrong..</h1>
+        <p>
+          Reason: <%- reason%>
+        </p>
+        <p>
+          Please contact administrators of your workspace<br>
+          Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">Manage app installation settings for your workspace</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>

+ 21 - 0
packages/slackbot-proxy/src/views/install-succeeded.ejs

@@ -0,0 +1,21 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">Congratulations!</h1>
+        <h2 class="my-5">GROWI Bot installation has succeeded!</h2>
+        <p>
+          Retry from
+          <a href=<%- appPageUrl %>>
+            Access to Slack App detail page.
+          </a>
+        </p>
+        <p>
+          <a class="btn btn-outline-success" href="https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html">Getting started</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>

+ 1 - 0
packages/slackbot-proxy/src/views/top.ejs

@@ -14,6 +14,7 @@
           <img alt="Add to Slack" height="40" width="139" src="/images/add-to-slack.png"/>
           <img alt="Add to Slack" height="40" width="139" src="/images/add-to-slack.png"/>
         </a>
         </a>
       </div>
       </div>
+      <a class="btn btn-outline-success" href="https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html">Getting started</a>
       <div class="d-flex justify-content-evenly my-3">
       <div class="d-flex justify-content-evenly my-3">
         <% if (isOfficialMode) { %>
         <% if (isOfficialMode) { %>
           <a href="/privacy">
           <a href="/privacy">

+ 4 - 0
packages/slackbot-proxy/tsconfig.base.json

@@ -2,10 +2,14 @@
   "extends": "../../tsconfig.base.json",
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
   "compilerOptions": {
   },
   },
+  "include": [
+    "src"
+  ],
   "exclude": [
   "exclude": [
     "node_modules",
     "node_modules",
     "config",
     "config",
     "dist",
     "dist",
+    "src/public/**",
     "**/*.test.ts"
     "**/*.test.ts"
   ]
   ]
 }
 }

+ 9 - 96
yarn.lock

@@ -4970,7 +4970,7 @@ camelcase@^6.2.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
   integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
   integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
 
 
-camelize@1.0.0, camelize@^1.0.0:
+camelize@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
   resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
 
 
@@ -5835,10 +5835,6 @@ content-disposition@0.5.3:
   dependencies:
   dependencies:
     safe-buffer "5.1.2"
     safe-buffer "5.1.2"
 
 
-content-security-policy-builder@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz#8749a1d542fcbe82237281ea9f716ce68b394dd2"
-
 content-type@~1.0.4:
 content-type@~1.0.4:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@@ -6475,10 +6471,6 @@ dashdash@^1.12.0, dashdash@^1.14.0:
   dependencies:
   dependencies:
     assert-plus "^1.0.0"
     assert-plus "^1.0.0"
 
 
-dasherize@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
-
 data-urls@^2.0.0:
 data-urls@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
   resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@@ -6493,15 +6485,10 @@ date-and-time@^1.0.0:
   resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-1.0.0.tgz#0062394bdf6f44e961f0db00511cb19cdf3cc0a5"
   resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-1.0.0.tgz#0062394bdf6f44e961f0db00511cb19cdf3cc0a5"
   integrity sha512-477D7ypIiqlXBkxhU7YtG9wWZJEQ+RUpujt2quTfgf4+E8g5fNUkB0QIL0bVyP5/TKBg8y55Hfa1R/c4bt3dEw==
   integrity sha512-477D7ypIiqlXBkxhU7YtG9wWZJEQ+RUpujt2quTfgf4+E8g5fNUkB0QIL0bVyP5/TKBg8y55Hfa1R/c4bt3dEw==
 
 
-date-fns@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
-  integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
-
-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-fns@^2.19.0, date-fns@^2.23.0:
+  version "2.23.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9"
+  integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==
 
 
 date-format@^2.0.0:
 date-format@^2.0.0:
   version "2.1.0"
   version "2.1.0"
@@ -6830,10 +6817,6 @@ dir-glob@^3.0.1:
   dependencies:
   dependencies:
     path-type "^4.0.0"
     path-type "^4.0.0"
 
 
-dns-prefetch-control@0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
-
 doctrine@3.0.0, doctrine@^3.0.0:
 doctrine@3.0.0, doctrine@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
@@ -6910,10 +6893,6 @@ domutils@^1.5.1:
     dom-serializer "0"
     dom-serializer "0"
     domelementtype "1"
     domelementtype "1"
 
 
-dont-sniff-mimetype@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58"
-
 dot-case@^3.0.4:
 dot-case@^3.0.4:
   version "3.0.4"
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
   resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
@@ -7952,10 +7931,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   dependencies:
   dependencies:
     homedir-polyfill "^1.0.1"
     homedir-polyfill "^1.0.1"
 
 
-expect-ct@0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.1.tgz#de84476a2dbcb85000d5903737e9bc8a5ba7b897"
-
 expect@^27.0.6:
 expect@^27.0.6:
   version "27.0.6"
   version "27.0.6"
   resolved "https://registry.yarnpkg.com/expect/-/expect-27.0.6.tgz#a4d74fbe27222c718fff68ef49d78e26a8fd4c05"
   resolved "https://registry.yarnpkg.com/expect/-/expect-27.0.6.tgz#a4d74fbe27222c718fff68ef49d78e26a8fd4c05"
@@ -8654,10 +8629,6 @@ fragment-cache@^0.2.1:
   dependencies:
   dependencies:
     map-cache "^0.2.2"
     map-cache "^0.2.2"
 
 
-frameguard@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9"
-
 fresh@0.5.2, fresh@^0.5.2:
 fresh@0.5.2, fresh@^0.5.2:
   version "0.5.2"
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -9585,37 +9556,6 @@ header-case@^2.0.4:
     capital-case "^1.0.4"
     capital-case "^1.0.4"
     tslib "^2.0.3"
     tslib "^2.0.3"
 
 
-helmet-crossdomain@0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.3.0.tgz#707e2df930f13ad61f76ed08e1bb51ab2b2e85fa"
-
-helmet-csp@2.7.1:
-  version "2.7.1"
-  resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.7.1.tgz#e8e0b5186ffd4db625cfcce523758adbfadb9dca"
-  dependencies:
-    camelize "1.0.0"
-    content-security-policy-builder "2.0.0"
-    dasherize "2.0.0"
-    platform "1.3.5"
-
-helmet@^3.13.0:
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.13.0.tgz#d6d46763538f77b437be77f06d0af42078b2c656"
-  dependencies:
-    dns-prefetch-control "0.1.0"
-    dont-sniff-mimetype "1.0.0"
-    expect-ct "0.1.1"
-    frameguard "3.0.0"
-    helmet-crossdomain "0.3.0"
-    helmet-csp "2.7.1"
-    hide-powered-by "1.0.0"
-    hpkp "2.0.0"
-    hsts "2.1.0"
-    ienoopen "1.0.0"
-    nocache "2.0.0"
-    referrer-policy "1.1.0"
-    x-xss-protection "1.1.0"
-
 helmet@^4.6.0:
 helmet@^4.6.0:
   version "4.6.0"
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
@@ -9625,10 +9565,6 @@ hex-color-regex@^1.1.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
 
 
-hide-powered-by@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b"
-
 highlight.js@9.18.1:
 highlight.js@9.18.1:
   version "9.18.1"
   version "9.18.1"
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c"
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c"
@@ -9684,10 +9620,6 @@ hosted-git-info@^4.0.1:
   dependencies:
   dependencies:
     lru-cache "^6.0.0"
     lru-cache "^6.0.0"
 
 
-hpkp@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672"
-
 hsl-regex@^1.0.0:
 hsl-regex@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
   resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
@@ -9696,10 +9628,6 @@ hsla-regex@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
   resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
 
 
-hsts@2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/hsts/-/hsts-2.1.0.tgz#cbd6c918a2385fee1dd5680bfb2b3a194c0121cc"
-
 html-comment-regex@^1.1.0:
 html-comment-regex@^1.1.0:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
@@ -9934,10 +9862,6 @@ ieee754@^1.1.4:
   version "1.1.8"
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
 
 
-ienoopen@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b"
-
 iferr@^0.1.5:
 iferr@^0.1.5:
   version "0.1.5"
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
@@ -13297,9 +13221,10 @@ no-case@^3.0.4:
     lower-case "^2.0.2"
     lower-case "^2.0.2"
     tslib "^2.0.3"
     tslib "^2.0.3"
 
 
-nocache@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980"
+nocache@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.1.tgz#54d8b53a7e0a0aa1a288cfceab8a3cefbcde67d4"
+  integrity sha512-Gh39xwJwBKy0OvFmWfBs/vDO4Nl7JhnJtkqNP76OUinQz7BiMoszHYrIDHHAaqVl/QKVxCEy4ZxC/XZninu7nQ==
 
 
 node-dev@^4.0.0:
 node-dev@^4.0.0:
   version "4.0.0"
   version "4.0.0"
@@ -14913,10 +14838,6 @@ plantuml-encoder@^1.2.5:
     pako "1.0.3"
     pako "1.0.3"
     utf8-bytes "0.0.1"
     utf8-bytes "0.0.1"
 
 
-platform@1.3.5:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
-
 pluralize@^8.0.0:
 pluralize@^8.0.0:
   version "8.0.0"
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
@@ -16597,10 +16518,6 @@ redux@^4.0.4:
     loose-envify "^1.4.0"
     loose-envify "^1.4.0"
     symbol-observable "^1.2.0"
     symbol-observable "^1.2.0"
 
 
-referrer-policy@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79"
-
 reflect-metadata@^0.1.13:
 reflect-metadata@^0.1.13:
   version "0.1.13"
   version "0.1.13"
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@@ -20638,10 +20555,6 @@ x-is-string@^0.1.0:
   resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
   resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
   integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
   integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
 
 
-x-xss-protection@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.1.0.tgz#4f1898c332deb1e7f2be1280efb3e2c53d69c1a7"
-
 xdg-basedir@^3.0.0:
 xdg-basedir@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"