fix(dashboard): attach Keycloak token on agent API calls (#90)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 6s
CI / Deploy Agent (push) Successful in 4m8s
CI / Deploy Dashboard (push) Successful in 4m58s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 6s
CI / Deploy Agent (push) Successful in 4m8s
CI / Deploy Dashboard (push) Successful in 4m58s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
This commit was merged in pull request #90.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
//! Authenticated HTTP client for talking to the compliance-agent.
|
||||
//!
|
||||
//! Every dashboard server function that hits `comp-dev.meghsakha.com/api/v1/*`
|
||||
//! must go through here so the Keycloak access token from the user's
|
||||
//! session is attached as `Authorization: Bearer <token>`. Without it
|
||||
//! the agent's M7.1 `require_jwt_auth` middleware rejects with 401
|
||||
//! "Missing authorization header".
|
||||
//!
|
||||
//! When Keycloak is not configured (dev convenience), the helper
|
||||
//! returns an unauthenticated builder — matching the agent's
|
||||
//! pass-through behavior in the same state.
|
||||
|
||||
use dioxus::prelude::ServerFnError;
|
||||
use dioxus_fullstack::FullstackContext;
|
||||
use reqwest::Method;
|
||||
|
||||
use super::auth::LOGGED_IN_USER_SESS_KEY;
|
||||
use super::server_state::ServerState;
|
||||
use super::user_state::UserStateInner;
|
||||
|
||||
/// Build a `RequestBuilder` for `<agent_api_url><path>` with the
|
||||
/// session's access token attached. `path` should include a leading
|
||||
/// `/`, e.g. `"/api/v1/repositories"`.
|
||||
pub async fn agent_request(
|
||||
method: Method,
|
||||
path: &str,
|
||||
) -> Result<reqwest::RequestBuilder, ServerFnError> {
|
||||
let state: ServerState = FullstackContext::extract().await?;
|
||||
let url = format!("{}{}", state.agent_api_url, path);
|
||||
let mut req = reqwest::Client::new().request(method, &url);
|
||||
req = attach_token(req, &state).await?;
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
/// Same as [`agent_request`] but for `GET`. Convenience for the common case.
|
||||
pub async fn agent_get(path: &str) -> Result<reqwest::RequestBuilder, ServerFnError> {
|
||||
agent_request(Method::GET, path).await
|
||||
}
|
||||
|
||||
/// Attach the session's bearer token if Keycloak is configured AND the
|
||||
/// session has a logged-in user. Otherwise leave the request as-is.
|
||||
///
|
||||
/// The Keycloak-disabled path mirrors the dashboard's `require_auth`
|
||||
/// middleware, which short-circuits when `state.keycloak.is_none()`.
|
||||
async fn attach_token(
|
||||
req: reqwest::RequestBuilder,
|
||||
state: &ServerState,
|
||||
) -> Result<reqwest::RequestBuilder, ServerFnError> {
|
||||
if state.keycloak.is_none() {
|
||||
return Ok(req);
|
||||
}
|
||||
let session: tower_sessions::Session = FullstackContext::extract().await?;
|
||||
let user: Option<UserStateInner> = session
|
||||
.get(LOGGED_IN_USER_SESS_KEY)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(format!("session read failed: {e}")))?;
|
||||
Ok(match user {
|
||||
Some(u) => req.bearer_auth(u.access_token),
|
||||
None => req,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user