Skip to content

SMOODEV-1863: Route th api agents through the user JWT (like th api crm)#174

Open
brentrager wants to merge 1 commit into
mainfrom
SMOODEV-1863-agents-user-jwt
Open

SMOODEV-1863: Route th api agents through the user JWT (like th api crm)#174
brentrager wants to merge 1 commit into
mainfrom
SMOODEV-1863-agents-user-jwt

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

th api agents show/update 403 on child orgs. The command authenticated with the org-locked M2M client_credentials token (require_authed()), and M2M tokens are intentionally bound to a single org — so acting on a child org is forbidden. th api agents list "worked" only because the native Rust LIST handler didn't apply the M2M org-lock (a leak — fixed in the paired monorepo PR).

th api crm never had this problem: it authenticates as the logged-in user (Supabase JWT via UserClient::from_user_session()), whose org membership carries cross-org (parent-org admin) access.

Fix — mirror th api crm exactly

crates/smooth-cli/src/smooai/agents.rs: build a UserClient at the top of cmd (the same selection crm.rs:564 etc. make) and run every verb on the user session — list/show/update/delete/summary/regenerate/knowledge/mint/create/generate-config. Drops the M2M require_authed() gate; org resolution moves to crate::active_org::resolve (the require_active_org client arg was unused).

crates/smooth-cli/src/smooai/user_client.rs: add put (set-knowledge) and post_empty (the no-body regenerate-* posts) so UserClient covers the agent verbs' method surface. post's signature is unchanged, so the th api crm call sites are untouched.

Auth selection (file:line)

agents.rs cmd() now opens with let client = UserClient::from_user_session().await?; — identical mechanism to th api crm (crm.rs:564, 616, 732 …). Previously let client = require_authed().await?; (M2M).

Tests

cargo test -p smooai-smooth-cli --bin th smooai::agents — 13 passed (the build_update_body/build_mint_body body-builder tests, unaffected by the client-selection change). The auth-selection itself is a wiring change with no unit-testable branch, and the workspace has no HTTP-mock infra; live verification is left to the human session per the pairing plan. fmt clean, clippy 0 errors.

Paired PR (MUST land together)

monorepo #2531 org-locks the native agents LIST handler against M2M. That PR makes M2M stricter (closing the LIST leak); this PR restores + extends access (list+show+update) via the user session. Landing #2531 alone would 403 the currently-working master-M2M list <child-org>. Strictly tighter M2M, never looser.

🤖 Generated with Claude Code

…i crm`)

`th api agents show/update` 403'd on child orgs because the command
authenticated with the org-locked M2M `client_credentials` token
(`require_authed()`), and M2M tokens are intentionally bound to a single
org. `th api crm` already avoids this by authenticating as the logged-in
user (Supabase JWT via `UserClient::from_user_session()`), whose org
membership carries cross-org (parent-org admin) access.

Mirror that exactly: `th api agents` now builds a `UserClient` at the top
of `cmd` and every verb (list/show/update/delete/summary/regenerate/
knowledge/mint/create/generate-config) runs on the user session. Drops the
M2M `require_authed()` gate; org resolution moves to `active_org::resolve`
(the arg `require_active_org` ignored anyway). `UserClient` gains `put`
(set-knowledge) and `post_empty` (the no-body `regenerate-*` posts) so it
covers the agent verbs' method surface — `post`'s signature is unchanged,
so `th api crm` call sites are untouched.

Paired with the monorepo PR (#2531) that org-locks the native agents LIST
handler against M2M. They MUST land together: the monorepo PR makes M2M
stricter (closing the LIST leak), this one restores + extends Brent's
access (list+show+update) via his user membership. Strictly tighter M2M,
never looser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 28a2240

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager

Copy link
Copy Markdown
Contributor Author

Superseded by #105 (root-cause fix at require_authed() — honors the user JWT for ALL th api/th admin groups, not just agents). Reviving #105 instead per the M2M Option A plan; holding this open (not auto-merging) so it can be reopened if #105 doesn't pan out. Your user_client.rs put/post_empty additions may still be worth cherry-picking if any agents verb needs them once #105 lands.

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