# RBAC ## What is RBAC? RBAC stands for role-based access control (sometimes referenced as role-based security) is an approach to restricting system access to authorized users. It is an approach to implement mandatory access control or discretionary access control. ## How is RBAC implemented? RBAC consist of 2 major parts: * rules * rules handler (aka CanUser) ### Rules So far we are enforcing only 2 roles: admin and moderator. The number of roles and the privilages associated with them might vary depending on the complexity of the project or it's goals. Simply add new object giving it a role name and add as many rules as you wish this role is authorized to do. ```json= const rules = { admin: [ 'basicMessages:create', 'basicMessages:read', 'contacts:create', 'contacts:read', 'contacts:update', 'contacts:delete', 'credentials:issue', 'credentials:read', 'credentials:reissue', 'credentials:revoke', 'demographics:create', 'demographics:read', 'demographics:update', 'demographics:delete', 'invitations:create', 'invitations:delete', 'roles:read', 'settings:read', 'settings:update', 'users:create', 'users:read', 'users:update', 'users:delete', 'users:updatePassword', 'users:updateRoles', ], moderator: [ 'basicMessages:create', 'basicMessages:read', 'contacts:create', 'contacts:read', 'contacts:update', 'credentials:issue', 'credentials:read', 'credentials:reissue', 'credentials:revoke', 'demographics:create', 'demographics:read', 'demographics:update', 'invitations:create', ], } ``` Note in case the user shares multiple roles duplication of the RBAC rules will be handled and only unique roles will be applied. ```javascript // Combine roles, ignore duplicate roles for (let i = 0; i < Object.keys(roles).length; i++) { permissions = permissions.concat( rules[roles[i]].filter((item) => permissions.indexOf(item) < 0) ) } ``` ### Rules handler The rules handler requires 3 main pieces to work: * rules * user * actions **Rules** are described in the section above and are imported as a file. **User** is set up on the login and may consist of different information. But what user must have is the roles property. Roles object of the user is an array of roles set up by the admin during the [user creation](http://link.here). ```json= {"id":3478,"username":"Vic","roles":["admin"]} ``` **Actions** is the list of privileges that the specific function, operation or component must meet to be able to function/render. Formated as a string list. ```javascript= "contacts:create, contacts:create" ``` ### RBAC handler (aka CanUser) for the front-end ```javascript= import rules from './rbac-rules' export const check = (rules, user, actions) => { // Get user roles if (!user) return false let roles = user.roles // Handle multiple roles by casting roles into array roles = roles instanceof Array ? roles : [roles] let permissions = [] // Combine roles, ignore duplicate roles for (let i = 0; i < Object.keys(roles).length; i++) { permissions = permissions.concat( rules[roles[i]].filter((item) => permissions.indexOf(item) < 0) ) } if (!permissions) { return false } // Determine all the permissions required by the component if (permissions) { const actionsList = actions.split(', ') // Check if the user has all required permissions for (const action in actionsList) { if (permissions.includes(actionsList[action])) { return true } } } return false } // Descision maker export const CanUser = (props) => check(rules, props.user, props.perform, props.data) ? props.yes() : props.no() CanUser.defaultProps = { yes: () => null, no: () => null, } ``` ### RBAC handler (aka CanUser) for the back-end ```javascript= const check = (rules, user, actions) => { // Get user roles let roles = user.roles if (!user) return false // Handle multiple roles by casting roles into array roles = roles instanceof Array ? roles : [roles] let permissions = [] // Combine roles, discard duplicate roles for (let i = 0; i < Object.keys(roles).length; i++) { permissions = permissions.concat( rules[roles[i]].filter((item) => permissions.indexOf(item) < 0), ) } if (!permissions) { return false } // Assign all permissions required by the component if (permissions) { const actionsList = actions.split(', ') // Check if the user has all required permissions for (const action in actionsList) { if (permissions.includes(actionsList[action])) { return true } } } return false } module.exports = check ``` ## Implementation and examples Since it is potentially possible to compromise the front-end and go around the checks, RBAC is enforsed on both the back- and front-ends of our agents for extra security. The back-end RBAC is enforsed for all the websocket calls only. ### Front-end examples Allow ContentFlexBox to render if the user has "contacts:create" permissions ```javascript= <CanUser user={localUser} perform="contacts:create" yes={() => ( <ContentFlexBox onClick={addContact} > Add Contact </ContentFlexBox> )} /> ``` If the privilages fail, don't render any content. ```javascript= <CanUser user={localUser} perform="settings:read" yes={() => ( <> <Item className={pathMatch === '/settings' ? 'active' : undefined} > <StyledLink to="/settings">Settings</StyledLink> </Item> </> )} no={() => ''} /> ``` Enforce the RBAC on the websocket messages (App.js) ```javascript= import { check } from './UI/CanUser' ... if ( check(rules, loggedInUserState, 'contacts:read', 'demographics:read') ) { sendMessage('CONTACTS', 'GET_ALL', { additional_tables: ['Demographic', 'Passport'], }) addLoadingProcess('CONTACTS') } ``` ### Back-end examples Enforce the RBAC on the websocket messages (websockets.js) ```javascript= // All you need for CanUser to work const check = require('./canUser') const rules = require('./rbac-rules') const cookie = require('cookie') const cookieParser = require('cookie-parser') let userCookieParsed = undefined ... wss.on('connection', (ws, req) => { console.log('New Websocket Connection') // Getting the user data from the cookie try { const cookies = cookie.parse(req.headers.cookie) const userCookie = cookieParser.signedCookie(cookies['user']) userCookieParsed = JSON.parse(userCookie.substring(2)) } catch (error) { } ... switch (context) { case 'USERS': switch (type) { case 'GET_ALL': if (check(rules, userCookieParsed, 'users:read')) { const users = await Users.getAll() sendMessage(ws, 'USERS', 'USERS', { users }) } else { sendMessage(ws, 'USERS', 'USER_ERROR', { error: 'ERROR: You are not authorized to fetch users.', }) } break ... ```