Skip to main content

codex_cli/auth/
current.rs

1use anyhow::Result;
2use serde_json::json;
3use std::io::ErrorKind;
4use std::path::Path;
5
6use crate::auth;
7use crate::auth::output::{self, AuthCurrentResult};
8use crate::fs;
9use crate::paths;
10
11pub fn run() -> Result<i32> {
12    run_with_json(false)
13}
14
15pub fn run_with_json(output_json: bool) -> Result<i32> {
16    let auth_file = match paths::resolve_auth_file() {
17        Some(path) => path,
18        None => {
19            if output_json {
20                output::emit_error(
21                    "auth current",
22                    "auth-file-not-configured",
23                    "CODEX_AUTH_FILE is not configured",
24                    None,
25                )?;
26            }
27            return Ok(1);
28        }
29    };
30
31    if !auth_file.is_file() {
32        if output_json {
33            output::emit_error(
34                "auth current",
35                "auth-file-not-found",
36                format!("{} not found", auth_file.display()),
37                Some(json!({
38                    "auth_file": auth_file.display().to_string(),
39                })),
40            )?;
41        } else {
42            eprintln!("codex: {} not found", auth_file.display());
43        }
44        return Ok(1);
45    }
46
47    let auth_key = auth::identity_key_from_auth_file(&auth_file).ok().flatten();
48    let auth_hash = match fs::sha256_file(&auth_file) {
49        Ok(hash) => hash,
50        Err(_) => {
51            eprintln!("codex: failed to hash {}", auth_file.display());
52            return Ok(1);
53        }
54    };
55
56    let secret_dir = match paths::resolve_secret_dir() {
57        Some(path) => path,
58        None => {
59            emit_secret_dir_error(
60                output_json,
61                "secret-dir-not-configured",
62                "CODEX_SECRET_DIR is not configured".to_string(),
63                None,
64            )?;
65            return Ok(1);
66        }
67    };
68    let entries = match std::fs::read_dir(&secret_dir) {
69        Ok(entries) => entries,
70        Err(err) if err.kind() == ErrorKind::NotFound => {
71            emit_secret_dir_error(
72                output_json,
73                "secret-dir-not-found",
74                format!("{} not found", secret_dir.display()),
75                Some(json!({
76                    "secret_dir": secret_dir.display().to_string(),
77                })),
78            )?;
79            return Ok(1);
80        }
81        Err(err) => {
82            emit_secret_dir_error(
83                output_json,
84                "secret-dir-read-failed",
85                format!("failed to read {}: {err}", secret_dir.display()),
86                Some(json!({
87                    "secret_dir": secret_dir.display().to_string(),
88                    "error": err.to_string(),
89                })),
90            )?;
91            return Ok(1);
92        }
93    };
94    let mut matched: Option<(String, MatchMode)> = None;
95
96    for entry in entries.flatten() {
97        let path = entry.path();
98        if path.extension().and_then(|s| s.to_str()) != Some("json") {
99            continue;
100        }
101
102        if let Some(key) = auth_key.as_deref()
103            && let Ok(Some(candidate_key)) = auth::identity_key_from_auth_file(&path)
104            && candidate_key == key
105        {
106            let candidate_hash = match fs::sha256_file(&path) {
107                Ok(hash) => hash,
108                Err(_) => {
109                    if output_json {
110                        output::emit_error(
111                            "auth current",
112                            "hash-failed",
113                            format!("failed to hash {}", path.display()),
114                            Some(json!({
115                                "path": path.display().to_string(),
116                            })),
117                        )?;
118                    } else {
119                        eprintln!("codex: failed to hash {}", path.display());
120                    }
121                    return Ok(1);
122                }
123            };
124            let mode = if candidate_hash == auth_hash {
125                MatchMode::Exact
126            } else {
127                MatchMode::Identity
128            };
129            matched = Some((file_name(&path), mode));
130            break;
131        }
132
133        let candidate_hash = match fs::sha256_file(&path) {
134            Ok(hash) => hash,
135            Err(_) => {
136                if output_json {
137                    output::emit_error(
138                        "auth current",
139                        "hash-failed",
140                        format!("failed to hash {}", path.display()),
141                        Some(json!({
142                            "path": path.display().to_string(),
143                        })),
144                    )?;
145                } else {
146                    eprintln!("codex: failed to hash {}", path.display());
147                }
148                return Ok(1);
149            }
150        };
151        if candidate_hash == auth_hash {
152            matched = Some((file_name(&path), MatchMode::Exact));
153            break;
154        }
155    }
156
157    if let Some((secret_name, mode)) = matched {
158        if output_json {
159            let match_mode = match mode {
160                MatchMode::Exact => "exact",
161                MatchMode::Identity => "identity",
162            };
163            output::emit_result(
164                "auth current",
165                AuthCurrentResult {
166                    auth_file: auth_file.display().to_string(),
167                    matched: true,
168                    matched_secret: Some(secret_name),
169                    match_mode: Some(match_mode.to_string()),
170                },
171            )?;
172        } else {
173            match mode {
174                MatchMode::Exact => {
175                    println!("codex: {} matches {}", auth_file.display(), secret_name);
176                }
177                MatchMode::Identity => {
178                    println!(
179                        "codex: {} matches {} (identity; secret differs)",
180                        auth_file.display(),
181                        secret_name
182                    );
183                }
184            }
185        }
186        return Ok(0);
187    }
188
189    if output_json {
190        output::emit_error(
191            "auth current",
192            "secret-not-matched",
193            format!("{} does not match any known secret", auth_file.display()),
194            Some(json!({
195                "auth_file": auth_file.display().to_string(),
196                "matched": false,
197            })),
198        )?;
199    } else {
200        println!(
201            "codex: {} does not match any known secret",
202            auth_file.display()
203        );
204    }
205    Ok(2)
206}
207
208#[derive(Copy, Clone)]
209enum MatchMode {
210    Exact,
211    Identity,
212}
213
214fn file_name(path: &Path) -> String {
215    path.file_name()
216        .and_then(|name| name.to_str())
217        .unwrap_or_default()
218        .to_string()
219}
220
221fn emit_secret_dir_error(
222    output_json: bool,
223    code: &str,
224    message: String,
225    details: Option<serde_json::Value>,
226) -> Result<()> {
227    if output_json {
228        output::emit_error("auth current", code, message, details)?;
229    } else {
230        eprintln!("codex: {message}");
231    }
232    Ok(())
233}