use base64::Engine;
use futures::future::join_all;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::io::Cursor;
use std::sync::{Mutex, OnceLock};
use std::time::Duration;
use tandem_providers::openai_codex_supported_model_rows;
use tandem_wire::{
WireProviderCatalog, WireProviderEntry, WireProviderModel, WireProviderModelLimit,
};
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use uuid::Uuid;
#[derive(Debug, Deserialize, Default)]
pub(super) struct AuthInput {
#[serde(alias = "apiKey", alias = "api_key")]
pub token: Option<String>,
}
#[derive(Debug, Deserialize, Default)]
pub(super) struct ApiTokenInput {
#[serde(alias = "apiToken", alias = "api_token")]
pub token: Option<String>,
#[serde(default)]
pub scopes: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Default)]
pub(super) struct ProviderOAuthCallbackInput {
pub code: Option<String>,
pub state: Option<String>,
pub error: Option<String>,
pub error_description: Option<String>,
}
#[derive(Debug, Deserialize, Default)]
pub(super) struct ProviderOAuthStatusQuery {
pub session_id: Option<String>,
}
#[derive(Debug, Deserialize, Default)]
pub(super) struct ProviderOAuthSessionImportInput {
pub auth_json: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ProviderOAuthSessionRecord {
pub session_id: String,
pub provider_id: String,
pub status: String,
pub created_at_ms: u64,
pub expires_at_ms: u64,
pub redirect_uri: String,
pub state: String,
pub code_verifier: String,
pub authorization_url: String,
pub error: Option<String>,
pub email: Option<String>,
#[serde(default = "TenantContext::local_implicit")]
pub tenant_context: TenantContext,
}
#[derive(Debug, Serialize)]
pub(super) struct LegacyProviderInfo {
pub id: String,
pub name: String,
pub models: Vec<String>,
pub configured: bool,
}
#[derive(Debug, Deserialize)]
struct OpenAiCodexTokenExchangeResponse {
access_token: Option<String>,
refresh_token: Option<String>,
id_token: Option<String>,
}
#[derive(Debug, Deserialize)]
struct OpenAiCodexApiKeyExchangeResponse {
access_token: String,
}
const OPENAI_CODEX_OAUTH_CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
const OPENAI_CODEX_OAUTH_ISSUER: &str = "https://auth.openai.com";
const OPENAI_CODEX_PROVIDER_ID: &str = "openai-codex";
const OPENAI_CODEX_DEFAULT_MODEL: &str = tandem_providers::OPENAI_CODEX_DEFAULT_MODEL;
const OPENAI_CODEX_API_BASE_URL: &str = "https://chatgpt.com/backend-api/codex";
const OPENAI_CODEX_OAUTH_REFRESH_SKEW_MS: u64 = 5 * 60 * 1000;
const OPENAI_CODEX_LOCAL_CALLBACK_ADDR: &str = "127.0.0.1:1455";
const OPENAI_CODEX_LOCAL_CALLBACK_URI: &str = "http://localhost:1455/auth/callback";
fn is_internal_secret_id(raw: &str) -> bool {
let normalized = raw.trim().to_ascii_lowercase();
normalized.starts_with("mcp_header::")
|| normalized.starts_with("channel::")
|| normalized.starts_with("__tenant__::")
}
fn request_actor(
request_principal: &RequestPrincipal,
tenant_context: &TenantContext,
) -> Option<String> {
request_principal
.actor_id
.clone()
.or_else(|| tenant_context.actor_id.clone())
}
pub(super) async fn get_config(State(state): State<AppState>) -> Json<Value> {
let effective = normalize_effective_config_with_identity(redacted(
state.config.get_effective_value().await,
));
let layers = normalize_layers_with_identity(redacted(state.config.get_layers_value().await));
Json(json!({
"effective": effective,
"layers": layers
}))
}
pub(super) async fn patch_config(
State(state): State<AppState>,
Extension(tenant): Extension<TenantContext>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Json(input): Json<Value>,
) -> Response {
if contains_secret_config_fields(&input) {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Secret provider keys are not accepted in config patches.",
"code": "CONFIG_SECRET_REJECTED",
"hint": "Use PUT /auth/{provider} or environment variables."
})),
)
.into_response();
}
let normalized_input = normalize_config_patch_input(input);
let allow_standalone_private_endpoint = locality.is_direct_loopback()
&& crate::http::host_authority::require_loopback_local_operator(
&state,
&tenant,
verified.as_deref(),
)
.is_ok();
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProjectConfigUpdate,
"project_config",
tenant.workspace_id.as_str(),
normalized_input.clone(),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
let updates_openai_codex_default = config_patch_updates_openai_codex_default(&normalized_input);
if let Err(error) = validate_provider_origin_patch(
&state,
&normalized_input,
allow_standalone_private_endpoint,
)
.await
{
return (
StatusCode::BAD_REQUEST,
Json(json!({
"ok": false,
"code": "PROVIDER_ORIGIN_CHANGE_REJECTED",
"error": error,
})),
)
.into_response();
}
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let effective = match state.config.patch_project(normalized_input).await {
Ok(effective) => effective,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
};
let effective = if updates_openai_codex_default {
ensure_openai_codex_runtime_provider(&state).await;
state.config.get_effective_value().await
} else {
effective
};
state
.providers
.reload(state.config.get().await.into())
.await;
Json(json!({ "effective": normalize_effective_config_with_identity(redacted(effective)) }))
.into_response()
}
pub(super) async fn global_config(State(state): State<AppState>) -> Json<Value> {
let global =
normalize_effective_config_with_identity(redacted(state.config.get_global_value().await));
let effective = normalize_effective_config_with_identity(redacted(
state.config.get_effective_value().await,
));
Json(json!({
"global": global,
"effective": effective
}))
}
pub(super) async fn global_config_patch(
State(state): State<AppState>,
Extension(tenant): Extension<TenantContext>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Json(input): Json<Value>,
) -> Response {
if contains_secret_config_fields(&input) {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Secret provider keys are not accepted in global config patches.",
"code": "CONFIG_SECRET_REJECTED",
"hint": "Use PUT /auth/{provider} or environment variables."
})),
)
.into_response();
}
let normalized_input = normalize_config_patch_input(input);
let allow_standalone_private_endpoint = locality.is_direct_loopback()
&& crate::http::host_authority::require_loopback_local_operator(
&state,
&tenant,
verified.as_deref(),
)
.is_ok();
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::GlobalConfigUpdate,
"deployment_config",
tenant
.deployment_id
.as_deref()
.unwrap_or("local-deployment"),
normalized_input.clone(),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = validate_provider_origin_patch(
&state,
&normalized_input,
allow_standalone_private_endpoint,
)
.await
{
return (
StatusCode::BAD_REQUEST,
Json(json!({
"ok": false,
"code": "PROVIDER_ORIGIN_CHANGE_REJECTED",
"error": error,
})),
)
.into_response();
}
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let effective = match state
.config
.patch_global(normalized_input)
.await
{
Ok(effective) => effective,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
};
state
.providers
.reload(state.config.get().await.into())
.await;
Json(json!({ "effective": normalize_effective_config_with_identity(redacted(effective)) }))
.into_response()
}
async fn validate_provider_origin_patch(
state: &AppState,
patch: &Value,
allow_standalone_private_endpoint: bool,
) -> Result<(), String> {
let Some(provider_root) = patch
.get("providers")
.and_then(Value::as_object)
.or_else(|| patch.get("provider").and_then(Value::as_object))
else {
return Ok(());
};
let current = state.config.get_effective_value().await;
for (provider_id, provider_patch) in provider_root {
let Some(origin_value) = provider_patch.as_object().and_then(|entry| entry.get("url")) else {
continue;
};
let configured_origin = origin_value
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string);
let default_origin = canonical_provider_origin(provider_id);
let requested_origin = configured_origin
.as_deref()
.or(default_origin)
.ok_or_else(|| {
format!(
"provider `{provider_id}` must declare a non-empty HTTPS origin"
)
})?;
if let Some(existing) = provider_config_value(¤t, provider_id) {
let existing_origin = existing
.get("url")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.or(default_origin);
if existing_origin.map(normalize_provider_origin)
!= Some(normalize_provider_origin(requested_origin))
{
return Err(format!(
"provider `{provider_id}` origin is immutable once configured; create a new provider ID and credential binding"
));
}
}
let local_provider = allow_standalone_private_endpoint
&& matches!(
provider_id.trim().to_ascii_lowercase().as_str(),
"ollama" | "llama_cpp" | "llama.cpp"
);
let validation = if local_provider {
crate::outbound_http::resolve_standalone_provider_url(requested_origin).await
} else {
crate::outbound_http::resolve_public_https_url(requested_origin).await
};
validation.map_err(|error| {
format!("provider `{provider_id}` origin is not permitted: {error}")
})?;
}
Ok(())
}
fn normalize_provider_origin(value: &str) -> String {
value.trim().trim_end_matches('/').to_ascii_lowercase()
}
fn canonical_provider_origin(provider_id: &str) -> Option<&'static str> {
match provider_id.trim().to_ascii_lowercase().as_str() {
"ollama" => Some("http://127.0.0.1:11434/v1"),
"openai" => Some("https://api.openai.com/v1"),
"openai-codex" => Some("https://chatgpt.com/backend-api/codex"),
"openrouter" => Some("https://openrouter.ai/api/v1"),
"llama_cpp" | "llama.cpp" => Some("http://127.0.0.1:8080/v1"),
"groq" => Some("https://api.groq.com/openai/v1"),
"mistral" => Some("https://api.mistral.ai/v1"),
"together" => Some("https://api.together.xyz/v1"),
"azure" => Some("https://example.openai.azure.com/openai/deployments/default"),
"bedrock" => Some("https://bedrock-runtime.us-east-1.amazonaws.com"),
"vertex" => Some("https://aiplatform.googleapis.com/v1"),
"copilot" => Some("https://api.githubcopilot.com"),
"anthropic" => Some("https://api.anthropic.com"),
"cohere" => Some("https://api.cohere.com/v2"),
_ => None,
}
}
pub(super) async fn get_config_identity(State(state): State<AppState>) -> Json<Value> {
let effective = normalize_effective_config_with_identity(redacted(
state.config.get_effective_value().await,
));
let identity = effective
.get("identity")
.cloned()
.unwrap_or_else(identity_default_value);
Json(json!({
"identity": identity,
"presets": personality_presets_catalog()
}))
}
pub(super) async fn patch_config_identity(
State(state): State<AppState>,
Extension(tenant): Extension<TenantContext>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Json(input): Json<Value>,
) -> Response {
if contains_secret_config_fields(&input) {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Secret provider keys are not accepted in config patches.",
"code": "CONFIG_SECRET_REJECTED",
"hint": "Use PUT /auth/{provider} or environment variables."
})),
)
.into_response();
}
let patch = if input.get("identity").is_some() {
normalize_config_patch_input(input)
} else {
normalize_config_patch_input(json!({ "identity": input }))
};
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProjectConfigUpdate,
"project_config",
tenant.workspace_id.as_str(),
patch.clone(),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let effective = match state.config.patch_project(patch).await {
Ok(effective) => effective,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
};
state
.providers
.reload(state.config.get().await.into())
.await;
Json(json!({
"identity": normalize_effective_config_with_identity(redacted(effective))
.get("identity")
.cloned()
.unwrap_or_else(identity_default_value),
"presets": personality_presets_catalog()
}))
.into_response()
}
pub(super) async fn config_providers(State(state): State<AppState>) -> Json<Value> {
let cfg = state.config.get_effective_value().await;
let mut providers = redacted(cfg.get("providers").cloned().unwrap_or_else(|| json!({})));
if let Some(codex) = providers
.get_mut(OPENAI_CODEX_PROVIDER_ID)
.and_then(Value::as_object_mut)
{
let resolved = tandem_providers::openai_codex_effective_default_model(
codex.get("default_model").and_then(Value::as_str),
);
codex.insert("default_model".to_string(), Value::String(resolved));
}
let default_provider = cfg.get("default_provider").cloned().unwrap_or(Value::Null);
Json(json!({
"providers": providers,
"default": default_provider
}))
}
pub(crate) fn provider_auth_security_dir_for_state(state: &AppState) -> std::path::PathBuf {
state
.memory_db_path
.parent()
.map(|parent| parent.join("security"))
.unwrap_or_else(|| std::path::PathBuf::from("security"))
}
pub(super) async fn list_providers(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
) -> Json<Value> {
let cfg = state.config.get().await;
let default = cfg.default_provider.unwrap_or_else(|| "local".to_string());
let connected = state
.providers
.list()
.await
.into_iter()
.map(|p| p.id)
.collect::<Vec<_>>();
let all = state.providers.list().await;
let mut wire = WireProviderCatalog::from_providers(all, connected);
let effective_cfg = state.config.get_effective_value().await;
let provider_auth_security_dir = provider_auth_security_dir_for_state(&state);
let persisted_auth = tandem_core::load_provider_auth_for_tenant_in_dir(
&provider_auth_security_dir,
&tenant_context,
);
let runtime_auth = if tenant_context.is_local_implicit() {
state.auth.read().await.clone()
} else {
HashMap::new()
};
let allow_shared_auth_sources = tenant_context.is_local_implicit();
let allow_standalone_private_endpoint =
crate::http::host_authority::standalone_local_runtime_posture(&state, &tenant_context);
let config_model_provider_set = merge_provider_models_from_config(&mut wire, &effective_cfg)
.into_iter()
.collect::<std::collections::HashSet<_>>();
for entry in &mut wire.all {
entry.models.clear();
entry.catalog_source = None;
entry.catalog_status = None;
entry.catalog_message = None;
}
wire.all.retain(|entry| entry.id != "local");
ensure_known_provider_entries(&mut wire);
let provider_catalog_results = join_all(wire.all.iter().map(|entry| {
let provider_id = entry.id.clone();
let effective_cfg = &effective_cfg;
let runtime_auth = &runtime_auth;
let persisted_auth = &persisted_auth;
let allow_shared_auth_sources = allow_shared_auth_sources;
let allow_standalone_private_endpoint = allow_standalone_private_endpoint;
async move {
let has_discovery_key = provider_config_api_key(
effective_cfg,
&provider_id,
runtime_auth,
persisted_auth,
allow_shared_auth_sources,
)
.is_some();
let result = if provider_requires_api_key(&provider_id) && !has_discovery_key {
if canonical_provider_id(&provider_id) == OPENAI_CODEX_PROVIDER_ID {
ProviderCatalogFetchResult::Static {
models: codex_starter_models(),
}
} else {
ProviderCatalogFetchResult::Unavailable {
message: format!(
"{} requires an API key before live model discovery is available.",
known_provider_name(&provider_id).unwrap_or(provider_id.as_str())
),
}
}
} else {
fetch_remote_provider_models(
&provider_id,
&effective_cfg,
&runtime_auth,
&persisted_auth,
allow_shared_auth_sources,
allow_standalone_private_endpoint,
)
.await
};
(provider_id, result)
}
}))
.await
.into_iter()
.collect::<HashMap<_, _>>();
for entry in &mut wire.all {
match provider_catalog_results.get(&entry.id) {
Some(ProviderCatalogFetchResult::Remote { models }) => {
entry.models = models.clone();
entry.catalog_source = Some("remote".to_string());
entry.catalog_status = Some("ok".to_string());
}
Some(ProviderCatalogFetchResult::Static { models }) => {
entry.models = models.clone();
entry.catalog_source = Some("static".to_string());
entry.catalog_status = Some("ok".to_string());
}
Some(ProviderCatalogFetchResult::Unavailable { message }) => {
entry.catalog_source = Some("empty".to_string());
entry.catalog_status = Some("unavailable".to_string());
entry.catalog_message = Some(message.clone());
}
Some(ProviderCatalogFetchResult::Error { message }) => {
entry.catalog_source = Some("empty".to_string());
entry.catalog_status = Some("error".to_string());
entry.catalog_message = Some(message.clone());
}
None => {
entry.catalog_source = Some("empty".to_string());
entry.catalog_status = Some("unavailable".to_string());
entry.catalog_message = Some(
"Live model discovery is unavailable. Enter a model ID manually.".to_string(),
);
}
}
}
merge_provider_models_from_config(&mut wire, &effective_cfg);
for entry in &mut wire.all {
if config_model_provider_set.contains(&entry.id)
&& entry.catalog_source.as_deref() != Some("remote")
{
entry.catalog_source = Some("config".to_string());
entry.catalog_status = Some("ok".to_string());
entry.catalog_message = None;
continue;
}
if entry.models.is_empty() && entry.catalog_status.is_none() {
entry.catalog_source = Some("empty".to_string());
entry.catalog_status = Some("unavailable".to_string());
entry.catalog_message =
Some("Live model discovery is unavailable. Enter a model ID manually.".to_string());
} else if entry.catalog_source.is_none() {
entry.catalog_source = Some("config".to_string());
entry.catalog_status = Some("ok".to_string());
entry.catalog_message = None;
}
}
Json(json!({
"all": wire.all,
"connected": wire.connected,
"default": default
}))
}
pub(super) async fn list_providers_legacy(
State(state): State<AppState>,
) -> Json<Vec<LegacyProviderInfo>> {
let connected_ids = state
.providers
.list()
.await
.into_iter()
.map(|p| p.id)
.collect::<std::collections::HashSet<_>>();
let providers = state
.providers
.list()
.await
.into_iter()
.map(|p| LegacyProviderInfo {
id: p.id.clone(),
name: p.name,
models: p.models.into_iter().map(|m| m.id).collect(),
configured: connected_ids.contains(&p.id),
})
.collect::<Vec<_>>();
Json(providers)
}
pub(super) async fn provider_auth(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
) -> Json<Value> {
let _ = refresh_openai_codex_oauth_if_needed(&state, &tenant_context).await;
let cfg = state.config.get_effective_value().await;
let providers_cfg = cfg
.get("providers")
.and_then(Value::as_object)
.cloned()
.unwrap_or_default();
let provider_auth_security_dir = provider_auth_security_dir_for_state(&state);
let persisted = tandem_core::load_provider_auth_for_tenant_in_dir(
&provider_auth_security_dir,
&tenant_context,
);
let persisted_credentials = tandem_core::load_provider_credentials_for_tenant_in_dir(
&provider_auth_security_dir,
&tenant_context,
);
let persisted_ids = persisted
.keys()
.filter(|id| !is_internal_secret_id(id))
.map(|id| id.to_ascii_lowercase())
.collect::<std::collections::HashSet<_>>();
let local_implicit = tenant_context.is_local_implicit();
let runtime_auth = if local_implicit {
state.auth.read().await.clone()
} else {
HashMap::new()
};
let connected = state
.providers
.list()
.await
.into_iter()
.map(|provider| provider.id.to_ascii_lowercase())
.collect::<std::collections::HashSet<_>>();
let mut known_ids = std::collections::HashSet::new();
if let Some(default_id) = cfg.get("default_provider").and_then(Value::as_str) {
let normalized = canonical_provider_id(default_id);
if !normalized.is_empty() {
known_ids.insert(normalized);
}
}
known_ids.insert(OPENAI_CODEX_PROVIDER_ID.to_string());
known_ids.extend(providers_cfg.keys().map(|id| canonical_provider_id(id)));
known_ids.extend(connected.iter().map(|id| canonical_provider_id(id)));
known_ids.extend(runtime_auth.keys().map(|id| canonical_provider_id(id)));
known_ids.extend(persisted_ids.iter().map(|id| canonical_provider_id(id)));
known_ids.extend(
persisted_credentials
.keys()
.map(|id| canonical_provider_id(id)),
);
let mut ids = known_ids.into_iter().collect::<Vec<_>>();
ids.sort();
let mut providers = serde_json::Map::new();
for provider_id in ids {
let has_runtime_key = runtime_auth
.get(&provider_id)
.map(|token| !token.trim().is_empty())
.unwrap_or(false)
|| provider_id_aliases(&provider_id).iter().any(|alias| {
runtime_auth
.get(*alias)
.map(|token| !token.trim().is_empty())
.unwrap_or(false)
});
let has_config_key = local_implicit
&& provider_config_value(&cfg, &provider_id)
.and_then(Value::as_object)
.map(|entry| {
entry
.get("api_key")
.or_else(|| entry.get("apiKey"))
.and_then(Value::as_str)
.map(|token| !token.trim().is_empty())
.unwrap_or(false)
})
.unwrap_or(false);
let has_env_key = local_implicit && provider_has_env_secret(&provider_id);
let has_persisted_key = persisted_ids.contains(&provider_id)
|| provider_id_aliases(&provider_id)
.iter()
.any(|alias| persisted_ids.contains(*alias));
let oauth_credential = provider_id_aliases(&provider_id)
.iter()
.find_map(|alias| persisted_credentials.get(*alias))
.or_else(|| persisted_credentials.get(&provider_id));
let has_oauth_credential = oauth_credential.is_some();
let local_session_available = local_implicit
&& provider_id == OPENAI_CODEX_PROVIDER_ID
&& tandem_core::load_openai_codex_cli_oauth_credential().is_some();
let has_key = has_env_key || has_runtime_key || has_config_key || has_persisted_key;
let source = if has_oauth_credential {
"oauth"
} else if has_env_key {
"env"
} else if has_persisted_key {
"persisted"
} else if has_runtime_key || has_config_key {
"runtime"
} else {
"none"
};
let configured = connected.contains(&provider_id);
let auth_kind = match oauth_credential {
Some(tandem_core::ProviderCredential::OAuth(_)) => "oauth",
_ => "api_key",
};
let mut payload = json!({
"has_key": has_key || has_oauth_credential,
"configured": configured,
"connected": configured && (has_key || has_oauth_credential || !provider_requires_api_key(&provider_id)),
"source": source,
"auth_kind": auth_kind,
"status": if has_oauth_credential { "connected" } else if has_key { "configured" } else { "missing" },
"local_session_available": local_session_available,
});
if let Some(tandem_core::ProviderCredential::OAuth(oauth)) = oauth_credential {
payload["expires_at_ms"] = json!(oauth.expires_at_ms);
payload["email"] = json!(oauth.email);
payload["display_name"] = json!(oauth.display_name);
payload["managed_by"] = json!(oauth.managed_by);
payload["account_id"] = json!(oauth.account_id);
if oauth.expires_at_ms <= crate::now_ms() {
payload["status"] = json!("reauth_required");
payload["connected"] = json!(false);
}
}
providers.insert(provider_id, payload);
}
Json(json!({ "providers": providers }))
}
pub(super) async fn provider_oauth_authorize(
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Extension(tenant_context): Extension<TenantContext>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Path(id): Path<String>,
) -> Response {
let provider_id = canonical_provider_id(&id);
if provider_id != OPENAI_CODEX_PROVIDER_ID {
return Json(json!({
"ok": false,
"error": format!("oauth is not supported for provider `{provider_id}`"),
}))
.into_response();
}
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant_context,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProviderCredentialUpdate,
"provider_credential",
provider_id.clone(),
json!({"provider_id": provider_id, "operation": "oauth_authorize"}),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let session_id = Uuid::new_v4().to_string();
let created_at_ms = crate::now_ms();
let expires_at_ms = created_at_ms.saturating_add(10 * 60 * 1000);
let (code_verifier, code_challenge) = generate_pkce_pair();
let state_token = generate_oauth_state();
let effective_cfg = state.config.get_effective_value().await;
let hosted_managed = hosted_managed_from_config(&effective_cfg);
let public_panel_base_url = provider_oauth_public_panel_base_url(&effective_cfg, &headers);
let engine_base_url = provider_oauth_engine_base_url(&state);
let callback_base_url = public_panel_base_url
.as_deref()
.unwrap_or(engine_base_url.as_str());
let use_control_panel_callback = public_panel_base_url.is_some();
let use_hosted_codex_callback = hosted_managed
|| use_control_panel_callback
|| !provider_oauth_base_url_is_loopback(&engine_base_url);
if provider_id == OPENAI_CODEX_PROVIDER_ID && !use_hosted_codex_callback {
if let Err(error) = ensure_openai_codex_local_callback_server(state.clone()).await {
return Json(json!({
"ok": false,
"error": format!("failed to start local Codex callback server: {error}"),
}))
.into_response();
}
}
let redirect_uri = if provider_id == OPENAI_CODEX_PROVIDER_ID && !use_hosted_codex_callback {
OPENAI_CODEX_LOCAL_CALLBACK_URI.to_string()
} else if use_control_panel_callback {
provider_oauth_redirect_uri_for_control_panel_base(callback_base_url, &provider_id)
} else {
provider_oauth_redirect_uri_for_base(callback_base_url, &provider_id)
};
let authorization_url =
build_openai_codex_authorization_url(&redirect_uri, &code_challenge, &state_token);
state.oauth.provider_sessions().write().await.insert(
session_id.clone(),
ProviderOAuthSessionRecord {
session_id: session_id.clone(),
provider_id,
status: "pending".to_string(),
created_at_ms,
expires_at_ms,
redirect_uri,
state: state_token,
code_verifier,
authorization_url: authorization_url.clone(),
error: None,
email: None,
tenant_context,
},
);
Json(json!({
"ok": true,
"provider_id": OPENAI_CODEX_PROVIDER_ID,
"session_id": session_id,
"authorizationUrl": authorization_url,
"expires_at_ms": expires_at_ms,
}))
.into_response()
}
pub(super) async fn provider_oauth_status(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
Path(id): Path<String>,
Query(query): Query<ProviderOAuthStatusQuery>,
) -> Json<Value> {
let provider_id = canonical_provider_id(&id);
if provider_id != OPENAI_CODEX_PROVIDER_ID {
return Json(json!({
"ok": false,
"error": format!("oauth is not supported for provider `{provider_id}`"),
}));
}
let _ = refresh_openai_codex_oauth_if_needed(&state, &tenant_context).await;
if let Some(session_id) = query.session_id.as_deref() {
if let Some(session) = state.oauth.provider_sessions().read().await.get(session_id) {
if session.tenant_context != tenant_context {
return Json(json!({
"ok": true,
"status": "missing",
"connected": false,
"local_session_available": tenant_context.is_local_implicit()
&& tandem_core::load_openai_codex_cli_oauth_credential().is_some(),
}));
}
return Json(json!({
"ok": true,
"session_id": session.session_id,
"status": session.status,
"error": session.error,
"email": session.email,
"expires_at_ms": session.expires_at_ms,
}));
}
}
let provider_auth_security_dir = provider_auth_security_dir_for_state(&state);
if let Some(tandem_core::ProviderCredential::OAuth(oauth)) =
tandem_core::load_provider_credentials_for_tenant_in_dir(
&provider_auth_security_dir,
&tenant_context,
)
.remove(OPENAI_CODEX_PROVIDER_ID)
{
return Json(json!({
"ok": true,
"status": if oauth.expires_at_ms <= crate::now_ms() { "reauth_required" } else { "connected" },
"connected": oauth.expires_at_ms > crate::now_ms(),
"email": oauth.email,
"display_name": oauth.display_name,
"managed_by": oauth.managed_by,
"account_id": oauth.account_id,
"expires_at_ms": oauth.expires_at_ms,
"local_session_available": tenant_context.is_local_implicit()
&& tandem_core::load_openai_codex_cli_oauth_credential().is_some(),
}));
}
Json(json!({
"ok": true,
"status": "missing",
"connected": false,
"local_session_available": tenant_context.is_local_implicit()
&& tandem_core::load_openai_codex_cli_oauth_credential().is_some(),
}))
}
pub(super) async fn provider_oauth_callback_get(
State(state): State<AppState>,
Path(id): Path<String>,
Query(input): Query<ProviderOAuthCallbackInput>,
) -> Response {
let payload = finish_provider_oauth_callback(state, id, input).await;
let ok = payload.get("ok").and_then(Value::as_bool).unwrap_or(false);
let title = if ok {
"Codex Account Connected"
} else {
"Codex Account Connection Failed"
};
let detail = if ok {
payload
.get("email")
.and_then(Value::as_str)
.map(|email| {
format!("Connected as {email}. You can close this tab and return to Tandem.")
})
.unwrap_or_else(|| {
"Codex account connected. You can close this tab and return to Tandem.".to_string()
})
} else {
payload
.get("error")
.and_then(Value::as_str)
.unwrap_or("The OAuth callback could not be completed.")
.to_string()
};
Html(render_provider_oauth_result_page(&title, &detail, ok)).into_response()
}
fn escape_html(text: &str) -> String {
let mut escaped = String::with_capacity(text.len());
for ch in text.chars() {
match ch {
'&' => escaped.push_str("&"),
'<' => escaped.push_str("<"),
'>' => escaped.push_str(">"),
'"' => escaped.push_str("""),
'\'' => escaped.push_str("'"),
_ => escaped.push(ch),
}
}
escaped
}
fn render_provider_oauth_result_page(title: &str, detail: &str, ok: bool) -> String {
let title = escape_html(title);
let detail = escape_html(detail);
let status_text = if ok {
"Connected"
} else {
"Connection needs attention"
};
let status_tone = if ok { "#4ade80" } else { "#fb7185" };
let status_border = if ok {
"rgba(74, 222, 128, 0.35)"
} else {
"rgba(251, 113, 133, 0.35)"
};
let accent_glow = if ok {
"rgba(59, 130, 246, 0.28)"
} else {
"rgba(244, 63, 94, 0.28)"
};
let icon_path = if ok {
r#"<path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm4.3 7.7-4.9 5a1 1 0 0 1-.7.3 1 1 0 0 1-.7-.3l-2.4-2.4a1 1 0 1 1 1.4-1.4l1.7 1.7 4.2-4.3a1 1 0 1 1 1.4 1.4Z"/>"#
} else {
r#"<path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm4.2 12.8a1 1 0 1 1-1.4 1.4L12 13.4l-2.8 2.8a1 1 0 0 1-1.4-1.4l2.8-2.8-2.8-2.8a1 1 0 0 1 1.4-1.4l2.8 2.8 2.8-2.8a1 1 0 1 1 1.4 1.4L13.4 12l2.8 2.8Z"/>"#
};
format!(
r#"<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<style>
:root {{
color-scheme: dark;
--bg: #030712;
--bg-2: #0a1020;
--card: rgba(8, 12, 24, 0.98);
--border: rgba(148, 163, 184, 0.15);
--text: #ecf2ff;
--muted: #a9b8d4;
--accent: {status_tone};
--accent-bg: rgba(15, 23, 42, 0.82);
--accent-border: {status_border};
--accent-glow: {accent_glow};
}}
* {{ box-sizing: border-box; }}
html, body {{ min-height: 100%; }}
body {{
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 24%),
radial-gradient(circle at top right, rgba(79, 70, 229, 0.12), transparent 28%),
linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%);
}}
body::before {{
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(148, 163, 184, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(148, 163, 184, 0.035) 1px, transparent 1px);
background-size: 40px 40px;
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.65), transparent);
opacity: 0.5;
}}
.shell {{
position: relative;
width: min(720px, 100%);
}}
.card {{
position: relative;
overflow: hidden;
border-radius: 0;
border: 1px solid var(--border);
background: linear-gradient(180deg, rgba(8, 12, 24, 0.98), rgba(5, 8, 16, 0.98));
box-shadow: 0 28px 70px rgba(0, 0, 0, 0.5);
padding: 32px;
border-top: 3px solid var(--accent);
}}
.card::before {{
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--accent-glow), transparent);
pointer-events: none;
}}
.brand {{
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 28px;
position: relative;
z-index: 1;
}}
.mark {{
width: 46px;
height: 46px;
border-radius: 0;
display: grid;
place-items: center;
flex: none;
background: rgba(37, 99, 235, 0.14);
border: 1px solid rgba(59, 130, 246, 0.4);
}}
.mark svg {{
width: 28px;
height: 28px;
fill: white;
}}
.brand-copy {{
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}}
.eyebrow {{
font-size: 12px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}}
.brand-copy strong {{
font-size: 18px;
font-weight: 700;
letter-spacing: -0.01em;
}}
.badge {{
margin-left: auto;
padding: 8px 12px;
border-radius: 0;
border: 1px solid var(--accent-border);
background: var(--accent-bg);
color: var(--accent);
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}}
h1 {{
position: relative;
z-index: 1;
margin: 0 0 14px;
font-size: clamp(28px, 4vw, 40px);
line-height: 1.08;
letter-spacing: -0.04em;
}}
p {{
position: relative;
z-index: 1;
margin: 0;
max-width: 60ch;
font-size: 16px;
line-height: 1.7;
color: var(--muted);
}}
.foot {{
position: relative;
z-index: 1;
margin-top: 28px;
display: flex;
flex-wrap: wrap;
gap: 12px;
color: #93a5c7;
font-size: 13px;
border-top: 1px solid rgba(148, 163, 184, 0.12);
padding-top: 16px;
}}
.pill {{
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-radius: 0;
border: 1px solid var(--border);
background: rgba(15, 23, 42, 0.56);
}}
.actions {{
position: relative;
z-index: 1;
margin-top: 28px;
display: flex;
flex-wrap: wrap;
gap: 12px;
}}
.button {{
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 0 16px;
border-radius: 0;
text-decoration: none;
font-weight: 700;
font-size: 14px;
}}
.button.primary {{
color: white;
background: linear-gradient(135deg, #2563eb, #4f46e5);
box-shadow: 0 12px 24px rgba(37, 99, 235, 0.24);
}}
.button.secondary {{
color: var(--text);
border: 1px solid var(--border);
background: rgba(15, 23, 42, 0.58);
}}
@media (max-width: 640px) {{
body {{ padding: 16px; }}
.card {{ padding: 24px; border-radius: 0; }}
.brand {{ align-items: flex-start; flex-direction: column; }}
.badge {{ margin-left: 0; }}
}}
</style>
</head>
<body>
<main class="shell">
<section class="card" aria-live="polite">
<div class="brand">
<div class="mark" aria-hidden="true">
<svg viewBox="0 0 24 24" role="presentation" focusable="false">{icon_path}</svg>
</div>
<div class="brand-copy">
<div class="eyebrow">Tandem</div>
<strong>Codex account</strong>
</div>
<div class="badge">{status_text}</div>
</div>
<h1>{title}</h1>
<p>{detail}</p>
<div class="actions">
<div class="pill">You can close this tab and return to Tandem.</div>
<div class="pill">Browser sign-in completed through Tandem's local auth bridge.</div>
</div>
<div class="foot">
<span>Secure browser handoff</span>
<span>•</span>
<span>Local OAuth callback</span>
</div>
</section>
</main>
</body>
</html>"#,
title = title,
detail = detail,
status_text = status_text,
status_tone = status_tone,
accent_glow = accent_glow,
icon_path = icon_path
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn openai_codex_authorization_url_matches_codex_cli_flow() {
let url = build_openai_codex_authorization_url(
"http://localhost:1455/auth/callback",
"challenge123",
"state123",
);
assert!(url.starts_with("https://auth.openai.com/oauth/authorize?"));
assert!(url.contains("redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback"));
assert!(url.contains("scope=openid%20profile%20email%20offline_access"));
assert!(url.contains("code_challenge=challenge123"));
assert!(url.contains("code_challenge_method=S256"));
assert!(url.contains("id_token_add_organizations=true"));
assert!(url.contains("codex_cli_simplified_flow=true"));
assert!(url.contains("state=state123"));
assert!(!url.contains("originator="));
assert!(!url.contains("api.connectors.read"));
assert!(!url.contains("api.connectors.invoke"));
}
#[test]
fn hosted_codex_redirect_uses_control_panel_callback_route() {
let url = provider_oauth_redirect_uri_for_control_panel_base(
"https://test.hosted.tandem.ac",
OPENAI_CODEX_PROVIDER_ID,
);
assert_eq!(
url,
"https://test.hosted.tandem.ac/api/engine/provider/openai-codex/oauth/callback"
);
}
#[test]
fn oauth_callback_page_escapes_untrusted_text() {
let html = render_provider_oauth_result_page(
"Connected <script>",
"Email: user@example.com & friends",
true,
);
assert!(html.contains("<script>"));
assert!(html.contains("& friends"));
assert!(!html.contains("<script>"));
}
}
pub(super) async fn provider_oauth_callback_post(
State(state): State<AppState>,
Path(id): Path<String>,
Json(input): Json<ProviderOAuthCallbackInput>,
) -> Response {
let payload = finish_provider_oauth_callback(state, id, input).await;
let status = if payload.get("code").and_then(Value::as_str)
== Some("PROTECTED_AUDIT_PERSISTENCE_FAILED")
{
StatusCode::INTERNAL_SERVER_ERROR
} else {
StatusCode::OK
};
(status, Json(payload)).into_response()
}
async fn openai_codex_local_callback_get(
State(state): State<AppState>,
Query(input): Query<ProviderOAuthCallbackInput>,
) -> Response {
let response = provider_oauth_callback_get(
State(state),
Path(OPENAI_CODEX_PROVIDER_ID.to_string()),
Query(input),
)
.await;
stop_openai_codex_local_callback_server();
response
}
async fn openai_codex_local_callback_post(
State(state): State<AppState>,
Json(input): Json<ProviderOAuthCallbackInput>,
) -> Response {
let response = provider_oauth_callback_post(
State(state),
Path(OPENAI_CODEX_PROVIDER_ID.to_string()),
Json(input),
)
.await;
stop_openai_codex_local_callback_server();
response
}
pub(super) async fn provider_oauth_disconnect(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
Extension(request_principal): Extension<RequestPrincipal>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Path(id): Path<String>,
) -> Response {
let provider_id = canonical_provider_id(&id);
if provider_id != OPENAI_CODEX_PROVIDER_ID {
return Json(json!({
"ok": false,
"error": format!("oauth is not supported for provider `{provider_id}`"),
}))
.into_response();
}
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant_context,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProviderCredentialUpdate,
"provider_credential",
provider_id.clone(),
json!({"provider_id": provider_id, "operation": "oauth_disconnect"}),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let mut credential_guard = state
.oauth
.provider_credential_guard(&tenant_context, OPENAI_CODEX_PROVIDER_ID)
.await;
let snapshot = snapshot_openai_codex_oauth_mutation(&state, &tenant_context).await;
credential_guard.advance_generation();
let persisted_removed =
match delete_openai_codex_oauth_credential(&state, &tenant_context).await {
Ok(removed) => removed,
Err(error) => {
return Json(json!({ "ok": false, "error": error.to_string() })).into_response();
}
};
state
.providers
.clear_tenant_provider_bearer_token(&tenant_context, OPENAI_CODEX_PROVIDER_ID)
.await;
let mut runtime_removed = false;
if tenant_context.is_local_implicit() {
runtime_removed = state
.config
.delete_runtime_provider_key(OPENAI_CODEX_PROVIDER_ID)
.await
.is_ok();
state.auth.write().await.remove(OPENAI_CODEX_PROVIDER_ID);
stop_openai_codex_local_callback_server();
}
if persisted_removed || runtime_removed {
if let Err(error) = crate::audit::append_protected_audit_event(
&state,
"provider.oauth.deleted",
&tenant_context,
request_actor(&request_principal, &tenant_context),
json!({
"providerID": OPENAI_CODEX_PROVIDER_ID,
"runtimeRemoved": runtime_removed,
"persistedRemoved": persisted_removed,
}),
)
.await
{
if let Err(rollback_error) =
restore_openai_codex_oauth_mutation(&state, &tenant_context, &snapshot).await
{
tracing::error!(
error = ?rollback_error,
"failed to restore provider OAuth state after protected audit failure"
);
}
return super::protected_audit_error_response(error).into_response();
}
ensure_openai_codex_runtime_provider(&state).await;
state
.providers
.reload(state.config.get().await.into())
.await;
}
Json(json!({
"ok": persisted_removed || runtime_removed,
}))
.into_response()
}
pub(super) async fn provider_oauth_local_session(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
Extension(request_principal): Extension<RequestPrincipal>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Path(id): Path<String>,
) -> Response {
if !tenant_context.is_local_implicit() {
return (
StatusCode::FORBIDDEN,
Json(json!({
"ok": false,
"code": "LOCAL_PROVIDER_SESSION_REQUIRES_LOCAL_IMPLICIT_TENANT",
"error": "Local provider sessions are available only to the local implicit tenant.",
})),
)
.into_response();
}
let provider_id = canonical_provider_id(&id);
if provider_id != OPENAI_CODEX_PROVIDER_ID {
return Json(json!({
"ok": false,
"error": format!("oauth is not supported for provider `{provider_id}`"),
}))
.into_response();
}
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant_context,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProviderCredentialUpdate,
"provider_credential",
provider_id.clone(),
json!({"provider_id": provider_id, "operation": "oauth_import_local_session"}),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let mut credential_guard = state
.oauth
.provider_credential_guard(&tenant_context, OPENAI_CODEX_PROVIDER_ID)
.await;
let Some(credential) = tandem_core::load_openai_codex_cli_oauth_credential() else {
return Json(json!({
"ok": false,
"error": "No local Codex CLI session was found. Sign in with the Codex CLI on this machine first.",
}))
.into_response();
};
let snapshot = snapshot_openai_codex_oauth_mutation(&state, &tenant_context).await;
credential_guard.advance_generation();
let backend =
match persist_openai_codex_oauth_credential(&state, &tenant_context, credential.clone())
.await
{
Ok(tandem_core::ProviderAuthBackend::Keychain) => "keychain",
Ok(tandem_core::ProviderAuthBackend::File) => "file",
Err(error) => {
let _ =
restore_openai_codex_oauth_mutation(&state, &tenant_context, &snapshot).await;
return Json(json!({
"ok": false,
"error": error.to_string(),
}))
.into_response();
}
};
apply_openai_codex_runtime_credential(&state, &tenant_context, Some(&credential), true).await;
if let Err(error) = crate::audit::append_protected_audit_event(
&state,
"provider.oauth.updated",
&tenant_context,
request_actor(&request_principal, &tenant_context),
json!({
"providerID": OPENAI_CODEX_PROVIDER_ID,
"backend": backend,
"managedBy": "codex-cli",
"email": credential.email,
}),
)
.await
{
if let Err(rollback_error) =
restore_openai_codex_oauth_mutation(&state, &tenant_context, &snapshot).await
{
tracing::error!(
error = ?rollback_error,
"failed to restore provider OAuth state after protected audit failure"
);
}
return super::protected_audit_error_response(error).into_response();
}
Json(json!({
"ok": true,
"provider_id": OPENAI_CODEX_PROVIDER_ID,
"managed_by": "codex-cli",
"backend": backend,
"email": credential.email,
"display_name": credential.display_name,
"expires_at_ms": credential.expires_at_ms,
}))
.into_response()
}
pub(super) async fn provider_oauth_session_import(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
Extension(request_principal): Extension<RequestPrincipal>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Path(id): Path<String>,
Json(input): Json<ProviderOAuthSessionImportInput>,
) -> Response {
let provider_id = canonical_provider_id(&id);
if provider_id != OPENAI_CODEX_PROVIDER_ID {
return Json(json!({
"ok": false,
"error": format!("oauth is not supported for provider `{provider_id}`"),
}))
.into_response();
}
let import_digest = format!(
"{:x}",
Sha256::digest(
input
.auth_json
.as_deref()
.unwrap_or_default()
.as_bytes()
)
);
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant_context,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProviderCredentialUpdate,
"provider_credential",
provider_id.clone(),
json!({
"provider_id": provider_id,
"operation": "oauth_import",
"credential_digest": import_digest,
}),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
let raw_auth_json = input.auth_json.unwrap_or_default();
let trimmed_auth_json = raw_auth_json.trim();
if trimmed_auth_json.is_empty() {
return Json(json!({
"ok": false,
"error": "Codex auth.json content cannot be empty.",
}))
.into_response();
}
let auth_json: Value = match serde_json::from_str(trimmed_auth_json) {
Ok(value) => value,
Err(error) => {
return Json(json!({
"ok": false,
"error": format!("Codex auth.json is not valid JSON: {error}"),
}))
.into_response();
}
};
let Some(mut credential) = tandem_core::oauth_credential_from_codex_auth_json(&auth_json)
else {
return Json(json!({
"ok": false,
"error": "The imported Codex auth.json could not be parsed into a credential.",
}))
.into_response();
};
credential.managed_by = "codex-upload".to_string();
let mut credential_guard = state
.oauth
.provider_credential_guard(&tenant_context, OPENAI_CODEX_PROVIDER_ID)
.await;
let snapshot = snapshot_openai_codex_oauth_mutation(&state, &tenant_context).await;
let cli_auth_snapshot = tenant_context
.is_local_implicit()
.then(tandem_core::load_openai_codex_cli_auth_json)
.flatten();
credential_guard.advance_generation();
if tenant_context.is_local_implicit() {
if let Err(error) = tandem_core::write_openai_codex_cli_auth_json(&auth_json) {
return Json(json!({
"ok": false,
"error": format!("failed to persist Codex auth.json: {error}"),
}))
.into_response();
}
}
let backend =
match persist_openai_codex_oauth_credential(&state, &tenant_context, credential.clone())
.await
{
Ok(tandem_core::ProviderAuthBackend::Keychain) => "keychain",
Ok(tandem_core::ProviderAuthBackend::File) => "file",
Err(error) => {
let _ =
restore_openai_codex_oauth_mutation(&state, &tenant_context, &snapshot).await;
if tenant_context.is_local_implicit() {
let _ =
tandem_core::restore_openai_codex_cli_auth_json(cli_auth_snapshot.as_ref());
}
return Json(json!({
"ok": false,
"error": error.to_string(),
}))
.into_response();
}
};
apply_openai_codex_runtime_credential(&state, &tenant_context, Some(&credential), true).await;
if let Err(error) = crate::audit::append_protected_audit_event(
&state,
"provider.oauth.updated",
&tenant_context,
request_actor(&request_principal, &tenant_context),
json!({
"providerID": OPENAI_CODEX_PROVIDER_ID,
"backend": backend,
"managedBy": "codex-upload",
"email": credential.email,
}),
)
.await
{
if let Err(rollback_error) =
restore_openai_codex_oauth_mutation(&state, &tenant_context, &snapshot).await
{
tracing::error!(
error = ?rollback_error,
"failed to restore provider OAuth state after protected audit failure"
);
}
if tenant_context.is_local_implicit() {
if let Err(rollback_error) =
tandem_core::restore_openai_codex_cli_auth_json(cli_auth_snapshot.as_ref())
{
tracing::error!(
error = ?rollback_error,
"failed to restore Codex CLI auth after protected audit failure"
);
}
}
return super::protected_audit_error_response(error).into_response();
}
Json(json!({
"ok": true,
"provider_id": OPENAI_CODEX_PROVIDER_ID,
"managed_by": "codex-upload",
"backend": backend,
"email": credential.email,
"display_name": credential.display_name,
"expires_at_ms": credential.expires_at_ms,
}))
.into_response()
}
pub(super) async fn set_auth(
State(state): State<AppState>,
Extension(tenant_context): Extension<TenantContext>,
Extension(request_principal): Extension<RequestPrincipal>,
Extension(locality): Extension<crate::http::host_authority::RequestLocality>,
verified: Option<Extension<tandem_types::VerifiedTenantContext>>,
Path(id): Path<String>,
Json(input): Json<AuthInput>,
) -> Response {
let normalized_id = id.trim().to_ascii_lowercase();
if normalized_id.is_empty() {
return Json(json!({"ok": false, "error": "provider id cannot be empty"})).into_response();
}
let token = input.token.unwrap_or_default().trim().to_string();
if token.is_empty() {
return Json(json!({"ok": false, "error": "token cannot be empty"})).into_response();
}
let token_digest = format!("{:x}", Sha256::digest(token.as_bytes()));
let (grant, effect) = match crate::http::host_authority::authorize_administrative_effect(
&state,
&tenant_context,
verified.as_deref(),
locality,
crate::action_authorization::HostAction::ProviderCredentialUpdate,
"provider_credential",
normalized_id.clone(),
json!({"provider_id": normalized_id, "operation": "set", "token_digest": token_digest}),
)
.await
{
Ok(authorized) => authorized,
Err(status) => return status.into_response(),
};
let mut credential_guard = state
.oauth
.provider_credential_guard(&tenant_context, &normalized_id)
.await;
let _persistence_guard = state.oauth.provider_credential_persistence_guard().await;
let provider_auth_security_dir = provider_auth_security_dir_for_state(&state);
let snapshot = snapshot_api_key_mutation(
&state,
&provider_auth_security_dir,
&tenant_context,
&normalized_id,
)
.await;
if let Err(error) = grant.revalidate(&state, &effect) {
return crate::http::host_authority::host_authorization_status(error).into_response();
}
credential_guard.advance_generation();
let backend = match tandem_core::set_provider_auth_for_tenant_in_dir(
&provider_auth_security_dir,
&tenant_context,
&normalized_id,
&token,
) {
Ok(tandem_core::ProviderAuthBackend::Keychain) => "keychain",
Ok(tandem_core::ProviderAuthBackend::File) => "file",
Err(err) => {
rollback_api_key_mutation(
&state,
&provider_auth_security_dir,
&tenant_context,
&normalized_id,
&snapshot,
"provider API-key persistence failure",
)
.await;
return provider_auth_mutation_error_response(
&normalized_id,
"PROVIDER_AUTH_PERSISTENCE_FAILED",
format!("failed to persist provider auth: {err}"),
);
}
};
if tenant_context.is_local_implicit() {
let patch = json!({
"providers": {
normalized_id.clone(): {
"api_key": token.clone()
}
}
});
if let Err(error) = state.config.patch_runtime(patch).await {
rollback_api_key_mutation(
&state,
&provider_auth_security_dir,
&tenant_context,
&normalized_id,
&snapshot,
"provider API-key runtime patch failure",
)
.await;
return provider_auth_mutation_error_response(
&normalized_id,
"PROVIDER_AUTH_RUNTIME_PATCH_FAILED",
format!("failed to update provider runtime auth: {error}"),
);
}
state
.auth
.write()
.await
.insert(normalized_id.clone(), token);
state
.providers
.reload(state.config.get().await.into())
.await;
}
if let Err(error) = crate::audit::append_protected_audit_event(
&state,
"provider.secret.updated",
&tenant_context,
request_actor(&request_principal, &tenant_context),
json!({
"providerID": normalized_id,
"backend": backend,
}),
)
.await
{
rollback_api_key_mutation(
&state,
&provider_auth_security_dir,
&tenant_context,
&normalized_id,
&snapshot,
"provider API-key audit failure",
)
.await;
return super::protected_audit_error_response(error).into_response();
}
Json(json!({"ok": true, "id": normalized_id, "backend": backend})).into_response()
}