Authorization in FDM Core
The @svenvw/fdm-core
package includes a built-in authorization system to control access to resources based on user roles. This system operates primarily using the fdm-authz
database schema and associated functions within the library.
Core Concepts
The authorization model is based on Role-Based Access Control (RBAC) and uses the following concepts:
- Principal (
PrincipalId
): Represents the user attempting an action. This is typically the user ID obtained from the authentication system (fdm-authn
schema,user
table). - Resource (
Resource
): The type of entity being accessed. Defined resources include:farm
field
cultivation
harvesting
fertilizer_application
soil_analysis
user
(for managing user details/settings)organization
(if multi-tenancy/organizations are used)
- Resource ID (
ResourceId
): The specific unique identifier of the resource instance (e.g., a specificb_id_farm
,b_id
,p_app_id
). - Role (
Role
): The role assigned to a principal for a specific resource. Defined roles include:owner
: Typically full control over the resource and its children.advisor
: Often read/write access for management purposes.researcher
: Usually limited to read access for analysis.
- Action (
Action
): The operation the principal wants to perform. Defined actions include:read
: View resource details.write
: Create or modify the resource or related actions.list
: View a list of resources (often checked at a higher level, e.g., listing fields within a farm).share
: Grant or revoke roles for the resource.
- Permissions: A predefined mapping (within
authorization.ts
) defines whichRole
can perform whichAction
on eachResource
type. For example, anowner
of afarm
canread
,write
,list
, andshare
that farm and its related child resources (fields, cultivations etc.), while aresearcher
might only haveread
access.
How it Works
- Granting Access: Access is granted by assigning a
Role
to aPrincipalId
for a specificResourceId
. This is done using thegrantRole
function, which creates a record in thefdm-authz.role
table.import { grantRole, FdmServerType, PrincipalId } from '@svenvw/fdm-core';
// Assume fdm, principalId, targetUserId, farmId are defined
// Grant the 'advisor' role for a specific farm to another user
// await grantRole(fdm, 'farm', 'advisor', farmId, targetUserId); - Revoking Access: Access is revoked using the
revokeRole
function, which marks the corresponding record in thefdm-authz.role
table as deleted (soft delete).import { revokeRole, FdmServerType, PrincipalId } from '@svenvw/fdm-core';
// Assume fdm, principalId, targetUserId, farmId are defined
// Revoke the 'advisor' role for a specific farm from a user
// await revokeRole(fdm, 'farm', 'advisor', farmId, targetUserId); - Checking Permissions: When you call most data manipulation or retrieval functions in
fdm-core
(e.g.,addField
,getField
,addCultivation
), they internally call thecheckPermission
function.checkPermission
takes theprincipalId
, the targetresource
, the intendedaction
, and theresource_id
.- It determines the required roles for the action based on the predefined
permissions
. - Resource Chain Lookup: It then uses an internal helper (
getResourceChain
) to identify the hierarchical relationship of the target resource. FDM resources often have a parent-child relationship. For example:- A
fertilizer_application
happens on afield
. - A
field
belongs to afarm
. - A
cultivation
happens on afield
, which belongs to afarm
. - A
harvesting
event relates to acultivation
, which happens on afield
, which belongs to afarm
. ThegetResourceChain
function queries the database to find these links and returns an ordered list representing this hierarchy, like[{ resource: 'farm', resource_id: 'farm123' }, { resource: 'field', resource_id: 'fieldABC' }, { resource: 'fertilizer_application', resource_id: 'fertAppXYZ' }]
.
- A
- Permission Check Along the Chain:
checkPermission
iterates through this chain, starting from the most specific resource (e.g.,fertilizer_application
) up to the highest level (e.g.,farm
). At each level, it checks if theprincipalId
has one of the required roles assigned specifically for that resource bead (e.g., checking roles forfertAppXYZ
, then roles forfieldABC
, then roles forfarm123
) in thefdm-authz.role
table. - Cascading Permissions: If permission is granted at any level in the chain (e.g., the user is
owner
of thefarm
), the check succeeds, and the original function proceeds. This effectively means permissions granted at higher levels (like the farm) cascade down to child resources (like fields, cultivations, applications within that farm). - If no required role is found for the principal at any level of the chain,
checkPermission
throws a "Permission denied" error, halting the original function call.
- Listing Accessible Resources: The
listResources
function can be used to find all resources of a specific type (e.g.,farm
) that a user has permission to perform a certain action on (e.g.,read
).import { listResources, FdmServerType, PrincipalId } from '@svenvw/fdm-core';
// Assume fdm, principalId are defined
// Get all farm IDs the user can read
// const readableFarmIds = await listResources(fdm, 'farm', 'read', principalId);
// console.log(readableFarmIds); - Auditing: Every call to
checkPermission
(whether successful or failed) is logged as an entry in thefdm-authz.audit
table. This provides a trail of who attempted what action on which resource, when, from where (origin
), whether it was allowed, and which role assignment granted the access if successful.
Key Considerations
- Principal ID: All core functions that involve data access require the
principalId
to perform permission checks. - Role Management: Granting and revoking roles is typically an administrative task, often performed via dedicated UI elements or scripts that call
grantRole
andrevokeRole
. - Resource Chain: Understanding the resource hierarchy (
getResourceChain
) is crucial for predicting how permissions propagate (e.g., farm owner permissions cascade down). - Predefined Permissions: The mapping between roles, resources, and actions is currently hardcoded in the
permissions
array withinauthorization.ts
. Modifying these permissions requires changing the library code.