mirror of
https://github.com/openai/codex.git
synced 2026-05-21 19:45:26 +00:00
validate api key before login success
This commit is contained in:
@@ -343,6 +343,7 @@ use codex_mcp::resolve_oauth_scopes;
|
||||
use codex_memories_write::clear_memory_roots_contents;
|
||||
use codex_model_provider::ProviderAccountError;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider::validate_api_key_with_models_endpoint;
|
||||
use codex_models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
|
||||
@@ -271,6 +271,8 @@ impl AccountRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
self.validate_api_key(¶ms.api_key).await?;
|
||||
|
||||
match login_with_api_key(
|
||||
&self.config.codex_home,
|
||||
¶ms.api_key,
|
||||
@@ -284,6 +286,21 @@ impl AccountRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_api_key(&self, api_key: &str) -> Result<(), JSONRPCErrorError> {
|
||||
if !self.config.model_provider.requires_openai_auth {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
validate_api_key_with_models_endpoint(self.config.model_provider.clone(), api_key)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
CodexErr::UnexpectedStatus(err) if matches!(err.status.as_u16(), 401 | 403) => {
|
||||
invalid_request("API key is invalid or unusable.")
|
||||
}
|
||||
err => internal_error(format!("failed to validate api key: {err}")),
|
||||
})
|
||||
}
|
||||
|
||||
async fn login_api_key_v2(&self, request_id: ConnectionRequestId, params: LoginApiKeyParams) {
|
||||
let result = self
|
||||
.login_api_key_common(¶ms)
|
||||
|
||||
@@ -883,7 +883,23 @@ async fn external_auth_refresh_invalid_access_token_fails_turn() -> Result<()> {
|
||||
#[tokio::test]
|
||||
async fn login_account_api_key_succeeds_and_notifies() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), CreateConfigTomlParams::default())?;
|
||||
let mock_server = MockServer::start().await;
|
||||
create_config_toml(
|
||||
codex_home.path(),
|
||||
CreateConfigTomlParams {
|
||||
requires_openai_auth: Some(true),
|
||||
base_url: Some(format!("{}/v1", mock_server.uri())),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/v1/models"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"models": []
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
@@ -928,6 +944,45 @@ async fn login_account_api_key_succeeds_and_notifies() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_account_api_key_rejects_unusable_key_before_persisting() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mock_server = MockServer::start().await;
|
||||
create_config_toml(
|
||||
codex_home.path(),
|
||||
CreateConfigTomlParams {
|
||||
requires_openai_auth: Some(true),
|
||||
base_url: Some(format!("{}/v1", mock_server.uri())),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/v1/models"))
|
||||
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
|
||||
"error": { "message": "Invalid API key" }
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_login_account_api_key_request("sk-invalid-key")
|
||||
.await?;
|
||||
let err: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(err.error.message, "API key is invalid or unusable.");
|
||||
assert!(!codex_home.path().join("auth.json").exists());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_account_api_key_rejected_when_forced_chatgpt() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -9,6 +9,7 @@ pub use auth::unauthenticated_auth_provider;
|
||||
pub use bearer_auth_provider::BearerAuthProvider;
|
||||
pub use bearer_auth_provider::BearerAuthProvider as CoreAuthProvider;
|
||||
pub use codex_protocol::account::ProviderAccount;
|
||||
pub use models_endpoint::validate_api_key_with_models_endpoint;
|
||||
pub use provider::ModelProvider;
|
||||
pub use provider::ProviderAccountError;
|
||||
pub use provider::ProviderAccountResult;
|
||||
|
||||
@@ -16,6 +16,7 @@ use codex_login::CodexAuth;
|
||||
use codex_login::collect_auth_env_telemetry;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::client_version_to_whole;
|
||||
use codex_models_manager::manager::ModelsEndpointClient;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::error::CodexErr;
|
||||
@@ -36,6 +37,7 @@ const MODELS_ENDPOINT: &str = "/models";
|
||||
pub(crate) struct OpenAiModelsEndpoint {
|
||||
provider_info: ModelProviderInfo,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
auth_override: Option<CodexAuth>,
|
||||
}
|
||||
|
||||
impl OpenAiModelsEndpoint {
|
||||
@@ -46,10 +48,23 @@ impl OpenAiModelsEndpoint {
|
||||
Self {
|
||||
provider_info,
|
||||
auth_manager,
|
||||
auth_override: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_auth(provider_info: ModelProviderInfo, auth: CodexAuth) -> Self {
|
||||
Self {
|
||||
provider_info,
|
||||
auth_manager: None,
|
||||
auth_override: Some(auth),
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Option<CodexAuth> {
|
||||
if let Some(auth) = self.auth_override.as_ref() {
|
||||
return Some(auth.clone());
|
||||
}
|
||||
|
||||
match self.auth_manager.as_ref() {
|
||||
Some(auth_manager) => auth_manager.auth().await,
|
||||
None => None,
|
||||
@@ -65,6 +80,16 @@ impl OpenAiModelsEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn validate_api_key_with_models_endpoint(
|
||||
provider_info: ModelProviderInfo,
|
||||
api_key: &str,
|
||||
) -> CoreResult<()> {
|
||||
OpenAiModelsEndpoint::with_auth(provider_info, CodexAuth::from_api_key(api_key))
|
||||
.list_models(&client_version_to_whole())
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsEndpointClient for OpenAiModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user