Rules
Introduction
Authorization rules in Platformatic DB define what operations users can perform on the REST or GraphQL APIs.
Defining Rules
Basic Rule Structure
Every authorization rule must include the following:
role(required) — Specifies the user role name as a string, which must align with roles set by an external authentication service.entityorentities(optional) — Defines one or more Platformatic DB entities the rule applies to. At least one ofentityorentitiesmust be specified. Use'*'to apply the rule to all entities; rules are evaluated in order, so more specific rules for the same role must be defined before the wildcard one.defaults(optional) — Sets default values for entity fields from user metadata.
How Roles Are Matched
The roles of the requesting user are read from the user metadata under the key configured by authorization.roleKey (default X-PLATFORMATIC-ROLE, a comma-separated list) or from a nested property path configured with authorization.rolePath (e.g. user.roles). Unauthenticated requests get the single role configured by authorization.anonymousRole (default anonymous).
For every operation on an entity, the rules of that entity are evaluated in the order they are defined and, by default, the first rule whose role matches one of the user's roles wins (first-match). For example, with:
{
"authorization": {
"rules": [
{ "role": "admin", "entity": "page", "find": true, "save": true, "delete": true },
{ "role": "user", "entity": "page", "find": true, "save": true, "delete": false },
{ "role": "anonymous", "entity": "page", "find": true, "save": false, "delete": false }
]
}
}
- a user with roles
admin,usermatches the first rule and can delete pages; - a user with role
usermatches the second rule and cannot delete; - an unauthenticated request matches the
anonymousrule and can only read.
If no rule matches the user's roles, the operation is rejected.
With authorization.roleMergeStrategy set to most-permissive (default first-match), all the rules matching any of the user's roles are merged instead, and truthy permissions win over falsy ones.
Supported Operations
Each rule can specify permissions for CRUD operations (find, save, delete). Here's an example illustrating how these permissions are structured:
{
"role": "user",
"entity": "page",
"find": true,
"save": false,
"delete": {
"checks": {
"userId": "X-PLATFORMATIC-USER-ID"
}
}
}
This configuration allows users with the user role to find and delete pages where the userId matches their user ID, but they cannot save changes to pages.
Advanced Authorization Controls
Operation Checks
For more fine-grained control, use the checks field to define conditions under which operations can be executed. Every entity operation — such as find, insert, save or delete — can have authorization checks specified for them. This value can be false (operation disabled) or true (operation enabled with no checks).
{
"role": "user",
"entity": "page",
"find": {
"checks": {
"userId": "X-PLATFORMATIC-USER-ID"
}
}
}
Here a user with a user role executes a findPage operation and can access all the data for userId metadata with the value key X-PLATFORMATIC-USER-ID. It's possible to specify more complex rules using all the supported where clause operators.
Note that userId MUST exist as a field in the database table to use this feature.
GraphQL events and subscriptions
Platformatic DB supports GraphQL subscriptions, which require specific authorization checks based on find permissions. The only permissions that are supported are:
find: false, the subscription for that role is disabledfind: { checks: { [prop]: 'X-PLATFORMATIC-PROP' } }validates that the given prop is equalfind: { checks: { [prop]: { eq: 'X-PLATFORMATIC-PROP' } } }validates that the given prop is equal
Conflicting rules across roles for different equality checks will not be supported.
Restrict Access to Entity Fields
Platformatic DB allows the specification of fields arrays in authorization rules to limit the columns a user can interact with during database operations.
For save operations, it's important to include all not-nullable fields in the configuration to prevent runtime errors due to missing data. Platformatic performs these checks at startup to ensure configurations are correct.
{
"rule": {
"entity": "page",
"role": "user",
"find": {
"checks": {
"userId": "X-PLATFORMATIC-USER-ID"
},
"fields": ["id", "title"]
}
}
}
In this configuration, a user with the user role can only access the id and title fields of the page entity.
Set Entity Fields from User Metadata
Defaults are used in database insert and are default fields added automatically populated from user metadata
Example:
{
"defaults": {
"userId": "X-PLATFORMATIC-USER-ID"
}
}
When a new entity is created, the userId field is automatically populated with the value from the user's metadata.
Programmatic Rules
For advanced use cases, rules can be defined programmatically with JavaScript functions instead of declarative checks. The find, save and delete operations accept an async function receiving { user, ctx, where } which must return the where clause to apply, and defaults values can be async functions receiving { user, ctx, input }.
In a Platformatic DB application, register programmatic rules from a plugin using app.platformatic.addRulesForRoles, which appends rules to the ones defined in the configuration file:
/// <reference path="./global.d.ts" />
export default async function (app) {
app.platformatic.addRulesForRoles([
{
role: 'user',
entity: 'page',
async find ({ user, ctx, where }) {
return {
...where,
userId: {
eq: user['X-PLATFORMATIC-USER-ID']
}
}
},
async save ({ user, ctx, where }) {
return {
...where,
userId: {
eq: user['X-PLATFORMATIC-USER-ID']
}
}
},
async delete ({ user, ctx, where }) {
return {
...where,
userId: {
eq: user['X-PLATFORMATIC-USER-ID']
}
}
},
defaults: {
userId: async function ({ user, ctx, input }) {
return user['X-PLATFORMATIC-USER-ID']
}
}
}
])
}
When building a service manually with @platformatic/db-core, the same rules can be passed directly to the @platformatic/db-authorization plugin:
import auth from '@platformatic/db-authorization'
// In this example, the user role can only delete the pages edited before yesterday
app.register(auth, {
jwt: {
secret: 'supersecret'
},
roleKey: 'X-PLATFORMATIC-ROLE',
anonymousRole: 'anonymous',
rules: [{
role: 'user',
entity: 'page',
find: true,
save: true,
async delete ({ user, ctx, where }) {
return {
...where,
editedAt: {
lt: yesterday
}
}
},
defaults: {
userId: 'X-PLATFORMATIC-USER-ID'
}
}]
})
Access validation on entity mapper for plugins
To assert that a specific user with it's role(s) has the correct access rights to use entities on a Platformatic plugin the context should be passed to the entity mapper in order to verify its permissions like this:
To ensure that a specific user has the correct access rights to use entities within a Platformatic plugin, the user's context should be passed to the entity mapper. This integration allows the mapper to verify permissions based on the defined rules.
//plugin.js
app.post('/', async (req, reply) => {
const ctx = req.platformaticContext
await app.platformatic.entities.movie.find({
where: { /*...*/ },
ctx
})
})
Skip authorization rules
In custom plugins, you can skip authorization rules on entities programmatically by setting the skipAuth flag to true or not passing a ctx.
// this works even if the user's role doesn't have the `find` permission.
const result = await app.platformatic.entities.page.find({skipAuth: true, ...})
This has the same effect:
// this works even if the user's role doesn't have the `find` permission
const result = await app.platformatic.entities.page.find() // no `ctx`
This is useful for custom plugins for which the authentication is not necessary, so there is no user role set when invoked.
Skip authorization is only applicable in custom plugins and cannot be used in automatically generated REST and GraphQL APIs.
Avoid repetition of the same rule multiple times
To prevent redundancy and repetition of rules, you can condense similar rules for multiple entities into a single rule entry.
app.register(auth, {
jwt: {
secret: 'supersecret'
},
roleKey: 'X-PLATFORMATIC-ROLE',
anonymousRole: 'anonymous',
rules: [{
role: 'anonymous',
entities: ['category', 'page'],
find: true,
delete: false,
save: false
}]
})