Skip to main content

codex_helper_core/proxy/
admin.rs

1use std::net::SocketAddr;
2
3use axum::body::Body;
4use axum::extract::{ConnectInfo, State};
5use axum::http::{Request, Response, StatusCode};
6use axum::middleware::Next;
7
8use crate::dashboard_core::RemoteAdminAccessCapabilities;
9
10use super::{ADMIN_PORT_OFFSET, ADMIN_TOKEN_ENV_VAR, ADMIN_TOKEN_HEADER};
11
12#[derive(Clone)]
13pub(super) struct AdminAccessConfig {
14    token: Option<String>,
15}
16
17impl AdminAccessConfig {
18    pub(super) fn from_env() -> Self {
19        let token = std::env::var(ADMIN_TOKEN_ENV_VAR)
20            .ok()
21            .map(|value| value.trim().to_string())
22            .filter(|value| !value.is_empty());
23        Self { token }
24    }
25}
26
27pub(super) fn admin_access_capabilities() -> RemoteAdminAccessCapabilities {
28    let access = AdminAccessConfig::from_env();
29    RemoteAdminAccessCapabilities {
30        loopback_without_token: true,
31        remote_requires_token: true,
32        remote_enabled: access.token.is_some(),
33        token_header: ADMIN_TOKEN_HEADER.to_string(),
34        token_env_var: ADMIN_TOKEN_ENV_VAR.to_string(),
35    }
36}
37
38pub fn admin_port_for_proxy_port(proxy_port: u16) -> u16 {
39    if proxy_port <= u16::MAX - ADMIN_PORT_OFFSET {
40        proxy_port + ADMIN_PORT_OFFSET
41    } else if proxy_port > ADMIN_PORT_OFFSET {
42        proxy_port - ADMIN_PORT_OFFSET
43    } else {
44        1
45    }
46}
47
48pub fn local_proxy_base_url(port: u16) -> String {
49    format!("http://127.0.0.1:{port}")
50}
51
52pub fn local_admin_base_url_for_proxy_port(proxy_port: u16) -> String {
53    local_proxy_base_url(admin_port_for_proxy_port(proxy_port))
54}
55
56pub fn admin_base_url_from_proxy_base_url(proxy_base_url: &str) -> Option<String> {
57    let mut url = reqwest::Url::parse(proxy_base_url).ok()?;
58    let port = url.port_or_known_default()?;
59    url.set_port(Some(admin_port_for_proxy_port(port))).ok()?;
60    Some(url.to_string().trim_end_matches('/').to_string())
61}
62
63pub fn admin_loopback_addr_for_proxy_port(proxy_port: u16) -> SocketAddr {
64    SocketAddr::from(([127, 0, 0, 1], admin_port_for_proxy_port(proxy_port)))
65}
66
67fn is_admin_path(path: &str) -> bool {
68    path == "/__codex_helper" || path.starts_with("/__codex_helper/")
69}
70
71#[derive(Clone, serde::Serialize)]
72pub(super) struct ProxyAdminDiscovery {
73    pub(super) api_version: u32,
74    pub(super) service_name: &'static str,
75    pub(super) admin_base_url: String,
76}
77
78pub(super) async fn require_admin_access(
79    State(access): State<AdminAccessConfig>,
80    ConnectInfo(peer_addr): ConnectInfo<SocketAddr>,
81    req: Request<Body>,
82    next: Next,
83) -> Result<Response<Body>, (StatusCode, String)> {
84    if peer_addr.ip().is_loopback() {
85        return Ok(next.run(req).await);
86    }
87
88    let provided = req
89        .headers()
90        .get(ADMIN_TOKEN_HEADER)
91        .and_then(|value| value.to_str().ok())
92        .map(str::trim)
93        .filter(|value| !value.is_empty());
94    let remote_allowed = access
95        .token
96        .as_deref()
97        .is_some_and(|expected| Some(expected) == provided);
98    if remote_allowed {
99        return Ok(next.run(req).await);
100    }
101
102    Err((
103        StatusCode::FORBIDDEN,
104        format!(
105            "admin routes are loopback-only; set {ADMIN_TOKEN_ENV_VAR} and send {ADMIN_TOKEN_HEADER} to allow remote access"
106        ),
107    ))
108}
109
110pub(super) async fn require_admin_path_only(
111    req: Request<Body>,
112    next: Next,
113) -> Result<Response<Body>, StatusCode> {
114    if is_admin_path(req.uri().path()) {
115        return Ok(next.run(req).await);
116    }
117    Err(StatusCode::NOT_FOUND)
118}
119
120pub(super) async fn reject_admin_paths_from_proxy(
121    req: Request<Body>,
122    next: Next,
123) -> Result<Response<Body>, StatusCode> {
124    if is_admin_path(req.uri().path()) {
125        return Err(StatusCode::NOT_FOUND);
126    }
127    Ok(next.run(req).await)
128}