Authorization and Root Entity Scope
Purpose
Root entity scope provides a reusable authorization boundary.
It answers:
Is this user allowed to operate on this entity or object?The same concept can be used across:
- entities,
- assets,
- properties,
- tags,
- groups,
- credentials,
- secret metadata,
- remote sessions,
- audit queries,
- future PostgreSQL RLS.
Root Entity
A root entity is a top-level scope boundary.
Examples:
Customer A
Customer B
Internal Operations
Demo TenantA user's access is represented as one or more allowed root entities.
Core Rule
A user may operate on a target entity only when:
target.path <@ allowed_root.pathwhere allowed_root is one of the user's active root scopes.
Capabilities
Root scope answers where a user may act.
Capabilities answer what a user may do.
Examples:
entity.read
entity.create
entity.update
entity.delete
entity.move
property.read
property.update
tag.assign
group.manage
vault.secret.reveal
audit.readAn operation normally requires both:
inside allowed root scope
AND
has required capabilityTable
Recommended table:
user_root_entity_scopesFields:
id
user_id
root_entity_id
capabilities jsonb
created_at
updated_at
deleted_atService Utility
Create a reusable authorization function early.
Example TypeScript shape:
type AssertEntityInUserScopeInput = {
userId: string;
targetEntityId: string;
capability: string;
};
async function assertEntityInUserScope(input: AssertEntityInUserScopeInput) {
// 1. Load target entity path.
// 2. Load user's allowed root paths and capabilities.
// 3. Check at least one root path contains target path.
// 4. Check required capability.
// 5. Throw a typed authorization error if denied.
}Query Pattern
SELECT 1
FROM entities target
JOIN user_root_entity_scopes scope ON scope.user_id = $user_id
JOIN entities root ON root.id = scope.root_entity_id
WHERE target.id = $target_entity_id
AND target.path <@ root.path
AND target.deleted_at IS NULL
AND root.deleted_at IS NULL
AND scope.deleted_at IS NULL;Capability checks can be performed in TypeScript or SQL depending on how capabilities are stored.
Multi-Root Users
A user may have access to multiple roots.
Examples:
- national manager,
- support engineer,
- integration service account,
- admin user.
Queries should support multiple allowed roots without loading the entire database.
Pattern:
WHERE EXISTS (
SELECT 1
FROM user_root_entity_scopes scope
JOIN entities root ON root.id = scope.root_entity_id
WHERE scope.user_id = $user_id
AND entities.path <@ root.path
AND scope.deleted_at IS NULL
)Reparenting and Root Scope
Moving an entity may change its root scope.
Default rule:
Disallow cross-root moves.If cross-root moves are required, they must be explicit privileged operations because they can affect:
- permissions,
- encryption key scope,
- audit visibility,
- integration ownership,
- external references,
- reporting.
Authorization By Object Type
Some operations target records that are not directly entities rows.
Examples:
- property value,
- tag assignment,
- group membership,
- relationship,
- secret version,
- audit event.
These records should carry or resolve to an entity id.
Example:
entity_property_values.entity_id -> entities.id
entity_tags.entity_id -> entities.id
entity_group_members.group_entity_id -> entities.id
entity_group_members.member_entity_id -> entities.idAuthorization should resolve the relevant entity and apply root scope.
Future RLS
The same model can support PostgreSQL RLS.
Conceptual policy:
USING (
EXISTS (
SELECT 1
FROM app_session_allowed_roots ar
WHERE entities.path <@ ar.root_path
)
)Do not rush RLS before service authorization is clear. Add it later as defense in depth.
Common Mistakes
Mistake: Checking only entity type
Bad:
User can edit assets, therefore allow edit.Correct:
User can edit assets AND asset is inside allowed root scope.Mistake: Checking only parent
A deeply nested asset may be many levels below the root. Use ltree path containment.
Mistake: Forgetting secondary entities
Relationships and group memberships often involve two entities. Check both where required.
Mistake: Mixing tenant and role
Tenant/root scope is not a role. A user may have the same role in one root and no access to another.