style(server): cargo fmt + clippy fixes for v2 keystone (CI green)
All checks were successful
All checks were successful
The Task 2/3/authz commits failed CI at the first gate (cargo fmt --all --check), which short-circuited before clippy/build/test ran. Verified on the build host (172.16.3.30): the v2 server compiles and all 18 tests pass; only 3 cosmetic issues blocked CI, all fixed here: - cargo fmt --all (whitespace, 3 files) - clippy unused_imports: drop ViewerClaims from auth/mod.rs re-export - clippy doc_overindented_list_items: de-indent one doc line in sessions.rs Testing Agent confirmed fmt + clippy -D warnings + build --release + test are all green with these applied. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
//! → a CONTROL token (input is forwarded to the agent).
|
//! → a CONTROL token (input is forwarded to the agent).
|
||||||
//! - else the `view` permission (see [`SESSION_VIEW_PERMISSION`])
|
//! - else the `view` permission (see [`SESSION_VIEW_PERMISSION`])
|
||||||
//! → a VIEW-ONLY token (the relay refuses to forward this viewer's input;
|
//! → a VIEW-ONLY token (the relay refuses to forward this viewer's input;
|
||||||
//! video still streams).
|
//! video still streams).
|
||||||
//! - else → 403 (standard envelope).
|
//! - else → 403 (standard envelope).
|
||||||
//!
|
//!
|
||||||
//! This is why the gate is no longer a single `view` check: `view` is held by
|
//! This is why the gate is no longer a single `view` check: `view` is held by
|
||||||
@@ -95,8 +95,13 @@ pub async fn mint_viewer_token(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> ApiResult<Json<ViewerTokenResponse>> {
|
) -> ApiResult<Json<ViewerTokenResponse>> {
|
||||||
let session_id = Uuid::parse_str(&id)
|
let session_id = Uuid::parse_str(&id).map_err(|_| {
|
||||||
.map_err(|_| err(StatusCode::BAD_REQUEST, "INVALID_SESSION_ID", "Invalid session ID"))?;
|
err(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"INVALID_SESSION_ID",
|
||||||
|
"Invalid session ID",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// TIERED AUTHORIZATION GATE (closes audit CRITICAL #1 — authz-strength split).
|
// TIERED AUTHORIZATION GATE (closes audit CRITICAL #1 — authz-strength split).
|
||||||
// Authentication alone is not enough, and a single `view` check is too coarse
|
// Authentication alone is not enough, and a single `view` check is too coarse
|
||||||
@@ -127,13 +132,17 @@ pub async fn mint_viewer_token(
|
|||||||
|
|
||||||
// The session must exist (live session manager is the
|
// The session must exist (live session manager is the
|
||||||
// source of truth for joinable sessions, matching GET /api/sessions/:id).
|
// source of truth for joinable sessions, matching GET /api/sessions/:id).
|
||||||
let session = state.sessions.get_session(session_id).await.ok_or_else(|| {
|
let session = state
|
||||||
err(
|
.sessions
|
||||||
StatusCode::NOT_FOUND,
|
.get_session(session_id)
|
||||||
"SESSION_NOT_FOUND",
|
.await
|
||||||
"Session not found",
|
.ok_or_else(|| {
|
||||||
)
|
err(
|
||||||
})?;
|
StatusCode::NOT_FOUND,
|
||||||
|
"SESSION_NOT_FOUND",
|
||||||
|
"Session not found",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// Resolve tenancy (Phase-1: always the default tenant). Carried in the
|
// Resolve tenancy (Phase-1: always the default tenant). Carried in the
|
||||||
// claim so the WS and Phase-4 isolation can enforce it.
|
// claim so the WS and Phase-4 isolation can enforce it.
|
||||||
|
|||||||
@@ -111,7 +111,10 @@ mod tests {
|
|||||||
let key = generate_agent_key();
|
let key = generate_agent_key();
|
||||||
assert!(key.starts_with(AGENT_KEY_PREFIX));
|
assert!(key.starts_with(AGENT_KEY_PREFIX));
|
||||||
// prefix + 32 bytes * 2 hex chars
|
// prefix + 32 bytes * 2 hex chars
|
||||||
assert_eq!(key.len(), AGENT_KEY_PREFIX.len() + AGENT_KEY_RANDOM_BYTES * 2);
|
assert_eq!(
|
||||||
|
key.len(),
|
||||||
|
AGENT_KEY_PREFIX.len() + AGENT_KEY_RANDOM_BYTES * 2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub mod jwt;
|
|||||||
pub mod password;
|
pub mod password;
|
||||||
pub mod token_blacklist;
|
pub mod token_blacklist;
|
||||||
|
|
||||||
pub use jwt::{Claims, JwtConfig, ViewerAccess, ViewerClaims};
|
pub use jwt::{Claims, JwtConfig, ViewerAccess};
|
||||||
pub use password::{generate_random_password, hash_password, verify_password};
|
pub use password::{generate_random_password, hash_password, verify_password};
|
||||||
pub use token_blacklist::TokenBlacklist;
|
pub use token_blacklist::TokenBlacklist;
|
||||||
|
|
||||||
|
|||||||
@@ -338,16 +338,18 @@ async fn validate_agent_api_key(state: &AppState, api_key: &str) -> AgentKeyAuth
|
|||||||
crate::auth::agent_keys::verify_agent_key(db.pool(), api_key).await
|
crate::auth::agent_keys::verify_agent_key(db.pool(), api_key).await
|
||||||
{
|
{
|
||||||
// Resolve the trusted identity from the authenticated key's machine.
|
// Resolve the trusted identity from the authenticated key's machine.
|
||||||
let trusted_agent_id = match db::machines::get_machine_by_id(db.pool(), machine_id)
|
let trusted_agent_id =
|
||||||
.await
|
match db::machines::get_machine_by_id(db.pool(), machine_id).await {
|
||||||
{
|
Ok(Some(machine)) => Some(machine.agent_id),
|
||||||
Ok(Some(machine)) => Some(machine.agent_id),
|
Ok(None) => None,
|
||||||
Ok(None) => None,
|
Err(e) => {
|
||||||
Err(e) => {
|
tracing::error!(
|
||||||
tracing::error!("Failed to resolve machine for authenticated agent key: {}", e);
|
"Failed to resolve machine for authenticated agent key: {}",
|
||||||
None
|
e
|
||||||
}
|
);
|
||||||
};
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
return AgentKeyAuth::PerAgentKey(trusted_agent_id);
|
return AgentKeyAuth::PerAgentKey(trusted_agent_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,13 +405,16 @@ pub async fn viewer_ws_handler(
|
|||||||
// 1. Signature + expiry + `purpose == "viewer"`. A login JWT fails this
|
// 1. Signature + expiry + `purpose == "viewer"`. A login JWT fails this
|
||||||
// (wrong claim shape / no `purpose`), so login tokens are no longer
|
// (wrong claim shape / no `purpose`), so login tokens are no longer
|
||||||
// accepted on the viewer plane.
|
// accepted on the viewer plane.
|
||||||
let claims = state.jwt_config.validate_viewer_token(&token).map_err(|e| {
|
let claims = state
|
||||||
warn!(
|
.jwt_config
|
||||||
"Viewer connection rejected from {}: invalid viewer token: {}",
|
.validate_viewer_token(&token)
|
||||||
client_ip, e
|
.map_err(|e| {
|
||||||
);
|
warn!(
|
||||||
StatusCode::UNAUTHORIZED
|
"Viewer connection rejected from {}: invalid viewer token: {}",
|
||||||
})?;
|
client_ip, e
|
||||||
|
);
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
})?;
|
||||||
|
|
||||||
// 2. Revocation check on the WS plane (CRITICAL #2): a logged-out / revoked
|
// 2. Revocation check on the WS plane (CRITICAL #2): a logged-out / revoked
|
||||||
// token must not grant live remote control even before natural expiry.
|
// token must not grant live remote control even before natural expiry.
|
||||||
|
|||||||
Reference in New Issue
Block a user