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 default_codex_auth_path() -> Result<PathBuf> {
32 let home = dirs::home_dir().ok_or_else(|| anyhow!("Could not find home directory"))?;
33 Ok(default_codex_auth_path_in(&home))
34}
35
36fn default_codex_auth_path_in(home: &std::path::Path) -> PathBuf {
39 home.join(".codex").join("auth.json")
40}
41
42pub fn write_auth_json(config: &CodexConfiguration) -> Result<()> {
44 let auth_path = get_auth_path(None)?;
45 write_auth_json_to_path(config, &auth_path)
46}
47
48fn write_auth_json_to_path(config: &CodexConfiguration, auth_path: &PathBuf) -> Result<()> {
50 let json_value = if config.auth_mode == "apikey" {
51 json!({
52 "auth_mode": "apikey",
53 "OPENAI_API_KEY": config.openai_api_key,
54 "tokens": null
55 })
56 } else {
57 json!({
58 "auth_mode": "chatgpt",
59 "OPENAI_API_KEY": config.openai_api_key,
60 "tokens": {
61 "id_token": config.id_token,
62 "access_token": config.access_token,
63 "refresh_token": config.refresh_token,
64 "account_id": config.account_id
65 },
66 "last_refresh": config.last_refresh
67 })
68 };
69
70 let json_string = serde_json::to_string_pretty(&json_value)
71 .map_err(|e| anyhow!("Failed to serialize auth.json: {}", e))?;
72
73 if let Some(parent) = auth_path.parent()
75 && !parent.exists()
76 {
77 fs::create_dir_all(parent).map_err(|e| anyhow!("Failed to create directory: {}", e))?;
78 }
79
80 fs::write(auth_path, json_string).map_err(|e| anyhow!("Failed to write auth.json: {}", e))?;
81
82 Ok(())
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use tempfile::TempDir;
89
90 #[test]
91 fn test_write_auth_json_chatgpt_mode() {
92 let temp_dir = TempDir::new().expect("Should create temp dir");
93 let auth_path = temp_dir.path().join(".codex").join("auth.json");
94
95 let config = CodexConfiguration {
96 alias_name: "test".to_string(),
97 auth_mode: "chatgpt".to_string(),
98 openai_api_key: None,
99 id_token: Some("test_id".to_string()),
100 access_token: Some("test_access".to_string()),
101 refresh_token: Some("test_refresh".to_string()),
102 account_id: Some("test_account".to_string()),
103 last_refresh: Some("2026-05-16T00:00:00Z".to_string()),
104 };
105
106 let result = write_auth_json_to_path(&config, &auth_path);
107 assert!(result.is_ok());
108
109 let content = fs::read_to_string(&auth_path).expect("Should read file");
110 let parsed: serde_json::Value = serde_json::from_str(&content).expect("Should parse");
111
112 assert_eq!(parsed["auth_mode"], "chatgpt");
113 assert_eq!(parsed["tokens"]["id_token"], "test_id");
114 assert_eq!(parsed["tokens"]["access_token"], "test_access");
115 assert_eq!(parsed["tokens"]["refresh_token"], "test_refresh");
116 assert_eq!(parsed["tokens"]["account_id"], "test_account");
117 }
118
119 #[test]
120 fn test_write_auth_json_apikey_mode() {
121 let temp_dir = TempDir::new().expect("Should create temp dir");
122 let auth_path = temp_dir.path().join(".codex").join("auth.json");
123
124 let config = CodexConfiguration {
125 alias_name: "test".to_string(),
126 auth_mode: "apikey".to_string(),
127 openai_api_key: Some("sk-ant-test-key".to_string()),
128 id_token: None,
129 access_token: None,
130 refresh_token: None,
131 account_id: None,
132 last_refresh: None,
133 };
134
135 let result = write_auth_json_to_path(&config, &auth_path);
136 assert!(result.is_ok());
137
138 let content = fs::read_to_string(&auth_path).expect("Should read file");
139 let parsed: serde_json::Value = serde_json::from_str(&content).expect("Should parse");
140
141 assert_eq!(parsed["auth_mode"], "apikey");
142 assert_eq!(parsed["OPENAI_API_KEY"], "sk-ant-test-key");
143 assert!(parsed["tokens"].is_null());
144 }
145
146 #[test]
147 fn test_default_codex_auth_path_ends_correctly() {
148 let path = default_codex_auth_path().expect("Should resolve default codex auth path");
149 let path_str = path.to_string_lossy();
150 assert!(
151 path_str.ends_with(".codex/auth.json") || path_str.ends_with(r".codex\auth.json"),
152 "expected path to end with .codex/auth.json, got {}",
153 path_str
154 );
155 }
156
157 #[test]
158 fn test_default_codex_auth_path_in_does_not_create_dir() {
159 let tmp = TempDir::new().expect("Should create tempdir");
163 let home = tmp.path();
164
165 let path = default_codex_auth_path_in(home);
166
167 assert_eq!(path, home.join(".codex").join("auth.json"));
168 assert!(
169 !home.join(".codex").exists(),
170 ".codex directory must not be created by default_codex_auth_path_in"
171 );
172 }
173}