UNPKG

@microtica/auth

Version:

Authentication and Authorization library

333 lines 14.5 kB
"use strict"; 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