Skip to content

Commit

Permalink
updated org app support based on proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengill committed Jul 15, 2020
1 parent 782aaac commit 75e7b24
Showing 1 changed file with 121 additions and 46 deletions.
167 changes: 121 additions & 46 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface AppOptions {
botId?: AuthorizeResult['botId']; // only used when authorize is not defined, shortcut for fetching
botUserId?: AuthorizeResult['botUserId']; // only used when authorize is not defined, shortcut for fetching
authorize?: Authorize; // either token or authorize
orgAuthorize?: Authorize;
receiver?: Receiver;
logger?: Logger;
logLevel?: LogLevel;
Expand All @@ -88,26 +89,35 @@ export { LogLevel, Logger } from '@slack/logger';
/** Authorization function - seeds the middleware processing and listeners with an authorization context */
export interface Authorize {
(
source: AuthorizeSourceData,
source: AuthorizeSourceData | OrgAuthorizeSourceData,
body?: AnyMiddlewareArgs['body'],
): Promise<AuthorizeResult>;
}

/** Authorization function inputs - authenticated data about an event for the authorization function */
export interface AuthorizeSourceData {
teamId: string | undefined;
teamId: string;
enterpriseId?: string;
userId?: string;
conversationId?: string;
}

/** Authorization function inputs - authenticated data about an event for the authorization function */
export interface OrgAuthorizeSourceData {
enterpriseId: string;
teamId?: string;
userId?: string;
conversationId?: string;
}

/** Authorization function outputs - data that will be available as part of event processing */
export interface AuthorizeResult {
// one of either botToken or userToken are required
botToken?: string; // used by `say` (preferred over userToken)
userToken?: string; // used by `say` (overridden by botToken)
botId?: string; // required for `ignoreSelf` global middleware
botUserId?: string; // optional but allows `ignoreSelf` global middleware be more filter more than just message events
teamId?: string;
[key: string]: any;
}

Expand Down Expand Up @@ -155,7 +165,7 @@ export default class App {

private clientOptions: WebClientOptions;

private clients: { [teamId: string]: WebClientPool } = {};
private clients: { [teamOrEnterpriseId: string]: WebClientPool } = {};

/** Receiver - ingests events from the Slack platform */
private receiver: Receiver;
Expand All @@ -166,6 +176,9 @@ export default class App {
/** Authorize */
private authorize!: Authorize;

/** Org Authorize */
private orgAuthorize!: Authorize;

/** Global middleware chain */
private middleware: Middleware<AnyMiddlewareArgs>[];

Expand All @@ -187,6 +200,7 @@ export default class App {
botId = undefined,
botUserId = undefined,
authorize = undefined,
orgAuthorize = undefined,
logger = undefined,
logLevel = undefined,
ignoreSelf = true,
Expand Down Expand Up @@ -269,24 +283,32 @@ export default class App {
}

if (token !== undefined) {
if (authorize !== undefined || usingOauth) {
if (authorize !== undefined || orgAuthorize !== undefined || usingOauth) {
throw new AppInitializationError(
`token as well as authorize options or oauth installer options were provided. ${tokenUsage}`,
`token as well as authorize, orgAuthorize, or oauth installer options were provided. ${tokenUsage}`,
);
}
this.authorize = singleTeamAuthorization(this.client, { botId, botUserId, botToken: token });
} else if (authorize === undefined && !usingOauth) {
// Todo: what should we do with orgAuthorize in a singeTeamAuthorize/token provided world?
this.orgAuthorize = singleTeamAuthorization(this.client, { botId, botUserId, botToken: token });
} else if (authorize === undefined && orgAuthorize === undefined && !usingOauth) {
throw new AppInitializationError(
`No token, no authorize options, and no oauth installer options provided. ${tokenUsage}`,
`No token, no authorize, no orgAuthorize, and no oauth installer options provided. ${tokenUsage}`,
);
} else if (authorize !== undefined && usingOauth) {
} else if ((authorize !== undefined || orgAuthorize !== undefined) && usingOauth) {
throw new AppInitializationError(
`Both authorize options and oauth installer options provided. ${tokenUsage}`,
);
} else if (authorize === undefined && usingOauth) {
} else if (authorize === undefined && orgAuthorize === undefined && usingOauth) {
this.authorize = (this.receiver as ExpressReceiver).installer!.authorize as Authorize;
} else if (authorize !== undefined && !usingOauth) {
this.orgAuthorize = (this.receiver as ExpressReceiver).installer!.orgAuthorize as Authorize;
} else if (authorize === undefined && orgAuthorize !== undefined && !usingOauth) {
this.orgAuthorize = orgAuthorize;
} else if (authorize !== undefined && orgAuthorize === undefined && !usingOauth) {
this.authorize = authorize;
} else if (authorize !== undefined && orgAuthorize !== undefined && !usingOauth) {
this.authorize = authorize;
this.orgAuthorize = orgAuthorize;
} else {
this.logger.error('Never should have reached this point, please report to the team');
assertNever();
Expand Down Expand Up @@ -518,15 +540,29 @@ export default class App {
// From this point on, we assume that body is not just a key-value map, but one of the types of bodies we expect
const bodyArg = body as AnyMiddlewareArgs['body'];

// Initialize context (shallow copy to enforce object identity separation)
const source = buildSource(type, conversationId, bodyArg);
let authorizeResult;

try {
authorizeResult = await this.authorize(source, bodyArg);
} catch (error) {
this.logger.warn('Authorization of incoming event did not succeed. No listeners will be called.');
return this.handleError(error);
let source;
if (bodyArg.is_enterprise_install) {
// This is an org app
// Initialize context (shallow copy to enforce object identity separation)
source = buildSource(type, conversationId, bodyArg);

try {
authorizeResult = await this.orgAuthorize(source, bodyArg);
} catch (error) {
this.logger.warn('Authorization of incoming event did not succeed. No listeners will be called.');
return this.handleError(error);
}
} else {
// Initialize context (shallow copy to enforce object identity separation)
source = buildSource(type, conversationId, bodyArg);

try {
authorizeResult = await this.authorize(source, bodyArg);
} catch (error) {
this.logger.warn('Authorization of incoming event did not succeed. No listeners will be called.');
return this.handleError(error);
}
}

const context: Context = { ...authorizeResult };
Expand Down Expand Up @@ -623,11 +659,21 @@ export default class App {
let client = this.client;
const token = selectToken(context);
if (token !== undefined) {
let pool = this.clients[source.teamId!];
if (pool === undefined) {
pool = this.clients[source.teamId!] = new WebClientPool();
let pool;
if (source.teamId !== undefined) {
pool = this.clients[source.teamId];
if (pool === undefined) {
pool = this.clients[source.teamId] = new WebClientPool();
}
} else if (source.enterpriseId !== undefined) {
pool = this.clients[source.enterpriseId];
if (pool === undefined) {
pool = this.clients[source.enterpriseId] = new WebClientPool();
}
}
if (pool !== undefined) {
client = pool.getOrCreate(token, this.clientOptions);
}
client = pool.getOrCreate(token, this.clientOptions);
}

// Dispatch event through the global middleware chain
Expand Down Expand Up @@ -698,34 +744,63 @@ function buildSource(
type: IncomingEventType,
channelId: string | undefined,
body: AnyMiddlewareArgs['body'],
): AuthorizeSourceData {
): AuthorizeSourceData | OrgAuthorizeSourceData {
// NOTE: potentially something that can be optimized, so that each of these conditions isn't evaluated more than once.
// if this makes it prettier, great! but we should probably check perf before committing to any specific optimization.

let source: AuthorizeSourceData | OrgAuthorizeSourceData;
// let source: OrgAuthorizeSourceData;
// tslint:disable:max-line-length
const source: AuthorizeSourceData = {
teamId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).team_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.user !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).user.team_id as string :
undefined),
enterpriseId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.enterprise !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).enterprise!.id as string :
undefined),
userId:
((type === IncomingEventType.Event) ?
((typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'string') ? (body as SlackEventMiddlewareArgs['body']).event.user as string :
(typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'object') ? (body as SlackEventMiddlewareArgs['body']).event.user.id as string :
((body as SlackEventMiddlewareArgs['body']).event.channel !== undefined && (body as SlackEventMiddlewareArgs['body']).event.channel.creator !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.channel.creator as string :
((body as SlackEventMiddlewareArgs['body']).event.subteam !== undefined && (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by as string :
undefined) :
(type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs)['body']).user.id as string :
(type === IncomingEventType.Command) ? (body as SlackCommandMiddlewareArgs['body']).user_id as string :
undefined),
conversationId: channelId,
};
if (body.is_enterprise_install) {

source = {
teamId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).team_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.user !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).user.team_id as string :
undefined),
enterpriseId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.enterprise !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).enterprise!.id as string :
''), // TODO: empty string for enterpriseID is wrong, should be assertNever
userId:
((type === IncomingEventType.Event) ?
((typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'string') ? (body as SlackEventMiddlewareArgs['body']).event.user as string :
(typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'object') ? (body as SlackEventMiddlewareArgs['body']).event.user.id as string :
((body as SlackEventMiddlewareArgs['body']).event.channel !== undefined && (body as SlackEventMiddlewareArgs['body']).event.channel.creator !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.channel.creator as string :
((body as SlackEventMiddlewareArgs['body']).event.subteam !== undefined && (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by as string :
undefined) :
(type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs)['body']).user.id as string :
(type === IncomingEventType.Command) ? (body as SlackCommandMiddlewareArgs['body']).user_id as string :
undefined),
conversationId: channelId,
};
} else {
source = {
teamId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).team_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.user !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).user.team_id as string :
''), // TODO: empty string for teamID is wrong, should be assertNever
enterpriseId:
((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.team !== null) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team!.enterprise_id as string :
((type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) && body.enterprise !== undefined) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).enterprise!.id as string :
undefined),
userId:
((type === IncomingEventType.Event) ?
((typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'string') ? (body as SlackEventMiddlewareArgs['body']).event.user as string :
(typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'object') ? (body as SlackEventMiddlewareArgs['body']).event.user.id as string :
((body as SlackEventMiddlewareArgs['body']).event.channel !== undefined && (body as SlackEventMiddlewareArgs['body']).event.channel.creator !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.channel.creator as string :
((body as SlackEventMiddlewareArgs['body']).event.subteam !== undefined && (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by as string :
undefined) :
(type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs)['body']).user.id as string :
(type === IncomingEventType.Command) ? (body as SlackCommandMiddlewareArgs['body']).user_id as string :
undefined),
conversationId: channelId,
};
}
// tslint:enable:max-line-length

return source;
Expand Down

0 comments on commit 75e7b24

Please sign in to comment.