UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

253 lines • 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const GitHubApi = require("@octokit/rest"); const bodyParser = require("body-parser"); const express = require("express"); const passport = require("passport"); const http = require("passport-http"); const bearer = require("passport-http-bearer"); const tokenHeader = require("passport-http-header-token"); const globals = require("../../../globals"); const AbstractAutomationServer_1 = require("../../../server/AbstractAutomationServer"); const logger_1 = require("../../../util/logger"); const port_1 = require("../../../util/port"); const health_1 = require("../../util/health"); const info_1 = require("../../util/info"); const memory_1 = require("../../util/memory"); const metric_1 = require("../../util/metric"); const string_1 = require("../../util/string"); const payloads_1 = require("../websocket/payloads"); /** * Registers an endpoint for every automation and exposes * metadataFromInstance at root. Responsible for marshalling into the appropriate structure */ class ExpressServer { constructor(automations, configuration, handler) { var _a; this.automations = automations; this.configuration = configuration; this.handler = handler; this.adminRoute = (req, res, next) => { req.__admin = true; next(); }; this.authenticate = (req, res, next) => { if (this.configuration.http.auth) { const strategies = []; if (this.configuration.http.auth.bearer && this.configuration.http.auth.bearer.enabled === true) { strategies.push("bearer"); } if (this.configuration.http.auth.basic && this.configuration.http.auth.basic.enabled === true) { strategies.push("basic"); } if (this.configuration.http.auth.token && this.configuration.http.auth.token.enabled === true) { strategies.push("token"); } if (strategies.length > 0) { passport.authenticate(strategies, { session: false })(req, res, next); } else { next(); } } else { next(); } }; this.exp = express(); this.exp.use(bodyParser.json((_a = this.configuration.http.bodyParser) === null || _a === void 0 ? void 0 : _a.options)); this.exp.use(require("helmet")()); this.exp.use(passport.initialize()); // Enable cors for all endpoints const cors = require("cors"); this.setupAuthentication(); // Set up routes this.exp.options(`${ApiBase}/health`, cors()); this.exp.get(`${ApiBase}/health`, cors(), (req, res) => { const h = health_1.health(); if (h.status !== health_1.HealthStatus.Up) { logger_1.logger.warn(`Health status: ${JSON.stringify(h)}`); } res.status(h.status === health_1.HealthStatus.Up ? 200 : 500).json(h); }); this.exp.options(`${ApiBase}/info`, cors()); this.exp.get(`${ApiBase}/info`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(info_1.info(automations.automations)); }); this.exp.options(`${ApiBase}/registration`, cors()); this.exp.get(`${ApiBase}/registration`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(payloads_1.prepareRegistration(automations.automations)); }); this.exp.options(`${ApiBase}/metrics`, cors()); this.exp.get(`${ApiBase}/metrics`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(metric_1.metrics()); }); this.exp.options(`${ApiBase}/memory/gc`, cors()); this.exp.put(`${ApiBase}/memory/gc`, cors(), this.adminRoute, this.authenticate, (req, res) => { memory_1.gc(); res.sendStatus(201); }); this.exp.options(`${ApiBase}/memory/heapdump`, cors()); this.exp.put(`${ApiBase}/memory/heapdump`, cors(), this.adminRoute, this.authenticate, (req, res) => { memory_1.heapDump(); res.sendStatus(201); }); this.exp.options(`${ApiBase}/memory/mtrace`, cors()); this.exp.put(`${ApiBase}/memory/mtrace`, cors(), this.adminRoute, this.authenticate, (req, res) => { memory_1.mtrace(); res.sendStatus(201); }); this.exp.options(`${ApiBase}/log/events`, cors()); this.exp.get(`${ApiBase}/log/events`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(globals.eventStore().events(req.query.from)); }); this.exp.options(`${ApiBase}/log/commands`, cors()); this.exp.get(`${ApiBase}/log/commands`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(globals.eventStore().commands(req.query.from)); }); this.exp.options(`${ApiBase}/log/messages`, cors()); this.exp.get(`${ApiBase}/log/messages`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(globals.eventStore().messages(req.query.from)); }); this.exp.options(`${ApiBase}/series/events`, cors()); this.exp.get(`${ApiBase}/series/events`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(globals.eventStore().eventSeries()); }); this.exp.options(`${ApiBase}/series/commands`, cors()); this.exp.get(`${ApiBase}/series/commands`, cors(), this.adminRoute, this.authenticate, (req, res) => { res.json(globals.eventStore().commandSeries()); }); this.exposeCommandHandlerInvocationRoute(this.exp, `${ApiBase}/command`, cors, (req, res, result) => { if (result.redirect && !req.get("x-atomist-no-redirect")) { res.redirect(result.redirect); } else { res.status(result.code === 0 ? 200 : 500).json(result); } }); this.exposeEventHandlerInvocationRoute(this.exp, `${ApiBase}/event`, cors, (req, res, result) => { const results = Array.isArray(result) ? result : [result]; const code = AbstractAutomationServer_1.noEventHandlersWereFound(result) ? 404 : results.some(r => r.code !== 0) ? 500 : 200; res.status(code).json(result); }); if (this.configuration.http.customizers.length > 0) { logger_1.logger.debug("Invoking http server customizers"); this.configuration.http.customizers.forEach(c => c(this.exp, this.authenticate)); } } run() { let portPromise; if (!this.configuration.http.port) { portPromise = port_1.scanFreePort(); } else { portPromise = Promise.resolve(this.configuration.http.port); } return portPromise .then(port => { this.configuration.http.port = port; const hostname = this.configuration.http.host || "0.0.0.0"; this.exp.listen(port, hostname, () => { logger_1.logger.debug(`Atomist automation client api running at 'http://${hostname}:${port}'`); return true; }).on("error", err => { logger_1.logger.error(`Failed to start automation client api: ${err.message}`); return false; }); }); } exposeCommandHandlerInvocationRoute(exp, url, cors, handle) { exp.post(url, cors(), this.authenticate, (req, res) => { this.handler.processCommand(req.body, result => { result.then(r => handle(req, res, r)); }); }); } exposeEventHandlerInvocationRoute(exp, url, cors, handle) { exp.post(url, cors(), this.authenticate, (req, res) => { this.handler.processEvent(req.body, result => { result.then(r => handle(req, res, r)); }); }); } setupAuthentication() { if (this.configuration.http.auth && this.configuration.http.auth.basic && this.configuration.http.auth.basic.enabled) { const user = this.configuration.http.auth.basic.username ? this.configuration.http.auth.basic.username : "admin"; const pwd = this.configuration.http.auth.basic.password ? this.configuration.http.auth.basic.password : string_1.guid(); passport.use("basic", new http.BasicStrategy((username, password, done) => { if (user === username && pwd === password) { done(null, { user: username }); } else { done(null, false); } })); if (!this.configuration.http.auth.basic.password) { logger_1.logger.debug(`Auto-generated credentials for web endpoints are user '${user}' and password '${pwd}'`); } } if (this.configuration.http.auth && this.configuration.http.auth.bearer && this.configuration.http.auth.bearer.enabled) { const org = this.configuration.http.auth.bearer.org; const adminOrg = this.configuration.http.auth.bearer.adminOrg; passport.use("bearer", new bearer.Strategy({ passReqToCallback: true, }, (req, token, done) => { const api = new GitHubApi(); api.authenticate({ type: "token", token }); api.users.getAuthenticated({}) .then(user => { if (adminOrg && req.__admin === true) { return api.orgs.checkMembership({ username: user.data.login, org: adminOrg, }) .then(() => { return user.data; }); } else if (org) { return api.orgs.checkMembership({ username: user.data.login, org, }) .then(() => { return user.data; }); } else { return user.data; } }) .then(user => { return done(null, { token, user }); }) .catch(err => { console.log(err); return done(null, false); }); })); } if (this.configuration.http.auth && this.configuration.http.auth.token && this.configuration.http.auth.token.enabled) { const cb = this.configuration.http.auth.token.verify || (token => Promise.resolve(false)); passport.use("token", new tokenHeader.Strategy((token, done) => { cb(token) .then(valid => { if (valid) { return done(null, { user: token }); } else { return done(null, false); } }) .catch(err => { console.log(err); return done(null, false); }); })); } } } exports.ExpressServer = ExpressServer; const ApiBase = ""; //# sourceMappingURL=ExpressServer.js.map