cc_switch/codex/
auth_writer.rs1use crate::codex::CodexConfiguration;
2use anyhow::{Result, anyhow};
3use serde_json::json;
4use std::fs;
5use std::path::PathBuf;
6
7fn get_auth_path(base_dir: Option<&PathBuf>) -> Result<PathBuf> {
9 let codex_dir = match base_dir {
10 Some(dir) => dir.join(".codex"),
11 None => dirs::home_dir()
12 .ok_or_else(|| anyhow!("Could not find home directory"))?
13 .join(".codex"),
14 };
15
16 if !codex_dir.exists() {
17 fs::create_dir_all(&codex_dir)
18 .map_err(|e| anyhow!("Failed to create .codex directory: {}", e))?;
19 }
20
21 Ok(codex_dir.join("auth.json"))
22}
23
24pub fn write_auth_json(config: &CodexConfiguration) -> Result<()> {
26 let auth_path = get_auth_path(None)?;
27 write_auth_json_to_path(config, &auth_path)
28}
29
30fn write_auth_json_to_path(config: &CodexConfiguration, auth_path: &PathBuf) -> Result<()> {
32 let json_value = if config.auth_mode == "apikey" {
33 json!({
34 "auth_mode": "apikey",
35 "OPENAI_API_KEY": config.openai_api_key,
36 "tokens": null
37 })
38 } else {
39 json!({
40 "auth_mode": "chatgpt",
41 "OPENAI_API_KEY": config.openai_api_key,
42 "tokens": {
43 "id_token": config.id_token,
44 "access_token": config.access_token,
45 "refresh_token": config.refresh_token,
46 "account_id": config.account_id
47 },
48 "last_refresh": config.last_refresh
49 })
50 };
51
52 let json_string = serde_json::to_string_pretty(&json_value)
53 .map_err(|e| anyhow!("Failed to serialize auth.json: {}", e))?;
54
55 if let Some(parent) = auth_path.parent()
57 && !parent.exists()
58 {
59 fs::create_dir_all(parent).map_err(|e| anyhow!("Failed to create directory: {}", e))?;
60 }
61
62 fs::write(auth_path, json_string).map_err(|e| anyhow!("Failed to write auth.json: {}", e))?;
63
64 Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use tempfile::TempDir;
71
72 #[test]
73 fn test_write_auth_json_chatgpt_mode() {
74 let temp_dir = TempDir::new().expect("Should create temp dir");
75 let auth_path = temp_dir.path().join(".codex").join("auth.json");
76
77 let config = CodexConfiguration {
78 alias_name: "test".to_string(),
79 auth_mode: "chatgpt".to_string(),
80 openai_api_key: None,
81 id_token: Some("test_id".to_string()),
82 access_token: Some("test_access".to_string()),
83 refresh_token: Some("test_refresh".to_string()),
84 account_id: Some("test_account".to_string()),
85 last_refresh: Some("2026-05-16T00:00:00Z".to_string()),
86 };
87
88 let result = write_auth_json_to_path(&config, &auth_path);
89 assert!(result.is_ok());
90
91 let content = fs::read_to_string(&auth_path).expect("Should read file");
92 let parsed: serde_json::Value = serde_json::from_str(&content).expect("Should parse");
93
94 assert_eq!(parsed["auth_mode"], "chatgpt");
95 assert_eq!(parsed["tokens"]["id_token"], "test_id");
96 assert_eq!(parsed["tokens"]["access_token"], "test_access");
97 assert_eq!(parsed["tokens"]["refresh_token"], "test_refresh");
98 assert_eq!(parsed["tokens"]["account_id"], "test_account");
99 }
100
101 #[test]
102 fn test_write_auth_json_apikey_mode() {
103 let temp_dir = TempDir::new().expect("Should create temp dir");
104 let auth_path = temp_dir.path().join(".codex").join("auth.json");
105
106 let config = CodexConfiguration {
107 alias_name: "test".to_string(),
108 auth_mode: "apikey".to_string(),
109 openai_api_key: Some("sk-ant-test-key".to_string()),
110 id_token: None,
111 access_token: None,
112 refresh_token: None,
113 account_id: None,
114 last_refresh: None,
115 };
116
117 let result = write_auth_json_to_path(&config, &auth_path);
118 assert!(result.is_ok());
119
120 let content = fs::read_to_string(&auth_path).expect("Should read file");
121 let parsed: serde_json::Value = serde_json::from_str(&content).expect("Should parse");
122
123 assert_eq!(parsed["auth_mode"], "apikey");
124 assert_eq!(parsed["OPENAI_API_KEY"], "sk-ant-test-key");
125 assert!(parsed["tokens"].is_null());
126 }
127}