Support staging OAuth client ID overrides (#28257)

## Summary

- allow app-server ChatGPT login to use a configured OAuth client ID
- reuse the same client ID for refresh and revoke requests
- cover staging login, refresh, and revoke request payloads

## Tests

- `just test -p codex-login`
- `just test -p codex-app-server
login_account_chatgpt_uses_debug_oauth_overrides`
- `just test -p codex-login
logout_with_revoke_revokes_refresh_token_then_removes_auth`
- `just fix -p codex-login`
- `just fix -p codex-app-server`
- `just fmt`
This commit is contained in:
Anton Panasenko authored and GitHub committed 2026-06-15 10:56:53 -07:00
1 parent aae4c0b02b
commit 336f907ec1
8 files changed
+108 -31

No files matched your search

+10 -2
View File
@@ -109,6 +109,7 @@ const REFRESH_TOKEN_URL: &str = "https://auth.openai.com/oauth/token";
pub(super) const REVOKE_TOKEN_URL: &str = "https://auth.openai.com/oauth/revoke";
pub const REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR: &str = "CODEX_REFRESH_TOKEN_URL_OVERRIDE";
pub const REVOKE_TOKEN_URL_OVERRIDE_ENV_VAR: &str = "CODEX_REVOKE_TOKEN_URL_OVERRIDE";
pub const CLIENT_ID_OVERRIDE_ENV_VAR: &str = "CODEX_APP_SERVER_LOGIN_CLIENT_ID";
static NEXT_DUMMY_AUTH_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Error)]
@@ -1021,7 +1022,7 @@ async fn request_chatgpt_token_refresh(
client: &CodexHttpClient,
) -> Result<RefreshResponse, RefreshTokenError> {
let refresh_request = RefreshRequest {
client_id: CLIENT_ID,
client_id: oauth_client_id(),
grant_type: "refresh_token",
refresh_token,
};
@@ -1116,7 +1117,7 @@ fn extract_refresh_token_error_code(body: &str) -> Option<String> {
#[derive(Serialize)]
struct RefreshRequest {
client_id: &'static str,
client_id: String,
grant_type: &'static str,
refresh_token: String,
}
@@ -1131,6 +1132,13 @@ struct RefreshResponse {
// Shared constant for token refresh (client id used for oauth token refresh flow)
pub const CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
pub fn oauth_client_id() -> String {
std::env::var(CLIENT_ID_OVERRIDE_ENV_VAR)
.ok()
.filter(|client_id| !client_id.trim().is_empty())
.unwrap_or_else(|| CLIENT_ID.to_string())
}
fn refresh_token_endpoint() -> String {
std::env::var(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR)
.unwrap_or_else(|_| REFRESH_TOKEN_URL.to_string())
+4 -4
View File
@@ -10,10 +10,10 @@ use std::time::Duration;
use codex_app_server_protocol::AuthMode as ApiAuthMode;
use codex_client::CodexHttpClient;
use super::manager::CLIENT_ID;
use super::manager::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
use super::manager::REVOKE_TOKEN_URL;
use super::manager::REVOKE_TOKEN_URL_OVERRIDE_ENV_VAR;
use super::manager::oauth_client_id;
use super::storage::AuthDotJson;
use super::util::try_parse_error_message;
use crate::default_client::create_client;
@@ -35,10 +35,10 @@ impl RevokeTokenKind {
}
}
fn client_id(self) -> Option<&'static str> {
fn client_id(self) -> Option<String> {
match self {
Self::Access => None,
Self::Refresh => Some(CLIENT_ID),
Self::Refresh => Some(oauth_client_id()),
}
}
}
@@ -48,7 +48,7 @@ struct RevokeTokenRequest<'a> {
token: &'a str,
token_type_hint: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<&'static str>,
client_id: Option<String>,
}
pub(super) async fn revoke_auth_tokens(
+2
View File
@@ -23,6 +23,7 @@ pub use auth::AuthKeyringBackendKind;
pub use auth::AuthManager;
pub use auth::AuthManagerConfig;
pub use auth::CLIENT_ID;
pub use auth::CLIENT_ID_OVERRIDE_ENV_VAR;
pub use auth::CODEX_ACCESS_TOKEN_ENV_VAR;
pub use auth::CODEX_API_KEY_ENV_VAR;
pub use auth::CodexAuth;
@@ -45,6 +46,7 @@ pub use auth::login_with_api_key;
pub use auth::login_with_bedrock_api_key;
pub use auth::logout;
pub use auth::logout_with_revoke;
pub use auth::oauth_client_id;
pub use auth::read_codex_access_token_from_env;
pub use auth::read_openai_api_key_from_env;
pub use auth::save_auth;