mirror of
https://github.com/openai/codex.git
synced 2026-04-28 10:21:06 +03:00
5.7 KiB
5.7 KiB
DOs
- Bold the logic: Prefer ChatGPT auth for Free/Plus/Pro/Team plans even if an API key is present.
- Bold the fallback: Read
OPENAI_API_KEYonly whenauth.jsonis missing (NotFound) andinclude_env_varis true. - Bold the error handling: If
auth.jsonexists but is malformed, return the error and do not fall back to env. - Bold the API key choice: Use API key when
auth.jsonhas an API key and no tokens. - Bold the enterprise routing: Use API key for Business/Enterprise/Edu or unknown plans.
- Bold the modeling: Represent plan with
PlanType(Known(KnownPlan)orUnknown(String)) and useserdeforKnownPlan. - Bold the helper: Add
read_openai_api_key_from_env()to encapsulate env access. - Bold the ChatGPT shape: In ChatGPT mode, set
api_key: Noneand keep only tokens/last_refresh; clearopenai_api_key. - Bold the display: Expose
IdTokenInfo::get_chatgpt_plan_type()for UI and title-case it for display. - Bold the tests: Use a minimal URL-safe, no-pad base64 JWT, a
LAST_REFRESHconstant, andtempdirhelpers.
// Auth selection (core decision points)
if let Ok(auth) = try_read_auth_json(&auth_file) {
let AuthDotJson { openai_api_key, tokens, last_refresh } = auth;
if let Some(api_key) = &openai_api_key {
match &tokens {
Some(tokens) if tokens.is_plan_that_should_use_api_key() => {
// Business/Enterprise/Edu/Unknown → API key
return Ok(Some(CodexAuth::from_api_key(api_key)));
}
Some(_) => {
// Free/Plus/Pro/Team → prefer ChatGPT
}
None => {
// API key but no tokens → API key
return Ok(Some(CodexAuth::from_api_key(api_key)));
}
}
}
// ChatGPT mode: no API key, keep tokens/last_refresh
return Ok(Some(CodexAuth {
api_key: None,
mode: AuthMode::ChatGPT,
auth_file,
auth_dot_json: Arc::new(Mutex::new(Some(AuthDotJson {
openai_api_key: None,
tokens,
last_refresh,
}))),
}));
} else if include_env_var && matches!(e.kind(), std::io::ErrorKind::NotFound) {
// Only when auth.json is missing
if let Some(k) = read_openai_api_key_from_env() {
return Ok(Some(CodexAuth::from_api_key(&k)));
}
return Ok(None);
} else {
// Malformed auth.json → surface error
return Err(e);
}
// Plan modeling
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
enum PlanType {
Known(KnownPlan),
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum KnownPlan { Free, Plus, Pro, Team, Business, Enterprise, Edu }
impl PlanType {
fn is_plan_that_should_use_api_key(&self) -> bool {
match self {
PlanType::Known(KnownPlan::Free | KnownPlan::Plus | KnownPlan::Pro | KnownPlan::Team) => false,
PlanType::Known(_) | PlanType::Unknown(_) => true,
}
}
}
impl TokenData {
pub(crate) fn is_plan_that_should_use_api_key(&self) -> bool {
self.id_token.chatgpt_plan_type.as_ref().is_none_or(|p| p.is_plan_that_should_use_api_key())
}
}
// Env helper
fn read_openai_api_key_from_env() -> Option<String> {
env::var(OPENAI_API_KEY_ENV_VAR).ok().filter(|s| !s.is_empty())
}
// UI display
let plan_text = info
.get_chatgpt_plan_type()
.map(|s| title_case(&s))
.unwrap_or_else(|| "Unknown".to_string());
lines.push(Line::from(vec![" • Plan: ".into(), plan_text.into()]));
if let Some(email) = &info.email {
lines.push(Line::from(vec![" • Login: ".into(), email.clone().into()]));
}
// Test helper: write a minimal auth.json
let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
serde_json::to_vec(&json!({ "alg": "none", "typ": "JWT" }))?,
);
let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
serde_json::to_vec(&json!({
"email": "user@example.com",
"https://api.openai.com/auth": {
"chatgpt_plan_type": "pro",
"user_id": "user-12345"
}
}))?,
);
let fake_jwt = format!("{header_b64}.{payload_b64}.{}",
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"sig"));
DON’Ts
- Bold the wrong fallback: Do not fall back to
OPENAI_API_KEYwhenauth.jsonexists but is malformed. - Bold the plan mistake: Do not use API key for Free/Plus/Pro/Team when tokens indicate those plans.
- Bold the token ignore: Do not ignore tokens/plan when choosing between ChatGPT and API key.
- Bold the leakage: Do not carry
openai_api_keyforward inauth_dot_jsonwhen using ChatGPT mode; set it toNone. - Bold the ambiguity: Do not treat unknown plan strings as ChatGPT-eligible; unknown plans use API key.
- Bold the env misuse: Do not read
OPENAI_API_KEYifinclude_env_varis false orauth.jsonis present. - Bold the ownership slip: Do not move
emailout ofIdTokenInfo; borrow (&info.email) and clone only when needed for ownership. - Bold the string handling: Do not hand-roll plan strings; use
PlanType/KnownPlanplusget_chatgpt_plan_type()andtitle_case.
// WRONG: falling back to env on malformed auth.json
match try_read_auth_json(&auth_file) {
Err(_) if include_env_var => read_openai_api_key_from_env().map(...), // ✗ Don’t do this
_ => {}
}
// WRONG: treating unknown plan as ChatGPT
match plan_type {
PlanType::Unknown(_) => use_chatgpt(), // ✗ Should use API key
_ => {}
}
// WRONG: keeping API key around in ChatGPT mode
CodexAuth { api_key: Some(api_key), mode: AuthMode::ChatGPT, .. } // ✗ api_key must be None