feat(auth): the door that can delegate no longer needs a person's session
`/mcp` — `email_send`, `slack_post`, `delegate` — authenticated with `authenticate`, which accepts only `full`. Nothing hands it a token today, so this cost nothing yet; the moment something did, the only credential that worked would have been an owner's session, held by an agent runtime. `SCOPE_AGENT_DOOR` is that credential's narrow form. `full` still works, so the UI and every human caller are unaffected, and the route now names what it accepts rather than accepting everything by default. The test that matters is not that each scope opens its own route: it is that holding one grants nothing the other has. Both tokens live where an agent can read them. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f52cff3e04
commit
22eeaa6f15
@@ -237,12 +237,21 @@ async fn mint_grant(
|
||||
}
|
||||
|
||||
/// Authenticate the bearer header → workspace/user. `None` if missing/invalid.
|
||||
///
|
||||
/// Accepts [`cm_auth::SCOPE_AGENT_DOOR`] as well as a person's session. This
|
||||
/// route is the one that can `delegate`, and the thing that will eventually
|
||||
/// hold a token for it is an agent runtime — so the narrow credential has to
|
||||
/// exist before something reaches for the only one that does.
|
||||
async fn authed(state: &AppState, headers: &HeaderMap) -> Option<cm_auth::AuthedUser> {
|
||||
let token = headers
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.strip_prefix("Bearer "))?;
|
||||
state.auth.authenticate(token).await.ok()
|
||||
state
|
||||
.auth
|
||||
.authenticate_scoped(token, cm_auth::SCOPE_AGENT_DOOR)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Resolve the specific claw making the call. Our ZeroClaw fork stamps the
|
||||
|
||||
Reference in New Issue
Block a user