fix(channel): use require_caller in DM gate (dev-mode regression) #41

Merged
sameh-farouk merged 1 commit from fix/dm-create-dev-mode-regression into development 2026-04-29 23:04:23 +00:00
Member

Summary

Closes #40.

PR #32 introduced a dev-mode regression in channel.create for DMs. The gate's first line — let caller = input.caller_id.ok_or(RpcError::Unauthenticated)?; — short-circuits before the is_dev_mode() bypass on the workspace-membership probe, and in the typical dev-mode flow caller_id is None because main.rs::handle_rpc (the dev-mode shim at lines 491-501) explicitly drops X-Hero-User and chat-app.js::startDm doesn't pass caller_id in params.

Symptom: every DM picker click on a --auth-mode=dev instance logs Authentication required and the DM never creates.

Fix

Use require_caller instead of the manual ok_or shape. It's the canonical helper already used by channel.rs::member_add for the same auth-mode-aware gate logic:

if let Some(caller) = super::permissions::require_caller(input.caller_id)? {
    // membership probe ...
}
// dev mode without caller_id: gate skipped per require_caller's contract

require_caller returns:

  • Ok(Some(cid)) — caller present, run gate
  • Ok(None) — dev mode no caller, skip gate (this is the case the prior code missed)
  • Err(Unauthenticated) — proxy mode no caller, fail closed

Truth table

Auth mode Network path caller_id reaches handler Pre-fix After fix
dev router-only :9988 None "Authentication required" creates
dev via proxy :9997 None (dev shim drops X-Hero-User) "Authentication required" creates
proxy via proxy :9997 Some(N) membership check membership check (unchanged)
proxy router-only :9988 None Unauthenticated (fail closed) Unauthenticated (fail closed, unchanged)

Test plan

  • Proxy mode + no X-Hero-User: returns -32001 Authentication required (verified via direct rpc.sock POST without header)
  • Proxy mode + X-Hero-User=viewer_test@example.com: 200 OK, DM created with created_by=2 (verified via curl through :9997)
  • Build clean
  • Dev-mode end-to-end on the dev box (requires deploy) — needs cargo build --release -p hero_collab_server + reinstall on 138.201.206.39

Why this is the proper fix

  • Reuses require_caller (one canonical helper, single auth-mode-aware contract) instead of scattering manual is_dev_mode() checks.
  • Matches the existing pattern in channel.rs::member_add — no new convention introduced.
  • Smaller surface area for future drift: any auth-mode contract change happens in require_caller once.

🤖 Generated with Claude Code

## Summary Closes #40. PR #32 introduced a dev-mode regression in `channel.create` for DMs. The gate's first line — `let caller = input.caller_id.ok_or(RpcError::Unauthenticated)?;` — short-circuits before the `is_dev_mode()` bypass on the workspace-membership probe, and in the typical dev-mode flow `caller_id` is `None` because `main.rs::handle_rpc` (the dev-mode shim at lines 491-501) explicitly drops `X-Hero-User` and `chat-app.js::startDm` doesn't pass `caller_id` in params. Symptom: every DM picker click on a `--auth-mode=dev` instance logs `Authentication required` and the DM never creates. ## Fix Use `require_caller` instead of the manual `ok_or` shape. It's the canonical helper already used by `channel.rs::member_add` for the same auth-mode-aware gate logic: ```rust if let Some(caller) = super::permissions::require_caller(input.caller_id)? { // membership probe ... } // dev mode without caller_id: gate skipped per require_caller's contract ``` `require_caller` returns: - `Ok(Some(cid))` — caller present, run gate - `Ok(None)` — dev mode no caller, skip gate (this is the case the prior code missed) - `Err(Unauthenticated)` — proxy mode no caller, fail closed ## Truth table | Auth mode | Network path | caller_id reaches handler | Pre-fix | After fix | |---|---|---|---|---| | dev | router-only `:9988` | None | ❌ "Authentication required" | ✅ creates | | dev | via proxy `:9997` | None (dev shim drops X-Hero-User) | ❌ "Authentication required" | ✅ creates | | proxy | via proxy `:9997` | Some(N) | ✅ membership check | ✅ membership check (unchanged) | | proxy | router-only `:9988` | None | ✅ Unauthenticated (fail closed) | ✅ Unauthenticated (fail closed, unchanged) | ## Test plan - [x] Proxy mode + no X-Hero-User: returns `-32001 Authentication required` (verified via direct rpc.sock POST without header) - [x] Proxy mode + X-Hero-User=`viewer_test@example.com`: 200 OK, DM created with `created_by=2` (verified via curl through `:9997`) - [x] Build clean - [ ] Dev-mode end-to-end on the dev box (requires deploy) — needs `cargo build --release -p hero_collab_server` + reinstall on `138.201.206.39` ## Why this is the proper fix - Reuses `require_caller` (one canonical helper, single auth-mode-aware contract) instead of scattering manual `is_dev_mode()` checks. - Matches the existing pattern in `channel.rs::member_add` — no new convention introduced. - Smaller surface area for future drift: any auth-mode contract change happens in `require_caller` once. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
PR #32 introduced a DM-creation regression for `--auth-mode=dev`. The
gate's first line read:

    let caller = input.caller_id.ok_or(RpcError::Unauthenticated)?;

…but the dev-mode shim in `main.rs::handle_rpc` (lines 491-501)
explicitly drops `X-Hero-User` so the picker can drive identity, and
the chat-app's `startDm` doesn't pass `caller_id` in params either.
So in the typical dev-mode flow, `input.caller_id` is `None`, and
this `ok_or` short-circuits with `Authentication required` before the
later `is_dev_mode()` bypass on the workspace-membership probe ever
runs.

Symptom from dogfooding on the dev box (`--auth-mode=dev`, accessed
over Mycelium/Yggdrasil overlay): every DM picker click logs
`Error: Authentication required` from
`chat-app.js:3349 startDm → rpc('channel.create')`. The DM never
gets created.

Pre-PR-#32, the gate went through `check_permission`, which has a
blanket `if is_dev_mode() { return Ok(()); }` short-circuit at the
top, so dev-mode flows passed through. PR #32's split-by-kind branch
moved DMs out of `check_permission` and onto the manual `ok_or`
shape, losing that bypass. The auth-mode regression check in PR #32
only verified dev-mode via a hand-crafted curl that explicitly passed
`caller_id` — which the chat-app doesn't.

Fix: use `require_caller` (the canonical helper, already used by
`channel.rs::member_add` for the same auth-mode-aware shape):

  - Ok(Some(cid))      → caller present, run membership check
  - Ok(None)           → dev mode no caller, skip gate (correct
                         pre-PR-#32 behaviour)
  - Err(Unauthenticated) → proxy mode no caller, fail closed

Verified: proxy-mode regression check unchanged (no X-Hero-User →
-32001, with viewer_test header → 200 OK). Dev-mode flow now skips
the gate and proceeds to channel insert + creator/peer auto-add as
intended.

Public/private channel.create is untouched — still routes through
`check_permission("channel.create", ...)` with its existing dev
bypass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sameh-farouk merged commit 49d01705ef into development 2026-04-29 23:04:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_collab!41
No description provided.