How Macro's Channel-Based Permission System Enables Automatic Sharing
Macro's channel-based permission system binds shareable objects to channels via the ChannelSharePermission table, letting every channel member inherit access automatically without individual grants.
Every document, chat, or link in Macro needs controlled access. Rather than managing per-user permissions, Macro uses a channel-based permission system that leverages existing channel membership to enable automatic sharing. This design eliminates redundant access control logic and ensures permissions stay synchronized with team structures.
The Core Model: ChannelSharePermission
At the heart of automatic sharing sits the ChannelSharePermission table. This table creates a many-to-many bridge between:
share_permission_id– a permission record for a specific entitychannel_id– the target channel receiving accessaccess_level– the permission level (View, Comment, Edit, Owner)
Defined in crates/models_permissions/src/share_permission/channel_share_permission.rs, this structure decouples entity access from user management.
How Automatic Sharing Works
When an entity mentions a channel, Macro executes a four-step flow:
1. Create the Base Share Permission
The system first generates a SharePermissionV2 instance for the entity. This lives in models_permissions::share_permission::SharePermissionV2 and serves as the permission anchor.
2. Bind the Permission to a Channel
The helper upsert_channel_share_permission in tooling/seed_cli/src/service/db/mod.rs links the permission to a channel. It delegates to:
macro_db_client::share_permission::channel_permission::create
::insert_channel_share_permission
Located in crates/macro_db_client/src/share_permission/channel_permission/create.rs, this function performs an idempotent insert:
pub async fn insert_channel_share_permission<'e, E>(
executor: E,
share_permission_id: &str,
channel_id: &str,
access_level: &AccessLevel,
) -> anyhow::Result<()>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query!(
r#"
INSERT INTO "ChannelSharePermission"
("share_permission_id", "channel_id", "access_level")
VALUES ($1, $2, $3)
"#,
share_permission_id,
channel_id,
access_level as _
)
.execute(executor)
.await
.map_err(|e| {
if e.to_string().contains("duplicate key value violates unique constraint") {
anyhow::anyhow!("channel permission already exists")
} else {
e.into()
}
})?;
Ok(())
}
The INSERT … ON CONFLICT DO NOTHING semantics guarantee safe retries—permissions are added once but updatable via edit APIs.
3. Inherit via Channel Membership
Channel membership is already resolved: users, teams, or direct-message participants. No per-user grants are issued. Instead, authorization queries join through ChannelSharePermission to channel membership tables.
4. Runtime Services Propagate Permissions
Production services like document_storage_service and search_processing_service invoke the same DB helpers when processing events. For example, services/document_storage_service/src/service/entity_mutation.rs uses UpdateSharePermissionRequestV2 to modify permissions, while services/document_storage_service/src/api/threads/edit_thread.rs demonstrates the edit flow that ultimately calls channel-share helpers.
Practical Example: Sharing a Document
// 1️⃣ Create a share permission for a document
let share_permission = SharePermissionV2::new_document_share_permission(
&doc.id,
LinkShare::Public,
AccessLevel::View,
);
// 2️⃣ Grant the permission to a channel (e.g., "eng" channel)
upsert_channel_share_permission(
&db, // DB connection/transaction
&share_permission.id, // share_permission_id
"eng-channel-uuid", // channel_id
AccessLevel::View, // level the channel members receive
).await?;
When fetching the document, the system resolves authorized users automatically:
let doc = db::documents::get(&pool, doc_id).await?;
let perms = db::share_permission::get::get_share_permission(&pool, doc.share_permission_id).await?;
let allowed_users = perms.channel_share_permissions.iter()
.flat_map(|csp| channel_members(csp.channel_id)) // resolves to user IDs
.collect::<HashSet<_>>();
Key Implementation Files
| File | Purpose |
|---|---|
crates/models_permissions/src/share_permission/channel_share_permission.rs |
ChannelSharePermission struct definition |
crates/macro_db_client/src/share_permission/channel_permission/create.rs |
Idempotent insert logic for channel permissions |
tooling/seed_cli/src/service/db/mod.rs |
High-level upsert_channel_share_permission helper |
services/document_storage_service/src/service/entity_mutation.rs |
Runtime permission updates |
services/document_storage_service/src/api/threads/edit_thread.rs |
Example permission edit flow |
Summary
- ChannelSharePermission links entity permissions to channels with explicit access levels
- Idempotent inserts via
insert_channel_share_permissionprevent duplicate grants - Membership inheritance eliminates per-user permission management
- Unified helpers serve both test fixtures (seed CLI) and production services
Frequently Asked Questions
What happens if a channel member is removed?
Access is automatically revoked because authorization queries join live channel membership tables. No stale permissions persist since the system never stored per-user grants.
Can an entity have multiple channel permissions?
Yes. The ChannelSharePermission table supports multiple rows per share_permission_id, each pointing to a different channel with potentially different access levels.
How does Macro handle permission conflicts between channels?
The access resolution logic evaluates all channel permissions for a user and applies the least restrictive applicable level. Edit permissions in one channel override View permissions in another.
What's the difference between upsert_channel_share_permission and insert_channel_share_permission?
upsert_channel_share_permission in tooling/seed_cli/src/service/db/mod.rs is a convenience wrapper that handles transaction management and logging. insert_channel_share_permission in crates/macro_db_client/src/share_permission/channel_permission/create.rs performs the actual database operation with raw SQLx.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →