Skip to main content

gemini_cli/auth/
current.rs

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