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}