n8n
Version:
n8n Workflow Automation Tool
150 lines • 7.37 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlackManualSetupService = void 0;
const node_crypto_1 = require("node:crypto");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const is_record_1 = require("@n8n/utils/is-record");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const bad_request_error_1 = require("../../../../../errors/response-errors/bad-request.error");
const not_found_error_1 = require("../../../../../errors/response-errors/not-found.error");
const cache_service_1 = require("../../../../../services/cache/cache.service");
const slack_methods_service_1 = require("./slack-methods.service");
const SLACK_APP_SETUP_CACHE_PREFIX = 'agents:slack-app-setup:';
const SLACK_APP_SETUP_TTL_MS = 60 * 60 * 1000;
function hasSessionShape(value) {
const keys = [
'projectId',
'agentId',
'userId',
'appId',
'clientId',
'clientSecret',
'signingSecret',
'redirectUrl',
];
return (0, is_record_1.isRecord)(value) && keys.every((key) => typeof value[key] === 'string');
}
let SlackManualSetupService = class SlackManualSetupService {
constructor(methods, userRepository, cacheService, cipher) {
this.methods = methods;
this.userRepository = userRepository;
this.cacheService = cacheService;
this.cipher = cipher;
}
async createApp(options) {
const appConfigurationToken = options.appConfigurationToken.trim();
if (!appConfigurationToken) {
throw new bad_request_error_1.BadRequestError('Slack app configuration token is required');
}
const agent = await this.methods.getAgent(options.agentId, options.projectId);
const redirectUrl = this.methods.callbackUrl(options.projectId, options.agentId);
const manifest = this.methods.buildManifest(agent.name, options.projectId, options.agentId, {
redirectUrl,
});
const response = await this.methods.callSlackApi('apps.manifest.create', {
token: appConfigurationToken,
manifest: JSON.stringify(manifest),
});
if (response.ok !== true) {
throw this.methods.slackError('create the Slack app', response);
}
const credentials = this.methods.childRecord(response, 'credentials');
const appId = this.methods.stringProperty(response, 'app_id');
const clientId = this.methods.stringProperty(credentials, 'client_id');
const clientSecret = this.methods.stringProperty(credentials, 'client_secret');
const signingSecret = this.methods.stringProperty(credentials, 'signing_secret');
const oauthAuthorizeUrl = this.methods.stringProperty(response, 'oauth_authorize_url');
if (!appId || !clientId || !clientSecret || !signingSecret || !oauthAuthorizeUrl) {
throw new bad_request_error_1.BadRequestError('Slack returned an incomplete app setup response');
}
const state = (0, node_crypto_1.randomBytes)(32).toString('hex');
const setupSession = {
projectId: options.projectId,
agentId: options.agentId,
userId: options.user.id,
appId,
clientId,
clientSecret,
signingSecret,
redirectUrl,
};
await this.cacheService.set(this.cacheKey(state), await this.cipher.encryptV2(JSON.stringify(setupSession)), SLACK_APP_SETUP_TTL_MS);
return {
appId,
installUrl: this.methods.installUrl(oauthAuthorizeUrl, state, redirectUrl),
};
}
async getManifest(options) {
const agent = await this.methods.getAgent(options.agentId, options.projectId);
return {
manifest: this.methods.buildManifest(agent.name, options.projectId, options.agentId),
};
}
async completeInstall(options) {
const session = await this.consumeSession(options.state);
if (session.projectId !== options.projectId || session.agentId !== options.agentId) {
throw new bad_request_error_1.BadRequestError('Slack app setup state does not match this agent');
}
const user = await this.userRepository.findOne({
where: { id: session.userId },
relations: ['role'],
});
if (!user)
throw new not_found_error_1.NotFoundError(`User "${session.userId}" not found`);
const agent = await this.methods.getAgent(session.agentId, session.projectId);
const tokenResponse = await this.methods.callSlackApi('oauth.v2.access', {
code: options.code,
redirect_uri: session.redirectUrl,
}, {
authorization: `Basic ${Buffer.from(`${session.clientId}:${session.clientSecret}`).toString('base64')}`,
});
if (tokenResponse.ok !== true) {
throw this.methods.slackError('finish Slack app installation', tokenResponse);
}
const accessToken = this.methods.stringProperty(tokenResponse, 'access_token');
if (!accessToken?.startsWith('xoxb-')) {
throw new bad_request_error_1.BadRequestError('Slack did not return a Bot User OAuth Token');
}
await this.methods.createAndConnectBotCredential({
agent,
user,
accessToken,
signingSecret: session.signingSecret,
});
}
async consumeSession(state) {
const cached = await this.cacheService.take(this.cacheKey(state));
if (typeof cached !== 'string') {
throw new bad_request_error_1.BadRequestError('Slack app setup state has expired or is invalid');
}
try {
const decrypted = await this.cipher.decryptV2(cached);
const session = (0, n8n_workflow_1.jsonParse)(decrypted, { fallbackValue: null });
if (hasSessionShape(session))
return session;
}
catch {
}
throw new bad_request_error_1.BadRequestError('Slack app setup state has expired or is invalid');
}
cacheKey(state) {
return `${SLACK_APP_SETUP_CACHE_PREFIX}${state}`;
}
};
exports.SlackManualSetupService = SlackManualSetupService;
exports.SlackManualSetupService = SlackManualSetupService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [slack_methods_service_1.SlackMethodsService, db_1.UserRepository, cache_service_1.CacheService, n8n_core_1.Cipher])
], SlackManualSetupService);
//# sourceMappingURL=slack-manual-setup.service.js.map