Skip to content

fix: authorize org routes by membership; drop CurrentUser.organizationId - #374

Merged
Makisuo merged 1 commit into
mainfrom
fix/org-scoped-auth
Oct 31, 2026
Merged

Makisuo merged 1 commit into
mainfrom
fix/org-scoped-auth

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The T3 Code integration page showed "Couldn't load your T3 Code link". In production, GET /integrations/t3code/:orgId answered 401: the trace stops after auth, with no object call.

The route checked currentUser.organizationId !== orgId, but nothing ever sets CurrentUser.organizationId. Session cookies (HazelSession), OAuth bearers and bot tokens all build CurrentUser with it undefined/null. It's a leftover from the old JWT-with-org auth, so every check against it failed:

  • API: the T3 Code routes, plus integration OAuth start (getOAuthUrl), connection status and disconnect, returned 401 for every member.
  • Web: pages that read shared.currentUser.organizationId never loaded their data (it was always null). That covers the integrations connection lists, the composer's mentionable bots and commands, Discord linking, and the draft's org. Test fixtures set it to a real org, which hid the problem.

Fix

  • Removed organizationId from CurrentUser (@hazel/domain and the web session), so the compiler now rejects any code that relies on it.
  • The integration and T3 Code HTTP routes check the directory's membership index with the existing requireMember.
  • RPC routing, bots.ts and requestOrganizationId dropped their user.organizationId fallbacks. They use the routing hint or object only, which is what they did in practice.
  • The web reads the route's org (shared.organization). The root redirect's membership query drops its "org in the JWT" branch.
  • The T3 Code logo is now T3 Code's own mark (apps/mobile/assets/widget/T3Mark.svg from pingdotgg/t3code), bundled as /images/integrations/t3code.svg, in place of Brandfetch's t3.chat (T3 Chat) icon.

Verification

  • bun run typecheck: 20/20 packages pass
  • Backend vitest: 318 passed
  • Web vitest: 1598 passed
  • oxlint on the changed files: no new warnings

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • Bug Fixes
    • Organization-scoped integrations and chat features now use the currently selected organization, improving consistency when switching organizations.
    • Membership checks for organization-specific pages and integrations now use the requested organization’s membership.
    • Bot listings and requests without an explicit organization no longer inherit the user’s active organization; public bot listings remain available.
  • Updates
    • Updated the T3 Code integration’s displayed logo.

CurrentUser.organizationId was never set (sessions, OAuth bearers and bot
tokens all leave it empty), so every check against it failed: the T3 Code
link, integration OAuth start, connection status and disconnect answered
401 for every member, and web pages that read it never loaded their org's
connections, mentionable bots or commands.

The field is gone from CurrentUser (backend and web), so no code can rely
on it again. The integration and T3 Code routes check the directory's
membership index (requireMember); the web reads the route's organization.

Also bundle T3 Code's own logo instead of Brandfetch's t3.chat icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Walkthrough

The change removes organizationId from CurrentUser schemas and producers. Backend organization selection uses explicit organization hints or worker routing context, and HTTP routes use membership checks. Frontend operations read organization IDs from shared organization state. The T3 Code logo domain changes to t3.codes.

Changes: Organization Context and Authorization

Layer / File(s) Summary
Remove organizationId from CurrentUser
packages/domain/src/current-user.ts, apps/web-foldkit/src/session.ts, apps/backend/src/services/..., apps/backend/src/routes/..., apps/backend/src/rpc/..., apps/backend/src/worker/..., apps/backend/src/**/*.test.ts, apps/web-foldkit/src/**/*.test.ts
CurrentUser schemas and constructors no longer include organizationId. Related test fixtures and policy helpers no longer set the field.
Use explicit backend organization context
apps/backend/src/rpc/handlers/bots.ts, apps/backend/src/rpc/routing.ts, apps/backend/src/worker/objects/org/organization.ts
RPC routing uses a supplied organization ID or the directory path. Bot installation checks no longer fall back to the caller’s active organization. Worker organization context comes from the routed organization object.
Authorize HTTP routes by membership
apps/backend/src/routes/integrations.http.ts, apps/backend/src/routes/t3code.http.ts
Integration and T3 Code routes use requireMember for the requested organization. The T3 Code route retrieves the current-user context after the membership check.
Read organization context from shared state
apps/web-foldkit/src/page/chat/channel/..., apps/web-foldkit/src/page/my-settings/linked-accounts/..., apps/web-foldkit/src/page/root/index.ts, apps/web-foldkit/src/page/settings/integrations/..., apps/web-foldkit/src/test/..., apps/web-foldkit/src/page/**/*.test.ts
Chat, linked-account, and integration operations derive organization IDs from shared organization state. The root membership query filters by user ID, and frontend CurrentUser fixtures no longer include organizationId.

