@microtica/auth
Version:
Authentication and Authorization library
333 lines • 14.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@microtica/utils");
const http_status_codes_1 = __importDefault(require("http-status-codes"));
const _ = __importStar(require("lodash"));
const entity_permission_1 = require("./entity-permission");
const UNAUTHORIZED_ERROR = "User is not authorized to perform this action";
var AccessPermission;
(function (AccessPermission) {
AccessPermission["GrantAccess"] = "GRANT_ACCESS";
AccessPermission["RevokeAccess"] = "REVOKE_ACCESS";
})(AccessPermission = exports.AccessPermission || (exports.AccessPermission = {}));
class AuthManager {
constructor(auth) {
this.auth = auth;
}
static serializePermissions(permissions) {
return permissions.join(",");
}
static deserializePermissions(permissions) {
return permissions.split(",");
}
/**
* Returns all the needed Authorization Headers
*
* @param headers The Header object of the request
* @returns Returns an object with all the AuthHeaders extracted from the Request.Headers
*/
static getAuthHeaders(headers) {
const authHeaders = {
groupIds: headers.groupids,
userId: headers.userid,
permissions: headers.permissions,
projectId: headers.projectid
};
return authHeaders;
}
/**
* Grants certain permissions to the given user or group on the entity provided.
*
* @param assigneeId The assigneeId you want to grant access to
* @param assigneeType The assigneeType of the assignee
* @param entityId The Entity you want to grant the access on
* @param permissions The permissions you want to be granted
* @returns Returns an object with a done boolean parameter
*/
async grantAccess(assigneeId, assigneeType, entityId, permissions) {
const existingPermissions = await entity_permission_1.EntityPermission.where({ assigneeId, entityId }).get();
if (existingPermissions) {
permissions = _.uniq(_.concat(existingPermissions.permissions, permissions));
return entity_permission_1.EntityPermission
.update({ permissions: AuthManager.serializePermissions(permissions) })
.where({ assigneeId, entityId })
.exec()
.thenReturn({ done: true });
}
return entity_permission_1.EntityPermission.insert({
assigneeId,
assigneeType,
entityId,
permissions: AuthManager.serializePermissions(permissions)
}).exec().thenReturn({ done: true });
}
/**
* Removes all access from the given assignee, on the given entity
*
* @param assigneeId The Assignee you want to revoke all access from
* @param entityId The entity you want that access revoked
* @returns Returns an object with a done boolean parameter
*/
async revokeAccess(assigneeId, entityId) {
return entity_permission_1.EntityPermission
.delete()
.where(entity_permission_1.EntityPermission.assigneeId.equals(assigneeId)
.and(entity_permission_1.EntityPermission.entityId.equals(entityId)))
.exec()
.thenReturn({ done: true });
}
/**
* Sets an Access List to the given entityId
*
* @param entityId The entity you want to set the access list to
* @param accessList The access list you want to set
* @returns Returns an object with a done boolean parameter
*/
async forceInheritAccess(entityId, accessList) {
const entityPermissions = _.map(accessList, al => ({
assigneeId: al.assigneeId,
assigneeType: al.assigneeType,
entityId,
permissions: AuthManager.serializePermissions(al.permissions)
}));
return entity_permission_1.EntityPermission.insert(entityPermissions)
.exec()
.thenReturn({ done: true });
}
/**
* Returns the Access List for the given Entity
*
* @param entityId The entity, which you want the access list for
* @returns Returns an array with data of the EntityPermission Table
*/
async getAccessList(entityId) {
const entityPermissions = await entity_permission_1.EntityPermission.where({ entityId }).all();
return _.map(entityPermissions, item => _.extend(item, {
permissions: AuthManager.deserializePermissions(item.permissions)
}));
}
/**
* Returns the Access List for multiple entities
*
* @param entityIds The entities, which you want the access list for
* @returns Returns a dictionary of arrays with data of the EntityPermission Table
*/
async getAccessListForMultipleEntities(entityIds) {
const entityPermissions = await entity_permission_1.EntityPermission.where(entity_permission_1.EntityPermission.entityId.in(entityIds)).all();
return entityPermissions.reduce((entities, entity) => {
if (!entities[entity.entityId]) {
entities[entity.entityId] = [];
}
entities[entity.entityId].push(_.extend(entity, {
permissions: AuthManager.deserializePermissions(entity.permissions)
}));
return entities;
}, {});
}
/**
* Returns an Access List for the given Assignee Type and Entity
*
* @param entityId The entity, which you want the access list for
* @param filterAssigneeType The assignee type you want from the access list
* @returns Returns an array of the Access Lists on the entityId
*/
async getAccessListByType(entityId, filterAssigneeType) {
const accessList = await this.getAccessList(entityId);
const filteredList = filterAssigneeType ?
_.filter(accessList, { assigneeType: filterAssigneeType }) :
accessList;
return _.map(filteredList, item => _.pick(item, ["assigneeId", "assigneeType", "permissions"]));
}
/**
* Returns a query of the entity table with the entities/data with sufficient access
* if given a parentId, returns a query of the entities/data the parent has access to
* if not, returns a query of the entities/data the User has access to
*
* @param entity The EntityTable
* @param parentId The Parent ID to check access against
* @returns A query with the EntityTable joined with EntityPermission Table
*/
listEntities(entity, parentId) {
const id = this.auth ? this.auth.id : undefined;
const groups = this.auth ? this.auth.groups : undefined;
let conditionNode;
if (parentId) {
conditionNode = entity_permission_1.EntityPermission.assigneeId.equals(parentId);
}
else {
conditionNode = !groups ?
entity_permission_1.EntityPermission.assigneeId.equals(id) :
entity_permission_1.EntityPermission.assigneeId.equals(id).or(entity_permission_1.EntityPermission.assigneeId.in(groups));
}
return entity
.select(entity.star())
.from(entity_permission_1.EntityPermission.join(entity).on(entity.id.equals(entity_permission_1.EntityPermission.entityId)))
.where(conditionNode).group(entity.id);
}
}
exports.AuthManager = AuthManager;
/**
* Returns the Authorization Function
*
* @param requiredPermissions The Required Permissions for the User to pass
* @returns Returns the authorize function
*/
function authorize(...requiredPermissions) {
/**
* Main Function which handles the Authorization part
* It dictates wether the User has sufficient permissions to pass
*
* @param req The Request object
* @param res The Response object
* @param next The NextFunction
* @returns Calls the NextFunction or returns a 401, Not authorized response
*/
return (req, res, next) => {
const userId = req.get("userId");
const authHeader = req.get("Authorization");
const permissionsHeader = req.get("permissions");
const groupIdsHeader = req.get("groupIds");
if (!authHeader || !userId) {
return res.status(401).send(utils_1.codedError(http_status_codes_1.default.UNAUTHORIZED, UNAUTHORIZED_ERROR));
}
const groupIds = groupIdsHeader ? groupIdsHeader.split(",") : [];
req.auth = {
id: userId,
type: "token",
plain: authHeader,
roles: [],
groups: groupIds
};
if (requiredPermissions.length > 0) {
const globalPermissions = permissionsHeader ? permissionsHeader.split(",") : [];
return shouldPass(requiredPermissions, globalPermissions) ?
next() :
res.status(401).send(utils_1.codedError(http_status_codes_1.default.UNAUTHORIZED, UNAUTHORIZED_ERROR));
}
else {
return next();
}
};
}
exports.authorize = authorize;
/**
* Returns the Authorization Function
*
* @param entityParam The Entity to authorize against
* @param requiredPermissions The Required Permissions for the User to pass
* @returns Returns the authorize function
*/
function authorizeStrict(entityParam, requiredPermissions) {
/**
* Main Function which handles the Authorization part
* It dictates wether the User has sufficient permissions to pass
*
* @param req The Request object
* @param res The Response object
* @param next The NextFunction
* @returns Calls the NextFunction or returns a 401, Not authorized response
*/
return (req, res, next) => {
const userId = req.get("userId");
const authHeader = req.get("Authorization");
const permissionsHeader = req.get("permissions");
const groupIdsHeader = req.get("groupIds");
const projectIdHeader = req.get("projectId");
if (!authHeader || !userId || !projectIdHeader) {
return res.status(401).send(utils_1.codedError(http_status_codes_1.default.UNAUTHORIZED, UNAUTHORIZED_ERROR));
}
const groupIds = groupIdsHeader ? groupIdsHeader.split(",") : [];
req.auth = {
id: userId,
type: "token",
plain: authHeader,
roles: [],
groups: groupIds
};
if (requiredPermissions.length > 0) {
const globalPermissions = permissionsHeader ? permissionsHeader.split(",") : [];
const entityId = req.params[entityParam];
const projectId = projectIdHeader;
getUserAndGroupPermissions(entityId, userId, groupIds).then(userData => {
getParentPermissions(entityId, projectId).then(parentData => {
const parentPermissions = _.flatten(_.map(parentData, data => data.permissions.split(",")));
const userPermissions = _.uniq(_.flatten(_.map(userData, data => data.permissions.split(","))));
return checkAccess(requiredPermissions, userPermissions, parentPermissions, globalPermissions) ?
next() :
res.status(401).send(utils_1.codedError(http_status_codes_1.default.UNAUTHORIZED, UNAUTHORIZED_ERROR));
});
});
}
else {
return next();
}
};
}
exports.authorizeStrict = authorizeStrict;
/**
* Returns the parent permissions on the given entity
*
* @param entityId The entity you want permissions for
* @param parentId The parent you want permissions for
* @returns Returns an array of the EntityPermission Table
*/
async function getParentPermissions(entityId, parentId) {
return entity_permission_1.EntityPermission.where(entity_permission_1.EntityPermission.entityId.equals(entityId)
.and(entity_permission_1.EntityPermission.assigneeId.equals(parentId))).all();
}
/**
* Returns all the permissions for the given user and his groups on the given entity
*
* @param entityId The Entity you want permissions for
* @param userId The User ID to get permissions for
* @param groupIds The Group to get permissions for
* @returns Returns an array of the EntityPermission Table
*/
async function getUserAndGroupPermissions(entityId, userId, groupIds) {
return entity_permission_1.EntityPermission.where(entity_permission_1.EntityPermission.entityId.equals(entityId)
.and(entity_permission_1.EntityPermission.assigneeId.equals(userId)
.or(entity_permission_1.EntityPermission.assigneeId.in(groupIds)))).all();
}
/**
* Checks if the permissions are sufficent
*
* @param requiredPermissions The Required Permissions needed to pass
* @param permissions The Permissions the assignee has
* @returns Returns a boolean
*/
function shouldPass(requiredPermissions, permissions) {
const overLappedPermissions = _.intersection(requiredPermissions, permissions);
return overLappedPermissions.length === requiredPermissions.length || _.includes(permissions, "OWNER");
}
/**
* Logic for authorization managment
* Checks if the user has specific permissions on the given entity,
* then checks if they are sufficient
* if there are no specific permissions,
* it checks the parent permissions and the global permissions if they are sufficient
*
* @param requiredPermissions The Required Permissions needed to pass
* @param userPermissions The Permissions the User has
* @param parentPermissions The Permissions the Parent has
* @param globalPermissions The Global Permissions the User has on the Parent
* @returns Returns a boolean
*/
function checkAccess(requiredPermissions, userPermissions, parentPermissions, globalPermissions) {
if (userPermissions.length !== 0) {
return shouldPass(requiredPermissions, userPermissions);
}
else {
return shouldPass(requiredPermissions, parentPermissions) && shouldPass(requiredPermissions, globalPermissions);
}
}
//# sourceMappingURL=auth.js.map