Skip to content

Commit

Permalink
Merge branch 'v3' of github.com:slackapi/bolt-js into v3
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengill committed Jan 12, 2021
2 parents b4f8336 + 3fce059 commit 4fe9345
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 161 deletions.
31 changes: 18 additions & 13 deletions docs/_basic/authenticating_oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Bolt for JavaScript does not support OAuth for [custom receivers](#receiver). If

To learn more about the OAuth installation flow with Slack, [read the API documentation](https://api.slack.com/authentication/oauth-v2).

To add support for [org wide installations](https://api.slack.com/enterprise/apps), you will need Bolt for JavaScript version `2.5.0` or newer. You will have to update your `installationStore` to include `storeOrgInstallation` and `fetchOrgInstallation` methods. Lastly, make sure you have enabled org wide installations in your app configuration settings under **Org Level Apps**.
To add support for [org wide installations](https://api.slack.com/enterprise/apps), you will need Bolt for JavaScript version `3.0.0` or newer. Make sure you have enabled org wide installations in your app configuration settings under **Org Level Apps**.
</div>

```javascript
Expand All @@ -29,21 +29,26 @@ const app = new App({
installationStore: {
storeInstallation: async (installation) => {
// change the line below so it saves to your database
return await database.set(installation.team.id, installation);
if (installation.isEnterpriseInstall) {
// support for org wide app installation
return await database.set(installation.enterprise.id, installation);
} else {
// single team app installation
return await database.set(installation.team.id, installation);
}
throw new Error('Failed saving installation data to installationStore');
},
fetchInstallation: async (InstallQuery) => {
// change the line below so it fetches from your database
return await database.get(InstallQuery.teamId);
},
storeOrgInstallation: async (installation) => {
// include this method if you want your app to support org wide installations
// change the line below so it saves to your database
return await database.set(installation.enterprise.id, installation);
},
fetchOrgInstallation: async (InstallQuery) => {
// include this method if you want your app to support org wide installations
// change the line below so it fetches from your database
return await database.get(InstallQuery.enterpriseId);
if (InstallQuery.isEnterpriseInstall && InstallQuery.enterpriseId !== undefined) {
// org wide app installation lookup
return await database.get(InstallQuery.enterpriseId);
}
if (InstallQuery.teamId !== undefined) {
// single team app installation lookup
return await database.get(InstallQuery.teamId);
}
throw new Error('Failed fetching installation');
},
},
});
Expand Down
31 changes: 18 additions & 13 deletions docs/_basic/ja_authenticating_oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Bolt for JavaScript は `slack/install` というパスも生成します。こ

Slack の OAuth インストールフローについてもっと知りたい場合は [API ドキュメント](https://api.slack.com/authentication/oauth-v2)を参照してください。

[Enterprise Grid の OrG 全体へのインストール](https://api.slack.com/enterprise/apps)への対応を追加する場合、Bolt for JavaScript のバージョン 2.5.0 以上を利用してください。また`installationStore``storeOrgInstallation``fetchOrgInstallation` というメソッドを追加する必要があります。そして、最後に Slack アプリの設定画面で **Org Level Apps** の設定が有効になっていることを忘れずに確認するようにしてください
[Enterprise Grid の OrG 全体へのインストール](https://api.slack.com/enterprise/apps)への対応を追加する場合、Bolt for JavaScript のバージョン 3.0.0 以上を利用してください。また Slack アプリの設定画面で **Org Level Apps** の設定が有効になっていることを確認してください
</div>

```javascript
Expand All @@ -28,21 +28,26 @@ const app = new App({
installationStore: {
storeInstallation: async (installation) => {
// 実際のデータベースに保存するために、ここのコードを変更
return await database.set(installation.team.id, installation);
if (installation.isEnterpriseInstall) {
// OrG 全体へのインストールに対応する場合
return await database.set(installation.enterprise.id, installation);
} else {
// 単独のワークスペースへのインストールの場合
return await database.set(installation.team.id, installation);
}
throw new Error('Failed saving installation data to installationStore');
},
fetchInstallation: async (InstallQuery) => {
// 実際のデータベースから取得するために、ここのコードを変更
return await database.get(InstallQuery.teamId);
},
storeOrgInstallation: async (installation) => {
// OrG 全体へのインストールに対応する場合はこのメソッドも追加
// 実際のデータベースから取得するために、ここのコードを変更
return await database.set(installation.enterprise.id, installation);
},
fetchOrgInstallation: async (InstallQuery) => {
// OrG 全体へのインストールに対応する場合はこのメソッドも追加
// 実際のデータベースから取得するために、ここのコードを変更
return await database.get(InstallQuery.enterpriseId);
if (InstallQuery.isEnterpriseInstall && InstallQuery.enterpriseId !== undefined) {
// OrG 全体へのインストール情報の参照
return await database.get(InstallQuery.enterpriseId);
}
if (InstallQuery.teamId !== undefined) {
// 単独のワークスペースへのインストール情報の参照
return await database.get(InstallQuery.teamId);
}
throw new Error('Failed fetching installation');
},
},
});
Expand Down
5 changes: 2 additions & 3 deletions docs/_tutorials/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Bolt includes a collection of initialization options to customize apps. There ar
| `clientId` | The client ID `string` from your app's configuration which is [required to configure OAuth](/bolt-js/concepts#authenticating-oauth). |
| `clientSecret` | The client secret `string` from your app's configuration which is [required to configure OAuth](/bolt-js/concepts#authenticating-oauth). |
| `stateSecret` | Recommended parameter (`string`) that's passed when [configuring OAuth](/bolt-js/concepts#authenticating-oauth) to prevent CSRF attacks |
| `installationStore` | Defines how to save and fetch installation data when [configuring OAuth](/bolt-js/concepts#authenticating-oauth). Contains two methods: `fetchInstallation` and `storeInstallation`. If you want org-app support, you should also add `fetchOrgInstallation` and `storeOrgInstallation`. The default `installationStore` is an in-memory store. |
| `installationStore` | Defines how to save and fetch installation data when [configuring OAuth](/bolt-js/concepts#authenticating-oauth). Contains two methods: `fetchInstallation` and `storeInstallation`. The default `installationStore` is an in-memory store. |
| `scopes` | Array of scopes that your app will request [within the OAuth process](/bolt-js/concepts#authenticating-oauth). |
| `installerOptions` | Optional object that can be used to customize [the default OAuth support](/bolt-js/concepts#authenticating-oauth). Read more in the OAuth documentation. |

Expand All @@ -109,8 +109,7 @@ App options are passed into the `App` constructor.
| `token` | A `string` from your app's configuration (under "Settings" > "Install App") required for calling the Web API. May not be passed when using `authorize`, `orgAuthorize`, or OAuth. |
| `botId` | Can only be used when `authorize` is not defined. The optional `botId` is the ID for your bot token (ex: `B12345`) which can be used to ignore messages sent by your app. If a `xoxb-` token is passed to your app, this value will automatically be retrieved by your app calling the [`auth.test` method](https://api.slack.com/methods/auth.test). |
| `botUserId` | Can only be used wihen `authorize` is not defined. The optional `botUserId` is distinct from the `botId`, as it's the user ID associated with your bot user used to identify direct mentions. If a `xoxb-` token is passed to your app, this value will automatically be retrieved by your app calling the [`auth.test` method](https://api.slack.com/methods/auth.test). |
| `authorize` | Function for multi-team installations that determines which token is associated with the incoming event. The `authorize` function is passed source data that always contains a `teamId`, and sometimes contains a `userId`, `conversationId`, `enterpriseId`, and `isEnterpriseInstall` (depending which information the incoming event contains). An `authorize` function should either return a `botToken`, `botId`, and `botUserId`, or could return a `userToken`. If using [built-in OAuth support](/bolt-js/concepts#authenticating-oauth), an `authorize` function will automatically be created so you do not need to pass one in. More information about `authorization` functions can be found on |
| `orgAuthorize` | Function similar to `authorize`, but used for apps installed to an entire enterprise organization. The `orgAuthorize` function is passed source data that always contains an `enterpriseId`, and sometimes contains a `teamId`, `userId`, `conversationId`, and `isEnterpriseInstall` (depending which information the incoming event contains). The `orgAuthorize` function should return the same information as the `authorize` function, with the addition of a `teamId` and `enterpriseId`.
| `authorize` | Function for multi-team installations that determines which token is associated with the incoming event. The `authorize` function is passed source data that sometimes contains a `userId`, `conversationId`, `enterpriseId`, `teamId` and `isEnterpriseInstall` (depending which information the incoming event contains). An `authorize` function should either return a `botToken`, `botId`, and `botUserId`, or could return a `userToken`. If using [built-in OAuth support](/bolt-js/concepts#authenticating-oauth), an `authorize` function will automatically be created so you do not need to pass one in. More information about `authorization` functions can be found on |
| `logger` | Option that allows you to pass a custom logger rather than using the built-in one. Loggers must implement specific methods ([the `Logger` interface](https://github.com/slackapi/node-slack-sdk/blob/main/packages/logger/src/index.ts)), which includes `setLevel(level: LogLevel)`, `getLevel()`, `setName(name: string)`, `debug(...msgs: any[])`, `info(...msgs: any[])`, `warn(...msgs: any[])`, and `error(...msgs: any[])`. More information about logging are [in the documentation](/bolt-js/concepts#logging) |
| `logLevel` | Option to control how much or what kind of information is logged. The `LogLevel` export contains the possible levels–in order of most to least information: `DEBUG`, `INFO`, `WARN`, and `ERROR`. By default, `logLevel` is set to `INFO`. More information on logging can be found [in the documentation](/bolt-js/concepts#logging). |
| `ignoreSelf` | `boolean` to enable a middleware function that ignores any messages coming from your app. Requires a `botId`. Defaults to `true`. |
Expand Down
2 changes: 1 addition & 1 deletion docs/_tutorials/using-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ This page helps describe how to use this package from a project that also uses T

### Minimum version

The latest major version of `@slack/bolt` is supported to build against a minimum TypeScript version of v3.7.
The latest major version of `@slack/bolt` is supported to build against a minimum TypeScript version of v4.1.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dist/**/*"
],
"engines": {
"node": ">=10.13.0",
"node": ">=12.13.0",
"npm": ">=6.4.1"
},
"scripts": {
Expand All @@ -47,7 +47,7 @@
"@slack/types": "^1.9.0",
"@slack/web-api": "^5.14.0",
"@types/express": "^4.16.1",
"@types/node": ">=10",
"@types/node": ">=12",
"@types/promise.allsettled": "^1.0.3",
"axios": "^0.21.1",
"express": "^4.16.4",
Expand Down Expand Up @@ -79,7 +79,7 @@
"ts-node": "^8.1.0",
"tsd": "^0.13.1",
"tslint-config-airbnb": "^5.11.1",
"typescript": "^3.7.2"
"typescript": "^4.1.0"
},
"tsd": {
"directory": "types-tests"
Expand Down
106 changes: 3 additions & 103 deletions src/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,6 @@ describe('App', () => {
assert(authorizeCallback.notCalled, 'Should not call the authorize callback on instantiation');
assert.instanceOf(app, App);
});
it('should succeed with an orgAuthorize callback', async () => {
// Arrange
const authorizeCallback = sinon.fake();
const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match

// Act
const app = new App({ orgAuthorize: authorizeCallback, signingSecret: '' });

// Assert
assert(authorizeCallback.notCalled, 'Should not call the orgAuthorize callback on instantiation');
assert.instanceOf(app, App);
});
it('should succeed with an authorize and orgAuthorize callback', async () => {
// Arrange
const authorizeCallback = sinon.fake();
const orgAuthorizeCallback = sinon.fake();
const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match

// Act
const app = new App({ orgAuthorize: orgAuthorizeCallback, authorize: authorizeCallback, signingSecret: '' });

// Assert
assert(authorizeCallback.notCalled, 'Should not call the authorize callback on instantiation');
assert(orgAuthorizeCallback.notCalled, 'Should not call the orgAuthorize callback on instantiation');
assert.instanceOf(app, App);
});
it('should fail without a token for single team authorization or authorize callback or oauth installer', async () => {
// Arrange
const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match
Expand Down Expand Up @@ -123,22 +97,6 @@ describe('App', () => {
assert(authorizeCallback.notCalled);
}
});
it('should fail when both a token and orgAuthorize callback are specified', async () => {
// Arrange
const authorizeCallback = sinon.fake();
const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match

// Act
try {
// eslint-disable-line @typescript-eslint/no-unused-expressions
new App({ token: '', orgAuthorize: authorizeCallback, signingSecret: '' });
assert.fail();
} catch (error) {
// Assert
assert.propertyVal(error, 'code', ErrorCode.AppInitializationError);
assert(authorizeCallback.notCalled);
}
});
it('should fail when both a token is specified and OAuthInstaller is initialized', async () => {
// Arrange
const authorizeCallback = sinon.fake();
Expand Down Expand Up @@ -171,28 +129,6 @@ describe('App', () => {
assert(authorizeCallback.notCalled);
}
});
it('should fail when both a orgAuthorize callback is specified and OAuthInstaller is initialized', async () => {
// Arrange
const authorizeCallback = sinon.fake();
const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match

// Act
try {
// eslint-disable-line @typescript-eslint/no-unused-expressions
new App({
orgAuthorize: authorizeCallback,
clientId: '',
clientSecret: '',
stateSecret: '',
signingSecret: '',
});
assert.fail();
} catch (error) {
// Assert
assert.propertyVal(error, 'code', ErrorCode.AppInitializationError);
assert(authorizeCallback.notCalled);
}
});
describe('with a custom receiver', () => {
it('should succeed with no signing secret', async () => {
// Arrange
Expand Down Expand Up @@ -1203,8 +1139,8 @@ describe('App', () => {
assert(fakeErrorHandler.notCalled);
});

// This test confirms orgAuthorize is being used for org events
it('should acknowledge any of possible org events', async () => {
// This test confirms authorize is being used for org events
it('should acknowledge any possible org events', async () => {
// Arrange
const ackFn = sinon.fake.resolves({});
const actionFn = sinon.fake.resolves({});
Expand All @@ -1220,7 +1156,7 @@ describe('App', () => {
const app = new App({
logger: fakeLogger,
receiver: fakeReceiver,
orgAuthorize: sinon.fake.resolves(dummyAuthorizationResult),
authorize: sinon.fake.resolves(dummyAuthorizationResult),
});

app.use(async ({ next }) => {
Expand Down Expand Up @@ -1311,42 +1247,6 @@ describe('App', () => {
assert.equal(ackFn.callCount, dummyReceiverEvents.length);
assert(fakeErrorHandler.notCalled);
});

it('should fail because no orgAuthorize was defined to handle org install events', async () => {
// Arrange
const ackFn = sinon.fake.resolves({});
const actionFn = sinon.fake.resolves({});
const overrides = buildOverrides([withNoopWebClient()]);
const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match
const dummyReceiverEvents = createOrgAppReceiverEvents();

// Act
const fakeLogger = createFakeLogger();
// only passed in authorize and not orgAuthorize
const app = new App({
logger: fakeLogger,
receiver: fakeReceiver,
authorize: sinon.fake.resolves(dummyAuthorizationResult),
});

app.use(async ({ next }) => {
await ackFn();
await next!();
});
app.action('block_action_id', async ({}) => {
await actionFn();
});

app.error(fakeErrorHandler);
await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event)));

// Assert
assert.equal(actionFn.callCount, 0);
assert.equal(ackFn.callCount, 0);
assert.equal(fakeErrorHandler.callCount, dummyReceiverEvents.length);
assert.instanceOf(fakeErrorHandler.firstCall.args[0], Error);
assert.propertyVal(fakeErrorHandler.firstCall.args[0], 'code', ErrorCode.AuthorizationError);
});
});

describe('respond()', () => {
Expand Down
Loading

0 comments on commit 4fe9345

Please sign in to comment.