mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
feat: load AgentIdentity from JWT login/env (#18904)
## Summary This PR lets programmatic AgentIdentity users provide one token through either stdin login or environment auth. `codex login --with-agent-identity` reads an Agent Identity JWT from stdin, validates that it has the required claims, and stores that token as the `agent_identity` value in `auth.json`. The file format is token-only; the decoded account and key fields are runtime state, not hand-authored auth.json fields. The Agent Identity JWT claim shape and decoder live in `codex-agent-identity`; `codex-login` only owns env/storage precedence and conversion into `CodexAuth::AgentIdentity`. When env auth is enabled, `CODEX_AGENT_IDENTITY` can provide the same JWT without writing auth state to disk. `CODEX_API_KEY` still wins if both env vars are set. Reference old stack: https://github.com/openai/codex/pull/17387/changes Reference JWT/env stack: https://github.com/openai/codex/pull/18176 ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. https://github.com/openai/codex/pull/18871: isolated Agent Identity crate 3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate Codex backend auth callsites through AuthProvider 5. This PR: accept AgentIdentity JWTs through login/env ## Testing Tests: targeted login and Agent Identity crate tests, CLI checks, scoped formatter/linter cleanup, and CI. --------- Co-authored-by: Shijie Rao <shijie.rao@openai.com>
This commit is contained in:
1 parent
ac2bffa443
commit
fed0a8f4fa
14 files changed
+587
-49
No files matched your search
@@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_agent_identity::AgentIdentityKey;
|
||||
use codex_agent_identity::normalize_chatgpt_base_url;
|
||||
use codex_agent_identity::register_agent_task;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use tokio::sync::OnceCell;
|
||||
@@ -10,7 +9,7 @@ use crate::default_client::build_reqwest_client;
|
||||
|
||||
use super::storage::AgentIdentityAuthRecord;
|
||||
|
||||
const DEFAULT_CHATGPT_BACKEND_BASE_URL: &str = "https://chatgpt.com/backend-api";
|
||||
const AGENT_IDENTITY_AUTHAPI_BASE_URL: &str = "https://auth.openai.com/api/accounts";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AgentIdentityAuth {
|
||||
@@ -43,17 +42,16 @@ impl AgentIdentityAuth {
|
||||
self.process_task_id.get().map(String::as_str)
|
||||
}
|
||||
|
||||
pub async fn ensure_runtime(&self, chatgpt_base_url: Option<String>) -> std::io::Result<()> {
|
||||
pub async fn ensure_runtime(&self) -> std::io::Result<()> {
|
||||
self.process_task_id
|
||||
.get_or_try_init(|| async {
|
||||
let base_url = normalize_chatgpt_base_url(
|
||||
chatgpt_base_url
|
||||
.as_deref()
|
||||
.unwrap_or(DEFAULT_CHATGPT_BACKEND_BASE_URL),
|
||||
);
|
||||
register_agent_task(&build_reqwest_client(), &base_url, self.key())
|
||||
.await
|
||||
.map_err(std::io::Error::other)
|
||||
register_agent_task(
|
||||
&build_reqwest_client(),
|
||||
AGENT_IDENTITY_AUTHAPI_BASE_URL,
|
||||
self.key(),
|
||||
)
|
||||
.await
|
||||
.map_err(std::io::Error::other)
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
|
||||
@@ -78,6 +78,44 @@ fn login_with_api_key_overwrites_existing_auth_json() {
|
||||
assert!(auth.tokens.is_none(), "tokens should be cleared");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_with_agent_identity_writes_only_token() {
|
||||
let dir = tempdir().unwrap();
|
||||
let auth_path = dir.path().join("auth.json");
|
||||
let record = agent_identity_record("account-123");
|
||||
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
|
||||
|
||||
super::login_with_agent_identity(dir.path(), &agent_identity, AuthCredentialsStoreMode::File)
|
||||
.expect("login_with_agent_identity should succeed");
|
||||
|
||||
let storage = FileAuthStorage::new(dir.path().to_path_buf());
|
||||
let auth = storage
|
||||
.try_read_auth_json(&auth_path)
|
||||
.expect("auth.json should parse");
|
||||
assert_eq!(auth.auth_mode, Some(AuthMode::AgentIdentity));
|
||||
assert_eq!(
|
||||
auth.agent_identity.as_deref(),
|
||||
Some(agent_identity.as_str())
|
||||
);
|
||||
assert!(auth.tokens.is_none(), "tokens should be cleared");
|
||||
assert!(auth.openai_api_key.is_none(), "API key should be cleared");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_with_agent_identity_rejects_invalid_jwt() {
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
let err =
|
||||
super::login_with_agent_identity(dir.path(), "not-a-jwt", AuthCredentialsStoreMode::File)
|
||||
.expect_err("invalid Agent Identity token should fail");
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::Other);
|
||||
assert!(
|
||||
!get_auth_file(dir.path()).exists(),
|
||||
"invalid Agent Identity token should not write auth.json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_auth_json_returns_none() {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -87,7 +125,7 @@ fn missing_auth_json_returns_none() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_api_key)]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let fake_jwt = write_auth_file(
|
||||
@@ -143,7 +181,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_api_key)]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn loads_api_key_from_auth_json() {
|
||||
let dir = tempdir().unwrap();
|
||||
let auth_file = dir.path().join("auth.json");
|
||||
@@ -581,7 +619,54 @@ impl Drop for EnvVarGuard {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(codex_auth_env)]
|
||||
fn load_auth_reads_agent_identity_from_env() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let expected_record = agent_identity_record("account-123");
|
||||
let agent_identity = fake_agent_identity_jwt(&expected_record).expect("fake agent identity");
|
||||
let _agent_guard = EnvVarGuard::set(CODEX_AGENT_IDENTITY_ENV_VAR, &agent_identity);
|
||||
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
)
|
||||
.expect("env auth should load")
|
||||
.expect("env auth should be present");
|
||||
|
||||
let CodexAuth::AgentIdentity(agent_identity) = auth else {
|
||||
panic!("env auth should load as agent identity");
|
||||
};
|
||||
assert_eq!(agent_identity.record(), &expected_record);
|
||||
assert!(
|
||||
!get_auth_file(codex_home.path()).exists(),
|
||||
"env auth should not write auth.json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(codex_auth_env)]
|
||||
fn load_auth_keeps_codex_api_key_env_precedence() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let record = agent_identity_record("account-123");
|
||||
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
|
||||
let _agent_guard = EnvVarGuard::set(CODEX_AGENT_IDENTITY_ENV_VAR, &agent_identity);
|
||||
let _api_key_guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env");
|
||||
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
)
|
||||
.expect("env auth should load")
|
||||
.expect("env auth should be present");
|
||||
|
||||
assert_eq!(auth.api_key(), Some("sk-env"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
login_with_api_key(codex_home.path(), "sk-test", AuthCredentialsStoreMode::File)
|
||||
@@ -604,7 +689,7 @@ async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_api_key)]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let _jwt = write_auth_file(
|
||||
@@ -634,7 +719,7 @@ async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_api_key)]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_allows_matching_workspace() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let _jwt = write_auth_file(
|
||||
@@ -662,6 +747,7 @@ async fn enforce_login_restrictions_allows_matching_workspace() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_forced_chatgpt_workspace_id_is_set()
|
||||
{
|
||||
let codex_home = tempdir().unwrap();
|
||||
@@ -683,7 +769,7 @@ async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_f
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_api_key)]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_blocks_env_api_key_when_chatgpt_required() {
|
||||
let _guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env");
|
||||
let codex_home = tempdir().unwrap();
|
||||
@@ -703,6 +789,35 @@ async fn enforce_login_restrictions_blocks_env_api_key_when_chatgpt_required() {
|
||||
);
|
||||
}
|
||||
|
||||
fn agent_identity_record(account_id: &str) -> AgentIdentityAuthRecord {
|
||||
AgentIdentityAuthRecord {
|
||||
agent_runtime_id: "agent-runtime-id".to_string(),
|
||||
agent_private_key: "private-key".to_string(),
|
||||
account_id: account_id.to_string(),
|
||||
chatgpt_user_id: "user-id".to_string(),
|
||||
email: "user@example.com".to_string(),
|
||||
plan_type: AccountPlanType::Pro,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn fake_agent_identity_jwt(record: &AgentIdentityAuthRecord) -> std::io::Result<String> {
|
||||
let encode = |bytes: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
|
||||
let header_b64 = encode(br#"{"alg":"EdDSA","typ":"JWT"}"#);
|
||||
let payload = json!({
|
||||
"agent_runtime_id": record.agent_runtime_id,
|
||||
"agent_private_key": record.agent_private_key,
|
||||
"account_id": record.account_id,
|
||||
"chatgpt_user_id": record.chatgpt_user_id,
|
||||
"email": record.email,
|
||||
"plan_type": record.plan_type,
|
||||
"chatgpt_account_is_fedramp": record.chatgpt_account_is_fedramp,
|
||||
});
|
||||
let payload_b64 = encode(&serde_json::to_vec(&payload)?);
|
||||
let signature_b64 = encode(b"sig");
|
||||
Ok(format!("{header_b64}.{payload_b64}.{signature_b64}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_type_maps_known_plan() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
|
||||
@@ -207,12 +207,12 @@ impl CodexAuth {
|
||||
return Ok(Self::from_api_key(api_key));
|
||||
}
|
||||
if auth_mode == ApiAuthMode::AgentIdentity {
|
||||
let Some(record) = auth_dot_json.agent_identity else {
|
||||
let Some(agent_identity) = auth_dot_json.agent_identity else {
|
||||
return Err(std::io::Error::other(
|
||||
"agent identity auth is missing an agent identity record.",
|
||||
"agent identity auth is missing an agent identity token.",
|
||||
));
|
||||
};
|
||||
return Ok(Self::AgentIdentity(AgentIdentityAuth::new(record)));
|
||||
return Self::from_agent_identity_jwt(&agent_identity);
|
||||
}
|
||||
|
||||
let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode);
|
||||
@@ -245,6 +245,11 @@ impl CodexAuth {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_agent_identity_jwt(jwt: &str) -> std::io::Result<Self> {
|
||||
let record = AgentIdentityAuthRecord::from_agent_identity_jwt(jwt)?;
|
||||
Ok(Self::AgentIdentity(AgentIdentityAuth::new(record)))
|
||||
}
|
||||
|
||||
pub fn auth_mode(&self) -> AuthMode {
|
||||
match self {
|
||||
Self::ApiKey(_) => AuthMode::ApiKey,
|
||||
@@ -318,10 +323,10 @@ impl CodexAuth {
|
||||
|
||||
pub async fn initialize_runtime(
|
||||
&self,
|
||||
chatgpt_base_url: Option<String>,
|
||||
_chatgpt_base_url: Option<String>,
|
||||
) -> std::io::Result<()> {
|
||||
match self {
|
||||
Self::AgentIdentity(auth) => auth.ensure_runtime(chatgpt_base_url).await,
|
||||
Self::AgentIdentity(auth) => auth.ensure_runtime().await,
|
||||
Self::ApiKey(_) | Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -474,6 +479,7 @@ impl ChatgptAuth {
|
||||
|
||||
pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY";
|
||||
pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY";
|
||||
pub const CODEX_AGENT_IDENTITY_ENV_VAR: &str = "CODEX_AGENT_IDENTITY";
|
||||
|
||||
pub fn read_openai_api_key_from_env() -> Option<String> {
|
||||
env::var(OPENAI_API_KEY_ENV_VAR)
|
||||
@@ -489,6 +495,13 @@ pub fn read_codex_api_key_from_env() -> Option<String> {
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
pub fn read_codex_agent_identity_from_env() -> Option<String> {
|
||||
env::var(CODEX_AGENT_IDENTITY_ENV_VAR)
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
/// Delete the auth.json file inside `codex_home` if it exists. Returns `Ok(true)`
|
||||
/// if a file was removed, `Ok(false)` if no auth file was present.
|
||||
pub fn logout(
|
||||
@@ -529,6 +542,23 @@ pub fn login_with_api_key(
|
||||
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
|
||||
}
|
||||
|
||||
/// Writes an `auth.json` that contains only the Agent Identity token.
|
||||
pub fn login_with_agent_identity(
|
||||
codex_home: &Path,
|
||||
agent_identity: &str,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
) -> std::io::Result<()> {
|
||||
AgentIdentityAuthRecord::from_agent_identity_jwt(agent_identity)?;
|
||||
let auth_dot_json = AuthDotJson {
|
||||
auth_mode: Some(ApiAuthMode::AgentIdentity),
|
||||
openai_api_key: None,
|
||||
tokens: None,
|
||||
last_refresh: None,
|
||||
agent_identity: Some(agent_identity.to_string()),
|
||||
};
|
||||
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
|
||||
}
|
||||
|
||||
/// Writes an in-memory auth payload for externally managed ChatGPT tokens.
|
||||
pub fn login_with_chatgpt_auth_tokens(
|
||||
codex_home: &Path,
|
||||
@@ -714,6 +744,10 @@ fn load_auth(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(agent_identity) = read_codex_agent_identity_from_env() {
|
||||
return CodexAuth::from_agent_identity_jwt(&agent_identity).map(Some);
|
||||
}
|
||||
|
||||
// Fall back to the configured persistent store (file/keyring/auto) for managed auth.
|
||||
let storage = create_auth_storage(codex_home.to_path_buf(), auth_credentials_store_mode);
|
||||
let auth_dot_json = match storage.load()? {
|
||||
|
||||
@@ -19,6 +19,7 @@ use std::sync::Mutex;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::token_data::TokenData;
|
||||
use codex_agent_identity::decode_agent_identity_jwt;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_keyring_store::DefaultKeyringStore;
|
||||
@@ -42,7 +43,7 @@ pub struct AuthDotJson {
|
||||
pub last_refresh: Option<DateTime<Utc>>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub agent_identity: Option<AgentIdentityAuthRecord>,
|
||||
pub agent_identity: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -56,6 +57,23 @@ pub struct AgentIdentityAuthRecord {
|
||||
pub chatgpt_account_is_fedramp: bool,
|
||||
}
|
||||
|
||||
impl AgentIdentityAuthRecord {
|
||||
pub(crate) fn from_agent_identity_jwt(jwt: &str) -> std::io::Result<Self> {
|
||||
let claims = decode_agent_identity_jwt(jwt, /*public_key_base64*/ None)
|
||||
.map_err(std::io::Error::other)?;
|
||||
|
||||
Ok(Self {
|
||||
agent_runtime_id: claims.agent_runtime_id,
|
||||
agent_private_key: claims.agent_private_key,
|
||||
account_id: claims.account_id,
|
||||
chatgpt_user_id: claims.chatgpt_user_id,
|
||||
email: claims.email,
|
||||
plan_type: claims.plan_type,
|
||||
chatgpt_account_is_fedramp: claims.chatgpt_account_is_fedramp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_auth_file(codex_home: &Path) -> PathBuf {
|
||||
codex_home.join("auth.json")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use serde_json::json;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use codex_keyring_store::tests::MockKeyringStore;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use keyring::Error as KeyringError;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -59,20 +58,21 @@ async fn file_storage_save_persists_auth_dot_json() -> anyhow::Result<()> {
|
||||
async fn file_storage_round_trips_agent_identity_auth() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
let storage = FileAuthStorage::new(codex_home.path().to_path_buf());
|
||||
let agent_identity = jwt_with_payload(json!({
|
||||
"agent_runtime_id": "agent-runtime-id",
|
||||
"agent_private_key": "private-key",
|
||||
"account_id": "account-id",
|
||||
"chatgpt_user_id": "user-id",
|
||||
"email": "user@example.com",
|
||||
"plan_type": "pro",
|
||||
"chatgpt_account_is_fedramp": false,
|
||||
}));
|
||||
let auth_dot_json = AuthDotJson {
|
||||
auth_mode: Some(AuthMode::AgentIdentity),
|
||||
openai_api_key: None,
|
||||
tokens: None,
|
||||
last_refresh: None,
|
||||
agent_identity: Some(AgentIdentityAuthRecord {
|
||||
agent_runtime_id: "agent-runtime-id".to_string(),
|
||||
agent_private_key: "private-key".to_string(),
|
||||
account_id: "account-id".to_string(),
|
||||
chatgpt_user_id: "user-id".to_string(),
|
||||
email: "user@example.com".to_string(),
|
||||
plan_type: AccountPlanType::Pro,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
}),
|
||||
agent_identity: Some(agent_identity),
|
||||
};
|
||||
|
||||
storage.save(&auth_dot_json)?;
|
||||
@@ -82,6 +82,37 @@ async fn file_storage_round_trips_agent_identity_auth() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn file_storage_loads_agent_identity_as_jwt() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
let storage = FileAuthStorage::new(codex_home.path().to_path_buf());
|
||||
let agent_identity_jwt = jwt_with_payload(json!({
|
||||
"agent_runtime_id": "agent-runtime-id",
|
||||
"agent_private_key": "private-key",
|
||||
"account_id": "account-id",
|
||||
"chatgpt_user_id": "user-id",
|
||||
"email": "user@example.com",
|
||||
"plan_type": "pro",
|
||||
"chatgpt_account_is_fedramp": false,
|
||||
}));
|
||||
let auth_file = get_auth_file(codex_home.path());
|
||||
std::fs::write(
|
||||
&auth_file,
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"auth_mode": "agentIdentity",
|
||||
"agent_identity": agent_identity_jwt,
|
||||
}))?,
|
||||
)?;
|
||||
|
||||
let loaded = storage.load()?;
|
||||
|
||||
assert_eq!(
|
||||
loaded.expect("auth should load").agent_identity.as_deref(),
|
||||
Some(agent_identity_jwt.as_str())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_storage_delete_removes_auth_file() -> anyhow::Result<()> {
|
||||
let dir = tempdir()?;
|
||||
@@ -217,6 +248,14 @@ fn auth_with_prefix(prefix: &str) -> AuthDotJson {
|
||||
}
|
||||
}
|
||||
|
||||
fn jwt_with_payload(payload: serde_json::Value) -> String {
|
||||
let encode = |bytes: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
|
||||
let header_b64 = encode(br#"{"alg":"EdDSA","typ":"JWT"}"#);
|
||||
let payload_b64 = encode(&serde_json::to_vec(&payload).expect("payload should serialize"));
|
||||
let signature_b64 = encode(b"sig");
|
||||
format!("{header_b64}.{payload_b64}.{signature_b64}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyring_auth_storage_load_returns_deserialized_auth() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
|
||||
@@ -22,6 +22,7 @@ pub use auth::AuthDotJson;
|
||||
pub use auth::AuthManager;
|
||||
pub use auth::AuthManagerConfig;
|
||||
pub use auth::CLIENT_ID;
|
||||
pub use auth::CODEX_AGENT_IDENTITY_ENV_VAR;
|
||||
pub use auth::CODEX_API_KEY_ENV_VAR;
|
||||
pub use auth::CodexAuth;
|
||||
pub use auth::ExternalAuth;
|
||||
@@ -37,9 +38,11 @@ pub use auth::UnauthorizedRecovery;
|
||||
pub use auth::default_client;
|
||||
pub use auth::enforce_login_restrictions;
|
||||
pub use auth::load_auth_dot_json;
|
||||
pub use auth::login_with_agent_identity;
|
||||
pub use auth::login_with_api_key;
|
||||
pub use auth::logout;
|
||||
pub use auth::logout_with_revoke;
|
||||
pub use auth::read_codex_agent_identity_from_env;
|
||||
pub use auth::read_openai_api_key_from_env;
|
||||
pub use auth::save_auth;
|
||||
pub use auth_env_telemetry::AuthEnvTelemetry;
|
||||
|
||||
Reference in new issue
Block a user