slack-cloudflare-workers
Version:
Slack app development framework for Cloudflare Workers
231 lines • 13.9 kB
JavaScript
;
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