diff --git a/src/App.spec.ts b/src/App.spec.ts index 133fbd0eb..6ca052ced 100644 --- a/src/App.spec.ts +++ b/src/App.spec.ts @@ -921,6 +921,110 @@ describe('App', () => { // Assert assert.isTrue(workedAsExpected); }); + it('should be skipped for tokens_revoked events #674', async () => { + // Arrange + const fakeAxiosPost = sinon.fake.resolves({}); + overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); + const MockApp = await importApp(overrides); + + // Act + let workedAsExpected = false; + let authorizeCallCount = 0; + const app = new MockApp({ + receiver: fakeReceiver, + authorize: async () => { + authorizeCallCount += 1; + return {}; + }, + }); + app.event('tokens_revoked', async () => { + workedAsExpected = true; + }); + + // The authorize must be called for other events + await fakeReceiver.sendEvent({ + ack: noop, + body: { + enterprise_id: 'E_org_id', + api_app_id: 'A111', + event: { + type: 'app_mention', + }, + type: 'event_callback', + }, + }); + assert.equal(authorizeCallCount, 1); + + await fakeReceiver.sendEvent({ + ack: noop, + body: { + enterprise_id: 'E_org_id', + api_app_id: 'A111', + event: { + type: 'tokens_revoked', + tokens: { + oauth: ['P'], + bot: ['B'], + }, + }, + type: 'event_callback', + }, + }); + + // Assert + assert.equal(authorizeCallCount, 1); // still 1 + assert.isTrue(workedAsExpected); + }); + it('should be skipped for app_uninstalled events #674', async () => { + // Arrange + const fakeAxiosPost = sinon.fake.resolves({}); + overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); + const MockApp = await importApp(overrides); + + // Act + let workedAsExpected = false; + let authorizeCallCount = 0; + const app = new MockApp({ + receiver: fakeReceiver, + authorize: async () => { + authorizeCallCount += 1; + return {}; + }, + }); + app.event('app_uninstalled', async () => { + workedAsExpected = true; + }); + + // The authorize must be called for other events + await fakeReceiver.sendEvent({ + ack: noop, + body: { + enterprise_id: 'E_org_id', + api_app_id: 'A111', + event: { + type: 'app_mention', + }, + type: 'event_callback', + }, + }); + assert.equal(authorizeCallCount, 1); + + await fakeReceiver.sendEvent({ + ack: noop, + body: { + enterprise_id: 'E_org_id', + api_app_id: 'A111', + event: { + type: 'app_uninstalled', + }, + type: 'event_callback', + }, + }); + + // Assert + assert.equal(authorizeCallCount, 1); // still 1 + assert.isTrue(workedAsExpected); + }); }); describe('routing', () => { diff --git a/src/App.ts b/src/App.ts index 13d8cb84c..f38b1c960 100644 --- a/src/App.ts +++ b/src/App.ts @@ -792,25 +792,32 @@ export default class App { const source = buildSource(type, conversationId, bodyArg, isEnterpriseInstall); let authorizeResult: AuthorizeResult; - try { - if (source.isEnterpriseInstall) { - authorizeResult = await this.authorize(source as AuthorizeSourceData, bodyArg); - } else { - authorizeResult = await this.authorize(source as AuthorizeSourceData, bodyArg); + if (type === IncomingEventType.Event && isEventTypeToSkipAuthorize(event.body.event.type)) { + authorizeResult = { + enterpriseId: source.enterpriseId, + teamId: source.teamId, + }; + } else { + try { + if (source.isEnterpriseInstall) { + authorizeResult = await this.authorize(source as AuthorizeSourceData, bodyArg); + } else { + authorizeResult = await this.authorize(source as AuthorizeSourceData, bodyArg); + } + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const e = error as any; + this.logger.warn('Authorization of incoming event did not succeed. No listeners will be called.'); + e.code = ErrorCode.AuthorizationError; + // disabling due to https://github.com/typescript-eslint/typescript-eslint/issues/1277 + // eslint-disable-next-line consistent-return + return this.handleError({ + error: e, + logger: this.logger, + body: bodyArg, + context: {}, + }); } - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const e = error as any; - this.logger.warn('Authorization of incoming event did not succeed. No listeners will be called.'); - e.code = ErrorCode.AuthorizationError; - // disabling due to https://github.com/typescript-eslint/typescript-eslint/issues/1277 - // eslint-disable-next-line consistent-return - return this.handleError({ - error: e, - logger: this.logger, - body: bodyArg, - context: {}, - }); } // Try to set teamId from AuthorizeResult before using one from source @@ -1448,6 +1455,13 @@ function buildRespondFn( }; } +// token revocation use cases +// https://github.com/slackapi/bolt-js/issues/674 +const eventTypesToSkipAuthorize = ['app_uninstalled', 'tokens_revoked']; +function isEventTypeToSkipAuthorize(eventType: string) { + return eventTypesToSkipAuthorize.includes(eventType); +} + // ---------------------------- // Instrumentation // Don't change the position of the following code