fix(aver-server): shared store, consent recovery, OAuth hardening#7
fix(aver-server): shared store, consent recovery, OAuth hardening#75queezer wants to merge 1 commit into
Conversation
- Share one Store across MCP sessions: build_router opens AverTools once and hands every session the same Arc<Mutex<_>>, preserving the single-writer invariant (per-session Store::open raced log rotation and claim-id pre-allocation on the same memory_dir). - Consent empty-grant trap: zero-scope approvals no longer skip the consent screen (consent_covers([],[]) == true trapped users forever), and POST /oauth/consent/revoke now exposes AuthDb::revoke_consent so users can revoke + re-consent over HTTP. - recall drops the advertised-but-discarded alpha parameter; hybrid weighting needs an embedding client the server does not have. - Token endpoint: RFC 6749 expires_in, section 5.2 JSON error bodies; refresh tokens rotate on use with RFC 6819 reuse detection (family revocation); 401s carry WWW-Authenticate: Bearer. - Hardening: auth-DB upsert failures surface as 500 instead of a swallowed stub; ALTER TABLE migrations only ignore duplicate-column errors; PKCE verify reuses the constant-time comparison; scope validation rejects empty path segments and caps length at 256; serialization failures map to McpError instead of an empty success block; stale origin/consolidate docs fixed; embedding-model label is a named const; AVER_PORT parse errors carry context.
📝 WalkthroughWalkthroughOAuth token rotation and consent revocation are added, MCP sessions share one tools store, and scope and recall contracts are tightened. Error responses, authentication handling, documentation, and integration coverage are updated accordingly. ChangesOAuth integrity
Shared MCP storage
Scope and tool contracts
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant OAuthToken
participant AuthDb
participant TokenFamily
Client->>OAuthToken: Submit authorization_code or refresh_token grant
OAuthToken->>AuthDb: Validate grant and load token state
AuthDb-->>OAuthToken: Return authorization or refresh data
OAuthToken->>TokenFamily: Rotate token or revoke reused family
TokenFamily-->>OAuthToken: Return token pair or error
OAuthToken-->>Client: Return JSON token response
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
crates/aver-server/src/oauth.rs (1)
11-24: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winDo not maintain a home-grown constant-time primitive.
Use a vetted implementation already approved by the project, such as a
ConstantTimeEqabstraction, instead of making two security boundaries depend on this local loop. Thesubtlecrate provides that dedicated contract. (docs.rs)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aver-server/src/oauth.rs` around lines 11 - 24, Replace the local constant_time_eq byte loop with the project-approved subtle::ConstantTimeEq implementation, updating callers in the PKCE and consent-flow comparisons to use its constant-time equality contract and convert the result to bool as needed. Remove the home-grown helper while preserving the existing equality behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aver-server/src/auth.rs`:
- Around line 577-582: Update the refresh-token model and rotation flow around
the revoked-token handling in the authentication implementation to persist a
unique family identifier, inherit the existing identifier when issuing rotated
tokens, and assign a new identifier for a fresh login or consent generation.
Change revoke_token_family and its callers to target that family identifier
rather than the broad user/client pair, ensuring reuse revokes only the affected
lineage while preserving current-token behavior.
- Line 531: Update refresh_access_token() so token revocation and
issue_token_pair() persistence execute within one database transaction. Make
revocation conditional on the presented token, require exactly one affected row
before minting, and commit both the revocation and replacement-row inserts
together so any failure rolls back the entire rotation.
In `@crates/aver-server/src/consent.rs`:
- Around line 490-495: Update the error branches in the authentication paths,
including the branches around html_error at the shown locations, to log the
detailed anyhow error server-side and pass a fixed generic message to html_error
instead of interpolating err. Apply the same behavior to all referenced paths
while preserving the existing HTTP status and response structure.
- Around line 989-995: Update AuthDb::revoke_consent in auth.rs to execute the
client_consents, access_tokens, and refresh_tokens updates within a single
database transaction, committing only after all succeed and rolling back on any
failure. Keep the existing error propagation used by the caller in consent.rs.
In `@crates/aver-server/src/http.rs`:
- Around line 452-457: Update the token response around the JSON containing
access_token and refresh_token to include Cache-Control: no-store and Pragma:
no-cache headers, while preserving the existing response body and status
behavior.
- Around line 423-429: Update the "authorization_code" validation branch in the
token request handler to include request.redirect_uri.is_empty() alongside the
existing required-field checks. Return token_error with BAD_REQUEST and
"invalid_request" before database exchange when redirect_uri is omitted, while
preserving validation for the other fields.
- Around line 414-417: Update the oauth_token extractor handling so form parsing
failures, including invalid content types, malformed encoding, and missing
required fields, are handled inside oauth_token rather than short-circuiting.
Route every such failure through token_error and return the token JSON contract
with error set to invalid_request, while preserving normal TokenRequest
processing.
---
Nitpick comments:
In `@crates/aver-server/src/oauth.rs`:
- Around line 11-24: Replace the local constant_time_eq byte loop with the
project-approved subtle::ConstantTimeEq implementation, updating callers in the
PKCE and consent-flow comparisons to use its constant-time equality contract and
convert the result to bool as needed. Remove the home-grown helper while
preserving the existing equality behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bdb3d5cd-d82d-4dc5-a676-525b844513ea
📒 Files selected for processing (17)
README.mdcrates/aver-server/src/auth.rscrates/aver-server/src/config.rscrates/aver-server/src/consent.rscrates/aver-server/src/http.rscrates/aver-server/src/mcp.rscrates/aver-server/src/oauth.rscrates/aver-server/src/origin.rscrates/aver-server/src/scope_resolution.rscrates/aver-server/src/tools.rscrates/aver-server/tests/http_routes.rscrates/aver-server/tests/oauth_consent_flow.rscrates/aver-server/tests/refresh_and_cors.rscrates/aver-server/tests/scope_mcp.rscrates/aver-server/tests/scope_resolution.rscrates/aver-server/tests/shared_store.rscrates/aver-server/tests/tools.rs
💤 Files with no reviewable changes (2)
- crates/aver-server/tests/scope_mcp.rs
- crates/aver-server/tests/tools.rs
| granted_scopes, | ||
| )?; | ||
| let refresh_token = existing_refresh_token.unwrap_or_else(random_session_id); | ||
| let refresh_token = random_session_id(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="crates/aver-server/src/auth.rs"
echo '--- outline ---'
ast-grep outline "$FILE" --view expanded || true
echo '--- lines 480-640 ---'
sed -n '480,640p' "$FILE" | cat -n
echo '--- search for refresh token / transaction keywords ---'
rg -n "refresh_token|transaction|begin|commit|rollback|affected|execute|query" "$FILE"Repository: 5queezer/aver
Length of output: 13032
🏁 Script executed:
sed -n '500,620p' crates/aver-server/src/auth.rs | cat -nRepository: 5queezer/aver
Length of output: 5902
🏁 Script executed:
#!/bin/bash
set -euo pipefail
grep -n "let refresh_token = random_session_id();" -n crates/aver-server/src/auth.rsRepository: 5queezer/aver
Length of output: 204
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- refresh_access_token call sites ---'
rg -n "refresh_access_token\(" crates -g '!target'
echo '--- transaction usage in auth.rs ---'
rg -n "transaction|BEGIN|COMMIT|ROLLBACK|savepoint|with_transaction|unchecked_transaction" crates/aver-server/src/auth.rs
echo '--- auth db open / connection setup ---'
sed -n '175,240p' crates/aver-server/src/auth.rs | cat -nRepository: 5queezer/aver
Length of output: 1403
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path("crates/aver-server/src/auth.rs")
text = path.read_text()
def extract(fn_name):
m = re.search(rf'(?m)^\s*(?:pub\s+)?fn\s+{re.escape(fn_name)}\b', text)
if not m:
return None
start = m.start()
brace = text.find("{", m.end())
depth = 0
i = brace
while i < len(text):
c = text[i]
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
return text[start:i+1]
i += 1
return None
for fn in ["issue_token_pair", "refresh_access_token", "revoke_token_family"]:
body = extract(fn)
print(f"--- {fn} ---")
if body:
print(body)
else:
print("not found")
print("--- transaction keywords in auth.rs ---")
for kw in ["transaction(", "begin", "commit", "rollback", "savepoint"]:
hits = [m.start() for m in re.finditer(kw, text, flags=re.I)]
print(kw, len(hits), hits[:10])
PYRepository: 5queezer/aver
Length of output: 4947
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '420,455p' crates/aver-server/src/http.rs | cat -nRepository: 5queezer/aver
Length of output: 1807
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- AuthDb struct and open ---'
sed -n '175,240p' crates/aver-server/src/auth.rs | cat -n
echo '--- any Clone/Arc/Mutex around AuthDb in repo ---'
rg -n "AuthDb|Arc<Mutex<|Mutex<.*AuthDb|Connection" crates/aver-server/src -g '!target'Repository: 5queezer/aver
Length of output: 5596
Move refresh rotation into one transaction. refresh_access_token() revokes the presented token, then issue_token_pair() persists the replacement rows in separate statements. If anything fails between those writes, the old token is dead and no successor exists. Make the revoke conditional, require one affected row, and commit revoke + mint together.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/auth.rs` at line 531, Update refresh_access_token() so
token revocation and issue_token_pair() persistence execute within one database
transaction. Make revocation conditional on the presented token, require exactly
one affected row before minting, and commit both the revocation and
replacement-row inserts together so any failure rolls back the entire rotation.
| if row.revoked_at.is_some() { | ||
| // Reuse of an already-rotated (or revoked) refresh token: treat | ||
| // as potential token theft and kill the whole family for this | ||
| // (user, client), RFC 6819 §5.2.2.3 style. | ||
| self.revoke_token_family(&row.user_id, row.client_id.as_deref())?; | ||
| anyhow::bail!("refresh token reuse detected; token family revoked"); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Track the actual refresh-token family.
Keying reuse revocation by (user_id, client_id) lets any retired token from an older login or consent generation revoke every current login for that client. Persist a family identifier, inherit it during rotation, and revoke only that lineage.
Also applies to: 596-610
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/auth.rs` around lines 577 - 582, Update the
refresh-token model and rotation flow around the revoked-token handling in the
authentication implementation to persist a unique family identifier, inherit the
existing identifier when issuing rotated tokens, and assign a new identifier for
a fresh login or consent generation. Change revoke_token_family and its callers
to target that family identifier rather than the broad user/client pair,
ensuring reuse revokes only the affected lineage while preserving current-token
behavior.
| Err(err) => { | ||
| return html_error( | ||
| StatusCode::INTERNAL_SERVER_ERROR, | ||
| "Server error", | ||
| &format!("Failed to authenticate the user: {err}"), | ||
| ); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Keep database errors out of browser responses.
These paths expose the full anyhow chain to remote clients. Log the detailed failure server-side; return a fixed generic message to the browser.
Also applies to: 733-738, 917-922
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/consent.rs` around lines 490 - 495, Update the error
branches in the authentication paths, including the branches around html_error
at the shown locations, to log the detailed anyhow error server-side and pass a
fixed generic message to html_error instead of interpolating err. Apply the same
behavior to all referenced paths while preserving the existing HTTP status and
response structure.
| if let Err(err) = auth_db.revoke_consent(&user.id, &form.client_id) { | ||
| return html_error( | ||
| StatusCode::INTERNAL_SERVER_ERROR, | ||
| "Server error", | ||
| &format!("Failed to revoke consent: {err}"), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate all revoke_consent definitions and call sites.
rg -n "revoke_consent" crates/aver-server -S
# Map the likely implementation file(s) before reading them.
for f in $(rg -l "revoke_consent" crates/aver-server -S); do
echo "### AST outline for $f"
ast-grep outline "$f" --view expanded || true
doneRepository: 5queezer/aver
Length of output: 10093
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the implementation around revoke_consent once located.
for f in $(rg -l "revoke_consent" crates/aver-server -S); do
echo "### $f"
rg -n -C 8 "revoke_consent" "$f" -S
doneRepository: 5queezer/aver
Length of output: 5301
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrow to the file under review and its surrounding module.
sed -n '930,1035p' crates/aver-server/src/consent.rsRepository: 5queezer/aver
Length of output: 3254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for transaction handling around consent revocation and token revocation paths.
rg -n -C 4 "revoke_consent|revoke_token|revoke.*consent|refresh token|access token|transaction|begin" crates/aver-server/src -SRepository: 5queezer/aver
Length of output: 9459
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "revoke_consent" crates/aver-server -S
for f in $(rg -l "revoke_consent" crates/aver-server -S); do
echo "### $f"
ast-grep outline "$f" --view expanded || true
rg -n -C 8 "revoke_consent" "$f" -S
done
sed -n '930,1035p' crates/aver-server/src/consent.rsRepository: 5queezer/aver
Length of output: 18126
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '760,790p' crates/aver-server/src/auth.rs
echo
sed -n '596,615p' crates/aver-server/src/auth.rsRepository: 5queezer/aver
Length of output: 2168
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "transaction\(|Transaction<|BEGIN TRANSACTION|commit\(|rollback\(" crates/aver-server/src/auth.rs -SRepository: 5queezer/aver
Length of output: 151
Make consent revocation atomic. crates/aver-server/src/auth.rs:765 updates client_consents, access_tokens, and refresh_tokens as separate autocommit writes. A failure between statements can leave consent revoked while live credentials remain usable. Wrap the three updates in one transaction.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/consent.rs` around lines 989 - 995, Update
AuthDb::revoke_consent in auth.rs to execute the client_consents, access_tokens,
and refresh_tokens updates within a single database transaction, committing only
after all succeed and rolling back on any failure. Keep the existing error
propagation used by the caller in consent.rs.
| async fn oauth_token( | ||
| axum::extract::State(state): axum::extract::State<HttpState>, | ||
| Form(request): Form<TokenRequest>, | ||
| ) -> Result<Json<serde_json::Value>, axum::http::StatusCode> { | ||
| ) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the handler and related error path.
FILE="crates/aver-server/src/http.rs"
echo "== file lines around oauth_token =="
sed -n '360,520p' "$FILE" | cat -n
echo
echo "== search for token_error and TokenRequest =="
rg -n "token_error|TokenRequest|oauth_token|FormRejection|invalid_request" "$FILE"Repository: 5queezer/aver
Length of output: 4987
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect Axum extractor usage elsewhere in the repo, if any.
rg -n "Result<.*Form<|Form<.*Result|extract::rejection|FormRejection" crates -SRepository: 5queezer/aver
Length of output: 151
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read Axum docs in-source references from the repository, if vendored or documented.
rg -n "supports wrapping extractors in Result|FormRejection|invalid_request" . -SRepository: 5queezer/aver
Length of output: 683
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="crates/aver-server/src/http.rs"
echo "== file excerpt =="
sed -n '360,520p' "$FILE" | cat -n
echo
echo "== relevant symbols =="
rg -n "token_error|TokenRequest|oauth_token|FormRejection|invalid_request" "$FILE" -nRepository: 5queezer/aver
Length of output: 4947
🌐 Web query:
Axum Form extractor rejection happens before handler Result extractor customize rejections docs.rs FormRejection invalid_request
💡 Result:
In Axum, extractors like Form run and potentially return a rejection before the handler function is ever called [1][2]. If an extraction fails, the request is terminated with an error response, and the handler logic is bypassed [1][3]. To customize the error response for a Form rejection, you have three primary options: 1. Handle the Rejection in the Handler: You can wrap the Form extractor in Result<Form<T>, FormRejection> within your handler's signature [1]. This allows you to match on the FormRejection enum and return a custom response [1][4]. async fn handler(payload: Result<Form, FormRejection>) -> impl IntoResponse { match payload { Ok(Form(form)) => { /* success */ } Err(rejection) => { // Return custom status or body (StatusCode::BAD_REQUEST, format!("Custom error: {}", rejection.body_text)) } } } 2. Use WithRejection (Recommended for reuse): The axum-extra crate provides a WithRejection wrapper [5]. It allows you to transform the default FormRejection into your own custom error type [3][5]. Your custom type must implement IntoResponse and From<FormRejection> [5]. async fn handler(WithRejection(Form(form), _): WithRejection<Form, MyCustomError>) { //... } 3. Create a Custom Extractor: You can implement FromRequest manually for a custom struct [6][7]. Inside your from_request implementation, you call the Form extractor, catch the Result, and map the Err(FormRejection) variant to your own error type before returning it [6]. Note that Form extraction may consume the request body [8][2]. Because of this, it is critical that the Form extractor be placed last in the handler's argument list if multiple extractors are used [8][2]. The FormRejection enum includes variants such as InvalidFormContentType, FailedToDeserializeForm, FailedToDeserializeFormBody, and BytesRejection, which you can inspect to tailor your error handling [9][10].
Citations:
- 1: https://docs.rs/axum/latest/axum/extract/index.html
- 2: https://tokio-rs-axum.mintlify.app/api/extractors/form
- 3: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/with_rejection.rs
- 4: How to custom extractors error response? tokio-rs/axum#354
- 5: https://docs.rs/axum-extra/latest/axum_extra/extract/struct.WithRejection.html
- 6: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/custom_extractor.rs
- 7: https://github.com/tokio-rs/axum/blob/60a0d283/examples/validator/src/main.rs
- 8: https://docs.rs/axum/latest/axum/struct.Form.html
- 9: https://docs.rs/axum/latest/axum/extract/rejection/enum.FormRejection.html
- 10: https://docs.rs/axum/latest/src/axum/extract/rejection.rs.html
Map form rejections to the token JSON contract. Form<TokenRequest> still short-circuits before oauth_token on bad content type, malformed encoding, or missing required fields, so those failures bypass token_error and return Axum’s default rejection. Wrap the extractor in Result or parse the form yourself so every token-endpoint input error returns invalid_request.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/http.rs` around lines 414 - 417, Update the
oauth_token extractor handling so form parsing failures, including invalid
content types, malformed encoding, and missing required fields, are handled
inside oauth_token rather than short-circuiting. Route every such failure
through token_error and return the token JSON contract with error set to
invalid_request, while preserving normal TokenRequest processing.
| "authorization_code" => { | ||
| if request.code.is_empty() | ||
| || request.client_id.is_empty() | ||
| || request.code_verifier.is_empty() | ||
| { | ||
| return Err(token_error(StatusCode::BAD_REQUEST, "invalid_request")); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Treat an omitted redirect_uri as invalid_request.
It is required for this authorization-code contract, but an empty value currently reaches the database exchange and becomes invalid_grant.
Proposed fix
if request.code.is_empty()
|| request.client_id.is_empty()
|| request.code_verifier.is_empty()
+ || request.redirect_uri.is_empty()
{📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "authorization_code" => { | |
| if request.code.is_empty() | |
| || request.client_id.is_empty() | |
| || request.code_verifier.is_empty() | |
| { | |
| return Err(token_error(StatusCode::BAD_REQUEST, "invalid_request")); | |
| } | |
| "authorization_code" => { | |
| if request.code.is_empty() | |
| || request.client_id.is_empty() | |
| || request.code_verifier.is_empty() | |
| || request.redirect_uri.is_empty() | |
| { | |
| return Err(token_error(StatusCode::BAD_REQUEST, "invalid_request")); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/http.rs` around lines 423 - 429, Update the
"authorization_code" validation branch in the token request handler to include
request.redirect_uri.is_empty() alongside the existing required-field checks.
Return token_error with BAD_REQUEST and "invalid_request" before database
exchange when redirect_uri is omitted, while preserving validation for the other
fields.
| Ok(Json(serde_json::json!({ | ||
| "access_token": tokens.access_token, | ||
| "refresh_token": tokens.refresh_token, | ||
| "token_type": "Bearer", | ||
| "expires_in": ACCESS_TOKEN_TTL_SECS, | ||
| }))) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Prevent caching of issued tokens.
Responses containing access and refresh tokens must include Cache-Control: no-store and Pragma: no-cache; returning bare Json omits both. (rfc-editor.org)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aver-server/src/http.rs` around lines 452 - 457, Update the token
response around the JSON containing access_token and refresh_token to include
Cache-Control: no-store and Pragma: no-cache headers, while preserving the
existing response body and status behavior.
Fixes the server-hardening code-review findings against
crates/aver-server. Re-verified each finding against master after 35a36be (token TTLs + loopback CORS already landed upstream;AuthDb::revoke_consentalready revoked tokens at the DB layer — the missing pieces were the HTTP route and the skip-loop gate).Findings → fixes
1. Per-session
Storebroke single-writer (MAJOR)build_routernow opensAverToolsonce at startup and the session factory clones the sameArc<Mutex<AverTools>>intoAverMcpService::from_shared_tools(http.rs,mcp.rs). rusqlite connections are Send-not-Sync, so the existingArc<Mutex<_>>pattern (same asAuthDb) is used. Per-session state stays limited to auth/scope request extensions. Nobusy_timeoutadded — that belongs to the core PR.tests/shared_store.rs— two concurrent MCP sessions write claims and recall sees both;mcp.rsunit testservices_sharing_tools_observe_each_others_writes(threaded writers).2. Consent empty-grant trap (MAJOR)
Zero-scope approvals recorded
granted_scopes=""andconsent_covers([], []) == trueskipped the consent screen forever, minting tokens that fail everyrequire_scope. Conservative fix (chosen over mapping empty→all SUPPORTED, which would grant more than asked): the skip path now requires a non-empty grant (consent.rs), so the screen re-renders andrecord_consent's upsert overwrites the empty row. NewPOST /oauth/consent/revokeroute (session-cookie + origin validated) exposes the previously unreachableAuthDb::revoke_consent.empty_scope_approval_does_not_trap_user_in_skip_loop(regression for the stuck loop),revoke_route_revokes_consent_and_tokens_and_allows_reconsent. Existingapprove_..._skips_screenupdated to grantclaims:read(it previously pinned the trap).3.
alphavalidated then discarded (MAJOR)recallno longer advertisesalpha(removed from the MCP schema andtools::RecallParams). Threading it was not viable: hybrid recall needs anEmbeddingClientand aver-server has none (aver-core built without theollamafeature; wiring one would break the offline/deterministic test rule). aver-core'srecall_hybrid_claims_with_alphais untouched for CLI/BEAM use. Instructions card needed no change (it never mentioned alpha).alphafields removed fromtests/tools.rs/tests/scope_mcp.rs; obsoleterecall_tool_rejects_alpha_outside_unit_intervaldeleted.4. OAuth gaps (MAJOR)
expires_in(ACCESS_TOKEN_TTL_SECS, nowpub).(user, client)token family (RFC 6819 §5.2.2.3 reuse detection).tests/refresh_and_cors.rsupdated (it pinned reuse) + newrefresh_token_reuse_revokes_token_family.AuthDb::revoke_consent— now reachable via the revoke route (covered end-to-end by the route test).5. Minor batch
/oauth/tokenreturns RFC 6749 §5.2 JSON errors (invalid_grant/invalid_request/unsupported_grant_type) —oauth_token_route_returns_rfc6749_json_errors.WWW-Authenticate: Bearer(RFC 6750 §3) — asserted inhttp_routes.rs.tracing_unavailable_warnstub removed:authenticate_requestreturnsResult; auth-DB failures surface as HTML 500.let _ =ALTER TABLE migrations →apply_column_migration: ignores only the duplicate-column error (rusqlite 0.32 maps it toErrorCode::Unknown/ext 1 + message; comment explains), propagates everything else; expiry backfills propagate too.constant_time_eq(moved tooauth.rs, shared with the CSRF check).///,a//b) and caps length at 256 bytes —rejects_empty_path_segments,rejects_overlong_scope.json_tool_resultmaps serialization failure toMcpErrorinstead of an empty success block.origin.rsmodule doc corrected;consolidatescope doc reworded to match the"all"-only implementation;"nomic-embed-text"is nowDEFAULT_EMBEDDING_MODEL;AVER_PORTparse has.context("invalid AVER_PORT").6.
adapters.rswiringStill test-only (only
tests/adapter_boundaries.rsreferences it; no production path inmain.rs/http.rs/mcp.rs). Left as-is — intentional ADR-0016 boundary.Verification
cargo fmtapplied.cargo clippy -p aver-server --all-targets -- -D warnings— clean.cargo test -p aver-server— 172 passed, 0 failed, 0 ignored across all test binaries (deterministic/offline, no#[ignore]).Docs: README endpoint list + consent/token flow paragraph updated (rotation, reuse detection,
expires_in, revoke route, empty-grant behavior). Nodoc/adr/*.mdedits; noautoresearch.jsonlchanges; aver-core untouched (privacy filtering / log-first ordering preserved).Summary by CodeRabbit
New Features
Bug Fixes
Documentation