codex_cli/auth/
current.rs1use 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}