T3 Code Integration Metadata

Layer / File(s) Summary
Update T3 Code logo metadata
apps/web-foldkit/src/core/lib/integrations/__data.ts
The T3 Code integration entry uses t3.codes as its logo domain and no longer specifies logoType.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant IntegrationsHTTP
  participant T3CodeHTTP
  participant requireMember
  participant CurrentUserContext
  IntegrationsHTTP->>requireMember: requireMember(orgId)
  T3CodeHTTP->>requireMember: requireMember(orgId)
  T3CodeHTTP->>CurrentUserContext: retrieve after membership check
Loading

Possibly related PRs

  • MapleTechLabs/hazel#370: Adds bot authentication and organization routing, including the fallback behavior changed here.
  • MapleTechLabs/hazel#329: Adds authentication paths that construct CurrentUser values, whose organization context changes here.
  • MapleTechLabs/hazel#359: Adds the T3 Code routes and integration handlers whose authorization changes here.
Merge Risk: High · up to afb91

This change fixes organization authorization for members, but integration connection endpoints still let an authenticated user write a connection into an organization they do not belong to. Add membership checks to the API-key connect handler and the OAuth callback before merging. The bot command route/channel mismatch is a smaller follow-up.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@maple-review-bot

maple-review-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 7/10 · likely safe to merge. Untested authorization wiring on the org HTTP routes decides the number; requireMember itself is covered by cross-org.test.ts.
1 warning −1 · tests partial −1 · risk high −1

Replaces the dead CurrentUser.organizationId check on the integration and T3 Code routes with the directory membership check, and moves the web onto the route's organization. The fix is right and the deleted fallbacks were unreachable in production; nothing pins the new route guard.

  • requireOrg and three integration handlers authorize via requireMember.
  • CurrentUser.organizationId gone from the domain and web session schemas.
  • Web subscriptions read shared.organization.id; RPC org fallbacks dropped.
  • T3 Code gets a bundled logo in place of Brandfetch's t3.chat.

Findings

🟠 Warning · F1 · No test pins that requireOrg admits a member and refuses an outsider

tests · apps/backend/src/routes/t3code.http.ts:115

The behavior this PR fixes — an org route answering a member whose session carries no organization — is exercised by no test. t3code.http.test.ts covers only checkCallback and requireT3CodeBot, so a return to an always-failing org check would pass CI again, as it did before.

Export `requireOrg` and test it against a seeded directory the way `cross-org.test.ts` does for `requireMember`: an active `membership_index` row passes, an outsider fails with `UnauthorizedError`.
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit afb91ae84aec663d1b20c1e476f75c5a67f06e1c. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · tests · apps/backend/src/routes/t3code.http.ts:115
No test pins that `requireOrg` admits a member and refuses an outsider
The behavior this PR fixes — an org route answering a member whose session carries no organization — is exercised by no test. `t3code.http.test.ts` covers only `checkCallback` and `requireT3CodeBot`, so a return to an always-failing org check would pass CI again, as it did before.
Suggested fix: Export `requireOrg` and test it against a seeded directory the way `cross-org.test.ts` does for `requireMember`: an active `membership_index` row passes, an outsider fails with `UnauthorizedError`.

afb91ae · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

return currentUser
})
/** The caller, once they are an active member of `orgId` (sessions carry no organization). */
const requireOrg = (orgId: OrganizationId) => requireMember(orgId).pipe(Effect.andThen(CurrentUser.Context))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

No test pins that requireOrg admits a member and refuses an outsider

F1 · Warning · tests

The behavior this PR fixes — an org route answering a member whose session carries no organization — is exercised by no test. t3code.http.test.ts covers only checkCallback and requireT3CodeBot, so a return to an always-failing org check would pass CI again, as it did before.

Export `requireOrg` and test it against a seeded directory the way `cross-org.test.ts` does for `requireMember`: an active `membership_index` row passes, an outsider fails with `UnauthorizedError`.
🤖 Prompt to fix with an AI agent
In `apps/backend/src/routes/t3code.http.ts:115`: No test pins that `requireOrg` admits a member and refuses an outsider.

The behavior this PR fixes — an org route answering a member whose session carries no organization — is exercised by no test. `t3code.http.test.ts` covers only `checkCallback` and `requireT3CodeBot`, so a return to an always-failing org check would pass CI again, as it did before.

