mirror of
https://github.com/openai/codex.git
synced 2026-09-04 15:08:45 +00:00
## What changed - Add the experimental `amazonBedrockAccessKeys` app-server login flow, persist its credentials in the configured auth store, and use them for SigV4-signed Bedrock requests. - Report `bedrockAccessKeys` as a distinct auth mode and document access-key login alongside AWS profile and environment credential setup. - Clear the selected Bedrock provider, AWS settings, and Bedrock-specific model on logout while leaving external AWS credentials and `.env` untouched. - Reject managed access-key login when higher-precedence configuration would prevent its region or profile settings from taking effect. ## Testing - Cover access-key login, storage, request signing, configuration conflicts, status reporting, and logout cleanup. GitOrigin-RevId: bbf297887c6503d3651ceb2aad85c4d626e32723
250 lines
8.3 KiB
Rust
250 lines
8.3 KiB
Rust
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use strum_macros::Display;
|
|
use thiserror::Error;
|
|
|
|
/// Authentication mode for OpenAI-backed providers.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthMode {
|
|
/// OpenAI API key provided by the caller and stored by Codex.
|
|
ApiKey,
|
|
/// ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).
|
|
Chatgpt,
|
|
/// ChatGPT auth tokens supplied by an external host application.
|
|
#[serde(rename = "chatgptAuthTokens")]
|
|
#[strum(serialize = "chatgptAuthTokens")]
|
|
ChatgptAuthTokens,
|
|
/// Codex backend auth supplied as request headers.
|
|
#[serde(rename = "headers")]
|
|
#[strum(serialize = "headers")]
|
|
Headers,
|
|
/// Programmatic Codex auth backed by a registered Agent Identity.
|
|
#[serde(rename = "agentIdentity")]
|
|
#[strum(serialize = "agentIdentity")]
|
|
AgentIdentity,
|
|
/// Programmatic Codex auth backed by a personal access token.
|
|
#[serde(rename = "personalAccessToken")]
|
|
#[strum(serialize = "personalAccessToken")]
|
|
PersonalAccessToken,
|
|
/// Amazon Bedrock bearer token managed by Codex.
|
|
#[serde(rename = "bedrockApiKey")]
|
|
#[strum(serialize = "bedrockApiKey")]
|
|
BedrockApiKey,
|
|
/// Amazon Bedrock AWS access keys managed by Codex.
|
|
#[serde(rename = "bedrockAccessKeys")]
|
|
#[strum(serialize = "bedrockAccessKeys")]
|
|
BedrockAccessKeys,
|
|
}
|
|
|
|
impl AuthMode {
|
|
/// Returns whether this mode represents an authenticated human ChatGPT account.
|
|
pub fn has_chatgpt_account(self) -> bool {
|
|
match self {
|
|
Self::Chatgpt | Self::ChatgptAuthTokens | Self::PersonalAccessToken => true,
|
|
Self::ApiKey
|
|
| Self::Headers
|
|
| Self::AgentIdentity
|
|
| Self::BedrockApiKey
|
|
| Self::BedrockAccessKeys => false,
|
|
}
|
|
}
|
|
|
|
/// Returns whether this mode is backed by Codex services rather than a direct model API.
|
|
pub fn uses_codex_backend(self) -> bool {
|
|
match self {
|
|
Self::Chatgpt
|
|
| Self::ChatgptAuthTokens
|
|
| Self::Headers
|
|
| Self::AgentIdentity
|
|
| Self::PersonalAccessToken => true,
|
|
Self::ApiKey | Self::BedrockApiKey | Self::BedrockAccessKeys => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum PlanType {
|
|
Known(KnownPlan),
|
|
Unknown(String),
|
|
}
|
|
|
|
impl PlanType {
|
|
pub fn from_raw_value(raw: &str) -> Self {
|
|
match raw.to_ascii_lowercase().as_str() {
|
|
"free" => Self::Known(KnownPlan::Free),
|
|
"go" => Self::Known(KnownPlan::Go),
|
|
"plus" => Self::Known(KnownPlan::Plus),
|
|
"pro" => Self::Known(KnownPlan::Pro),
|
|
"prolite" => Self::Known(KnownPlan::ProLite),
|
|
"team" => Self::Known(KnownPlan::Team),
|
|
"self_serve_business_prolite" => Self::Known(KnownPlan::SelfServeBusinessProLite),
|
|
"self_serve_business_usage_based" => {
|
|
Self::Known(KnownPlan::SelfServeBusinessUsageBased)
|
|
}
|
|
"business" => Self::Known(KnownPlan::Business),
|
|
"ent26" => Self::Known(KnownPlan::Ent26),
|
|
"enterprise_cbp_automation" => Self::Known(KnownPlan::EnterpriseCbpAutomation),
|
|
"enterprise_cbp_usage_based" => Self::Known(KnownPlan::EnterpriseCbpUsageBased),
|
|
"enterprise" | "hc" => Self::Known(KnownPlan::Enterprise),
|
|
"education" | "edu" => Self::Known(KnownPlan::Edu),
|
|
"edu_plus" => Self::Known(KnownPlan::EduPlus),
|
|
"edu_pro" => Self::Known(KnownPlan::EduPro),
|
|
_ => Self::Unknown(raw.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum KnownPlan {
|
|
Free,
|
|
Go,
|
|
Plus,
|
|
Pro,
|
|
ProLite,
|
|
Team,
|
|
#[serde(rename = "self_serve_business_prolite")]
|
|
SelfServeBusinessProLite,
|
|
#[serde(rename = "self_serve_business_usage_based")]
|
|
SelfServeBusinessUsageBased,
|
|
Business,
|
|
Ent26,
|
|
#[serde(rename = "enterprise_cbp_automation")]
|
|
EnterpriseCbpAutomation,
|
|
#[serde(rename = "enterprise_cbp_usage_based")]
|
|
EnterpriseCbpUsageBased,
|
|
#[serde(alias = "hc")]
|
|
Enterprise,
|
|
#[serde(alias = "education")]
|
|
Edu,
|
|
#[serde(rename = "edu_plus")]
|
|
EduPlus,
|
|
#[serde(rename = "edu_pro")]
|
|
EduPro,
|
|
}
|
|
|
|
impl KnownPlan {
|
|
pub fn display_name(self) -> &'static str {
|
|
match self {
|
|
Self::Free => "Free",
|
|
Self::Go => "Go",
|
|
Self::Plus => "Plus",
|
|
Self::Pro => "Pro",
|
|
Self::ProLite => "Pro Lite",
|
|
Self::Team => "Team",
|
|
Self::SelfServeBusinessProLite => "Self Serve Business ProLite",
|
|
Self::SelfServeBusinessUsageBased => "Self Serve Business Usage Based",
|
|
Self::Business => "Business",
|
|
Self::Ent26 => "Enterprise",
|
|
Self::EnterpriseCbpAutomation => "Enterprise (Automation)",
|
|
Self::EnterpriseCbpUsageBased => "Enterprise CBP Usage Based",
|
|
Self::Enterprise => "Enterprise",
|
|
Self::Edu => "Edu",
|
|
Self::EduPlus => "Edu Plus",
|
|
Self::EduPro => "Edu Pro",
|
|
}
|
|
}
|
|
|
|
pub fn raw_value(self) -> &'static str {
|
|
match self {
|
|
Self::Free => "free",
|
|
Self::Go => "go",
|
|
Self::Plus => "plus",
|
|
Self::Pro => "pro",
|
|
Self::ProLite => "prolite",
|
|
Self::Team => "team",
|
|
Self::SelfServeBusinessProLite => "self_serve_business_prolite",
|
|
Self::SelfServeBusinessUsageBased => "self_serve_business_usage_based",
|
|
Self::Business => "business",
|
|
Self::Ent26 => "ent26",
|
|
Self::EnterpriseCbpAutomation => "enterprise_cbp_automation",
|
|
Self::EnterpriseCbpUsageBased => "enterprise_cbp_usage_based",
|
|
Self::Enterprise => "enterprise",
|
|
Self::Edu => "edu",
|
|
Self::EduPlus => "edu_plus",
|
|
Self::EduPro => "edu_pro",
|
|
}
|
|
}
|
|
|
|
pub fn is_workspace_account(self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::Team
|
|
| Self::SelfServeBusinessProLite
|
|
| Self::SelfServeBusinessUsageBased
|
|
| Self::Business
|
|
| Self::Ent26
|
|
| Self::EnterpriseCbpAutomation
|
|
| Self::EnterpriseCbpUsageBased
|
|
| Self::Enterprise
|
|
| Self::Edu
|
|
| Self::EduPlus
|
|
| Self::EduPro
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
|
#[error("{message}")]
|
|
pub struct RefreshTokenFailedError {
|
|
pub reason: RefreshTokenFailedReason,
|
|
pub message: String,
|
|
}
|
|
|
|
impl RefreshTokenFailedError {
|
|
pub fn new(reason: RefreshTokenFailedReason, message: impl Into<String>) -> Self {
|
|
Self {
|
|
reason,
|
|
message: message.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum RefreshTokenFailedReason {
|
|
Expired,
|
|
Exhausted,
|
|
Revoked,
|
|
Other,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::KnownPlan;
|
|
use super::PlanType;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn plan_type_deserializes_raw_aliases() {
|
|
assert_eq!(
|
|
serde_json::from_str::<PlanType>("\"hc\"").expect("hc should deserialize"),
|
|
PlanType::Known(KnownPlan::Enterprise)
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_str::<PlanType>("\"education\"")
|
|
.expect("education should deserialize"),
|
|
PlanType::Known(KnownPlan::Edu)
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_str::<PlanType>("\"enterprise_cbp_automation\"")
|
|
.expect("enterprise cbp automation should deserialize"),
|
|
PlanType::Known(KnownPlan::EnterpriseCbpAutomation)
|
|
);
|
|
for (raw, known) in [
|
|
("edu_plus", KnownPlan::EduPlus),
|
|
("edu_pro", KnownPlan::EduPro),
|
|
] {
|
|
let expected = PlanType::Known(known);
|
|
assert_eq!(PlanType::from_raw_value(raw), expected);
|
|
assert_eq!(
|
|
serde_json::from_value::<PlanType>(serde_json::json!(raw))
|
|
.expect("plan should deserialize"),
|
|
expected
|
|
);
|
|
assert_eq!(known.raw_value(), raw);
|
|
}
|
|
}
|
|
}
|