--- title: Access Control Tutorial Source Code nextjs: metadata: title: Access Control Tutorial Source Code description: Complete source code walkthrough for the TerminusDB JavaScript access control tutorial. keywords: access control, RBAC, JavaScript, tutorial, source code openGraph: images: https://assets.terminusdb.com/docs/technical-documentation-terminuscms-og.png alternates: canonical: https://terminusdb.org/docs/access-control-tutorial-source/ media: [] --- This page provides a detailed walkthrough of the access control tutorial code. The code uses the `AccessControl` class in the TerminusDB JavaScript Client library to manage user access control for Organizations and databases. ## Setup and Initialization Import the TerminusDB client and set up the configuration: ```javascript /* Import terminusdb */ const TerminusClient = require("terminusdb") /* Import the list of allowed actions */ const { ACTIONS } = TerminusClient.UTILS /* We are using TerminusDB for Authorization so we'll create all the Users with a NO_KEY password */ const NO_KEY = "NO_KEY" /* TerminusDB server host url */ const serverHost = "http://127.0.0.1:6363" ``` Initialize the TerminusDB Client with the admin's credentials. Only the global admin can create Teams/Organizations and Users: ```javascript const client = new TerminusClient.WOQLClient(serverHost, {user:"admin", key:"root"}) const accessControl = new TerminusClient.AccessControl(serverHost, {user:"admin", key:"root"}) ``` ## Define Custom Roles Define the role names and their associated actions: ```javascript /* Role names */ const customReaderRole = "reader" const customWriterRole = "writer" const customAppAdmin = "appAdmin" const customSchemaWriter = "schema_writer" const appAdminActions = [ ACTIONS.CREATE_DATABASE, ACTIONS.DELETE_DATABASE, ACTIONS.SCHEMA_READ_ACCESS, ACTIONS.SCHEMA_WRITE_ACCESS, ACTIONS.INSTANCE_READ_ACCESS, ACTIONS.INSTANCE_WRITE_ACCESS, ACTIONS.COMMIT_READ_ACCESS, ACTIONS.COMMIT_WRITE_ACCESS, ACTIONS.META_READ_ACCESS, ACTIONS.META_WRITE_ACCESS, ACTIONS.CLASS_FRAME, ACTIONS.BRANCH, ACTIONS.CLONE, ACTIONS.FETCH, ACTIONS.PUSH, ACTIONS.REBASE ] const readerActions = [ ACTIONS.COMMIT_WRITE_ACCESS, ACTIONS.SCHEMA_READ_ACCESS, ACTIONS.INSTANCE_READ_ACCESS, ACTIONS.CLASS_FRAME ] const writerActions = [ ACTIONS.COMMIT_WRITE_ACCESS, ACTIONS.SCHEMA_READ_ACCESS, ACTIONS.INSTANCE_WRITE_ACCESS, ACTIONS.CLASS_FRAME ] const schemaWriterActions = [ACTIONS.SCHEMA_WRITE_ACCESS] ``` ## Create Custom Roles Roles are sets of actions (permissions) that you can use to grant or restrict access to specific resources and operations. Only the global admin can create Roles: ```javascript async function createCustomRoles() { try { // Create the appAdmin role const roleAppAdminId = await accessControl.createRole(customAppAdmin, appAdminActions) console.log(`The role ${roleAppAdminId} has been created`) // Create the reader role const roleReaderId = await accessControl.createRole(customReaderRole, readerActions) console.log(`The role ${roleReaderId} has been created`) // Create the writer role const roleWriterId = await accessControl.createRole(customWriterRole, writerActions) console.log(`The role ${roleWriterId} has been created`) // Create the schema_writer role const roleSchemaWriterId = await accessControl.createRole(customSchemaWriter, schemaWriterActions) console.log(`The role ${roleSchemaWriterId} has been created`) } catch(err) { const errorType = err.data && err.data["api:error"] ? err.data["api:error"]["@type"] : null if (errorType !== 'api:DocumentIdAlreadyExists') { throw err } console.log(`The Document already exists`) } } ``` ## Define Users, Teams, and Databases The dataproducts (or databases) are created under a Team (or Organization). By linking a User to a Team (capability), the User has access to all the dataproducts under this Team: ```javascript const user__01 = "User__01" const user__02 = "User__02" const user__03 = "User__03" const team__01 = "Team__01" const team__02 = "Team__02" const team__03 = "Team__03" const db__01 = "dataproduct__01" const db__02 = "dataproduct__02" ``` ## Create Teams Using the admin user, we create three different teams. At this moment no user is connected with these teams: ```javascript async function createTeams() { const team_id01 = await accessControl.createOrganization(team__01) console.log("team__01 has been created", team_id01) const team_id02 = await accessControl.createOrganization(team__02) console.log("team__02 has been created", team_id02) const team_id03 = await accessControl.createOrganization(team__03) console.log("team__03 has been created", team_id03) } ``` ## Create Users We create users with the same password `NO_KEY` since we are using TerminusDB for authorization: ```javascript async function createUsers() { const user_id01 = await accessControl.createUser(user__01, NO_KEY) console.log("user__01 has been created", user_id01) const user_id02 = await accessControl.createUser(user__02, NO_KEY) console.log("user__02 has been created", user_id02) const user_id03 = await accessControl.createUser(user__03, NO_KEY) console.log("user__03 has been created", user_id03) } ``` ## Connect Teams with Users The TerminusDB administrator (admin) can create capabilities that let users access specific resources. A capability is a connection between Roles and Resources (teams or dataproducts): ```javascript async function connectTeamsWithUserAppAdminRole() { /* User_01 is the appAdmin of team__01, team__03 and the databases controlled by the team */ const link01 = await accessControl.manageCapability( user__01, team__01, ["appAdmin"], "grant", "organization" ) console.log(`The user ${user__01} has been added to ${team__01} with appAdmin role`) const link03 = await accessControl.manageCapability( user__01, team__03, ["appAdmin"], "grant", "organization" ) console.log(`The user ${user__01} has been added to ${team__03} with appAdmin role`) /* User_02 is the appAdmin of team__02 and the databases controlled by the team */ const link02 = await accessControl.manageCapability( user__02, team__02, ["appAdmin"], "grant", "organization" ) console.log(`The user ${user__02} has been added to ${team__02} with appAdmin role`) } async function connectTeam01WithOtherUsers() { /* Connect User__02 with reader role to access team__01 resources */ const link04 = await accessControl.manageCapability( user__02, team__01, ["reader"], "grant", "organization" ) console.log(`The user ${user__02} has been added to ${team__01} with reader role`) /* Connect User__03 with reader and writer roles */ const link05 = await accessControl.manageCapability( user__03, team__01, ["reader", "writer"], "grant", "organization" ) console.log(`The user ${user__03} has been added to ${team__01} with reader and writer roles`) } ``` ## Create Databases Create a new TerminusDB client instance for User__01. The User can create a database under a team only if they have a Role that allows `create_database`: ```javascript async function createDB() { const clientTeam01 = new TerminusClient.WOQLClient(serverHost, { user: user__01, key: NO_KEY, organization: team__01 }) await clientTeam01.createDatabase(db__01, { label: db__01, comment: "add db", schema: true }) console.log(`The dataproduct ${db__01} has been created by ${user__01}`) await clientTeam01.createDatabase(db__02, { label: db__02, comment: "add db", schema: true }) console.log(`The dataproduct ${db__02} has been created by ${user__01}`) } ``` ## Test Permission Restrictions Test that User__02 does not have permission to create databases under team__01: ```javascript async function errorCreateDatabase() { try { const clientTeamUser02 = new TerminusClient.WOQLClient(serverHost, { user: user__02, key: NO_KEY, organization: team__01 }) const accessControlUser02 = new TerminusClient.AccessControl(serverHost, { user: user__02, key: NO_KEY, organization: team__01 }) /* We can see the list of actions allowed for the connected user */ const roles = await accessControlUser02.getTeamUserRoles(user__02) console.log("user_02 roles", JSON.stringify(roles, null, 4)) /* This user does not have the "create_database" level of access */ await clientTeamUser02.createDatabase("test_db", { label: "test_db", comment: "add db", schema: true }) } catch(err) { const message = err.data ? err.data["api:message"] : err.message console.log("user__02 does not have permission to create databases", message) } } ``` ## Add Roles at Database Level The user has no specific permissions at data product level, but each data product inherits the team access level. You can increase permissions for a User to access a specific dataproduct: ```javascript async function addCapabilityRolesForDataproduct() { /* For database resources, pass the team/dataproduct path */ const link06 = await accessControl.manageCapability( user__02, `${team__01}/${db__02}`, ["writer", "schema_writer"], "grant", "database" ) console.log(`The user ${user__02} has been added to ${team__01}/${db__02} with writer and schema_writer roles`) const accessControl01 = new TerminusClient.AccessControl(serverHost, { user: user__02, key: NO_KEY, organization: team__01 }) /* We can see the updated roles for user__02 */ const roles = await accessControl01.getTeamUserRoles(user__02) console.log("user_02 roles", JSON.stringify(roles, null, 4)) } ``` ## Cleanup: Delete Resources {% callout type="warning" title="Important Deletion Order" %} You must be careful when removing dataproducts, teams, and users. For removing a team, you have to first remove all the databases under that team and revoke all user capabilities. {% /callout %} ```javascript async function deleteDatabase(db) { try { const clientTeam01 = new TerminusClient.WOQLClient(serverHost, { user: user__01, key: NO_KEY, organization: team__01 }) await clientTeam01.deleteDatabase(db) console.log(`The dataproduct ${db} has been deleted`) } catch(err) { console.log(`The dataproduct ${db} doesn't exist`) } } async function deleteUsersAndTeamsIfExists() { try { /* Important: delete the databases before the User with appAdmin role. If no appAdmin user is related with the Organization you cannot remove the databases */ await deleteDatabase(db__01) await deleteDatabase(db__02) /* The organization cannot be removed as it is referred to by a capability. We have to revoke the capability to the organization before removing. The Users still exist, just not related with the Organization anymore */ await accessControl.manageCapability( user__02, team__01, ["reader"], "revoke", "organization" ) console.log(`${user__02} has been removed from ${team__01}`) await accessControl.manageCapability( user__03, team__01, ["reader", "writer"], "revoke", "organization" ) console.log(`${user__03} has been removed from ${team__01}`) await accessControl.manageCapability( user__01, team__01, ["appAdmin"], "revoke", "organization" ) console.log(`${user__01} has been removed from ${team__01}`) /* Now we can remove team__01 */ await accessControl.deleteOrganization(team__01) console.log(`${team__01} has been deleted`) await accessControl.manageCapability( user__02, team__02, ["appAdmin"], "revoke", "organization" ) console.log(`${user__02} has been removed from ${team__02}`) await accessControl.manageCapability( user__01, team__03, ["appAdmin"], "revoke", "organization" ) console.log(`${user__01} has been removed from ${team__03}`) await accessControl.deleteOrganization(team__02) console.log(`${team__02} has been deleted`) await accessControl.deleteOrganization(team__03) console.log(`${team__03} has been deleted`) await accessControl.deleteUser(user__01) console.log(`${user__01} has been deleted`) await accessControl.deleteUser(user__02) console.log(`${user__02} has been deleted`) await accessControl.deleteUser(user__03) console.log(`${user__03} has been deleted`) } catch(err) { console.log(err.message) } } async function removeRoles() { await accessControl.deleteRole(customAppAdmin) console.log(`The ${customAppAdmin} role has been deleted`) await accessControl.deleteRole(customWriterRole) console.log(`The ${customWriterRole} role has been deleted`) await accessControl.deleteRole(customReaderRole) console.log(`The ${customReaderRole} role has been deleted`) await accessControl.deleteRole(customSchemaWriter) console.log(`The ${customSchemaWriter} role has been deleted`) } ``` ## Run the Tutorial The main function orchestrates all the steps: ```javascript async function run() { try { /* Create custom roles */ await createCustomRoles() console.log("The Roles have been created...") /* Create all the teams */ await createTeams() console.log("The Teams have been created...") /* Create all the users */ await createUsers() console.log("The Users have been created...") /* Connect users with teams assigning specific roles */ await connectTeamsWithUserAppAdminRole() await connectTeam01WithOtherUsers() /* Create dataproducts */ await createDB() console.log("The dataproducts have been created...") /* Check permission - demonstrates no permission for user_02 */ await errorCreateDatabase() console.log("Demonstrated: no permission for user_02 to create database...") /* Update permission for dataproduct The User has a role access level for the team and all databases under it. The admin can assign a different role for a specific database. The role at database level works only if it is higher than team access level */ await addCapabilityRolesForDataproduct() console.log("The permission for a dataproduct has been granted...") /* Cleanup */ await deleteUsersAndTeamsIfExists() console.log("Dataproducts, teams and users have been deleted...") await removeRoles() console.log("Roles have been deleted...") } catch(err) { const data = err.data || {} console.log(err.message) if (data.message) console.log(data.message) } } run() ``` ## Next Steps - Learn more about [WOQL queries](/docs/woql-explanation/) for data operations - Explore the [JavaScript Client Reference](/docs/javascript/) for all available methods - Check out the [Schema Reference Guide](/docs/schema-reference-guide/) for designing your data models