macula_authorization (macula v0.20.5)
View SourceMesh Authorization Module for UCAN/DID-based access control.
This module implements decentralized authorization for Macula mesh operations using UCAN (User Controlled Authorization Networks) and DID (Decentralized Identifiers). All DID parsing and UCAN validation is implemented inline with no external dependencies.
Namespace Ownership Model
DIDs map to namespaces they own: - did:macula:io.macula.rgfaber owns io.macula.rgfaber.* - Parent DIDs can access child namespaces (hierarchical) - did:macula:io.macula can access everything in io.macula.*
Authorization Flow
1. Extract caller DID from connection/message 2. Parse topic/procedure to extract namespace 3. Check if caller owns namespace → Allow 4. If not owner, check for valid UCAN grant → Allow/Deny
Public Topics
Topics containing .public. segment are world-readable: - io.macula.rgfaber.public.announcements → Anyone can subscribe - Publishing still requires ownership or UCAN grant
Summary
Functions
Check if caller is authorized to announce/declare a procedure.
Check if a capability grants access to a resource for an operation.
Check if caller DID owns a namespace.
Check if caller is authorized to publish to a topic.
Check if caller is authorized to make an RPC call.
Check if caller is authorized to subscribe to a topic.
Check if caller is authorized to subscribe with UCAN support. Full version with UCAN token parameter.
Extract the identity portion from a DID.
Extract namespace from a topic or procedure.
Check if one namespace is an ancestor of another.
Check if a topic is public (contains .public. segment).
Resolve and validate a caller DID.
Validate a UCAN token for a specific operation.
Types
-type auth_error() ::
unauthorized | invalid_ucan | expired_ucan | revoked_ucan | insufficient_capability |
invalid_did | namespace_mismatch.
-type auth_result() :: {ok, authorized} | {error, auth_error()}.
-type did() :: binary().
-type namespace() :: binary().
-type operation() :: binary().
-type procedure() :: binary().
-type topic() :: binary().
-type ucan_token() :: binary() | undefined.
Functions
-spec check_announce(CallerDID :: did(), Procedure :: procedure(), Opts :: auth_opts()) -> auth_result().
Check if caller is authorized to announce/declare a procedure.
Announcing ALWAYS requires namespace ownership. UCAN grants cannot give announce rights (prevents namespace hijacking).
-spec check_capability_match(Capability :: map(), Resource :: binary(), Operation :: operation()) -> boolean().
Check if a capability grants access to a resource for an operation.
Capability format: #{<<"with">> => Resource, <<"can">> => Operation}
Wildcards supported: - io.macula.rgfaber.* matches any resource in namespace - mesh:* matches any mesh operation
-spec check_namespace_ownership(CallerDID :: did(), Namespace :: namespace()) -> {ok, owner | ancestor} | {error, not_owner}.
Check if caller DID owns a namespace.
Returns: - {ok, owner} if DID identity matches namespace exactly - {ok, ancestor} if DID identity is parent of namespace - {error, not_owner} otherwise
-spec check_publish(CallerDID :: did(), Topic :: topic(), UcanToken :: ucan_token(), Opts :: auth_opts()) -> auth_result().
Check if caller is authorized to publish to a topic.
Publishing requires ownership or UCAN grant. Public topics do NOT grant publish rights.
-spec check_rpc_call(CallerDID :: did(), Procedure :: procedure(), UcanToken :: ucan_token(), Opts :: auth_opts()) -> auth_result().
Check if caller is authorized to make an RPC call.
Authorization succeeds if: 1. Caller owns the procedure's namespace, OR 2. Caller is ancestor of the namespace (parent access), OR 3. Caller has a valid UCAN with mesh:call capability
-spec check_subscribe(CallerDID :: did(), Topic :: topic(), Opts :: auth_opts()) -> auth_result().
Check if caller is authorized to subscribe to a topic.
Authorization succeeds if: 1. Topic is public (contains .public.), OR 2. Caller owns the namespace, OR 3. Caller is ancestor, OR 4. Caller has UCAN with mesh:subscribe capability
-spec check_subscribe(CallerDID :: did(), Topic :: topic(), UcanToken :: ucan_token(), Opts :: auth_opts()) -> auth_result().
Check if caller is authorized to subscribe with UCAN support. Full version with UCAN token parameter.
Extract the identity portion from a DID.
did:macula:io.macula.rgfaber → io.macula.rgfaber
Uses macula_did_cache for performance - repeated lookups for the same DID return cached results without re-parsing.
Extract namespace from a topic or procedure.
Namespace is the first 3 segments of a dotted path: - io.macula.rgfaber.place_order → io.macula.rgfaber - io.macula.public.events → io.macula.public - Short topics return the topic itself as namespace.
Check if one namespace is an ancestor of another.
io.macula is ancestor of io.macula.rgfaberio.macula.rgfaber is ancestor of io.macula.rgfaber.services
Check if a topic is public (contains .public. segment).
Public topics allow subscription without ownership or UCAN.
Resolve and validate a caller DID.
Parses the DID and returns the parsed components if valid. Uses macula_did_cache for performance.
-spec validate_ucan_for_operation(UcanToken :: ucan_token(), CallerDID :: did(), Resource :: binary(), Operation :: operation()) -> auth_result().
Validate a UCAN token for a specific operation.
Checks: 1. Token is well-formed and not expired 2. Audience matches caller DID 3. Token has required capability for operation 4. Token is not revoked