Skip to content

Commit

Permalink
added oauth package to bolt
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengill committed Apr 20, 2020
1 parent 0497c9b commit 40092b9
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 21 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"@slack/logger": "^2.0.0",
"@slack/oauth": "file:../node-slack-sdk/packages/oauth",
"@slack/types": "^1.4.0",
"@slack/web-api": "^5.8.0",
"@types/express": "^4.16.1",
Expand Down
74 changes: 57 additions & 17 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export interface AppOptions {
signingSecret?: ExpressReceiverOptions['signingSecret'];
endpoints?: ExpressReceiverOptions['endpoints'];
processBeforeResponse?: ExpressReceiverOptions['processBeforeResponse'];
clientId?: ExpressReceiverOptions['clientId'];
clientSecret?: ExpressReceiverOptions['clientSecret'];
stateStore?: ExpressReceiverOptions['stateStore']; // default ClearStateStore
stateSecret?: ExpressReceiverOptions['stateSecret']; // required when using default stateStore
installationStore?: ExpressReceiverOptions['installationStore']; // default MemoryInstallationStore
authVersion?: ExpressReceiverOptions['authVersion']; // default 'v2'
scopes?: ExpressReceiverOptions['scopes'];
metadata?: ExpressReceiverOptions['metadata'];
agent?: Agent;
clientTls?: Pick<SecureContextOptions, 'pfx' | 'key' | 'passphrase' | 'cert' | 'ca'>;
convoStore?: ConversationStore | false;
Expand All @@ -83,7 +91,7 @@ export { LogLevel, Logger } from '@slack/logger';
export interface Authorize {
(
source: AuthorizeSourceData,
body: AnyMiddlewareArgs['body'],
body?: AnyMiddlewareArgs['body'],
): Promise<AuthorizeResult>;
}

Expand Down Expand Up @@ -158,7 +166,7 @@ export default class App {
private logger: Logger;

/** Authorize */
private authorize: Authorize;
private authorize!: Authorize;

/** Global middleware chain */
private middleware: Middleware<AnyMiddlewareArgs>[];
Expand Down Expand Up @@ -186,6 +194,14 @@ export default class App {
ignoreSelf = true,
clientOptions = undefined,
processBeforeResponse = false,
clientId = undefined,
clientSecret = undefined,
stateStore = undefined,
stateSecret = undefined,
installationStore = undefined,
authVersion = 'v2',
scopes = undefined,
metadata = undefined,
}: AppOptions = {}) {

if (typeof logger === 'undefined') {
Expand Down Expand Up @@ -218,21 +234,6 @@ export default class App {
clientTls,
));

if (token !== undefined) {
if (authorize !== undefined) {
throw new AppInitializationError(
`Both token and authorize options provided. ${tokenUsage}`,
);
}
this.authorize = singleTeamAuthorization(this.client, { botId, botUserId, botToken: token });
} else if (authorize === undefined) {
throw new AppInitializationError(
`No token and no authorize options provided. ${tokenUsage}`,
);
} else {
this.authorize = authorize;
}

this.middleware = [];
this.listeners = [];

Expand All @@ -252,11 +253,50 @@ export default class App {
signingSecret,
endpoints,
processBeforeResponse,
clientId,
clientSecret,
stateStore,
stateSecret,
installationStore,
authVersion,
metadata,
scopes,
logger: this.logger,
});
}
}

let usingBuiltinOauth = undefined;
if (
clientId !== undefined
&& clientSecret !== undefined
&& (stateSecret !== undefined || stateStore !== undefined)
&& this.receiver instanceof ExpressReceiver
) {
usingBuiltinOauth = true;
}

if (token !== undefined) {
if (authorize !== undefined || usingBuiltinOauth !== undefined) {
throw new AppInitializationError(
`token as well as authorize options or installer options were provided. ${tokenUsage}`,
);
}
this.authorize = singleTeamAuthorization(this.client, { botId, botUserId, botToken: token });
} else if (authorize === undefined && usingBuiltinOauth === undefined) {
throw new AppInitializationError(
`No token, no authorize options, and no installer options provided. ${tokenUsage}`,
);
} else if (authorize !== undefined && usingBuiltinOauth !== undefined) {
throw new AppInitializationError(
`Both authorize options and installer options provided. ${tokenUsage}`,
);
} else if (authorize === undefined && usingBuiltinOauth !== undefined) {
this.authorize = (this.receiver as ExpressReceiver).oauthAuthorize as Authorize;
} else if (authorize !== undefined && usingBuiltinOauth === undefined) {
this.authorize = authorize;
}

