Search documentation of macula

# macula_authorization (macula v0.44.2)

View Source

Mesh 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

## Types

auth_error/0
auth_opts/0
auth_result/0
did/0
namespace/0
operation/0
procedure/0
topic/0
ucan_token/0

## Functions

check_announce(CallerDID, Procedure, Opts)

Check if caller is authorized to announce/declare a procedure.

check_capability_match(Capability, Resource, Operation)

Check if a capability grants access to a resource for an operation.

check_namespace_ownership(CallerDID, Namespace)

Check if caller DID owns a namespace.

check_publish(CallerDID, Topic, UcanToken, Opts)

Check if caller is authorized to publish to a topic.

check_rpc_call(CallerDID, Procedure, UcanToken, Opts)

Check if caller is authorized to make an RPC call.

check_subscribe(CallerDID, Topic, Opts)

Check if caller is authorized to subscribe to a topic.

check_subscribe(CallerDID, Topic, UcanToken, Opts)

Check if caller is authorized to subscribe with UCAN support. Full version with UCAN token parameter.

extract_identity_from_did(DID)

Extract the identity portion from a DID.

extract_namespace(TopicOrProcedure)

Extract namespace from a topic or procedure.

is_ancestor_namespace(Parent, Child)

Check if one namespace is an ancestor of another.

is_public_topic(Topic)

Check if a topic is public (contains `.public.` segment).

resolve_caller_did(CallerDID)

Resolve and validate a caller DID.

validate_ucan_for_operation(UcanToken, CallerDID, Resource, Operation)

Validate a UCAN token for a specific operation.

# Types

# auth_error/0

    -type auth_error() ::
              unauthorized | invalid_ucan | expired_ucan | revoked_ucan | insufficient_capability |
              invalid_did | namespace_mismatch.

# auth_opts/0

    -type auth_opts() :: #{atom() => term()}.

# auth_result/0

    -type auth_result() :: {ok, authorized} | {error, auth_error()}.

# did/0

    -type did() :: binary().

# namespace/0

    -type namespace() :: binary().

# operation/0

    -type operation() :: binary().

# procedure/0

    -type procedure() :: binary().

# topic/0

    -type topic() :: binary().

# ucan_token/0

    -type ucan_token() :: binary() | undefined.

# Functions

# check_announce(CallerDID, Procedure, Opts)

    -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).

# check_capability_match(Capability, Resource, Operation)

    -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

# check_namespace_ownership(CallerDID, Namespace)

    -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

# check_publish(CallerDID, Topic, UcanToken, Opts)

    -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.

# check_rpc_call(CallerDID, Procedure, UcanToken, Opts)

    -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

# check_subscribe(CallerDID, Topic, Opts)

    -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

# check_subscribe(CallerDID, Topic, UcanToken, Opts)

    -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_identity_from_did(DID)

    -spec extract_identity_from_did(DID :: did()) -> {ok, Identity :: binary()} | {error, invalid_did}.

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(TopicOrProcedure)

    -spec extract_namespace(TopicOrProcedure :: binary()) -> namespace().

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.

# is_ancestor_namespace(Parent, Child)

    -spec is_ancestor_namespace(Parent :: namespace(), Child :: namespace()) -> boolean().

Check if one namespace is an ancestor of another.

`io.macula` is ancestor of `io.macula.rgfaber` `io.macula.rgfaber` is ancestor of `io.macula.rgfaber.services`

# is_public_topic(Topic)

    -spec is_public_topic(Topic :: topic()) -> boolean().

Check if a topic is public (contains `.public.` segment).

Public topics allow subscription without ownership or UCAN.

# resolve_caller_did(CallerDID)

    -spec resolve_caller_did(CallerDID :: did()) -> {ok, Components :: map()} | {error, invalid_did}.

Resolve and validate a caller DID.

Parses the DID and returns the parsed components if valid. Uses `macula_did_cache` for performance.

# validate_ucan_for_operation(UcanToken, CallerDID, Resource, Operation)

    -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