UNPKG

fully-api

Version:

API framework for Fully Stacked, LLC REST-ful APIs

412 lines 18.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AccessControl = void 0; const ErrorObj_1 = require("./ErrorObj"); const Q = require('q'); const aws_sdk_1 = __importDefault(require("aws-sdk")); const fs_1 = __importDefault(require("fs")); const crypto = __importStar(require("crypto")); const path_1 = __importDefault(require("path")); const rootDir = path_1.default.dirname(require.main.filename); let AccessControlExtension; var ext_path = rootDir + '/accessControl_ext.js'; var default_path = '../accessControl_ext.js'; if (fs_1.default.existsSync(ext_path)) { try { AccessControlExtension = require(ext_path).AccessControlExtension; } catch (e) { // tslint:disable-next-line:no-console console.log('Error loading Access control extension. Falling back to default file.', e); AccessControlExtension = require(default_path).AccessControlExtension; } } else { var ext_path = rootDir + '/services/accessControl_ext.js'; if (fs_1.default.existsSync(ext_path)) { try { AccessControlExtension = require(ext_path).AccessControlExtension; } catch (e) { // tslint:disable-next-line:no-console console.log('Error loading Access control extension. Falling back to default file.', e); AccessControlExtension = require(default_path).AccessControlExtension; } } else { try { console.log('No custom accessControl_ext file located. Falling back to default file.'); AccessControlExtension = require(default_path).AccessControlExtension; } catch (e) { // tslint:disable-next-line:no-console console.error("Failed to load default accessControl_ext extension", e); } } } // =============================================================================== // UTILITY FUNCTIONS // =============================================================================== /** @class AccessControl representing system access controls. */ class AccessControl { /** * Creates an instance of Access Control service. * * @author: Fully Stacked,LLC * @param config instance of a config file. * @param util instance of utilities service. * @param s instance of settings service. * @param d instance of data access service. */ constructor(config, util, s, d) { this.permissions = { some: 'some', all: 'all' }; this.s3 = new aws_sdk_1.default.S3(); this.config = config; this.utilities = util; this.settings = s; this.dataAccess = d; this.extension = new AccessControlExtension(this, util, s, d); // this.extension = new AccessControlExtension(this, util, s); } init(b, f, rs) { const ac = this; const deferred = Q.defer(); this.bucket = b; this.file = f; this.remoteSettings = rs; if (this.remoteSettings == null || this.remoteSettings === false) { try { this.securityObj = require(this.file); this.data = this.securityObj; deferred.resolve(true); } catch (e) { const errorObj = new ErrorObj_1.ErrorObj(403, 'ac0001', __filename, 'init', 'unauthorized', 'You are not authorized to access this endpoint', null); deferred.reject(errorObj); } } else { this.s3.getObject({ Bucket: this.bucket, Key: this.file }, function (err, res) { if (!err) { this.securityObj = JSON.parse(res.Body.toString()); this.data = this.securityObj; deferred.resolve(true); } else { const errorObj = new ErrorObj_1.ErrorObj(500, 'ac0002', __filename, 'init', 'error getting file from S3'); deferred.reject(errorObj); } }); } return deferred.promise; } reload() { const ac = this; const deferred = Q.defer(); ac.init(this.bucket, this.file, this.remoteSettings) .then(function (res) { deferred.resolve(res); }) .fail(function (err) { const errorObj = new ErrorObj_1.ErrorObj(500, 'ac0003', __filename, 'reload', 'error while reloading access control'); deferred.reject(errorObj); }); return deferred.promise; } save(doNetworkReload) { const deferred = Q.defer(); if (this.remoteSettings == null || this.remoteSettings === false) { const fswrite = Q.denodeify(fs_1.default.writeFile); fswrite(this.file, JSON.stringify(this.data, null, 4)) .then(function (write_res) { deferred.resolve(true); }) .fail(function (err) { const errorObj = new ErrorObj_1.ErrorObj(500, 'ac0004', __filename, 'save', 'error writing to Security config file'); deferred.reject(errorObj); }); } else { this.s3.putObject({ Bucket: this.bucket, Key: this.file, Body: JSON.stringify(this.data, null, 4) }, function (err, save_res) { if (!err) { if (doNetworkReload === true) { this.settings.reloadNetwork() .then(function (reload_res) { deferred.resolve(true); }) .fail(function (err) { const errorObj = new ErrorObj_1.ErrorObj(500, 'ac0005', __filename, 'save', 'error reloading servers', err); deferred.reject(errorObj); }); } else { deferred.resolve(true); } } else { const errorObj = new ErrorObj_1.ErrorObj(500, 'ac0006', __filename, 'save', 'error writing Security config file to S3', 'External error', err); deferred.reject(errorObj); } }); } return deferred.promise; } ; // ---------------------------------------------------------------- // SESSION TOKENS // ---------------------------------------------------------------- /** * validates a session token. * * @param tkn the session token to test * @param continueWhenInvalid should the function return instead of error if invalid token. * @param callback callback function * @returns {object}: {is_valid:true, session:find_results} */ validateToken(tkn, continueWhenInvalid, callback) { const deferred = Q.defer(); if (tkn === undefined || tkn === null) { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac0005', __filename, 'validateToken', 'no token provided'); deferred.reject(errorObj); } deferred.promise.nodeify(callback); return deferred.promise; } this.dataAccess.findSessionByToken(tkn) .then(function (find_results) { deferred.resolve({ is_valid: true, session: find_results }); }) .fail(function (err) { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { if (err !== undefined && err !== null && typeof (err.addToError) === 'function') { err.setStatus(401); err.setMessages('could not find session for this token', 'unauthorized'); deferred.reject(err.addToError(__filename, 'validateToken', 'could not find session for this token')); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1004', __filename, 'validateToken', 'could not find session for this token', 'unauthorized', err); deferred.reject(errorObj); } } }); deferred.promise.nodeify(callback); return deferred.promise; } ; // ---------------------------------------------------------------- // BASIC AUTH // ---------------------------------------------------------------- /** * Validate a basic auth header * * @param {object} authHeader auth header object * @param {object} continueWhenInvalid should function continue instead of error when invalid * @param callback callback function * @returns {object}: {is_valid:true, session:find_results} */ validateBasicAuth(authHeader, continueWhenInvalid = false, callback) { const deferred = Q.defer(); const [authType, authToken] = (authHeader.split(' '))[0]; if (authType.toLowerCase() === 'basic') { const [clientId, clientSecret] = Buffer.from(authToken, 'base64').toString().split(':'); if (clientId && clientSecret) { this.dataAccess.findOneByIDField('fsapi_user_auth', 'user_id', clientId) .then((usr) => { if (!usr.is_locked) { const saltedSecret = clientSecret + usr.salt; const hashedClientSecret = crypto.createHash('sha256').update(saltedSecret).digest('hex'); if (hashedClientSecret === usr.client_secret) { // VALID deferred.resolve({ is_valid: true, client_id: clientId }); } else { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1009', __filename, 'validateBasicAuth', 'Authentication Error', 'unauthorized', null); deferred.reject(errorObj); } } } else { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1008', __filename, 'validateBasicAuth', 'User is locked', 'unauthorized', null); deferred.reject(errorObj); } } }) .fail((usrErr) => { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1007', __filename, 'validateBasicAuth', 'Authentication Error', 'unauthorized', null); deferred.reject(errorObj); } }); } else { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1006', __filename, 'validateBasicAuth', 'Malformed basic auth', 'unauthorized', null); deferred.reject(errorObj); } } } else { if (continueWhenInvalid) { deferred.resolve({ is_valid: false }); } else { const errorObj = new ErrorObj_1.ErrorObj(401, 'ac1005', __filename, 'validateBasicAuth', 'Malformed basic auth', 'unauthorized', null); deferred.reject(errorObj); } } deferred.promise.nodeify(callback); return deferred.promise; } // ---------------------------------------------------------------- // CHECK ROLES & PERMISSIONS // ---------------------------------------------------------------- /** * Verify the user has access to the requested endpoint/service call * * @param {object} req espress style request object * @param {object} serviceCall descriptor object for a service call. * @param callback callback function * @returns {boolean}: true if user has access, else false. */ verifyAccess(req, serviceCall, callback) { const deferred = Q.defer(); const userObj = req.this_user; if (userObj == null) { const errorObj = new ErrorObj_1.ErrorObj(403, 'ac0010', __filename, 'verifyAccess', 'no user object found on the request', 'Unauthorized', null); deferred.reject(errorObj); deferred.promise.nodeify(callback); return deferred.promise; } if (userObj.is_locked) { const errorObj = new ErrorObj_1.ErrorObj(403, 'ac0009', __filename, 'verifyAccess', 'bsuser is locked', 'Unauthorized', null); deferred.reject(errorObj); deferred.promise.nodeify(callback); return deferred.promise; } let accessGranted = false; if (userObj.hasOwnProperty('security_roles')) { userRolesLoop: for (let roleIdx = 0; roleIdx < userObj.security_roles.length; roleIdx++) { const userRole = userObj.security_roles[roleIdx]; allRolesLoop: for (let allRolesIdx = 0; allRolesIdx < this.securityObj.security_roles.length; allRolesIdx++) { const securityRole = this.securityObj.security_roles[allRolesIdx]; if (userRole === securityRole.name) { areasLoop: for (let areaIdx = 0; areaIdx < securityRole.areas.length; areaIdx++) { const area = securityRole.areas[areaIdx]; if (area.name === serviceCall.area) { if (area.permission === this.permissions.all) { accessGranted = true; break userRolesLoop; } else if (area.permission == this.permissions.some) { routeLoop: for (let routeIdx = 0; routeIdx < area.validRoutes.length; routeIdx++) { const route = area.validRoutes[routeIdx]; if (route.controller === serviceCall.controller && route.version === serviceCall.version) { if (route.permission === this.permissions.all) { accessGranted = true; break userRolesLoop; } else if (route.permission === this.permissions.some) { methodLoop: for (let methodIdx = 0; methodIdx < route.methods.length; methodIdx++) { const method = route.methods[methodIdx]; if (method.verb === serviceCall.verb && method.call === serviceCall.call) { accessGranted = true; break userRolesLoop; } } } } } } } } } } } } else { // FAILURE } if (accessGranted) { deferred.resolve(true); } else { const errorObj = new ErrorObj_1.ErrorObj(403, 'ac0007', __filename, 'verifyAccess', 'not authorized to use this endpoint'); deferred.reject(errorObj); } deferred.promise.nodeify(callback); return deferred.promise; } ; /** * Verify the specified security role exists * * @param {string} roleName name of security role to check * @param callback callback function * @returns {boolean}: true if security role exists, else false */ roleExists(roleName, callback) { const deferred = Q.defer(); roleName = roleName.toLowerCase(); const allRoles = []; for (let rIdx = 0; rIdx < this.securityObj.security_roles.length; rIdx++) { allRoles.push(this.securityObj.security_roles[rIdx].name.toLowerCase()); } if (allRoles.indexOf(roleName) !== -1) { deferred.resolve(true); } else { deferred.resolve(false); } deferred.promise.nodeify(callback); return deferred.promise; } ; } exports.AccessControl = AccessControl; //# sourceMappingURL=accessControl.js.map