Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose useful functions #1955

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 2 additions & 28 deletions src/App.ts
Expand Up @@ -54,7 +54,7 @@ import {
SlashCommand,
WorkflowStepEdit,
} from './types';
import { IncomingEventType, getTypeAndConversation, assertNever } from './helpers';
import { IncomingEventType, getTypeAndConversation, assertNever, isBodyWithTypeEnterpriseInstall, isEventTypeToSkipAuthorize } from './helpers';
import { CodedError, asCodedError, AppInitializationError, MultipleListenerError, ErrorCode, InvalidCustomPropertyError } from './errors';
import { AllMiddlewareArgs, contextBuiltinKeys } from './types/middleware';
import { StringIndexed } from './types/helpers';
Expand Down Expand Up @@ -878,7 +878,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
const source = buildSource(type, conversationId, bodyArg, isEnterpriseInstall);

let authorizeResult: AuthorizeResult;
if (type === IncomingEventType.Event && isEventTypeToSkipAuthorize(event.body.event.type)) {
if (type === IncomingEventType.Event && isEventTypeToSkipAuthorize(event)) {
authorizeResult = {
enterpriseId: source.enterpriseId,
teamId: source.teamId,
Expand Down Expand Up @@ -1522,25 +1522,6 @@ function buildSource<IsEnterpriseInstall extends boolean>(
};
}

function isBodyWithTypeEnterpriseInstall(body: AnyMiddlewareArgs['body'], type: IncomingEventType): boolean {
if (type === IncomingEventType.Event) {
const bodyAsEvent = body as SlackEventMiddlewareArgs['body'];
if (Array.isArray(bodyAsEvent.authorizations) && bodyAsEvent.authorizations[0] !== undefined) {
return !!bodyAsEvent.authorizations[0].is_enterprise_install;
}
}
// command payloads have this property set as a string
if (typeof body.is_enterprise_install === 'string') {
return body.is_enterprise_install === 'true';
}
// all remaining types have a boolean property
if (body.is_enterprise_install !== undefined) {
return body.is_enterprise_install;
}
// as a fallback we assume it's a single team installation (but this should never happen)
return false;
}

function isBlockActionOrInteractiveMessageBody(
body: SlackActionMiddlewareArgs['body'],
): body is SlackActionMiddlewareArgs<BlockAction | InteractiveMessage>['body'] {
Expand All @@ -1562,13 +1543,6 @@ 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);
}

function escapeHtml(input: string | undefined | null): string {
if (input) {
return input.replace(/&/g, '&amp;')
Expand Down
115 changes: 114 additions & 1 deletion src/helpers.spec.ts
@@ -1,6 +1,7 @@
import 'mocha';
import { assert } from 'chai';
import { getTypeAndConversation, IncomingEventType } from './helpers';
import { isBodyWithTypeEnterpriseInstall, getTypeAndConversation, IncomingEventType, isEventTypeToSkipAuthorize } from './helpers';
import { AnyMiddlewareArgs, ReceiverEvent, SlackEventMiddlewareArgs } from './types';

describe('Helpers', () => {
describe('getTypeAndConversation()', () => {
Expand Down Expand Up @@ -110,6 +111,118 @@ describe('Helpers', () => {
});
});
});

describe(`${isBodyWithTypeEnterpriseInstall.name}()`, () => {
describe('with body of event type', () => {
// Arrange
const dummyEventBody: SlackEventMiddlewareArgs['body'] = {
token: '',
team_id: '',
api_app_id: '',
event_id: '',
event_time: 0,
type: 'event_callback',
event: {
type: 'app_home_opened',
user: '',
channel: '',
event_ts: '',
},
authorizations: [{
enterprise_id: '',
is_bot: true,
team_id: '',
user_id: '',
is_enterprise_install: true,
}],
};

it('should resolve the is_enterprise_install field', () => {
// Act
const isEnterpriseInstall = isBodyWithTypeEnterpriseInstall(dummyEventBody);
// Assert
assert(isEnterpriseInstall === true);
});

it('should resolve the is_enterprise_install with provided event type', () => {
// Act
const isEnterpriseInstall = isBodyWithTypeEnterpriseInstall(dummyEventBody, IncomingEventType.Event);
// Assert
assert(isEnterpriseInstall === true);
});
});

describe('with is_enterprise_install as a string value', () => {
// Arrange
const dummyEventBody = {
is_enterprise_install: 'true',
} as AnyMiddlewareArgs['body'];

it('should resolve is_enterprise_install as truthy', () => {
// Act
const isEnterpriseInstall = isBodyWithTypeEnterpriseInstall(dummyEventBody);
// Assert
assert(isEnterpriseInstall === true);
});
});

describe('with is_enterprise_install as boolean value', () => {
// Arrange
const dummyEventBody = {
is_enterprise_install: true,
} as AnyMiddlewareArgs['body'];

it('should resolve is_enterprise_install as truthy', () => {
// Act
const isEnterpriseInstall = isBodyWithTypeEnterpriseInstall(dummyEventBody);
// Assert
assert(isEnterpriseInstall === true);
});
});

describe('with is_enterprise_install undefined', () => {
// Arrange
const dummyEventBody = {} as AnyMiddlewareArgs['body'];

it('should resolve is_enterprise_install as falsy', () => {
// Act
const isEnterpriseInstall = isBodyWithTypeEnterpriseInstall(dummyEventBody);
// Assert
assert(isEnterpriseInstall === false);
});
});
});

describe(`${isEventTypeToSkipAuthorize.name}()`, () => {
describe('receiver events that can be skipped', () => {
it('should return truthy when event can be skipped', () => {
// Arrange
const dummyEventBody = { ack: async () => { }, body: { event: { type: 'app_uninstalled' } } } as ReceiverEvent;
// Act
const isEnterpriseInstall = isEventTypeToSkipAuthorize(dummyEventBody);
// Assert
assert(isEnterpriseInstall === true);
});

it('should return falsy when event can not be skipped', () => {
// Arrange
const dummyEventBody = { ack: async () => { }, body: { event: { type: '' } } } as ReceiverEvent;
// Act
const isEnterpriseInstall = isEventTypeToSkipAuthorize(dummyEventBody);
// Assert
assert(isEnterpriseInstall === false);
});

it('should return falsy when event is invalid', () => {
// Arrange
const dummyEventBody = { ack: async () => { }, body: {} } as ReceiverEvent;
// Act
const isEnterpriseInstall = isEventTypeToSkipAuthorize(dummyEventBody);
// Assert
assert(isEnterpriseInstall === false);
});
});
});
});

function createFakeActions(conversationId: string): any[] {
Expand Down
43 changes: 43 additions & 0 deletions src/helpers.ts
Expand Up @@ -8,6 +8,8 @@ import {
SlackAction,
OptionsSource,
MessageShortcut,
AnyMiddlewareArgs,
ReceiverEvent,
} from './types';

/**
Expand All @@ -22,6 +24,11 @@ export enum IncomingEventType {
Shortcut,
}

// ----------------------------
// For skipping authorize with event

const eventTypesToSkipAuthorize = ['app_uninstalled', 'tokens_revoked'];

/**
* Helper which finds the type and channel (if any) that any specific incoming event is related to.
*
Expand Down Expand Up @@ -101,6 +108,42 @@ export function getTypeAndConversation(body: any): { type?: IncomingEventType; c
return {};
}

/**
* Helper which determines if the body of a request is enterprise install.
*
* Providing the type is optional but if you do the execution will be faster
*/
export function isBodyWithTypeEnterpriseInstall(body: AnyMiddlewareArgs['body'], type?: IncomingEventType): boolean {
const _type = type !== undefined ? type : getTypeAndConversation(body).type;

if (_type === IncomingEventType.Event) {
const bodyAsEvent = body as SlackEventMiddlewareArgs['body'];
if (Array.isArray(bodyAsEvent.authorizations) && bodyAsEvent.authorizations[0] !== undefined) {
return !!bodyAsEvent.authorizations[0].is_enterprise_install;
}
}
// command payloads have this property set as a string
if (typeof body.is_enterprise_install === 'string') {
return body.is_enterprise_install === 'true';
}
// all remaining types have a boolean property
if (body.is_enterprise_install !== undefined) {
return body.is_enterprise_install;
}
// as a fallback we assume it's a single team installation (but this should never happen)
return false;
}

/**
* Helper which determines if the event type will skip Authorize.
*
* Token revocation use cases
* https://github.com/slackapi/bolt-js/issues/674
*/
export function isEventTypeToSkipAuthorize(event: ReceiverEvent): boolean {
return eventTypesToSkipAuthorize.includes(event.body.event?.type);
}

/* istanbul ignore next */

/** Helper that should never be called, but is useful for exhaustiveness checking in conditional branches */
Expand Down