@atomist/automation-client
Version:
Atomist API for software low-level client
253 lines • 11.7 kB
JavaScript
"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