auth.rs

 1use crate::{AppState, Error, db::UserId, rpc::Principal};
 2use anyhow::Context as _;
 3use axum::{
 4    http::{self, Request, StatusCode},
 5    middleware::Next,
 6    response::IntoResponse,
 7};
 8use cloud_api_types::GetAuthenticatedUserResponse;
 9pub use rpc::auth::random_token;
10use std::sync::Arc;
11
12/// Validates the authorization header and adds an Extension<Principal> to the request.
13/// Authorization: <user-id> <token>
14///   <token> can be an access_token attached to that user, or an access token of an admin
15///   or (in development) the string ADMIN:<config.api_token>.
16/// Authorization: "dev-server-token" <token>
17pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
18    let mut auth_header = req
19        .headers()
20        .get(http::header::AUTHORIZATION)
21        .and_then(|header| header.to_str().ok())
22        .ok_or_else(|| {
23            Error::http(
24                StatusCode::UNAUTHORIZED,
25                "missing authorization header".to_string(),
26            )
27        })?
28        .split_whitespace();
29
30    let state = req.extensions().get::<Arc<AppState>>().unwrap();
31
32    let first = auth_header.next().unwrap_or("");
33    if first == "dev-server-token" {
34        Err(Error::http(
35            StatusCode::UNAUTHORIZED,
36            "Dev servers were removed in Zed 0.157 please upgrade to SSH remoting".to_string(),
37        ))?;
38    }
39
40    let user_id = UserId(first.parse().map_err(|_| {
41        Error::http(
42            StatusCode::BAD_REQUEST,
43            "missing user id in authorization header".to_string(),
44        )
45    })?);
46
47    let access_token = auth_header.next().ok_or_else(|| {
48        Error::http(
49            StatusCode::BAD_REQUEST,
50            "missing access token in authorization header".to_string(),
51        )
52    })?;
53
54    let http_client = state.http_client.clone().expect("no HTTP client");
55
56    let response = http_client
57        .get(format!("{}/client/users/me", state.config.zed_cloud_url()))
58        .header("Content-Type", "application/json")
59        .header("Authorization", format!("{user_id} {access_token}"))
60        .send()
61        .await
62        .context("failed to validate access token")?;
63    if let Ok(response) = response.error_for_status() {
64        let response_body: GetAuthenticatedUserResponse = response
65            .json()
66            .await
67            .context("failed to parse response body")?;
68
69        let user_id = UserId(response_body.user.id);
70
71        let user = state
72            .db
73            .get_user_by_id(user_id)
74            .await?
75            .with_context(|| format!("user {user_id} not found"))?;
76
77        req.extensions_mut().insert(Principal::User(user));
78        return Ok::<_, Error>(next.run(req).await);
79    }
80
81    Err(Error::http(
82        StatusCode::UNAUTHORIZED,
83        "invalid credentials".to_string(),
84    ))
85}