Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Organize the source files in the OAuth package (#1430)
- Loading branch information
Showing
26 changed files
with
1,564 additions
and
1,139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ package-lock.json | |
/.nyc_output | ||
/coverage | ||
*.lcov | ||
tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// This is intentionally structurally identical to AuthorizeResult from App | ||
// It is redefined so that this class remains loosely coupled to the rest | ||
// of Bolt. | ||
export interface AuthorizeResult { | ||
botToken?: string; | ||
botRefreshToken?: string; | ||
botTokenExpiresAt?: number; // utc, seconds | ||
userToken?: string; | ||
userRefreshToken?: string; | ||
userTokenExpiresAt?: number; // utc, seconds | ||
botId?: string; | ||
botUserId?: string; | ||
teamId?: string; | ||
enterpriseId?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { assert } from 'chai'; | ||
import { describe, it } from 'mocha'; | ||
import sinon from 'sinon'; | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
|
||
import { CallbackOptions } from './callback-options'; | ||
import { MissingStateError } from './errors'; | ||
|
||
describe('CallbackOptions', async () => { | ||
it('should have success and failure', async () => { | ||
const callbackOptions: CallbackOptions = { | ||
success: async (installation, options, req, resp) => { | ||
assert.isNotNull(installation); | ||
assert.isNotNull(options); | ||
assert.isNotNull(req); | ||
assert.isNotNull(resp); | ||
}, | ||
failure: async (installation, options, req, resp) => { | ||
assert.isNotNull(installation); | ||
assert.isNotNull(options); | ||
assert.isNotNull(req); | ||
assert.isNotNull(resp); | ||
}, | ||
}; | ||
assert.isNotNull(callbackOptions); | ||
const installation = { | ||
enterprise: undefined, | ||
team: { | ||
id: 'T111', | ||
}, | ||
bot: { | ||
id: 'B111', | ||
userId: 'W111', | ||
scopes: ['commands'], | ||
token: 'xoxb-', | ||
}, | ||
user: { | ||
id: 'W222', | ||
scopes: undefined, | ||
token: undefined, | ||
}, | ||
}; | ||
const options = { | ||
scopes: ['commands', 'chat:write'], | ||
}; | ||
const req = sinon.createStubInstance(IncomingMessage) as IncomingMessage; | ||
const resp = sinon.createStubInstance(ServerResponse) as ServerResponse; | ||
callbackOptions.success!(installation, options, req, resp); | ||
|
||
const error = new MissingStateError(); | ||
callbackOptions.failure!(error, options, req, resp); | ||
}); | ||
|
||
// TODO: tests for default callbacks | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { CodedError } from './errors'; | ||
import { InstallURLOptions } from './install-url-options'; | ||
import { Installation, OrgInstallation } from './installation'; | ||
|
||
export interface CallbackOptions { | ||
// success is given control after handleCallback() has stored the | ||
// installation. when provided, this function must complete the | ||
// callbackRes. | ||
success?: ( | ||
installation: Installation | OrgInstallation, | ||
options: InstallURLOptions, | ||
callbackReq: IncomingMessage, | ||
callbackRes: ServerResponse, | ||
) => void; | ||
|
||
// failure is given control when handleCallback() fails at any point. | ||
// when provided, this function must complete the callbackRes. | ||
// default: | ||
// serve a generic "Error" web page (show detailed cause in development) | ||
failure?: ( | ||
error: CodedError, | ||
options: InstallURLOptions, | ||
callbackReq: IncomingMessage, | ||
callbackRes: ServerResponse, | ||
) => void; | ||
} | ||
|
||
// Default function to call when OAuth flow is successful | ||
export function defaultCallbackSuccess( | ||
installation: Installation, | ||
_options: InstallURLOptions | undefined, | ||
_req: IncomingMessage, | ||
res: ServerResponse, | ||
): void { | ||
let redirectUrl: string; | ||
|
||
if (isNotOrgInstall(installation) && installation.appId !== undefined) { | ||
// redirect back to Slack native app | ||
// Changes to the workspace app was installed to, to the app home | ||
redirectUrl = `slack://app?team=${installation.team.id}&id=${installation.appId}`; | ||
} else if (isOrgInstall(installation)) { | ||
// redirect to Slack app management dashboard | ||
redirectUrl = `${installation.enterpriseUrl}manage/organization/apps/profile/${installation.appId}/workspaces/add`; | ||
} else { | ||
// redirect back to Slack native app | ||
// does not change the workspace the slack client was last in | ||
redirectUrl = 'slack://open'; | ||
} | ||
const htmlResponse = `<html> | ||
<meta http-equiv="refresh" content="0; URL=${redirectUrl}"> | ||
<body> | ||
<h1>Success! Redirecting to the Slack App...</h1> | ||
<button onClick="window.location = '${redirectUrl}'">Click here to redirect</button> | ||
</body></html>`; | ||
res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
res.end(htmlResponse); | ||
} | ||
|
||
// Default function to call when OAuth flow is unsuccessful | ||
export function defaultCallbackFailure( | ||
_error: CodedError, | ||
_options: InstallURLOptions, | ||
_req: IncomingMessage, | ||
res: ServerResponse, | ||
): void { | ||
res.writeHead(500, { 'Content-Type': 'text/html' }); | ||
res.end('<html><body><h1>Oops, Something Went Wrong! Please Try Again or Contact the App Owner</h1></body></html>'); | ||
} | ||
|
||
// ------------------------------------------ | ||
// Internals | ||
// ------------------------------------------ | ||
|
||
// Type guard to narrow Installation type to OrgInstallation | ||
function isOrgInstall(installation: Installation): installation is OrgInstallation { | ||
return installation.isEnterpriseInstall || false; | ||
} | ||
|
||
function isNotOrgInstall(installation: Installation): installation is Installation<'v1' | 'v2', false> { | ||
return !(isOrgInstall(installation)); | ||
} |
Oops, something went wrong.