UNPKG

slack-cloudflare-workers

Version:

Slack app development framework for Cloudflare Workers

231 lines 13.9 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _KVInstallationStore_env, _KVInstallationStore_storage, _KVInstallationStore_tokenRotator, _KVInstallationStore_authTestCacheEnabled, _KVInstallationStore_authTestCacheExpirationSecs, _KVInstallationStore_authTestCacheNamespace; Object.defineProperty(exports, "__esModule", { value: true }); exports.KVInstallationStore = void 0; exports.toBotInstallationQuery = toBotInstallationQuery; exports.toUserInstallationQuery = toUserInstallationQuery; exports.toBotInstallationKey = toBotInstallationKey; exports.toUserInstallationKey = toUserInstallationKey; const slack_edge_1 = require("slack-edge"); const serializable_slack_api_response_1 = require("./serializable-slack-api-response"); class KVInstallationStore { constructor(env, namespace, options = {}) { _KVInstallationStore_env.set(this, void 0); _KVInstallationStore_storage.set(this, void 0); _KVInstallationStore_tokenRotator.set(this, void 0); /** * Whether to enable caching of `auth.test` responses. */ _KVInstallationStore_authTestCacheEnabled.set(this, void 0); /** * The TTL expiration time in seconds for each cache entry. * 0 or negative value indicates the cache is permanent. The default is 10 minutes. * @see https://developers.cloudflare.com/kv/api/write-key-value-pairs/#expiring-keys */ _KVInstallationStore_authTestCacheExpirationSecs.set(this, void 0); /** * The KVNamespace to use for caching `auth.test` responses. */ _KVInstallationStore_authTestCacheNamespace.set(this, void 0); __classPrivateFieldSet(this, _KVInstallationStore_env, env, "f"); __classPrivateFieldSet(this, _KVInstallationStore_storage, namespace, "f"); __classPrivateFieldSet(this, _KVInstallationStore_tokenRotator, new slack_edge_1.TokenRotator({ clientId: env.SLACK_CLIENT_ID, clientSecret: env.SLACK_CLIENT_SECRET, }), "f"); if (options.authTestCacheEnabled && !options.authTestCacheStorage) { throw new slack_edge_1.ConfigError("authTestCacheStorage must be provided when authTestCacheEnabled is true"); } __classPrivateFieldSet(this, _KVInstallationStore_authTestCacheEnabled, options.authTestCacheEnabled ?? false, "f"); __classPrivateFieldSet(this, _KVInstallationStore_authTestCacheNamespace, options.authTestCacheStorage, "f"); __classPrivateFieldSet(this, _KVInstallationStore_authTestCacheExpirationSecs, options.authTestCacheExpirationSecs ?? 10 * 60, "f"); } async save(installation, request = undefined) { await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").put(toBotInstallationKey(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, installation), JSON.stringify(installation)); await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").put(toUserInstallationKey(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, installation), JSON.stringify(installation)); } async findBotInstallation(query) { const storedString = await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").get(toBotInstallationQuery(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, query)); if (storedString) { return JSON.parse(storedString); } return undefined; } async findUserInstallation(query) { const storedString = await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").get(toUserInstallationQuery(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, query)); if (storedString) { return JSON.parse(storedString); } return undefined; } async deleteBotInstallation(query) { await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").delete(toBotInstallationQuery(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, query)); } async deleteUserInstallation(query) { await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").delete(toUserInstallationQuery(__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID, query)); } async deleteAll(query) { const clientId = __classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_CLIENT_ID; if (!query.enterpriseId && !query.teamId) { return; // for safety } const e = query.enterpriseId ? query.enterpriseId : "_"; const prefix = query.teamId ? `${clientId}/${e}:${query.teamId}` : `${clientId}/${e}:`; var keys = []; const first = await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").list({ prefix }); keys = keys.concat(first.keys.map((k) => k.name)); if (!first.list_complete) { var cursor = first.cursor; while (cursor) { const response = await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").list({ prefix, cursor }); keys = keys.concat(response.keys.map((k) => k.name)); cursor = response.list_complete ? undefined : response.cursor; } } for (const key of keys) { await __classPrivateFieldGet(this, _KVInstallationStore_storage, "f").delete(key); } } toAuthorize() { return async (req) => { const query = { isEnterpriseInstall: req.context.isEnterpriseInstall, enterpriseId: req.context.enterpriseId, teamId: req.context.teamId, userId: req.context.userId, }; try { const bot = await this.findBotInstallation(query); if (bot && bot.bot_refresh_token) { const maybeRefreshed = await __classPrivateFieldGet(this, _KVInstallationStore_tokenRotator, "f").performRotation({ bot: { access_token: bot.bot_token, refresh_token: bot.bot_refresh_token, token_expires_at: bot.bot_token_expires_at, }, }); if (maybeRefreshed && maybeRefreshed.bot) { bot.bot_token = maybeRefreshed.bot.access_token; bot.bot_refresh_token = maybeRefreshed.bot.refresh_token; bot.bot_token_expires_at = maybeRefreshed.bot.token_expires_at; await this.save(bot); } } const botClient = new slack_edge_1.SlackAPIClient(bot?.bot_token, { logLevel: __classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_LOGGING_LEVEL, }); const botAuthTest = await this.callAuthTest(botClient, bot?.bot_token); const botScopes = botAuthTest.headers.get("x-oauth-scopes")?.split(",") ?? bot?.bot_scopes ?? []; const userQuery = {}; Object.assign(userQuery, query); if (__classPrivateFieldGet(this, _KVInstallationStore_env, "f").SLACK_USER_TOKEN_RESOLUTION !== "installer") { userQuery.enterpriseId = req.context.actorEnterpriseId; userQuery.teamId = req.context.actorTeamId; userQuery.userId = req.context.actorUserId; } const user = await this.findUserInstallation(query); if (user && user.user_refresh_token) { const maybeRefreshed = await __classPrivateFieldGet(this, _KVInstallationStore_tokenRotator, "f").performRotation({ user: { access_token: user.user_token, refresh_token: user.user_refresh_token, token_expires_at: user.user_token_expires_at, }, }); if (maybeRefreshed && maybeRefreshed.user) { user.user_token = maybeRefreshed.user.access_token; user.user_refresh_token = maybeRefreshed.user.refresh_token; user.user_token_expires_at = maybeRefreshed.user.token_expires_at; await this.save(user); } } let userAuthTest = undefined; if (user) { userAuthTest = await this.callAuthTest(botClient, user.user_token); } return { enterpriseId: bot?.enterprise_id, teamId: bot?.team_id, team: botAuthTest.team, url: botAuthTest.url, botId: botAuthTest.bot_id, botUserId: botAuthTest.user_id, botToken: bot?.bot_token, botScopes, userId: user ? user.user_id : undefined, user: userAuthTest?.user, userToken: user?.user_token, userScopes: user?.user_scopes, }; } catch (e) { throw new slack_edge_1.AuthorizeError(`Failed to authorize (error: ${e}, query: ${JSON.stringify(query)})`); } }; } /** * Calls the `auth.test` Slack API method, first checking the auth.test cache if enabled. * @param client - The Slack API client to use for the request. * @param token - The token to use for the request, and/or cache key. * @returns The response from the `auth.test` Slack API method. */ async callAuthTest(client, token) { if (token && __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheEnabled, "f") && __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheNamespace, "f")) { // Check the cache first const cachedResponse = await __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheNamespace, "f").get(token); if (cachedResponse) { const serializableAuthTestResponse = JSON.parse(cachedResponse); return (0, serializable_slack_api_response_1.fromSerializableSlackAPIResponse)(serializableAuthTestResponse); } // If not cached, call the API and cache successful results const authTestResponse = await client.auth.test(); if (authTestResponse?.ok && !authTestResponse.error) { const serializableAuthTestResponse = (0, serializable_slack_api_response_1.toSerializableSlackAPIResponse)(authTestResponse); const permanentCacheEnabled = __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheExpirationSecs, "f") <= 0; await __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheNamespace, "f").put(token, JSON.stringify(serializableAuthTestResponse), { expirationTtl: permanentCacheEnabled ? undefined : __classPrivateFieldGet(this, _KVInstallationStore_authTestCacheExpirationSecs, "f"), }); } return authTestResponse; } else { return await client.auth.test(); } } } exports.KVInstallationStore = KVInstallationStore; _KVInstallationStore_env = new WeakMap(), _KVInstallationStore_storage = new WeakMap(), _KVInstallationStore_tokenRotator = new WeakMap(), _KVInstallationStore_authTestCacheEnabled = new WeakMap(), _KVInstallationStore_authTestCacheExpirationSecs = new WeakMap(), _KVInstallationStore_authTestCacheNamespace = new WeakMap(); function toBotInstallationQuery(clientId, q) { const e = q.enterpriseId ? q.enterpriseId : "_"; const t = q.teamId && !q.isEnterpriseInstall ? q.teamId : "_"; return `${clientId}/${e}:${t}`; } function toUserInstallationQuery(clientId, q) { const e = q.enterpriseId ? q.enterpriseId : "_"; const t = q.teamId && !q.isEnterpriseInstall ? q.teamId : "_"; const u = q.userId ? q.userId : "_"; return `${clientId}/${e}:${t}:${u}`; } function toBotInstallationKey(clientId, installation) { const e = installation.enterprise_id ?? "_"; const t = installation.team_id && !installation.is_enterprise_install ? installation.team_id : "_"; return `${clientId}/${e}:${t}`; } function toUserInstallationKey(clientId, installation) { const e = installation.enterprise_id ?? "_"; const t = installation.team_id && !installation.is_enterprise_install ? installation.team_id : "_"; const u = installation.user_id ?? "_"; return `${clientId}/${e}:${t}:${u}`; } //# sourceMappingURL=kv-installation-store.js.map