feat: unified cross-channel access control (#311) - #312
Merged
Conversation
…allow-list, access requests
Introduces a single access-control primitive across web, Telegram, and Slack.
Each adapter's only new responsibility is translating its native identity into
a verified email; all downstream concerns (allow-list, access requests,
per-user memory) run off that key.
Changes
-------
Schema (migration `access_control`):
- agent_ownership.require_email, open_access (via AccessPolicyMixin)
- telegram_chat_links.verified_email, verified_at
- new access_requests(agent_name, email, channel, status, decided_by, decided_at)
Adapters:
- ChannelAdapter gains resolve_verified_email + prompt_auth
- Telegram /login state machine reuses email_login_codes; /logout, /whoami
- Slack resolves via users.info (workspace OAuth is already verified)
- message_router adds a single gate: owner/admin/sharing → open_access →
access_request. Group chats bypass. Unpoliced agents keep legacy behavior.
Routes (routers/sharing.py):
- GET/PUT /api/agents/{name}/access-policy
- GET /api/agents/{name}/access-requests
- POST /api/agents/{name}/access-requests/{id}/decide
(approve inserts into agent_sharing + whitelists email if email auth is on)
UI: SharingPanel.vue gains policy toggles and a pending-requests inbox.
Tests: 21 smoke tests in test_channel_access_control.py, all passing.
Docs: new feature-flow document + updates to agent-sharing, telegram-integration,
slack-integration flows.
Closes #311
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vybe
added a commit
that referenced
this pull request
Apr 13, 2026
…licy Retire the per-link require_email flag on agent_public_links in favor of agent_ownership.require_email + open_access (the policy already used by Slack and Telegram via adapters.message_router). Public web chat now runs the same unified gate: owner/admin/shared → allow; open_access → allow; else pending access_request + 403. - routers/public.py: helpers _agent_requires_email / _agent_allows_open_access; all link["require_email"] reads replaced; public_chat applies the shared gate after email verification - routers/public_links.py, db_models.py, db/public_links.py, database.py: drop require_email from Pydantic models, responses, and SQL; column retained in schema (Slack legacy slack_link_connections join still reads it) and written as 0 by default - db/migrations.py: new public_link_require_email_unified migration ORs any legacy per-link require_email=1 into agent_ownership.require_email=1 so existing email-required links stay email-required - PublicLinksPanel.vue: remove "Email Required" badge + require_email checkbox + payload fields; point users to the Channel Access Policy Tests: tests/test_public_links.py — 6 new TestUnifiedAccessPolicy smoke tests; existing fixtures rewritten to drive /access-policy instead of the per-link flag. 27 passed, 4 skipped (pre-existing SMTP/DB prereqs). Docs: public-agent-links.md and unified-channel-access-control.md updated; channel coverage table now shows web as live and the #252 TODO is resolved. Refs #311 #312 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vybe
added a commit
that referenced
this pull request
Apr 13, 2026
…ccess_requests schema Expands the Sharing section from 3 to 6 endpoints (access-policy get/set, access-requests list/decide), annotates new agent_ownership columns (require_email, open_access), and adds the access_requests table + telegram_chat_links verified_email column per #311/#312. Refs #311 #312 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the unified cross-channel access control primitive from #311: one verified email, one allow-list, one gate. Each adapter translates its native identifier into a verified email; everything downstream (sharing, access requests, per-user memory) keys off that single identity.
require_email/open_accessonagent_ownership(viaAccessPolicyMixin),verified_email/verified_atontelegram_chat_links, newaccess_requeststable.ChannelAdapter.resolve_verified_email+prompt_auth; Telegram/loginstate machine (reusesemail_login_codes); Slack viausers.info.message_routerthat checks owner/admin/agent_sharing, falls through toopen_access, or upserts a pending access request. Group chats bypass; unpoliced agents retain legacy permissive behavior.GET/PUT /api/agents/{name}/access-policy,GET /api/agents/{name}/access-requests,POST /api/agents/{name}/access-requests/{id}/decide(approve inserts intoagent_sharing+ whitelists email).SharingPanel.vuegains policy toggles and a pending-requests inbox.docs/memory/feature-flows/unified-channel-access-control.md+ updates toagent-sharing,telegram-integration,slack-integrationflows.Design notes
agent_sharingbecomes the cross-channel allow-list (same email admits the user on web, Telegram, and Slack).public_user_memory(MEM-001) now keys off the verified email automatically — the router usesverified_emailassource_user_emailwhen present./loginuses the existingemail_login_codesinfrastructure (same 6-digit code table as web email auth)./loginstep — the workspace OAuth token already proves identity, sousers.infois called live (requires theusers:read.emailscope).Architectural invariants respected
AccessPolicyMixin.OwnedAgentByNamefor owner-gated endpoints.Test plan
tests/test_channel_access_control.py— 21/21 passing (15.6s)access_controlinschema_migrations)upsert_access_request→list_access_requests→decide_access_request(approve=True)inserts intoagent_sharingemail_has_agent_accessreturns True for admin, False for stranger/login user@example.com→ code email →/login 123456→ subsequent messages pass (not run — requires live bot)require_email=Truefetches email viausers.info(not run — requires live workspace)Closes #311
🤖 Generated with Claude Code