Access Management
Tutorials
Filter Data by User Roles

Filtering Data by User Roles

User roles will determine what kind of access each user has to data in a system. For example, different users with different roles will have different data access to /video routes. A Channel Owner might have access to all videos available and a Content Creator may have access only to videos that they have created.

ROQ provides access management works based on a query plan which ROQ computes based on the current user's permission and the database tables. For more information about how it works, please read this documentation section.

User Query Plans

Backend

We can get the user query plans using queryPlans() (opens in a new tab) API. For example to get the query plans for all users:

const queryPlans = await roqClient.asSuperAdmin().queryPlans()

The queryPlans will return such data:

 {
  queryPlans: [
	{
      kind: 'noAccess',
      queryPlan: null,
      role: 'marketing',
      entity: 'review',
      userIdField: 'userId',
      operation: 'delete'
    },
    {
      kind: 'fullAccess',
      queryPlan: null,
      role: 'channel-owner',
      entity: 'comment',
      userIdField: 'roq_user_id',
      operation: 'read'
    },
	{
	  kind: 'restricted',
	  queryPlan: {
		from: 'video',
		to: 'user',
		case: 'many-to-one',
		fromField: 'user_id',
		toField: 'id',
		in: { from: 'user', fromField: 'roq_user_id', userId: [Array] }
	  },
	  role: 'content-creator',
	  entity: 'video',
	  userIdField: 'roq_user_id',
	  operation: 'update'
	}
	...
   ]
}

In local development, this method is internally used by the generated application to get the query plans and then cached them to the user disk, and then ROQ uses the hasAccess() method to retrieve these query plans relevant to the user's roles, the entity, and the operation they are attempting. Then it evaluates these plans to decide if the user has the necessary access.

The code for this example is in the src/server/middlewares folder in the generated application.

For example, to get the video list from route https://localhost:3000/videos with the current user role as Channel Owner.

const { allowed } = await authorizationClient.hasAccess(
	convertRouteToEntityUtil(mainPath.split('/').pop()),
    {
    	roqUserId,
        roles: user.roles,
        tenantId: user.tenantId,
    },
    convertMethodToOperation(req.method as HttpMethod),
);

The code above will automatically check if the user has access to read operations within the current roles.

The convertRouteToEntityUtil() will get the last segment of the URL path into the corresponding entity name, which is the video (the videos mapping) and the convertMethodToOperation(req.method as HttpMethod) will convert req.method into read or GET HTTP method.

With the user data:

{ 
	roqUserId: "80c81294-f338-4819-9b1f-acc314babd5f",
	roles: "channel-owner",
	tenantId: "ee764883-f2c3-4486-9de6-070345e350af"
}

The hasAccess() method will check according to the query plan if the user has access to read the video list.

const { allowed } = await authorizationClient.hasAccess(
	"video",
    { 
		roqUserId: "80c81294-f338-4819-9b1f-acc314babd5f",
		roles: "channel-owner",
		tenantId: "ee764883-f2c3-4486-9de6-070345e350af"
	},
    ResourceOperationEnum.Read
);

If allowed return true which means the user is allowed to have access to the video list on http://localhost:3000/videos.

hasAccess() is a utility method from the @roq/prismajs package that uses the query plan to determine whether a user has access to a particular entity.

Client Side

ROQs also expose the hasAccess() method to the client side internally as a hook to the original method on the backend. This method resides in the @roq/nextjs package.

On the client, it has the form:

hasAccess(entity: string, operation: AccessOperationEnum, service?: AccessServiceEnum): boolean;

For example to check if the current user role has access to create a video with access route /video/create:

import { AccessOperationEnum, useAuthorizationApi } from "@roq/nextjs"
 
function VideoListPage() {
    const { hasAccess } = useAuthorizationApi()
	return (
		{hasAccess("video", AccessOperationEnum.CREATE, AccessServiceEnum.PROJECT) && (
			<NextLink href={`/videos/create`} passHref legacyBehavior>
				<Button onClick={(e) => e.stopPropagation()} colorScheme="blue" mr="4" as="a">
					Create
				</Button>
			</NextLink>
		)}   
	)
}