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

Improve App initialization error logs + docs #1250

Merged
merged 3 commits into from
Jan 20, 2022
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
4 changes: 2 additions & 2 deletions docs/_basic/authenticating_oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ order: 15
To make your Slack app ready for distribution, you will need to implement OAuth and store installation information (i.e. access tokens) securely. Bolt supports OAuth and will handle most of the work for you by setting up OAuth routes and verifying state.

You will need to provide your:
* `clientId`, `clientSecret`, `stateSecret` and `scopes`
* An `installationStore` option with `storeInstallation` and `fetchInstallation` handlers defined for storing installation data to a database *(recommended for production)*
* `clientId`, `clientSecret`, `stateSecret` and `scopes` (required)
* An `installationStore` option with `storeInstallation` and `fetchInstallation` handlers defined for storing installation data to a database *(optional, but highly recommended for production)*
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement on clarity. I always appreciate when arguments are explicitly "required" or "optional"


---

Expand Down
53 changes: 29 additions & 24 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ const validViewTypes = ['view_closed', 'view_submission'];
// ----------------------------
// For the constructor

const tokenUsage = 'Apps used in one workspace should be initialized with a token. Apps used in many workspaces ' +
'should be initialized with oauth installer or authorize.';
const tokenUsage = 'Apps used in a single workspace can be initialized with a token. Apps used in many workspaces ' +
'should be initialized with oauth installer options or authorize.';

/** App initialization options */
export interface AppOptions {
Expand Down Expand Up @@ -274,6 +274,8 @@ export default class App {
extendedErrorHandler = false,
}: AppOptions = {}) {
// this.logLevel = logLevel;

/* ------------------------ Developer mode ----------------------------- */
this.developerMode = developerMode;
if (developerMode) {
// Set logLevel to Debug in Developer Mode if one wasn't passed in
Expand All @@ -287,7 +289,7 @@ export default class App {
this.logLevel = logLevel ?? LogLevel.INFO;
}

// Set up logger
/* ------------------------ Set logger ----------------------------- */
if (typeof logger === 'undefined') {
// Initialize with the default logger
const consoleLogger = new ConsoleLogger();
Expand All @@ -304,7 +306,7 @@ export default class App {
this.errorHandler = defaultErrorHandler(this.logger) as AnyErrorHandler;
this.extendedErrorHandler = extendedErrorHandler;

// Set up client options
/* ------------------------ Set client options ------------------------*/
this.clientOptions = clientOptions !== undefined ? clientOptions : {};
if (agent !== undefined && this.clientOptions.agent === undefined) {
this.clientOptions.agent = agent;
Expand Down Expand Up @@ -343,8 +345,8 @@ export default class App {
...installerOptions,
};
if (socketMode && port !== undefined && this.installerOptions.port === undefined) {
// As SocketModeReceiver uses a custom port number to listen on only for the OAuth flow,
// only installerOptions.port is available in the constructor arguments.
// SocketModeReceiver only uses a custom port number when listening for the OAuth flow.
// Therefore only installerOptions.port is available in the constructor arguments.
this.installerOptions.port = port;
}

Expand All @@ -366,19 +368,21 @@ export default class App {
};
}

// Initialize receiver
/* --------------------- Initialize receiver ---------------------- */
if (receiver !== undefined) {
// Custom receiver
if (this.socketMode) {
throw new AppInitializationError('receiver cannot be passed when socketMode is set to true');
// Custom receiver supplied
if (this.socketMode === true) {
// socketMode = true should result in SocketModeReceiver being used as receiver
// TODO: Add case for when socketMode = true and receiver = SocketModeReceiver
// as this should not result in an error
throw new AppInitializationError('You cannot supply a custom receiver when socketMode is set to true.');
}
this.receiver = receiver;
} else if (this.socketMode) {
} else if (this.socketMode === true) {
srajiang marked this conversation as resolved.
Show resolved Hide resolved
if (appToken === undefined) {
throw new AppInitializationError('You must provide an appToken when using Socket Mode');
throw new AppInitializationError('You must provide an appToken when socketMode is set to true. To generate an appToken see: https://api.slack.com/apis/connections/socket#token');
srajiang marked this conversation as resolved.
Show resolved Hide resolved
}
this.logger.debug('Initializing SocketModeReceiver');
// Create default SocketModeReceiver
this.receiver = new SocketModeReceiver({
appToken,
clientId,
Expand All @@ -392,15 +396,14 @@ export default class App {
installerOptions: this.installerOptions,
customRoutes,
});
} else if (signatureVerification && signingSecret === undefined) {
// No custom receiver
} else if (signatureVerification === true && signingSecret === undefined) {
// Using default receiver HTTPReceiver, signature verification enabled, missing signingSecret
throw new AppInitializationError(
'Signing secret not found, so could not initialize the default receiver. Set a signing secret or use a ' +
'custom receiver.',
'signingSecret is required to initialize the default receiver. Set signingSecret or use a ' +
'custom receiver. You can find your Signing Secret in your Slack App Settings.',
);
} else {
this.logger.debug('Initializing HTTPReceiver');
// Create default HTTPReceiver
this.receiver = new HTTPReceiver({
signingSecret: signingSecret || '',
endpoints,
Expand All @@ -420,6 +423,7 @@ export default class App {
});
}

/* ------------------------ Set authorize ----------------------------- */
let usingOauth = false;
const httpReceiver = (this.receiver as HTTPReceiver);
if (
Expand All @@ -430,11 +434,11 @@ export default class App {
// and theoretically, doing a fully custom (non express) receiver that implements OAuth
usingOauth = true;
}

if (token !== undefined) {
if (authorize !== undefined || usingOauth) {
// If a token is supplied, the app is installed in at least one workspace
if (usingOauth || authorize !== undefined) {
throw new AppInitializationError(
`token as well as authorize or oauth installer options were provided. ${tokenUsage}`,
`You cannot provide a token along with either oauth installer options or authorize. ${tokenUsage}`,
);
}
this.authorize = singleAuthorization(
Expand All @@ -448,17 +452,17 @@ export default class App {
);
} else if (authorize === undefined && !usingOauth) {
throw new AppInitializationError(
`No token, no authorize, and no oauth installer options provided. ${tokenUsage}`,
`${tokenUsage} \n\nSince you have not provided a token or authorize, you might be missing one or more required oauth installer options. See https://slack.dev/bolt-js/concepts#authenticating-oauth for these required fields.\n`,
);
} else if (authorize !== undefined && usingOauth) {
throw new AppInitializationError(`Both authorize options and oauth installer options provided. ${tokenUsage}`);
throw new AppInitializationError(`You cannot provide both authorize and oauth installer options. ${tokenUsage}`);
} else if (authorize === undefined && usingOauth) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.authorize = httpReceiver.installer!.authorize;
} else if (authorize !== undefined && !usingOauth) {
this.authorize = authorize;
} else {
this.logger.error('Never should have reached this point, please report to the team');
this.logger.error('Something has gone wrong. Please report this issue to the maintainers. https://github.com/slackapi/bolt-js/issues');
assertNever();
}

Expand All @@ -474,6 +478,7 @@ export default class App {
this.use(conversationContext(store));
}

/* ------------------------ Initialize receiver ------------------------ */
// Should be last to avoid exposing partially initialized app
this.receiver.init(this);
}
Expand Down