Access Query Preset
Once you have defined possible options for subject and action you might realize that certain combinations of them should not be possible.
For example you don't have action truncate
on subject article
.
Therefore policies needs to be aware of that and filter out unsupported actions for certain subjects.
That is why in tutorial we've explicitly defined set of possible actions and filtered out unsupported ones:
import {Action} from './AccessQueryElements';
const allowedActionsForArticle = new Set<Action>([
'update',
'read',
'read-draft',
'create',
'publish',
'delete'
]);
accessControl
.registerPolicy(({action, subject, principal}) => {
if (principal === 'logged-in') {
// This is annooooyiiiing 😫😫😫
return allowedActionsForArticle.has(action) && subject === 'article';
}
});
It is cumbersome and requires extra maintenance. There is better solution though.
Access Query Preset
Access Query Preset is available as separate package @pallad/access-control-query-preset
.
- npm
- Yarn
- pnpm
npm install @pallad/access-control-query-preset
yarn add @pallad/access-control-query-preset
pnpm add @pallad/access-control-query-preset
What is it​
Access Query Preset allows to define combination of action
and subject
.
import {accessQueryPresetFactory} from "@pallad/access-control-query-preset";
const accessQueryPreset = accessQueryPresetFactory('article', create => ({
// can principal read given article?
canRead: create('read', Subject.Article),
// can principal create article withing given workspace?
canCreate: create('create', Subject.Workspace)
}));
Now you can create access query from it
const accessQuery = accessQueryPreset.canRead(
Subject.Article(1, 1),
principal
);
accessControl.isAllowed(accessQuery)
Since preset aggregates all possible combinations of actions and subjects it can wrap your policy function so it is never called for combinations that are not possible.
accessControl.registerPolicy(
accessQueryPreset.createPolicy(() => {
console.log('policy called');
})
)
// your policy will not be called since access query preset filtered out that access query for you
accessControl.isAllowed({action: 'random-one', subject: {}, principal: {}})
// `policy called`
accessControl.isAllowed(
accessQueryPreset.canRead(
Subject.Article(1, 1),
principal
)
);
Also maintains type safety!
accessQueryPreset.createPolicy(({action, subject}) => {
if (action === 'create') {
// at this stage typescript knows that subject is `Subject.Workspace`
} else {
// at this stage typescript knows that subject is `Subject.Article`
}
})
Maintaining uniqueness​
When your system gets more access query presets defined you might noticed that certain combinations of action
and subject
are not unique.
const articleQueryPreset = accessQueryPresetFactory('article', c => ({
canCreate: c('create', Subject.Workspace)
}));
const articleCategoryQueryPreset = accessQueryPresetFactory('article-category', c => ({
// exactly same combination as above
canCreate: c('create', Subject.Workspace)
}));
This might suggest that policy created from articleQueryPreset
might be called even we create access query from articleCategoryQueryPreset
.
accessControl
.registerPolicy(
articleQueryPreset.createPolicy(() => {
console.log('Article Query Preset Policy called');
})
)
.registerPolicy(
articleCategoryQueryPreset.createPolicy(() => {
console.log('Article Category Query Preset Policy called');
})
)
That is not the case actually
accessControl.isAllowed(articleQueryPreset.canCreate(Subject.Workspace(1), principal));
// 'Article Query Preset Policy called'
// note that second policy was not called
This is because AccessQueryPreset
do extra wrapping of a subject
with another object with subjectType
property that allows to identify all queries from Access Query Preset.
That being said AccessQueryPreset
should