Discord component button authorization and pairing fixes
Cluster 50627. Data as of
Nothing open on GitHub. Fix merged in #51730. Check it covers your case.
The short answer
- Known?
- Yes. 5 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #51730. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #51730. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These items relate to authorization issues with Discord MESSAGE_COMPONENT button interactions, specifically around pairing-approved users and requester permissions for component prompts. The work involves checking pairing store state and granting appropriate access for button clicks.
Open issues0
Open PRs0
Fixes on record
-
#51730 fix(discord): authorize pairing-approved users for component button clicks (#50627)
files:
plugins/platforms/discord/adapter.py,tests/gateway/test_discord_component_auth.pyShow the diff
-
plugins/platforms/discord/adapter.py@@ -5709,7 +5709,8 @@ def _component_check_auth( Mirrors the gateway's external-surface authorization model: component button clicks must be explicitly authorized by a Discord user/role - allowlist, a global user allowlist, or an explicit allow-all flag. + allowlist, a global user allowlist, an explicit allow-all flag, or + the pairing store (``hermes pairing approve``). Behavior: @@ -5719,6 +5720,7 @@ def _component_check_auth( - role allowlist set + interaction.user has no resolvable ``roles`` attribute (e.g. DM context with a role policy active) -> reject (fail closed) + - user is approved in the pairing store -> allow - otherwise -> reject """ if os.getenv("DISCORD_ALLOW_ALL_USERS", "").strip().lower() in {"true", "1", "yes"}: @@ -5740,11 +5742,13 @@ def _component_check_auth( if user is None: return False + # Resolve user ID once for both allowlist and pairing checks. + try: + uid = str(user.id) + except AttributeError: + uid = "" + if has_users: - try: - uid = str(user.id) - except AttributeError: - uid = "" if "*" in user_set or (uid and uid in user_set): return True @@ -5763,6 +5767,18 @@ def _component_check_auth( if user_role_ids & role_set: return True + # Check pairing store — mirrors ``authz_mixin._check_authorization`` + # so users approved via ``hermes pairing approve`` can interact with + # component buttons even without DISCORD_ALLOWED_USERS set. + if uid: + try: + from gateway.pairing import PairingStore + store = PairingStore() + if store.is_approved("discord", uid): + return True + except Exception: + pass + return False -
tests/gateway/test_discord_component_auth.py@@ -30,6 +30,8 @@ @pytest.fixture(autouse=True) def _clear_component_auth_env(monkeypatch): + from unittest.mock import MagicMock, patch + for name in ( "DISCORD_ALLOW_ALL_USERS", "GATEWAY_ALLOW_ALL_USERS", @@ -37,6 +39,13 @@ def _clear_component_auth_env(monkeypatch): ): monkeypatch.delenv(name, raising=False) + # Default-mock PairingStore so tests don't hit the filesystem. + # Pairing-specific tests override this with explicit mock values. + mock_store = MagicMock() + mock_store.is_approved.return_value = False + with patch("gateway.pairing.PairingStore", return_value=mock_store): + yield + # --------------------------------------------------------------------------- # Direct helper coverage -- the views all delegate to this helper, so @@ -304,3 +313,39 @@ def test_view_empty_allowlists_allow_with_explicit_allow_all(monkeypatch): monkeypatch.setenv("DISCORD_ALLOW_ALL_USERS", "true") view = ExecApprovalView(session_key="s", allowed_user_ids=set()) assert view._check_auth(_interaction(99999)) is True + + +# --------------------------------------------------------------------------- +# Pairing store: users approved via ``hermes pairing approve`` must be +# authorized even without DISCORD_ALLOWED_USERS / DISCORD_ALLOWED_ROLES. +# --------------------------------------------------------------------------- + + +def test_component_check_pairing_approved_user_passes(monkeypatch): + """User approved in pairing store passes even without allowlists.""" + from unittest.mock import MagicMock, patch + + mock_store = MagicMock() + mock_store.is_approved.return_value = True + # Override the autouse fixture's mock with approved=True + with patch("gateway.pairing.PairingStore", return_value=mock_store): + interaction = _interaction(11111) + assert _component_check_auth(interaction, set(), set()) is True + mock_store.is_approved.assert_called_once_with("discord", "11111") + + +def test_component_check_pairing_not_approved_user_rejected(monkeypatch): + """User NOT in pairing store is still rejected (fail-closed).""" + # The autouse fixture already mocks is_approved=False, so this + # just verifies the fail-closed path still works. + interaction = _interaction(99999) + assert _component_check_auth(interaction, set(), set()) is False + + +def test_component_check_pairing_import_error_graceful(monkeypatch): + """If PairingStore import fails, fall through to fail-closed.""" + from unittest.mock import patch + + with patch("gateway.pairing.PairingStore", side_effect=ImportError("simulated")): + interaction = _interaction(11111) + assert _component_check_auth(interaction, set(), set()) is False
-
3 PRs closed without merging
From the close comments: done elsewhere 2 · merged via another change 1
-
#50628 fix(discord): authorize requester on component prompts · done elsewhere →
7ff48a6,#51730,#50628,#50633,#50714 -
#50633 fix(discord): check pairing store for component button auth · merged via another change →
7ff48a6,#51730 -
#50714 fix(discord): authorize pairing-approved users for component button clicks (#50627) · done elsewhere →
7ff48a6,#51730,#50628,#50633,#50714
Closed issues · 1
- Closed#50627v0.17.0 (v2026.6.19): Discord MESSAGE_COMPONENT button interactions broken
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Merged PRs
- #51730
- Closed issues
- 1
- Attempts
- 3 PRs closed without merging
- Fix link checked
- 24 Jun 2026