Skip to main content

How To Call a Policy Check from Code

This page will run through how you can directly contact CERBOS to find out if:

  • A given user (Darth Vader)...
  • Can perform a given action (access, delete, etc)...
  • On a given resource (Death Star Plans).

We assume the following:

  • The app is named death-star and sits under the apps/death-star

1. Create a New Policy If Required

See How to Create a Cerbos Policy.

2. Create a Representation of Your Policies in Code

./apps/death-star/src/infrastructure/Cerbos/Resources.ts
import { Resource } from '@tsm/nest/cerbos';

export class DeathStarPlans implements Resource {
public readonly kind = 'death-star-plans';
public actions: ['view', 'create', 'destroy'];
public attributes: {
locationOfDeathStarWeakSpot: string;
};
}

2. Apply the Policy to Your Commands and Queries

In your application, you can now call the Cerbos API to check if a user can perform an action on a resource. Note the user is set by the ingress guards based on the access token in the request. For worker processes, the principal will be the service account.

./apps/death-star/src/application/Command/CreateDeathStarPlans.ts
import { ApplyPolicy } from '@tsm/nest/cerbos';
import { DeathStarPlans } from '../../infrastructure/Cerbos/Resources';

@ApplyPolicy(DeathStarPlans, 'create', (command: CreateDeathStarPlans) => ({
id: ulid(), // the id is unknown as it hasn't been created yet
attributes: {
locationOfDeathStarWeakSpot: command.locationOfDeathStarWeakSpot,
},
}))
export class CreateDeathStarPlans extends ICommand {
constructor(private readonly locationOfDeathStarWeakSpot: string) {}
}
./apps/death-star/src/application/Command/UpdateDeathStarPlans.ts
import { ApplyPolicy } from '@tsm/nest/cerbos';
import { DeathStarPlans } from '../../infrastructure/Cerbos/Resources';

@ApplyPolicy(DeathStarPlans, 'update', (command: UpdateDeathStarPlans) => ({
id: command.planId,
attributes: {
locationOfDeathStarWeakSpot: command.locationOfDeathStarWeakSpot,
},
}))
export class UpdateDeathStarPlans extends ICommand {
constructor(
private readonly planId: string,
private readonly locationOfDeathStarWeakSpot: string,
) {}
}

Querying for Data inside a Policies Check

Occasionally you will need extra data to make a decision. In this case, you can pass services into the mapping function by using the inject argument in the ApplyPolicy decorator:

./apps/death-star/src/application/Command/DestroyDeathStarPlans.ts
import { ApplyPolicy } from '@tsm/nest/cerbos';
import { DeathStarPlans } from '../../infrastructure/Cerbos/Resources';

@ApplyPolicy(
DeathStarPlans,
'destroy',
async (command: DestroyDeathStarPlans, repository: DeathStarPlansRepository) => {
const plan = await repository.find(command.planId);
return {
id: plan.id,
attributes: {
locationOfDeathStarWeakSpot: plan.locationOfDeathStarWeakSpot,
},
};
},
[DeathStarPlansRepository], // This will inject the DeathStarPlansRepository into the
// mapping function. You can map as many services as you
// need and they will be injected in the order they are
// listed. The services must be "provided" by a module.
)
export class DestroyDeathStarPlans extends ICommand {
constructor(private readonly planId: string) {}
}