// Conditionally use a global middleware that ignores events (including messages) that are sent from this app
if (ignoreSelf) {
this.use(ignoreSelfMiddleware());
Expand Down
68 changes: 64 additions & 4 deletions src/ExpressReceiver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { AnyMiddlewareArgs, Receiver, ReceiverEvent } from './types';
import { createServer, Server } from 'http';
import express, { Request, Response, Application, RequestHandler } from 'express';
import express, { Request, Response, Application, RequestHandler, Router } from 'express';
import rawBody from 'raw-body';
import querystring from 'querystring';
import crypto from 'crypto';
import tsscmp from 'tsscmp';
import App from './App';
import { ReceiverAuthenticityError, ReceiverMultipleAckError } from './errors';
import { Logger, ConsoleLogger } from '@slack/logger';
import { InstallProvider, StateStore, InstallationStore, OAuthInterface, CallbackOptions } from '@slack/oauth';

// TODO: we throw away the key names for endpoints, so maybe we should use this interface. is it better for migrations?
// if that's the reason, let's document that with a comment.
Expand All @@ -18,6 +19,15 @@ export interface ExpressReceiverOptions {
[endpointType: string]: string;
};
processBeforeResponse?: boolean;
clientId?: string;
clientSecret?: string;
stateStore?: StateStore; // default ClearStateStore
stateSecret?: string; // ClearStateStoreOptions['secret']; // required when using default stateStore
installationStore?: InstallationStore; // default MemoryInstallationStore
authVersion?: 'v1' | 'v2'; // default 'v2'
scopes?: string | string[];
metadata?: string;
oAuthCallbackOptions?: CallbackOptions;
}

/**
Expand All @@ -32,18 +42,28 @@ export default class ExpressReceiver implements Receiver {
private bolt: App | undefined;
private logger: Logger;
private processBeforeResponse: boolean;
public router: Router;
public oauthAuthorize: OAuthInterface['authorize'] | undefined;

constructor({
signingSecret = '',
logger = new ConsoleLogger(),
endpoints = { events: '/slack/events' },
processBeforeResponse = false,
clientId = undefined,
clientSecret = undefined,
stateStore = undefined,
stateSecret = undefined,
installationStore = undefined,
authVersion = 'v2',
metadata = undefined,
scopes = undefined,
}: ExpressReceiverOptions) {
this.app = express();
// TODO: what about starting an https server instead of http? what about other options to create the server?
this.server = createServer(this.app);

const expressMiddleware: RequestHandler[] = [
const expressMiddleware = [
verifySignatureAndParseRawBody(logger, signingSecret),
respondToSslCheck,
respondToUrlVerification,
Expand All @@ -52,9 +72,49 @@ export default class ExpressReceiver implements Receiver {

this.processBeforeResponse = processBeforeResponse;
this.logger = logger;
const endpointList: string[] = typeof endpoints === 'string' ? [endpoints] : Object.values(endpoints);
const endpointList = typeof endpoints === 'string' ? [endpoints] : Object.values(endpoints);
this.router = Router();
for (const endpoint of endpointList) {
this.app.post(endpoint, ...expressMiddleware);
this.router.post(endpoint, ...expressMiddleware);
}
this.app.use(this.router);
this.oauthAuthorize = undefined;

let oauthInstaller: OAuthInterface | undefined = undefined;
if (
clientId !== undefined
&& clientSecret !== undefined
&& (stateSecret !== undefined || stateStore !== undefined)
) {

oauthInstaller = new InstallProvider({
clientId,
clientSecret,
stateSecret,
stateStore,
installationStore,
authVersion,
});
}

// Add OAuth routes to receiver
if (oauthInstaller !== undefined) {
this.app.use('/slack/install', (req, res) => {
return oauthInstaller!.handleCallback(req, res, {});
});
this.app.get('/slack/begin_auth', (_req, res, next) => {
// TODO, take in arguments or function for
oauthInstaller!.generateInstallUrl({
metadata,
scopes: scopes!,
}).then((url: string) => {
res.send(`<a href=${url}><img alt=""Add to Slack"" height="40" width="139"
src="https://platform.slack-edge.com/img/add_to_slack.png"
srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x,
https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>`);
}).catch(next);
});
this.oauthAuthorize = oauthInstaller.authorize;
}
}

Expand Down

0 comments on commit 40092b9

Please sign in to comment.