Access Control Tutorial Source Code

Open inAnthropic

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:

Example: 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:

Example: 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:

Example: 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:

Example: 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:

Example: 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:

Example: 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:

Example: 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):

Example: 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:

Example: 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:

Example: 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:

Example: 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

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.

Example: 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:

Example: 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

Was this helpful?