@slack/oauth
Advanced tools
Comparing version
@@ -28,5 +28,5 @@ /// <reference types="node" /> | ||
/** | ||
* Fetches data from the installationStore. | ||
* Fetches data from the installationStore for non Org Installations. | ||
*/ | ||
authorize(source: InstallationQuery): Promise<AuthorizeResult>; | ||
authorize(source: InstallationQuery<boolean>): Promise<AuthorizeResult>; | ||
/** | ||
@@ -68,3 +68,3 @@ * Returns a URL that is suitable for including in an Add to Slack button | ||
export interface CallbackOptions { | ||
success?: (installation: Installation, options: InstallURLOptions, callbackReq: IncomingMessage, callbackRes: ServerResponse) => void; | ||
success?: (installation: Installation | OrgInstallation, options: InstallURLOptions, callbackReq: IncomingMessage, callbackRes: ServerResponse) => void; | ||
failure?: (error: CodedError, options: InstallURLOptions, callbackReq: IncomingMessage, callbackRes: ServerResponse) => void; | ||
@@ -77,13 +77,56 @@ } | ||
export interface InstallationStore { | ||
storeInstallation: (installation: Installation, logger?: Logger) => Promise<void>; | ||
fetchInstallation: (query: InstallationQuery, logger?: Logger) => Promise<Installation>; | ||
storeInstallation<AuthVersion extends 'v1' | 'v2'>(installation: Installation<AuthVersion, false>, logger?: Logger): Promise<void>; | ||
storeOrgInstallation?(installation: OrgInstallation, logger?: Logger): Promise<void>; | ||
fetchInstallation: (query: InstallationQuery<false>, logger?: Logger) => Promise<Installation<'v1' | 'v2', false>>; | ||
fetchOrgInstallation?: (query: OrgInstallationQuery, logger?: Logger) => Promise<OrgInstallation>; | ||
} | ||
export interface Installation { | ||
team: { | ||
/** | ||
* An individual installation of the Slack app. | ||
* | ||
* This interface creates a representation for installations that normalizes the responses from OAuth grant exchanges | ||
* across auth versions (responses from the Web API methods `oauth.v2.access` and `oauth.access`). It describes some of | ||
* these differences using the `AuthVersion` generic placeholder type. | ||
* | ||
* This interface also represents both installations which occur on individual Slack workspaces and on Slack enterprise | ||
* organizations. The `IsEnterpriseInstall` generic placeholder type is used to describe some of those differences. | ||
* | ||
* This representation is designed to be used both when producing data that should be stored by an InstallationStore, | ||
* and when consuming data that is fetched from an InstallationStore. Most often, InstallationStore implementations | ||
* are a database. If you are going to implement an InstallationStore, it's advised that you **store as much of the | ||
* data in these objects as possible so that you can return as much as possible inside `fetchInstallation()`**. | ||
* | ||
* A few properties are synthesized with a default value if they are not present when returned from | ||
* `fetchInstallation()`. These properties are optional in the interface so that stored installations from previous | ||
* versions of this library (from before those properties were introduced) continue to work without requiring a breaking | ||
* change. However the synthesized default values are not always perfect and are based on some assumptions, so this is | ||
* why it's recommended to store as much of that data as possible in any InstallationStore. | ||
* | ||
* Some of the properties (e.g. `team.name`) can change between when the installation occurred and when it is fetched | ||
* from the InstallationStore. This can be seen as a reason not to store those properties. In most workspaces these | ||
* properties rarely change, and for most Slack apps having a slightly out of date value has no impact. However if your | ||
* app uses these values in a way where it must be up to date, it's recommended to implement a caching strategy in the | ||
* InstallationStore to fetch the latest data from the Web API (using methods such as `auth.test`, `teams.info`, etc.) | ||
* as often as it makes sense for your Slack app. | ||
* | ||
* TODO: IsEnterpriseInstall is always false when AuthVersion is v1 | ||
*/ | ||
export interface Installation<AuthVersion extends ('v1' | 'v2') = ('v1' | 'v2'), IsEnterpriseInstall extends boolean = boolean> { | ||
/** | ||
* TODO: when performing a “single workspace” install with the admin scope on the enterprise, | ||
* is the team property returned from oauth.access? | ||
*/ | ||
team: IsEnterpriseInstall extends true ? undefined : { | ||
id: string; | ||
name: string; | ||
/** Left as undefined when not returned from fetch. */ | ||
name?: string; | ||
}; | ||
enterprise?: { | ||
/** | ||
* When the installation is an enterprise install or when the installation occurs on the org to acquire `admin` scope, | ||
* the name and ID of the enterprise org. | ||
*/ | ||
enterprise: IsEnterpriseInstall extends true ? EnterpriseInfo : (EnterpriseInfo | undefined); | ||
user: { | ||
token: AuthVersion extends 'v1' ? string : (string | undefined); | ||
scopes: AuthVersion extends 'v1' ? string[] : (string[] | undefined); | ||
id: string; | ||
name?: string; | ||
}; | ||
@@ -93,25 +136,44 @@ bot?: { | ||
scopes: string[]; | ||
id?: string; | ||
id: string; | ||
userId: string; | ||
}; | ||
user: { | ||
token?: string; | ||
scopes?: string[]; | ||
id: string; | ||
}; | ||
incomingWebhook?: { | ||
url: string; | ||
channel: string; | ||
channelId: string; | ||
configurationUrl: string; | ||
/** Left as undefined when not returned from fetch. */ | ||
channel?: string; | ||
/** Left as undefined when not returned from fetch. */ | ||
channelId?: string; | ||
/** Left as undefined when not returned from fetch. */ | ||
configurationUrl?: string; | ||
}; | ||
appId: string | undefined; | ||
tokenType?: string; | ||
/** The App ID, which does not vary per installation. Left as undefined when not returned from fetch. */ | ||
appId?: AuthVersion extends 'v2' ? string : undefined; | ||
/** When the installation contains a bot user, the token type. Left as undefined when not returned from fetch. */ | ||
tokenType?: 'bot'; | ||
/** | ||
* When the installation is an enterprise org install, the URL of the landing page for all workspaces in the org. | ||
* Left as undefined when not returned from fetch. | ||
*/ | ||
enterpriseUrl?: AuthVersion extends 'v2' ? string : undefined; | ||
/** Whether the installation was performed on an enterprise org. Synthesized as `false` when not present. */ | ||
isEnterpriseInstall?: IsEnterpriseInstall; | ||
/** The version of Slack's auth flow that produced this installation. Synthesized as `v2` when not present. */ | ||
authVersion?: AuthVersion; | ||
} | ||
export interface InstallationQuery { | ||
teamId: string; | ||
enterpriseId?: string; | ||
/** | ||
* A type to describe enterprise organization installations. | ||
*/ | ||
export declare type OrgInstallation = Installation<'v2', true>; | ||
interface EnterpriseInfo { | ||
id: string; | ||
name?: string; | ||
} | ||
export interface InstallationQuery<isEnterpriseInstall extends boolean> { | ||
teamId: isEnterpriseInstall extends false ? string : undefined; | ||
enterpriseId: isEnterpriseInstall extends true ? string : (string | undefined); | ||
userId?: string; | ||
conversationId?: string; | ||
isEnterpriseInstall: isEnterpriseInstall; | ||
} | ||
export declare type OrgInstallationQuery = InstallationQuery<true>; | ||
export interface AuthorizeResult { | ||
@@ -122,4 +184,6 @@ botToken?: string; | ||
botUserId?: string; | ||
teamId?: string; | ||
enterpriseId?: string; | ||
} | ||
export { Logger, LogLevel } from './logger'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -108,3 +108,3 @@ "use strict"; | ||
/** | ||
* Fetches data from the installationStore. | ||
* Fetches data from the installationStore for non Org Installations. | ||
*/ | ||
@@ -117,6 +117,17 @@ InstallProvider.prototype.authorize = function (source) { | ||
case 0: | ||
_a.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, this.installationStore.fetchInstallation(source, this.logger)]; | ||
_a.trys.push([0, 5, , 6]); | ||
queryResult = void 0; | ||
if (!source.isEnterpriseInstall) return [3 /*break*/, 2]; | ||
if (this.installationStore.fetchOrgInstallation === undefined) { | ||
throw new Error('Installation Store is missing the fetchOrgInstallation method'); | ||
} | ||
return [4 /*yield*/, this.installationStore.fetchOrgInstallation(source, this.logger)]; | ||
case 1: | ||
queryResult = _a.sent(); | ||
return [3 /*break*/, 4]; | ||
case 2: return [4 /*yield*/, this.installationStore.fetchInstallation(source, this.logger)]; | ||
case 3: | ||
queryResult = _a.sent(); | ||
_a.label = 4; | ||
case 4: | ||
if (queryResult === undefined) { | ||
@@ -127,2 +138,18 @@ throw new Error('Failed fetching data from the Installation Store'); | ||
authResult.userToken = queryResult.user.token; | ||
if (queryResult.team !== undefined) { | ||
authResult.teamId = queryResult.team.id; | ||
} | ||
else if (source.teamId !== undefined) { | ||
/** | ||
* since queryResult is a org installation, it won't have team.id. If one was passed in via source, | ||
* we should add it to the authResult | ||
*/ | ||
authResult.teamId = source.teamId; | ||
} | ||
if (queryResult.enterprise !== undefined) { | ||
authResult.enterpriseId = queryResult.enterprise.id; | ||
} | ||
else if (source.enterpriseId !== undefined) { | ||
authResult.enterpriseId = source.enterpriseId; | ||
} | ||
if (queryResult.bot !== undefined) { | ||
@@ -134,6 +161,6 @@ authResult.botToken = queryResult.bot.token; | ||
return [2 /*return*/, authResult]; | ||
case 2: | ||
case 5: | ||
error_1 = _a.sent(); | ||
throw new errors_1.AuthorizationError(error_1.message); | ||
case 3: return [2 /*return*/]; | ||
case 6: return [2 /*return*/]; | ||
} | ||
@@ -205,8 +232,9 @@ }); | ||
InstallProvider.prototype.handleCallback = function (req, res, options) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var parsedUrl, code, state, installOptions, client, resp, installation, botId, botId, error_2; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
var parsedUrl, code, state, installOptions, client, installation, resp, v1Resp, v1Installation, authResult, botId, v2Resp, v2Installation, authResult, authResult, error_2; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_a.trys.push([0, 10, , 11]); | ||
_b.trys.push([0, 17, , 18]); | ||
if (req.url !== undefined) { | ||
@@ -225,6 +253,6 @@ parsedUrl = url_1.parse(req.url, true); | ||
case 1: | ||
installOptions = _a.sent(); | ||
client = new web_api_1.WebClient('', this.clientOptions); | ||
installOptions = _b.sent(); | ||
client = new web_api_1.WebClient(undefined, this.clientOptions); | ||
installation = void 0; | ||
resp = void 0; | ||
installation = void 0; | ||
if (!(this.authVersion === 'v1')) return [3 /*break*/, 5]; | ||
@@ -238,32 +266,31 @@ return [4 /*yield*/, client.oauth.access({ | ||
case 2: | ||
// convert response type from WebApiCallResult to OAuthResponse | ||
resp = (_a.sent()); | ||
// resp obj for v1 - https://api.slack.com/methods/oauth.access#response | ||
installation = { | ||
team: { id: resp.team_id, name: resp.team_name }, | ||
appId: resp.app_id, | ||
v1Resp = _b.sent(); | ||
v1Installation = { | ||
team: { id: v1Resp.team_id, name: v1Resp.team_name }, | ||
enterprise: v1Resp.enterprise_id === null ? undefined : { id: v1Resp.enterprise_id }, | ||
user: { | ||
token: resp.access_token, | ||
scopes: resp.scope.split(','), | ||
id: resp.user_id !== undefined ? resp.user_id : '', | ||
token: v1Resp.access_token, | ||
scopes: v1Resp.scope.split(','), | ||
id: v1Resp.user_id, | ||
}, | ||
// synthesized properties: enterprise installation is unsupported in v1 auth | ||
isEnterpriseInstall: false, | ||
authVersion: 'v1', | ||
}; | ||
if (!(resp.bot !== undefined)) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, getBotId(resp.bot.bot_access_token, this.clientOptions)]; | ||
if (!(v1Resp.bot !== undefined)) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, runAuthTest(v1Resp.bot.bot_access_token, this.clientOptions)]; | ||
case 3: | ||
botId = _a.sent(); | ||
installation.bot = { | ||
authResult = _b.sent(); | ||
botId = authResult.bot_id; | ||
v1Installation.bot = { | ||
id: botId, | ||
scopes: ['bot'], | ||
token: resp.bot.bot_access_token, | ||
userId: resp.bot.bot_user_id, | ||
token: v1Resp.bot.bot_access_token, | ||
userId: v1Resp.bot.bot_user_id, | ||
}; | ||
_a.label = 4; | ||
_b.label = 4; | ||
case 4: | ||
if (resp.enterprise_id !== null) { | ||
installation.enterprise = { | ||
id: resp.enterprise_id, | ||
}; | ||
} | ||
return [3 /*break*/, 8]; | ||
resp = v1Resp; | ||
installation = v1Installation; | ||
return [3 /*break*/, 12]; | ||
case 5: return [4 /*yield*/, client.oauth.v2.access({ | ||
@@ -276,32 +303,47 @@ code: code, | ||
case 6: | ||
// convert response type from WebApiCallResult to OAuthResponse | ||
resp = (_a.sent()); | ||
return [4 /*yield*/, getBotId(resp.access_token, this.clientOptions)]; | ||
case 7: | ||
botId = _a.sent(); | ||
// resp obj for v2 - https://api.slack.com/methods/oauth.v2.access#response | ||
installation = { | ||
team: resp.team, | ||
appId: resp.app_id, | ||
v2Resp = _b.sent(); | ||
v2Installation = { | ||
team: v2Resp.team === null ? undefined : v2Resp.team, | ||
enterprise: v2Resp.enterprise == null ? undefined : v2Resp.enterprise, | ||
user: { | ||
token: resp.authed_user.access_token, | ||
scopes: resp.authed_user.scope !== undefined ? resp.authed_user.scope.split(',') : undefined, | ||
id: resp.authed_user.id, | ||
token: v2Resp.authed_user.access_token, | ||
scopes: (_a = v2Resp.authed_user.scope) === null || _a === void 0 ? void 0 : _a.split(','), | ||
id: v2Resp.authed_user.id, | ||
}, | ||
bot: { | ||
scopes: resp.scope.split(','), | ||
token: resp.access_token, | ||
userId: resp.bot_user_id, | ||
id: botId, | ||
}, | ||
tokenType: resp.token_type, | ||
tokenType: v2Resp.token_type, | ||
isEnterpriseInstall: v2Resp.is_enterprise_install, | ||
appId: v2Resp.app_id, | ||
// synthesized properties | ||
authVersion: 'v2', | ||
}; | ||
if (resp.enterprise !== null) { | ||
installation.enterprise = { | ||
id: resp.enterprise.id, | ||
name: resp.enterprise.name, | ||
}; | ||
if (!(v2Resp.access_token !== undefined && v2Resp.scope !== undefined && v2Resp.bot_user_id !== undefined)) return [3 /*break*/, 8]; | ||
return [4 /*yield*/, runAuthTest(v2Resp.access_token, this.clientOptions)]; | ||
case 7: | ||
authResult = _b.sent(); | ||
v2Installation.bot = { | ||
scopes: v2Resp.scope.split(','), | ||
token: v2Resp.access_token, | ||
userId: v2Resp.bot_user_id, | ||
id: authResult.bot_id, | ||
}; | ||
if (v2Resp.is_enterprise_install) { | ||
// if it is an org enterprise install, add the enterprise url | ||
v2Installation.enterpriseUrl = authResult.url; | ||
} | ||
_a.label = 8; | ||
return [3 /*break*/, 11]; | ||
case 8: | ||
if (!(v2Resp.authed_user.access_token !== undefined && v2Resp.is_enterprise_install)) return [3 /*break*/, 10]; | ||
return [4 /*yield*/, runAuthTest(v2Resp.authed_user.access_token, this.clientOptions)]; | ||
case 9: | ||
authResult = _b.sent(); | ||
v2Installation.enterpriseUrl = authResult.url; | ||
return [3 /*break*/, 11]; | ||
case 10: | ||
// TODO: make this a coded error | ||
throw new Error('The response from the authorization URL contained inconsistent information. Please file a bug.'); | ||
case 11: | ||
resp = v2Resp; | ||
installation = v2Installation; | ||
_b.label = 12; | ||
case 12: | ||
if (resp.incoming_webhook !== undefined) { | ||
@@ -315,7 +357,17 @@ installation.incomingWebhook = { | ||
} | ||
// save access code to installationStore | ||
return [4 /*yield*/, this.installationStore.storeInstallation(installation, this.logger)]; | ||
case 9: | ||
// save access code to installationStore | ||
_a.sent(); | ||
if (!installation.isEnterpriseInstall) return [3 /*break*/, 14]; | ||
if (this.installationStore.storeOrgInstallation === undefined) { | ||
// TODO: make this a coded error | ||
throw new Error('Installation store is missing the storeOrgInstallation method'); | ||
} | ||
return [4 /*yield*/, this.installationStore.storeOrgInstallation(installation, this.logger)]; | ||
case 13: | ||
_b.sent(); | ||
return [3 /*break*/, 16]; | ||
case 14: return [4 /*yield*/, this.installationStore.storeInstallation(installation, this.logger)]; | ||
case 15: | ||
_b.sent(); | ||
_b.label = 16; | ||
case 16: | ||
// Call the success callback | ||
if (options !== undefined && options.success !== undefined) { | ||
@@ -329,6 +381,7 @@ this.logger.debug('calling passed in options.success'); | ||
} | ||
return [3 /*break*/, 11]; | ||
case 10: | ||
error_2 = _a.sent(); | ||
return [3 /*break*/, 18]; | ||
case 17: | ||
error_2 = _b.sent(); | ||
this.logger.error(error_2); | ||
// Call the failure callback | ||
if (options !== undefined && options.failure !== undefined) { | ||
@@ -342,4 +395,4 @@ this.logger.debug('calling passed in options.failure'); | ||
} | ||
return [3 /*break*/, 11]; | ||
case 11: return [2 /*return*/]; | ||
return [3 /*break*/, 18]; | ||
case 18: return [2 /*return*/]; | ||
} | ||
@@ -386,7 +439,15 @@ }); | ||
return __generator(this, function (_a) { | ||
// NOTE: installations on a single workspace that happen to be within an enterprise organization are stored by | ||
// the team ID as the key | ||
// TODO: what about installations on an enterprise (acting as a single workspace) with `admin` scope, which is not | ||
// an org install? | ||
if (logger !== undefined) { | ||
logger.warn('Storing Access Token. Please use a real Installation Store for production!'); | ||
} | ||
// db write | ||
this.devDB[installation.team.id] = installation; | ||
if (isNotOrgInstall(installation)) { | ||
this.devDB[installation.team.id] = installation; | ||
} | ||
else { | ||
throw new Error('Failed saving installation data to installationStore'); | ||
} | ||
return [2 /*return*/]; | ||
@@ -396,5 +457,20 @@ }); | ||
}; | ||
MemoryInstallationStore.prototype.storeOrgInstallation = function (installation, logger) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
if (logger !== undefined) { | ||
logger.warn('Storing Access Token. Please use a real Installation Store for production!'); | ||
} | ||
if (isOrgInstall(installation)) { | ||
this.devDB[installation.enterprise.id] = installation; | ||
} | ||
else { | ||
throw new Error('Failed saving installation data to installationStore'); | ||
} | ||
return [2 /*return*/]; | ||
}); | ||
}); | ||
}; | ||
MemoryInstallationStore.prototype.fetchInstallation = function (query, logger) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var item; | ||
return __generator(this, function (_a) { | ||
@@ -404,7 +480,22 @@ if (logger !== undefined) { | ||
} | ||
item = this.devDB[query.teamId]; | ||
return [2 /*return*/, item]; | ||
if (query.teamId !== undefined) { | ||
return [2 /*return*/, this.devDB[query.teamId]]; | ||
} | ||
throw new Error('Failed fetching installation'); | ||
}); | ||
}); | ||
}; | ||
MemoryInstallationStore.prototype.fetchOrgInstallation = function (query, logger) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
if (logger !== undefined) { | ||
logger.warn('Retrieving Access Token from DB. Please use a real Installation Store for production!'); | ||
} | ||
if (query.enterpriseId !== undefined) { | ||
return [2 /*return*/, this.devDB[query.enterpriseId]]; | ||
} | ||
throw new Error('Failed fetching installation'); | ||
}); | ||
}); | ||
}; | ||
return MemoryInstallationStore; | ||
@@ -415,3 +506,3 @@ }()); | ||
var redirectUrl; | ||
if (installation.team !== null && installation.team.id !== undefined && installation.appId !== undefined) { | ||
if (isNotOrgInstall(installation) && installation.appId !== undefined) { | ||
// redirect back to Slack native app | ||
@@ -421,2 +512,6 @@ // Changes to the workspace app was installed to, to the app home | ||
} | ||
else if (isOrgInstall(installation)) { | ||
// redirect to Slack app management dashboard | ||
redirectUrl = installation.enterpriseUrl + "manage/organization/apps/profile/" + installation.appId + "/workspaces/add"; | ||
} | ||
else { | ||
@@ -437,3 +532,3 @@ // redirect back to Slack native app | ||
// Gets the bot_id using the `auth.test` method. | ||
function getBotId(token, clientOptions) { | ||
function runAuthTest(token, clientOptions) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
@@ -448,8 +543,3 @@ var client, authResult; | ||
authResult = _a.sent(); | ||
if (authResult.bot_id !== undefined) { | ||
return [2 /*return*/, authResult.bot_id]; | ||
} | ||
// If a user token was used for auth.test, there is no bot_id | ||
// return an empty string in this case | ||
return [2 /*return*/, '']; | ||
return [2 /*return*/, authResult]; | ||
} | ||
@@ -459,4 +549,11 @@ }); | ||
} | ||
// Type guard to narrow Installation type to OrgInstallation | ||
function isOrgInstall(installation) { | ||
return installation.isEnterpriseInstall || false; | ||
} | ||
function isNotOrgInstall(installation) { | ||
return !(isOrgInstall(installation)); | ||
} | ||
var logger_2 = require("./logger"); | ||
exports.LogLevel = logger_2.LogLevel; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@slack/oauth", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"description": "Official library for interacting with Slack's Oauth endpoints", | ||
@@ -5,0 +5,0 @@ "author": "Slack Technologies, Inc.", |
@@ -35,3 +35,3 @@ # Slack OAuth | ||
This package exposes an `InstallProvider` class, which sets up the required configuration and exposes methods such as `generateInstallUrl`, `handleCallback`, `authorize` for use within your apps. At a minimum, `InstallProvider` takes a `clientId` and `clientSecret` (both which can be obtained under the **Basic Information** of your app configuration). `InstallProvider` also requires a `stateSecret`, which is used to encode the generated state, and later used to decode that same state to verify it wasn't tampered with during the OAuth flow. **Note**: This example is not ready for production because it only stores installations (tokens) in memory. Please go to the [storing installations in a database](#storing-installations-in-a-database) section to learn how to plug in your own database. | ||
This package exposes an `InstallProvider` class, which sets up the required configuration and exposes methods such as `generateInstallUrl`, `handleCallback`, `authorize`, `orgAuthorize` for use within your apps. At a minimum, `InstallProvider` takes a `clientId` and `clientSecret` (both which can be obtained under the **Basic Information** of your app configuration). `InstallProvider` also requires a `stateSecret`, which is used to encode the generated state, and later used to decode that same state to verify it wasn't tampered with during the OAuth flow. **Note**: This example is not ready for production because it only stores installations (tokens) in memory. Please go to the [storing installations in a database](#storing-installations-in-a-database) section to learn how to plug in your own database. | ||
@@ -146,3 +146,3 @@ ```javascript | ||
const htmlResponse = `<html><body>Success!</body></html>` | ||
res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | ||
res.end(htmlResponse); | ||
@@ -152,3 +152,3 @@ }, | ||
// Do custom failure logic here | ||
res.writeHead(500, { 'Content-Type': 'text/html' }); | ||
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' }); | ||
res.end('<html><body><h1>Oops, Something Went Wrong! Please Try Again or Contact the App Owner</h1></body></html>'); | ||
@@ -168,6 +168,8 @@ } | ||
An installation store is an object that provides two methods: `storeInstallation` and `fetchInstallation`. `storeInstallation` takes an `installation` as an argument, which is an object that contains all installation related data (like tokens, teamIds, enterpriseIds, etc). `fetchInstallation` takes in a `installQuery`, which is used to query the database. The `installQuery` can contain `teamId`, `enterpriseId`, `userId`, and `conversationId`. | ||
An installation store is an object that provides four methods: `storeInstallation`, `storeOrgInstallation`, `fetchInstallation` and `fetchOrgInstallation`. `storeInstallation` and `storeOrgInstallation` takes an `installation` as an argument, which is an object that contains all installation related data (like tokens, teamIds, enterpriseIds, etc). `fetchInstallation` and `fetchOrgInstallation` takes in a `installQuery`, which is used to query the database. The `installQuery` can contain `teamId`, `enterpriseId`, `userId`, and `conversationId`. | ||
In the following example, the `installationStore` option is used and the object is defined in line. The required methods are implemented by calling an example database library with simple get and set operations. | ||
**Note**: `fetchOrgInstallation` and `storeOrgInstallation` were introduced to support Org wide app installations (currently in beta). | ||
In the following example, the `installationStore` option is used and the object is defined in line. The methods are implemented by calling an example database library with simple get and set operations. | ||
```javascript | ||
@@ -181,6 +183,10 @@ const installer = new InstallProvider({ | ||
// returns nothing | ||
storeInstallation: (installation) => { | ||
storeInstallation: async (installation) => { | ||
// replace myDB.set with your own database or OEM setter | ||
myDB.set(installation.team.id, installation); | ||
return; | ||
if (installation.team.id !== undefined) { | ||
// non enterprise org app installation | ||
return myDB.set(installation.team.id, installation); | ||
} else { | ||
throw new Error('Failed saving installation data to installationStore'); | ||
} | ||
}, | ||
@@ -190,6 +196,26 @@ // takes in an installQuery as an argument | ||
// returns installation object from database | ||
fetchInstallation: (installQuery) => { | ||
fetchInstallation: async (installQuery) => { | ||
// replace myDB.get with your own database or OEM getter | ||
return myDB.get(installQuery.teamId); | ||
// non enterprise org app lookup | ||
return await myDB.get(installQuery.teamId); | ||
}, | ||
// takes in an installation object as an argument | ||
// returns nothing | ||
storeOrgInstallation: async (installation) => { | ||
// replace myDB.set with your own database or OEM setter | ||
if (installation.isEnterpriseInstall && installation.enterprise !== undefined) { | ||
// enterprise app, org wide installation | ||
return myDB.set(installation.enterprise.id, installation); | ||
} else { | ||
throw new Error('Failed saving installation data to installationStore'); | ||
} | ||
}, | ||
// takes in an installQuery as an argument | ||
// installQuery = {teamId: 'string', enterpriseId: 'string', userId: string, conversationId: 'string'}; | ||
// returns installation object from database | ||
fetchInstallation: async (installQuery) => { | ||
// replace myDB.get with your own database or OEM getter | ||
// enterprise org app installation lookup | ||
return await myDB.get(installQuery.enterpriseId); | ||
}, | ||
}, | ||
@@ -202,3 +228,3 @@ }); | ||
You can use the the `installationProvider.authorize()` function to fetch data that has been saved in your installation store. | ||
You can use the the `installationProvider.authorize()` function to fetch data that has been saved in your installation store. For Org wide app installations, you can use `installationProvider.orgAuthorize()` | ||
@@ -208,3 +234,4 @@ ```javascript | ||
// installQuery = {teamId: 'string', enterpriseId: 'string', userId: string, conversationId: 'string'}; | ||
const result = installer.installationStore.fetchInstallation({teamId:'my-team-ID', enterpriseId:'my-enterprise-ID'}); | ||
const result = installer.authorize({teamId: 'my-team-ID'}); | ||
const orgResult = installer.orgAuthorize({enterpriseId: 'my-enterprise-ID'}); | ||
/* | ||
@@ -225,3 +252,3 @@ result = { | ||
The `installer.authorize()` method only returns a subset of the installation data returned by the installation store. To fetch the entire saved installation, use the `installer.installationStore.fetchInstallation()` method. | ||
The `installer.authorize()`/`installer.orgAuthorize()` methods only returns a subset of the installation data returned by the installation store. To fetch the entire saved installation, use the `installer.installationStore.fetchInstallation()`/`installer.installationStore.fetchOrgInstallation()` methods. | ||
@@ -232,3 +259,4 @@ ```javascript | ||
// returns an installation object | ||
const result = installer.installationStore.fetchInstallation({teamId:'my-Team-ID'}); | ||
const result = await installer.installationStore.fetchInstallation({teamId:'my-team-ID', enterpriseId:'my-enterprise-ID'}); | ||
const orgResult = await installer.installationStore.fetchOrgInstallation({enterpriseId:'my-enterprise-ID'}); | ||
``` | ||
@@ -235,0 +263,0 @@ </details> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
84356
23.47%866
22.84%379
7.98%