Skip to main content

cc_switch/codex/
auth_writer.rs

1use crate::codex::CodexConfiguration;
2use anyhow::{Result, anyhow};
3use serde_json::json;
4use std::fs;
5use std::path::PathBuf;
6
7/// Build the auth.json path, using home directory or an override for testing
8fn 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
24/// Write CodexConfiguration to ~/.codex/auth.json
25pub 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
30/// Write CodexConfiguration to a specific path (for testing)
31fn 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    // Ensure parent directory exists
56    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}