Suggested fix: Export `requireOrg` and test it against a seeded directory the way `cross-org.test.ts` does for `requireMember`: an active `membership_index` row passes, an outsider fails with `UnauthorizedError`.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 70.01% 22071 / 31524
🔵 Statements 69.44% 25003 / 36005
🔵 Functions 59.51% 8655 / 14543
🔵 Branches 57.98% 10617 / 18309
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
apps/backend/src/policies/policy-test-helpers.ts 75% 100% 50% 73.91% 71-73, 80-82, 89-91
apps/backend/src/routes/integrations.http.ts 21.96% 15.1% 20% 21.7% 76, 120-138, 156-165, 177-183, 208, 233-247, 251-257, 270-379, 425-800, 814-888, 892, 904-938, 951-974, 978-982, 988-1016
apps/backend/src/routes/t3code.http.ts 33.57% 42.3% 18.18% 33.83% 98, 103-109, 112, 115, 118, 121-133, 136, 142-216, 265-365, 372, 384, 396-481
apps/backend/src/routes/api-v1/bot-auth.ts 88.88% 75% 100% 88.23% 26-29, 34-37
apps/backend/src/rpc/routing.ts 66.01% 66.66% 53.12% 67.04% 67, 91, 98, 103, 119, 147, 148, 189-192, 199, 255-304, 315-325, 335
apps/backend/src/rpc/handlers/bots.ts 61.69% 48.8% 60.31% 65.76% 84, 89, 126, 224-236, 244, 271, 278-356, 364, 366, 398, 410, 419, 428, 442, 477-488
apps/backend/src/rpc/middleware/auth.ts 95% 92% 88.88% 94.73% 103-111, 116-121
apps/backend/src/services/hazel-session.ts 70.68% 76.92% 57.14% 77.55% 41-54, 80, 87-90, 122-141, 146, 158, 171
apps/backend/src/services/oauth-bearer-auth.ts 24.13% 0% 8.33% 25% 23, 29-125
apps/backend/src/worker/objects/org/organization.ts 85% 37.5% 83.33% 89.47% 25, 67-70
apps/web-foldkit/src/session.ts 100% 50% 100% 100%
apps/web-foldkit/src/app/command.ts 48.64% 0% 0% 48.64% 18, 25, 32, 38-39, 45-48, 55-58, 66, 73-77, 95, 102-103, 111-125
apps/web-foldkit/src/core/lib/integrations/__data.ts 80% 12.5% 75% 75% 35-36
apps/web-foldkit/src/page/chat/channel/fixtures.test-support.ts 100% 100% 100% 100%
apps/web-foldkit/src/page/chat/channel/subscription.ts 28.94% 31.16% 17.7% 26.92% 50, 55-68, 82-102, 117-221, 233-254, 261-264, 271-272, 276-291, 358-402, 413-414, 428-436, 442-443
apps/web-foldkit/src/page/chat/channel/write.ts 88.82% 72.22% 88.78% 92.1% 111, 158-159, 164-165, 168-173, 179, 183, 245, 247, 248, 255, 280, 299-300, 330-331, 355, 397, 416-417, 432, 483-521, 547
apps/web-foldkit/src/page/my-settings/linked-accounts/subscription.ts 30% 0% 16.66% 22.22% 22-51
apps/web-foldkit/src/page/my-settings/linked-accounts/update.ts 94.28% 90.9% 90.9% 93.33% 17, 125
apps/web-foldkit/src/page/root/index.ts 72.5% 60% 62.96% 68.57% 30, 52, 83-104, 137-138
apps/web-foldkit/src/page/settings/integrations/index/subscription.ts 25% 0% 14.28% 14.28% 19-42
apps/web-foldkit/src/page/settings/integrations/installed/subscription.ts 40% 0% 25% 25% 14-21
apps/web-foldkit/src/page/settings/integrations/marketplace/subscription.ts 36.36% 0% 12.5% 33.33% 12-13, 19-36
apps/web-foldkit/src/test/pages-entry-fixtures.ts 92.85% 75% 100% 100% 49
apps/web-foldkit/src/test/pages-fixtures.ts 100% 100% 100% 100%
apps/web-foldkit/src/test/root-fixtures.ts 96% 100% 91.66% 94.11% 42
packages/domain/src/current-user.ts 100% 100% 100% 100%
Generated in workflow #1939 for commit afb91ae by the Vitest Coverage Report Action

@Makisuo
Makisuo merged commit 6b1fcaf into main Oct 31, 2026
7 of 8 checks passed
@Makisuo
Makisuo deleted the fix/org-scoped-auth branch October 31, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant