Repository navigation
fix(ai): bill and credit-check the session's user, not a re-decoded token - #1200
Open
data-bot-coasys wants to merge 1 commit into
Open
data-bot-coasys wants to merge 1 commit into
data-bot-coasys wants to merge 1 commit into
Conversation
…oken The AI billing hooks (bill_prompt_if_authed, bill_embed_if_authed), the WS credit check (check_compute_credits_ws) and the /v1 chat, embeddings and audio handlers each decoded the caller's token again to find the user. An expired token does not decode, so a user whose token expired during a long prompt was charged nothing, and a user without credits whose token had expired since the connection opened passed the credit check and reached the model. Every request already resolves its user once, when it is authenticated (RequestContext::user_email for WS sessions). This commit gives the /v1 extractor the same field (AuthContext::user_email, resolved in from_request_parts) and passes the user, not the token, into the AI service's prompt/embed methods, the billing hooks, the credit checks and the harness bridge. Internal callers that passed None still pass None. Tests, red before this fix: - api::ai_ws::credit_check_tests: a session with an expired token and no credits got 500 "Service not initialized" instead of 403. - api::openai_compat::tests::embeddings_credit_check_uses_the_session_user: same, 500 instead of 429. Fixes #1175 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 20 minutes. View limit details
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. Comment |
This was referenced Oct 1, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1167 (
fix/user-token-node-operations). Only the last commit is this PR.What was wrong
At e9dfb4e (the head of #1167), every AI billing path decoded the caller's token a second time to find the user:
rust-executor/src/ai_service/mod.rs:1789and:1811—bill_prompt_if_authed/bill_embed_if_authedrun after the model call and calluser_email_from_tokenon the token they were handed.rust-executor/src/api/ai_ws.rs:23—check_compute_credits_wstakes the token and decodes it.rust-executor/src/api/openai_compat/chat.rs:542(theuser_emailhelper used by chat, completions, the streaming path andnative_tools.rs:57),embeddings.rs:40,audio.rs:99and:125— the/v1handlers decode the token for their credit check.rust-executor/src/api/auth.rs:23—AuthContext::to_request_contextdecoded once more.An expired token does not decode. So when a token expires during a long prompt, the hook finds no user and charges nobody. And on a WS connection whose token expired after it opened, the credit check finds no user and lets a user with zero credits through to the model.
What changed
The user is resolved once, when the request is authenticated, and everything downstream reads that:
api/auth.rs—AuthContextgetsuser_email, set infrom_request_parts;to_request_contextcopies it instead of decoding again.ai_service/mod.rs—prompt,prompt_messages,prompt_messages_stream,prompt_with_tools,embedandbill_and_forward_stream_resulttakeuser_email: Option<String>instead ofauth_token. The two hooks takeOption<&str>and no longer touchuser_email_from_token.Nonestill means "bill nobody", so the internal callers that passedNoneare unchanged.api/ai_ws.rs—check_compute_credits_ws(ctx.user_email.as_deref())at all four call sites;ai.prompt/ai.embedpassctx.user_email.api/openai_compat/{chat,native_tools,embeddings,audio}.rs— the credit checks and the AI service calls useauth.user_email; theuser_email(&auth)helper is gone.api/openai_compat/harness_bridge.rs+perspectives/interpretation/run.rs— the bridge carries the billed user. The WS harness path billscontext.user_emailwhen it was given a token; the auto-processor passes no token and still bills nobody.Out of scope, left as is: the transcription stream (
open_transcription_stream,transcribe_buffer,verify_stream_ownership) still takes the token, because it also uses it for stream ownership and the DID. Not in the issue's list.Red before, green after
Both tests were written and run before the fix, on e9dfb4e + the tests only:
500 "Service not initialized" is the model call: the zero-credit user got past the credit check. With the fix both return the credit error (403 on WS, 429 on
/v1) before reaching the service.The tests build a session the way the dispatcher does (user resolved, token since expired:
test_utils::expired_user_tokensigns a JWT with the node's key andexpan hour in the past, and each test asserts the token no longer decodes). The control test incredit_check_testsshows the same session with credits passes the check.The three existing streaming-billing tests in
openai_compat/tests.rsnow pass the user instead of minting a JWT; the JWT helper they used is removed.How to verify
141 tests pass locally. To see the red state: check out
origin/fix/user-token-node-operations, cherry-pick only the test hunks of this commit (ai_ws.rscredit_check_tests,test_utils.rs, theembeddings_credit_check_uses_the_session_usertest with theuser_emailfield removed from itsAuthContextliteral), and run the same command.Fixes #1175
🤖 Generated with Claude Code