fully-api
Version:
API framework for Fully Stacked, LLC REST-ful APIs
412 lines • 18.4 kB
JavaScript
"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