Skip to main content

codex_cli/auth/
current.rs

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