mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Use scoped remote control server tokens
This commit is contained in:
@@ -2,6 +2,9 @@ use super::protocol::EnrollRemoteServerRequest;
|
||||
use super::protocol::EnrollRemoteServerResponse;
|
||||
use super::protocol::RemoteControlTarget;
|
||||
use axum::http::HeaderMap;
|
||||
use chrono::DateTime;
|
||||
use chrono::Duration;
|
||||
use chrono::Utc;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_state::RemoteControlEnrollmentRecord;
|
||||
use codex_state::StateRuntime;
|
||||
@@ -13,6 +16,8 @@ use tracing::warn;
|
||||
|
||||
const REMOTE_CONTROL_ENROLL_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
const REMOTE_CONTROL_RESPONSE_BODY_MAX_BYTES: usize = 4096;
|
||||
const REMOTE_CONTROL_SERVER_WEBSOCKET_SCOPE: &str = "remote_control_server_websocket";
|
||||
const REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECONDS: i64 = 60;
|
||||
|
||||
const REQUEST_ID_HEADER: &str = "x-request-id";
|
||||
const OAI_REQUEST_ID_HEADER: &str = "x-oai-request-id";
|
||||
@@ -28,6 +33,24 @@ pub(super) struct RemoteControlEnrollment {
|
||||
pub(super) server_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct RemoteControlServerToken {
|
||||
pub(super) bearer_token: String,
|
||||
pub(super) expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RemoteControlServerToken {
|
||||
pub(super) fn expires_soon(&self, now: DateTime<Utc>) -> bool {
|
||||
self.expires_at <= now + Duration::seconds(REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECONDS)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct RemoteControlEnrollmentResult {
|
||||
pub(super) enrollment: RemoteControlEnrollment,
|
||||
pub(super) server_token: Option<RemoteControlServerToken>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct RemoteControlConnectionAuth {
|
||||
pub(super) authorization_header_value: String,
|
||||
@@ -191,7 +214,7 @@ pub(crate) fn format_headers(headers: &HeaderMap) -> String {
|
||||
pub(super) async fn enroll_remote_control_server(
|
||||
remote_control_target: &RemoteControlTarget,
|
||||
auth: &RemoteControlConnectionAuth,
|
||||
) -> io::Result<RemoteControlEnrollment> {
|
||||
) -> io::Result<RemoteControlEnrollmentResult> {
|
||||
let enroll_url = &remote_control_target.enroll_url;
|
||||
let server_name = gethostname().to_string_lossy().trim().to_string();
|
||||
let request = EnrollRemoteServerRequest {
|
||||
@@ -246,18 +269,66 @@ pub(super) async fn enroll_remote_control_server(
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(RemoteControlEnrollment {
|
||||
account_id: auth.account_id.clone(),
|
||||
environment_id: enrollment.environment_id,
|
||||
server_id: enrollment.server_id,
|
||||
server_name,
|
||||
let server_token = remote_control_server_token_from_response(&enrollment)?;
|
||||
Ok(RemoteControlEnrollmentResult {
|
||||
enrollment: RemoteControlEnrollment {
|
||||
account_id: auth.account_id.clone(),
|
||||
environment_id: enrollment.environment_id,
|
||||
server_id: enrollment.server_id,
|
||||
server_name,
|
||||
},
|
||||
server_token,
|
||||
})
|
||||
}
|
||||
|
||||
fn remote_control_server_token_from_response(
|
||||
enrollment: &EnrollRemoteServerResponse,
|
||||
) -> io::Result<Option<RemoteControlServerToken>> {
|
||||
let Some(remote_control_token) = enrollment.remote_control_token.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let expires_at = enrollment.expires_at.as_ref().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"remote control enrollment response included a token without expires_at",
|
||||
)
|
||||
})?;
|
||||
let scopes = enrollment.scopes.as_ref().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"remote control enrollment response included a token without scopes",
|
||||
)
|
||||
})?;
|
||||
if !scopes
|
||||
.iter()
|
||||
.any(|scope| scope == REMOTE_CONTROL_SERVER_WEBSOCKET_SCOPE)
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"remote control enrollment response token is missing server websocket scope",
|
||||
));
|
||||
}
|
||||
let expires_at = DateTime::parse_from_rfc3339(expires_at)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid remote control token expires_at: {err}"),
|
||||
)
|
||||
})?
|
||||
.with_timezone(&Utc);
|
||||
|
||||
Ok(Some(RemoteControlServerToken {
|
||||
bearer_token: remote_control_token.clone(),
|
||||
expires_at,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::transport::remote_control::protocol::normalize_remote_control_url;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_state::StateRuntime;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -423,6 +494,50 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_control_server_token_from_response_accepts_legacy_response_without_token() {
|
||||
assert_eq!(
|
||||
remote_control_server_token_from_response(&EnrollRemoteServerResponse {
|
||||
server_id: "srv_e_test".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
remote_control_token: None,
|
||||
expires_at: None,
|
||||
scopes: None,
|
||||
})
|
||||
.expect("legacy response should parse"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_control_server_token_from_response_parses_scoped_token() {
|
||||
let server_token = remote_control_server_token_from_response(&EnrollRemoteServerResponse {
|
||||
server_id: "srv_e_test".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
remote_control_token: Some("remote-control-token".to_string()),
|
||||
expires_at: Some("2026-04-09T12:00:00Z".to_string()),
|
||||
scopes: Some(vec!["remote_control_server_websocket".to_string()]),
|
||||
})
|
||||
.expect("token response should parse")
|
||||
.expect("token should be present");
|
||||
|
||||
assert_eq!(server_token.bearer_token, "remote-control-token");
|
||||
assert!(
|
||||
!server_token.expires_soon(
|
||||
DateTime::parse_from_rfc3339("2026-04-09T11:58:30Z")
|
||||
.expect("timestamp should parse")
|
||||
.with_timezone(&Utc)
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
server_token.expires_soon(
|
||||
DateTime::parse_from_rfc3339("2026-04-09T11:59:30Z")
|
||||
.expect("timestamp should parse")
|
||||
.with_timezone(&Utc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn enroll_remote_control_server_parse_failure_includes_response_body() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
|
||||
@@ -25,6 +25,12 @@ pub(super) struct EnrollRemoteServerRequest {
|
||||
pub(super) struct EnrollRemoteServerResponse {
|
||||
pub(super) server_id: String,
|
||||
pub(super) environment_id: String,
|
||||
#[serde(default)]
|
||||
pub(super) remote_control_token: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(super) expires_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(super) scopes: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::transport::remote_control::client_tracker::ClientTracker;
|
||||
use crate::transport::remote_control::client_tracker::REMOTE_CONTROL_IDLE_SWEEP_INTERVAL;
|
||||
use crate::transport::remote_control::enroll::RemoteControlConnectionAuth;
|
||||
use crate::transport::remote_control::enroll::RemoteControlEnrollment;
|
||||
use crate::transport::remote_control::enroll::RemoteControlServerToken;
|
||||
use crate::transport::remote_control::enroll::enroll_remote_control_server;
|
||||
use crate::transport::remote_control::enroll::format_headers;
|
||||
use crate::transport::remote_control::enroll::load_persisted_remote_control_enrollment;
|
||||
@@ -17,6 +18,7 @@ use super::protocol::ServerEnvelope;
|
||||
use super::protocol::StreamId;
|
||||
use axum::http::HeaderValue;
|
||||
use base64::Engine;
|
||||
use chrono::Utc;
|
||||
use codex_core::util::backoff;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::UnauthorizedRecovery;
|
||||
@@ -113,6 +115,12 @@ struct WebsocketState {
|
||||
next_seq_id_by_stream: HashMap<(ClientId, StreamId), u64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RemoteControlEnrollmentState {
|
||||
enrollment: Option<RemoteControlEnrollment>,
|
||||
server_token: Option<RemoteControlServerToken>,
|
||||
}
|
||||
|
||||
pub(crate) struct RemoteControlWebsocket {
|
||||
remote_control_url: String,
|
||||
remote_control_target: Option<RemoteControlTarget>,
|
||||
@@ -120,7 +128,7 @@ pub(crate) struct RemoteControlWebsocket {
|
||||
auth_manager: Arc<AuthManager>,
|
||||
shutdown_token: CancellationToken,
|
||||
reconnect_attempt: u64,
|
||||
enrollment: Option<RemoteControlEnrollment>,
|
||||
enrollment_state: RemoteControlEnrollmentState,
|
||||
auth_recovery: UnauthorizedRecovery,
|
||||
client_tracker: Arc<Mutex<ClientTracker>>,
|
||||
state: Arc<Mutex<WebsocketState>>,
|
||||
@@ -191,7 +199,7 @@ impl RemoteControlWebsocket {
|
||||
auth_manager,
|
||||
shutdown_token,
|
||||
reconnect_attempt: 0,
|
||||
enrollment: None,
|
||||
enrollment_state: RemoteControlEnrollmentState::default(),
|
||||
auth_recovery,
|
||||
client_tracker: Arc::new(Mutex::new(client_tracker)),
|
||||
state: Arc::new(Mutex::new(WebsocketState {
|
||||
@@ -304,16 +312,14 @@ impl RemoteControlWebsocket {
|
||||
}
|
||||
return ConnectOutcome::Disabled;
|
||||
}
|
||||
connect_result = connect_remote_control_websocket_with_options(
|
||||
ConnectRemoteControlWebsocketOptions {
|
||||
remote_control_target: &remote_control_target,
|
||||
state_db: self.state_db.as_deref(),
|
||||
auth_manager: &self.auth_manager,
|
||||
auth_recovery: &mut self.auth_recovery,
|
||||
enrollment: &mut self.enrollment,
|
||||
subscribe_cursor: subscribe_cursor.as_deref(),
|
||||
app_server_client_name,
|
||||
},
|
||||
connect_result = connect_remote_control_websocket(
|
||||
&remote_control_target,
|
||||
self.state_db.as_deref(),
|
||||
&self.auth_manager,
|
||||
&mut self.auth_recovery,
|
||||
&mut self.enrollment_state,
|
||||
subscribe_cursor.as_deref(),
|
||||
app_server_client_name,
|
||||
) => connect_result,
|
||||
};
|
||||
|
||||
@@ -683,6 +689,7 @@ fn build_remote_control_websocket_request(
|
||||
websocket_url: &str,
|
||||
enrollment: &RemoteControlEnrollment,
|
||||
auth: &RemoteControlConnectionAuth,
|
||||
server_token: Option<&RemoteControlServerToken>,
|
||||
subscribe_cursor: Option<&str>,
|
||||
) -> io::Result<tungstenite::http::Request<()>> {
|
||||
let mut request = websocket_url.into_client_request().map_err(|err| {
|
||||
@@ -703,7 +710,10 @@ fn build_remote_control_websocket_request(
|
||||
"x-codex-protocol-version",
|
||||
REMOTE_CONTROL_PROTOCOL_VERSION,
|
||||
)?;
|
||||
set_remote_control_header(headers, "authorization", &auth.authorization_header_value)?;
|
||||
let authorization_header_value = server_token
|
||||
.map(|server_token| format!("Bearer {}", server_token.bearer_token))
|
||||
.unwrap_or_else(|| auth.authorization_header_value.clone());
|
||||
set_remote_control_header(headers, "authorization", &authorization_header_value)?;
|
||||
set_remote_control_header(headers, REMOTE_CONTROL_ACCOUNT_ID_HEADER, &auth.account_id)?;
|
||||
if auth.is_fedramp_account {
|
||||
set_remote_control_header(headers, REMOTE_CONTROL_FEDRAMP_HEADER, "true")?;
|
||||
@@ -774,13 +784,12 @@ pub(crate) async fn load_remote_control_auth(
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) async fn connect_remote_control_websocket(
|
||||
async fn connect_remote_control_websocket(
|
||||
remote_control_target: &RemoteControlTarget,
|
||||
state_db: Option<&StateRuntime>,
|
||||
auth_manager: &Arc<AuthManager>,
|
||||
auth_recovery: &mut UnauthorizedRecovery,
|
||||
enrollment: &mut Option<RemoteControlEnrollment>,
|
||||
enrollment_state: &mut RemoteControlEnrollmentState,
|
||||
subscribe_cursor: Option<&str>,
|
||||
app_server_client_name: Option<&str>,
|
||||
) -> io::Result<(
|
||||
@@ -792,7 +801,7 @@ pub(super) async fn connect_remote_control_websocket(
|
||||
state_db,
|
||||
auth_manager,
|
||||
auth_recovery,
|
||||
enrollment,
|
||||
enrollment_state,
|
||||
subscribe_cursor,
|
||||
app_server_client_name,
|
||||
})
|
||||
@@ -804,7 +813,7 @@ struct ConnectRemoteControlWebsocketOptions<'a> {
|
||||
state_db: Option<&'a StateRuntime>,
|
||||
auth_manager: &'a Arc<AuthManager>,
|
||||
auth_recovery: &'a mut UnauthorizedRecovery,
|
||||
enrollment: &'a mut Option<RemoteControlEnrollment>,
|
||||
enrollment_state: &'a mut RemoteControlEnrollmentState,
|
||||
subscribe_cursor: Option<&'a str>,
|
||||
app_server_client_name: Option<&'a str>,
|
||||
}
|
||||
@@ -820,7 +829,7 @@ async fn connect_remote_control_websocket_with_options(
|
||||
state_db,
|
||||
auth_manager,
|
||||
auth_recovery,
|
||||
enrollment,
|
||||
enrollment_state,
|
||||
subscribe_cursor,
|
||||
app_server_client_name,
|
||||
} = options;
|
||||
@@ -828,21 +837,26 @@ async fn connect_remote_control_websocket_with_options(
|
||||
ensure_rustls_crypto_provider();
|
||||
|
||||
let auth = load_remote_control_auth(auth_manager).await?;
|
||||
let enrollment_account_id = enrollment.as_ref().map(|enrollment| &enrollment.account_id);
|
||||
let enrollment_account_id = enrollment_state
|
||||
.enrollment
|
||||
.as_ref()
|
||||
.map(|enrollment| &enrollment.account_id);
|
||||
if enrollment_account_id.is_some_and(|account_id| account_id != &auth.account_id) {
|
||||
info!(
|
||||
"clearing in-memory remote control enrollment because account id changed: websocket_url={}, previous_account_id={:?}, current_account_id={:?}",
|
||||
remote_control_target.websocket_url,
|
||||
enrollment
|
||||
enrollment_state
|
||||
.enrollment
|
||||
.as_ref()
|
||||
.map(|enrollment| enrollment.account_id.as_str()),
|
||||
auth.account_id
|
||||
);
|
||||
*enrollment = None;
|
||||
enrollment_state.enrollment = None;
|
||||
enrollment_state.server_token = None;
|
||||
}
|
||||
|
||||
if enrollment.is_none() {
|
||||
*enrollment = load_persisted_remote_control_enrollment(
|
||||
if enrollment_state.enrollment.is_none() {
|
||||
enrollment_state.enrollment = load_persisted_remote_control_enrollment(
|
||||
state_db,
|
||||
remote_control_target,
|
||||
&auth.account_id,
|
||||
@@ -851,24 +865,38 @@ async fn connect_remote_control_websocket_with_options(
|
||||
.await;
|
||||
}
|
||||
|
||||
if enrollment.is_none() {
|
||||
if enrollment_state
|
||||
.server_token
|
||||
.as_ref()
|
||||
.is_some_and(|server_token| server_token.expires_soon(Utc::now()))
|
||||
{
|
||||
info!(
|
||||
"remote control server token is near expiry; creating a new enrollment: websocket_url={}, account_id={}",
|
||||
remote_control_target.websocket_url, auth.account_id
|
||||
);
|
||||
enrollment_state.enrollment = None;
|
||||
enrollment_state.server_token = None;
|
||||
}
|
||||
|
||||
if enrollment_state.enrollment.is_none() {
|
||||
info!(
|
||||
"creating new remote control enrollment: websocket_url={}, enroll_url={}, account_id={}",
|
||||
remote_control_target.websocket_url, remote_control_target.enroll_url, auth.account_id
|
||||
);
|
||||
let new_enrollment = match enroll_remote_control_server(remote_control_target, &auth).await
|
||||
{
|
||||
Ok(new_enrollment) => new_enrollment,
|
||||
Err(err)
|
||||
if err.kind() == ErrorKind::PermissionDenied
|
||||
&& recover_remote_control_auth(auth_recovery).await =>
|
||||
{
|
||||
return Err(io::Error::other(format!(
|
||||
"{err}; retrying after auth recovery"
|
||||
)));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let enrollment_result =
|
||||
match enroll_remote_control_server(remote_control_target, &auth).await {
|
||||
Ok(enrollment_result) => enrollment_result,
|
||||
Err(err)
|
||||
if err.kind() == ErrorKind::PermissionDenied
|
||||
&& recover_remote_control_auth(auth_recovery).await =>
|
||||
{
|
||||
return Err(io::Error::other(format!(
|
||||
"{err}; retrying after auth recovery"
|
||||
)));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let new_enrollment = enrollment_result.enrollment;
|
||||
if let Err(err) = update_persisted_remote_control_enrollment(
|
||||
state_db,
|
||||
remote_control_target,
|
||||
@@ -887,16 +915,18 @@ async fn connect_remote_control_websocket_with_options(
|
||||
new_enrollment.server_id,
|
||||
new_enrollment.environment_id
|
||||
);
|
||||
*enrollment = Some(new_enrollment);
|
||||
enrollment_state.enrollment = Some(new_enrollment);
|
||||
enrollment_state.server_token = enrollment_result.server_token;
|
||||
}
|
||||
|
||||
let enrollment_ref = enrollment.as_ref().ok_or_else(|| {
|
||||
let enrollment_ref = enrollment_state.enrollment.as_ref().ok_or_else(|| {
|
||||
io::Error::other("missing remote control enrollment after enrollment step")
|
||||
})?;
|
||||
let request = build_remote_control_websocket_request(
|
||||
&remote_control_target.websocket_url,
|
||||
enrollment_ref,
|
||||
&auth,
|
||||
enrollment_state.server_token.as_ref(),
|
||||
subscribe_cursor,
|
||||
)?;
|
||||
|
||||
@@ -925,16 +955,60 @@ async fn connect_remote_control_websocket_with_options(
|
||||
"failed to clear stale remote control enrollment in sqlite state db: {clear_err}"
|
||||
);
|
||||
}
|
||||
*enrollment = None;
|
||||
enrollment_state.enrollment = None;
|
||||
enrollment_state.server_token = None;
|
||||
}
|
||||
tungstenite::Error::Http(response)
|
||||
if matches!(response.status().as_u16(), 401 | 403) =>
|
||||
{
|
||||
if recover_remote_control_auth(auth_recovery).await {
|
||||
if enrollment_state.server_token.is_some() {
|
||||
info!(
|
||||
"remote control websocket token auth failed with HTTP {}; clearing enrollment before re-enrolling",
|
||||
response.status()
|
||||
);
|
||||
if let Err(clear_err) = update_persisted_remote_control_enrollment(
|
||||
state_db,
|
||||
remote_control_target,
|
||||
&auth.account_id,
|
||||
app_server_client_name,
|
||||
/*enrollment*/ None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"failed to clear remote control enrollment after token auth failure: {clear_err}"
|
||||
);
|
||||
}
|
||||
enrollment_state.enrollment = None;
|
||||
enrollment_state.server_token = None;
|
||||
return Err(io::Error::other(format!(
|
||||
"remote control websocket token auth failed with HTTP {}; re-enrolling",
|
||||
response.status()
|
||||
)));
|
||||
} else if recover_remote_control_auth(auth_recovery).await {
|
||||
return Err(io::Error::other(format!(
|
||||
"remote control websocket auth failed with HTTP {}; retrying after auth recovery",
|
||||
response.status()
|
||||
)));
|
||||
} else {
|
||||
info!(
|
||||
"remote control websocket rejected legacy account auth with HTTP {}; clearing enrollment before re-enrolling",
|
||||
response.status()
|
||||
);
|
||||
if let Err(clear_err) = update_persisted_remote_control_enrollment(
|
||||
state_db,
|
||||
remote_control_target,
|
||||
&auth.account_id,
|
||||
app_server_client_name,
|
||||
/*enrollment*/ None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"failed to clear legacy remote control enrollment after auth failure: {clear_err}"
|
||||
);
|
||||
}
|
||||
enrollment_state.enrollment = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -1087,6 +1161,81 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_remote_control_websocket_request_uses_server_token_when_available() {
|
||||
let enrollment = RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
};
|
||||
let auth = RemoteControlConnectionAuth {
|
||||
authorization_header_value: "AgentAssertion assertion".to_string(),
|
||||
account_id: "account_id".to_string(),
|
||||
is_fedramp_account: false,
|
||||
};
|
||||
let server_token = RemoteControlServerToken {
|
||||
bearer_token: "remote-control-token".to_string(),
|
||||
expires_at: Utc::now() + chrono::Duration::minutes(10),
|
||||
};
|
||||
|
||||
let request = build_remote_control_websocket_request(
|
||||
"ws://localhost:8080/backend-api/wham/remote/control/server",
|
||||
&enrollment,
|
||||
&auth,
|
||||
Some(&server_token),
|
||||
/*subscribe_cursor*/ None,
|
||||
)
|
||||
.expect("websocket request should build");
|
||||
|
||||
assert_eq!(
|
||||
request
|
||||
.headers()
|
||||
.get("authorization")
|
||||
.expect("authorization header should exist"),
|
||||
"Bearer remote-control-token"
|
||||
);
|
||||
assert_eq!(
|
||||
request
|
||||
.headers()
|
||||
.get(REMOTE_CONTROL_ACCOUNT_ID_HEADER)
|
||||
.expect("account id header should exist"),
|
||||
"account_id"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_remote_control_websocket_request_falls_back_to_account_token() {
|
||||
let enrollment = RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
};
|
||||
let auth = RemoteControlConnectionAuth {
|
||||
authorization_header_value: "AgentAssertion assertion".to_string(),
|
||||
account_id: "account_id".to_string(),
|
||||
is_fedramp_account: false,
|
||||
};
|
||||
|
||||
let request = build_remote_control_websocket_request(
|
||||
"ws://localhost:8080/backend-api/wham/remote/control/server",
|
||||
&enrollment,
|
||||
&auth,
|
||||
/*server_token*/ None,
|
||||
/*subscribe_cursor*/ None,
|
||||
)
|
||||
.expect("websocket request should build");
|
||||
|
||||
assert_eq!(
|
||||
request
|
||||
.headers()
|
||||
.get("authorization")
|
||||
.expect("authorization header should exist"),
|
||||
"AgentAssertion assertion"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_remote_control_websocket_request_includes_fedramp_header() {
|
||||
let request = build_remote_control_websocket_request(
|
||||
@@ -1102,6 +1251,7 @@ mod tests {
|
||||
account_id: "account_id".to_string(),
|
||||
is_fedramp_account: true,
|
||||
},
|
||||
/*server_token*/ None,
|
||||
/*subscribe_cursor*/ None,
|
||||
)
|
||||
.expect("request should build");
|
||||
@@ -1145,19 +1295,22 @@ mod tests {
|
||||
let state_db = remote_control_state_runtime(&codex_home).await;
|
||||
let auth_manager = remote_control_auth_manager();
|
||||
let mut auth_recovery = auth_manager.unauthorized_recovery();
|
||||
let mut enrollment = Some(RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
});
|
||||
let mut enrollment_state = RemoteControlEnrollmentState {
|
||||
enrollment: Some(RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
}),
|
||||
server_token: None,
|
||||
};
|
||||
|
||||
let err = match connect_remote_control_websocket(
|
||||
&remote_control_target,
|
||||
Some(state_db.as_ref()),
|
||||
&auth_manager,
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
&mut enrollment_state,
|
||||
/*subscribe_cursor*/ None,
|
||||
/*app_server_client_name*/ None,
|
||||
)
|
||||
@@ -1193,12 +1346,15 @@ mod tests {
|
||||
AuthCredentialsStoreMode::File,
|
||||
);
|
||||
let mut auth_recovery = auth_manager.unauthorized_recovery();
|
||||
let mut enrollment = Some(RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
});
|
||||
let mut enrollment_state = RemoteControlEnrollmentState {
|
||||
enrollment: Some(RemoteControlEnrollment {
|
||||
account_id: "account_id".to_string(),
|
||||
environment_id: "env_test".to_string(),
|
||||
server_id: "srv_e_test".to_string(),
|
||||
server_name: "test-server".to_string(),
|
||||
}),
|
||||
server_token: None,
|
||||
};
|
||||
save_auth(
|
||||
codex_home.path(),
|
||||
&remote_control_auth_dot_json("fresh-token"),
|
||||
@@ -1220,7 +1376,7 @@ mod tests {
|
||||
Some(state_db.as_ref()),
|
||||
&auth_manager,
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
&mut enrollment_state,
|
||||
/*subscribe_cursor*/ None,
|
||||
/*app_server_client_name*/ None,
|
||||
)
|
||||
@@ -1274,7 +1430,7 @@ mod tests {
|
||||
AuthCredentialsStoreMode::File,
|
||||
);
|
||||
let mut auth_recovery = auth_manager.unauthorized_recovery();
|
||||
let mut enrollment = None;
|
||||
let mut enrollment_state = RemoteControlEnrollmentState::default();
|
||||
save_auth(
|
||||
codex_home.path(),
|
||||
&remote_control_auth_dot_json("fresh-token"),
|
||||
@@ -1287,7 +1443,7 @@ mod tests {
|
||||
Some(state_db.as_ref()),
|
||||
&auth_manager,
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
&mut enrollment_state,
|
||||
/*subscribe_cursor*/ None,
|
||||
/*app_server_client_name*/ None,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user