botpress
Version:
The world's first CMS for bots. Easily create, manage and extend chatbots.
278 lines (223 loc) • 8.52 kB
JavaScript
;
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _bodyParser = require('body-parser');
var _bodyParser2 = _interopRequireDefault(_bodyParser);
var _express = require('express');
var _queryString = require('query-string');
var _queryString2 = _interopRequireDefault(_queryString);
var _utilRoles = require('@botpress/util-roles');
var _anonymous = require('./anonymous');
var _anonymous2 = _interopRequireDefault(_anonymous);
var _nonSecured = require('./non-secured');
var _nonSecured2 = _interopRequireDefault(_nonSecured);
var _secured = require('./secured');
var _secured2 = _interopRequireDefault(_secured);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const routersConditions = {};
const routers = {};
const API_RE = /\/api\/(botpress-[^\/]+).*$/i;
const maybeApply = (name, fn) => {
return (req, res, next) => {
const router = req.originalUrl.match(API_RE);
if (!router) {
return fn(req, res, next);
}
if (!routersConditions[router[1]]) {
return fn(req, res, next);
}
const condition = routersConditions[router[1]][name];
if (condition === false) {
next();
} else if (typeof condition === 'function' && condition(req) === false) {
next();
} else {
return fn(req, res, next);
}
};
};
module.exports = bp => {
const _authenticationMiddleware = (() => {
var _ref = _asyncToGenerator(function* (req, res, next) {
res.maybeSendRequireLogin = function () {
if (!bp.botfile.login.enabled) {
res.status(400).send({
message: 'Login must be turned on for this API method'
});
return true;
} else {
return false;
}
};
if (!bp.botfile.login.enabled) {
return next();
}
const user = yield bp.security.authenticate(req.headers.authorization);
if (!!user) {
req.user = user;
next();
} else {
res.status(401).location('/login').end();
}
});
return function _authenticationMiddleware(_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
})();
const getCloudRoles = (() => {
var _ref2 = _asyncToGenerator(function* (req) {
const { useCloud, enabled } = bp.botfile.login;
const isUsingCloud = !!useCloud && (yield bp.cloud.isPaired());
if (!isUsingCloud || !enabled) {
// No cloud, skip check
return false;
}
const { roles } = req.user || {};
if (!roles) {
return null;
}
return bp.cloud.getUserRoles(roles);
});
return function getCloudRoles(_x4) {
return _ref2.apply(this, arguments);
};
})();
const installProtector = app => {
app.secure = (operation, resource) => {
const wrap = method => (route, ...handlers) => {
const secureMiddleware = (() => {
var _ref3 = _asyncToGenerator(function* (req, res, next) {
try {
const roles = yield getCloudRoles(req);
if (roles === false) {
return next();
}
if (!(0, _utilRoles.checkMultipleRoles)(roles, operation, resource)) {
return res.sendStatus(403); // Forbidden
}
return next();
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
return function secureMiddleware(_x5, _x6, _x7) {
return _ref3.apply(this, arguments);
};
})();
return app[method](route, secureMiddleware, ...handlers);
};
return {
get: wrap('get'),
post: wrap('post'),
put: wrap('put'),
patch: wrap('patch'),
delete: wrap('delete')
};
};
};
const installRouter = app => {
/**
* Creates an HTTP [Express Router]{@link https://expressjs.com/} that is protected by authentication
* The router routes are available at "http://bot_url/api/:name"
* Where `name` is a string starting with `botpress-`
* @func
* @alias getRouter
* @memberOf! Botpress
* @param {String} name The name of the router. Must start with `botpress-`
* @param {object=} [conditions] See examples. Conditionally disables built-in Botpress middlewares.
* @example
* const securedRouter = bp.getRouter('botpress-custom')
* const publicRouter = bp.getRouter('botpress-custom', { auth: false })
*
* // Conditions can also be used like below
* const conditions = { 'auth': req => !/\/webhook/i.test(req.originalUrl) }
* const conditionalAuthentication = bp.getRouter('botpress-custom', conditions)
*/
bp.getRouter = (name, conditions) => {
if (!/^botpress-/.test(name)) {
throw new Error(`The name of a router must start with 'botpress-'. Received: ${name}`);
}
if (!routers[name]) {
const router = (0, _express.Router)();
routers[name] = router;
app.use(`/api/${name}/`, router);
}
if (conditions) {
routersConditions[name] = Object.assign(routersConditions[name] || {}, conditions);
}
installProtector(routers[name]);
return routers[name];
};
const links = {};
/**
* Creates a short link for a specific API route, making it easier to share, more verbose and elegant.
* Short links are available as `http://bot_url/s/{name}`
* @alias createShortlink
* @param {String} name Unique, url-friendly name of the short link
* @param {String} destination The original route to redirect to
* @param {Object} params Query parameters to pass the route. Will be serialized.
* @memberof! Botpress
* @example
* const config = {
botName: 'Superbot',
botConvoDescription: "Tell me something!",
backgroundColor: '#ffffff'
}
// Visiting "http://bot_url/s/chat" will display the webchat in fullscreen
bp.createShortlink('chat', '/lite', {
m: 'channel-web',
v: 'fullscreen',
options: JSON.stringify({ config: config })
})
*/
bp.createShortlink = (name, destination, params) => {
name = name.toLowerCase();
if (links[name]) {
throw new Error(`There's already a shortlink named "${name}"`);
}
const q = params ? '?' + _queryString2.default.stringify(params) : '';
links[name] = `${destination}${q}`;
};
app.get(`/s/:name`, (req, res) => {
const name = req.params.name.toLowerCase();
const query = _queryString2.default.stringify(req.query);
let link = links[name];
if (!link) {
return res.status(404).send({ error: `Shortlink "${name}" not registered` });
}
if (query) {
const hasQuery = /\?/g.test(link);
link = link.concat(`${hasQuery ? '&' : '?'}${query}`);
}
res.redirect(link);
});
};
const installMaybeUse = app => {
app.maybeUse = function () {
if (arguments.length === 3) {
app.use(arguments[0], maybeApply(arguments[1], arguments[2]));
} else if (arguments.length === 2) {
app.use(maybeApply(arguments[0], arguments[1]));
}
};
};
const install = (() => {
var _ref4 = _asyncToGenerator(function* (app) {
installRouter(app);
installProtector(app);
installMaybeUse(app);
app.maybeUse('bodyParser.json', _bodyParser2.default.json({ limit: _lodash2.default.get(bp.botfile, 'api.bodyMaxSize') || '1mb' }));
app.maybeUse('bodyParser.urlencoded', _bodyParser2.default.urlencoded({ extended: true }));
(0, _anonymous2.default)(bp, app);
app.use('/api/*', maybeApply('auth', _authenticationMiddleware));
(0, _nonSecured2.default)(bp, app);
(0, _secured2.default)(bp, app);
});
return function install(_x8) {
return _ref4.apply(this, arguments);
};
})();
return { install };
};
//# sourceMappingURL=api.js.map