mirror of
https://github.com/openai/codex.git
synced 2026-05-02 12:21:26 +03:00
codex: address create-api-key review feedback
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -313,23 +313,29 @@ async fn wait_for_default_project(
|
|||||||
let deadline = std::time::Instant::now() + Duration::from_secs(timeout_seconds);
|
let deadline = std::time::Instant::now() + Duration::from_secs(timeout_seconds);
|
||||||
loop {
|
loop {
|
||||||
let organizations = list_organizations(client, api_base, session_key).await?;
|
let organizations = list_organizations(client, api_base, session_key).await?;
|
||||||
let last_state = if let Some(organization) = select_active_organization(&organizations) {
|
let last_state = if organizations.is_empty() {
|
||||||
let projects = list_projects(client, api_base, session_key, &organization.id).await?;
|
"no organization found".to_string()
|
||||||
if let Some(project) = find_default_project(&projects) {
|
} else {
|
||||||
return Ok(ProvisioningTarget {
|
let ordered_organizations = organizations_by_preference(&organizations);
|
||||||
organization_id: organization.id.clone(),
|
let mut project_count = 0;
|
||||||
organization_title: organization.title.clone(),
|
for organization in ordered_organizations {
|
||||||
project_id: project.id.clone(),
|
let projects =
|
||||||
project_title: project.title.clone(),
|
list_projects(client, api_base, session_key, &organization.id).await?;
|
||||||
});
|
project_count += projects.len();
|
||||||
|
if let Some(project) = find_default_project(&projects) {
|
||||||
|
return Ok(ProvisioningTarget {
|
||||||
|
organization_id: organization.id.clone(),
|
||||||
|
organization_title: organization.title.clone(),
|
||||||
|
project_id: project.id.clone(),
|
||||||
|
project_title: project.title.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
format!(
|
format!(
|
||||||
"organization `{}` exists, but no default project is ready yet (saw {} projects).",
|
"checked {} organizations and {} projects, but no default project is ready yet.",
|
||||||
organization.id,
|
organizations.len(),
|
||||||
projects.len()
|
project_count
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
"no organization found".to_string()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if std::time::Instant::now() >= deadline {
|
if std::time::Instant::now() >= deadline {
|
||||||
@@ -345,16 +351,22 @@ async fn wait_for_default_project(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_active_organization(organizations: &[Organization]) -> Option<&Organization> {
|
fn organizations_by_preference(organizations: &[Organization]) -> Vec<&Organization> {
|
||||||
organizations
|
let mut ordered_organizations = organizations.iter().enumerate().collect::<Vec<_>>();
|
||||||
.iter()
|
ordered_organizations.sort_by_key(|(index, organization)| {
|
||||||
.find(|organization| organization.is_default)
|
let rank = if organization.is_default {
|
||||||
.or_else(|| {
|
0
|
||||||
organizations
|
} else if organization.personal {
|
||||||
.iter()
|
1
|
||||||
.find(|organization| organization.personal)
|
} else {
|
||||||
})
|
2
|
||||||
.or_else(|| organizations.first())
|
};
|
||||||
|
(rank, *index)
|
||||||
|
});
|
||||||
|
ordered_organizations
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, organization)| organization)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_default_project(projects: &[Project]) -> Option<&Project> {
|
fn find_default_project(projects: &[Project]) -> Option<&Project> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use wiremock::matchers::path;
|
|||||||
use wiremock::matchers::query_param;
|
use wiremock::matchers::query_param;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_active_organization_prefers_default_then_personal_then_first() {
|
fn organizations_by_preference_orders_default_then_personal_then_input_order() {
|
||||||
let organizations = vec![
|
let organizations = vec![
|
||||||
Organization {
|
Organization {
|
||||||
id: "org-first".to_string(),
|
id: "org-first".to_string(),
|
||||||
@@ -33,9 +33,12 @@ fn select_active_organization_prefers_default_then_personal_then_first() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let selected = select_active_organization(&organizations);
|
let selected = organizations_by_preference(&organizations);
|
||||||
|
|
||||||
assert_eq!(selected, organizations.get(2));
|
assert_eq!(
|
||||||
|
selected,
|
||||||
|
vec![&organizations[2], &organizations[1], &organizations[0]]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -97,6 +100,10 @@ async fn create_api_key_from_authorization_code_creates_api_key() {
|
|||||||
"id": "org-default",
|
"id": "org-default",
|
||||||
"title": "Default Org",
|
"title": "Default Org",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org-secondary",
|
||||||
|
"title": "Secondary Org",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
@@ -106,6 +113,15 @@ async fn create_api_key_from_authorization_code_creates_api_key() {
|
|||||||
.and(path("/dashboard/organizations/org-default/projects"))
|
.and(path("/dashboard/organizations/org-default/projects"))
|
||||||
.and(query_param("detail", "basic"))
|
.and(query_param("detail", "basic"))
|
||||||
.and(query_param("limit", "100"))
|
.and(query_param("limit", "100"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||||
|
"data": []
|
||||||
|
})))
|
||||||
|
.mount(&server)
|
||||||
|
.await;
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/dashboard/organizations/org-secondary/projects"))
|
||||||
|
.and(query_param("detail", "basic"))
|
||||||
|
.and(query_param("limit", "100"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
@@ -119,7 +135,7 @@ async fn create_api_key_from_authorization_code_creates_api_key() {
|
|||||||
.await;
|
.await;
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path(
|
.and(path(
|
||||||
"/dashboard/organizations/org-default/projects/proj-default/api_keys",
|
"/dashboard/organizations/org-secondary/projects/proj-default/api_keys",
|
||||||
))
|
))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||||
"key": {
|
"key": {
|
||||||
@@ -156,8 +172,8 @@ async fn create_api_key_from_authorization_code_creates_api_key() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
output,
|
output,
|
||||||
CreatedApiKey {
|
CreatedApiKey {
|
||||||
organization_id: "org-default".to_string(),
|
organization_id: "org-secondary".to_string(),
|
||||||
organization_title: Some("Default Org".to_string()),
|
organization_title: Some("Secondary Org".to_string()),
|
||||||
default_project_id: "proj-default".to_string(),
|
default_project_id: "proj-default".to_string(),
|
||||||
default_project_title: Some("Default Project".to_string()),
|
default_project_title: Some("Default Project".to_string()),
|
||||||
project_api_key: "sk-proj-123".to_string(),
|
project_api_key: "sk-proj-123".to_string(),
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ pub use server::LoginServer;
|
|||||||
pub use server::ServerOptions;
|
pub use server::ServerOptions;
|
||||||
pub use server::run_login_server;
|
pub use server::run_login_server;
|
||||||
|
|
||||||
pub use create_api_key::CreateApiKeyError;
|
|
||||||
pub use create_api_key::CreatedApiKey;
|
|
||||||
pub use create_api_key::PendingCreateApiKey;
|
|
||||||
pub use create_api_key::start_create_api_key;
|
|
||||||
pub use auth::AuthConfig;
|
pub use auth::AuthConfig;
|
||||||
pub use auth::AuthCredentialsStoreMode;
|
pub use auth::AuthCredentialsStoreMode;
|
||||||
pub use auth::AuthDotJson;
|
pub use auth::AuthDotJson;
|
||||||
@@ -40,4 +36,8 @@ pub use auth::logout;
|
|||||||
pub use auth::read_openai_api_key_from_env;
|
pub use auth::read_openai_api_key_from_env;
|
||||||
pub use auth::save_auth;
|
pub use auth::save_auth;
|
||||||
pub use codex_app_server_protocol::AuthMode;
|
pub use codex_app_server_protocol::AuthMode;
|
||||||
|
pub use create_api_key::CreateApiKeyError;
|
||||||
|
pub use create_api_key::CreatedApiKey;
|
||||||
|
pub use create_api_key::PendingCreateApiKey;
|
||||||
|
pub use create_api_key::start_create_api_key;
|
||||||
pub use token_data::TokenData;
|
pub use token_data::TokenData;
|
||||||
|
|||||||
@@ -385,16 +385,14 @@ fn process_authorization_code_request(
|
|||||||
parsed_url.query_pairs().into_owned().collect();
|
parsed_url.query_pairs().into_owned().collect();
|
||||||
|
|
||||||
if params.get("state").map(String::as_str) != Some(expected_state) {
|
if params.get("state").map(String::as_str) != Some(expected_state) {
|
||||||
return HandledRequest::ResponseAndExit {
|
let mut response = Response::from_string(
|
||||||
status: StatusCode(400),
|
"<h1>State mismatch</h1><p>Return to your terminal and try again.</p>",
|
||||||
headers: html_headers(),
|
)
|
||||||
body: b"<h1>State mismatch</h1><p>Return to your terminal and try again.</p>"
|
.with_status_code(400);
|
||||||
.to_vec(),
|
if let Some(header) = html_headers().into_iter().next() {
|
||||||
result: Err(io::Error::new(
|
response = response.with_header(header);
|
||||||
io::ErrorKind::PermissionDenied,
|
}
|
||||||
"State mismatch in OAuth callback.",
|
return HandledRequest::Response(response);
|
||||||
)),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error_code) = params.get("error") {
|
if let Some(error_code) = params.get("error") {
|
||||||
@@ -451,3 +449,24 @@ fn authorization_code_error_message(error_code: &str, error_description: Option<
|
|||||||
|
|
||||||
format!("Authentication failed: {error_code}")
|
format!("Authentication failed: {error_code}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_authorization_code_request_keeps_server_running_on_state_mismatch() {
|
||||||
|
let response = process_authorization_code_request(
|
||||||
|
"/auth/callback?state=wrong-state&code=auth-code",
|
||||||
|
"/auth/callback",
|
||||||
|
"expected-state",
|
||||||
|
);
|
||||||
|
|
||||||
|
match response {
|
||||||
|
HandledRequest::Response(_) => {}
|
||||||
|
HandledRequest::RedirectWithHeader(_) | HandledRequest::ResponseAndExit { .. } => {
|
||||||
|
panic!("state mismatch should return a response without exiting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user