Skip to content

Commit b54bf68

Browse files
committed
feat: unified token validation, 403 handling, bump v0.2.3
- Add require_token() and token_expired_exit() for consistent auth checks - All server requests detect 403 and prompt user to re-authenticate - Print token URL and auth command on token missing/expired - LLM 403 returns structured error with suggestions
1 parent 35c21d5 commit b54bf68

4 files changed

Lines changed: 68 additions & 34 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ members = [
1212
]
1313

1414
[workspace.package]
15-
version = "0.2.2"
15+
version = "0.2.3"
1616
edition = "2021"
1717
license = "Apache-2.0"
1818

crates/opencli-rs-ai/src/llm.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ pub async fn generate_with_llm(
6262
.await
6363
.map_err(|e| CliError::Http { message: format!("LLM request failed: {}", e), suggestions: vec![], source: None })?;
6464

65+
if resp.status().as_u16() == 403 {
66+
return Err(CliError::Http {
67+
message: "Token invalid or expired".into(),
68+
suggestions: vec![
69+
"Get a new token: https://autocli.ai/get-token".into(),
70+
"Then run: opencli-rs auth".into(),
71+
],
72+
source: None,
73+
});
74+
}
6575
if !resp.status().is_success() {
6676
let status = resp.status();
6777
let body = resp.text().await.unwrap_or_default();

crates/opencli-rs-cli/src/main.rs

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,44 @@ fn save_adapter(site: &str, name: &str, yaml: &str) {
161161
}
162162
}
163163

164+
const TOKEN_URL: &str = "https://autocli.ai/get-token";
165+
166+
/// Print token missing message and exit.
167+
fn require_token() -> String {
168+
let config = opencli_rs_ai::load_config();
169+
match config.autocli_token {
170+
Some(t) if !t.is_empty() => t,
171+
_ => {
172+
eprintln!("{}", t(
173+
"❌ 未认证,请先登录获取 Token",
174+
"❌ Not authenticated. Please login to get your token"
175+
));
176+
eprintln!(" {}", TOKEN_URL);
177+
eprintln!();
178+
eprintln!(" {}", t(
179+
"获取 Token 后运行: opencli-rs auth",
180+
"After getting your token, run: opencli-rs auth"
181+
));
182+
std::process::exit(1);
183+
}
184+
}
185+
}
186+
187+
/// Print token invalid/expired message and exit.
188+
fn token_expired_exit() -> ! {
189+
eprintln!("{}", t(
190+
"❌ Token 无效或已过期,请重新获取",
191+
"❌ Token is invalid or expired. Please get a new one"
192+
));
193+
eprintln!(" {}", TOKEN_URL);
194+
eprintln!();
195+
eprintln!(" {}", t(
196+
"获取新 Token 后运行: opencli-rs auth",
197+
"After getting a new token, run: opencli-rs auth"
198+
));
199+
std::process::exit(1);
200+
}
201+
164202
/// Adapter match from server search
165203
struct AdapterMatch {
166204
match_type: String,
@@ -192,6 +230,9 @@ async fn search_existing_adapters(url: &str, token: &str) -> Result<Vec<AdapterM
192230
.await
193231
.map_err(|_| t("❌ 服务器连接失败,请稍后再试", "❌ Server connection failed, please try again later").to_string())?;
194232

233+
if resp.status().as_u16() == 403 {
234+
token_expired_exit();
235+
}
195236
if !resp.status().is_success() {
196237
return Err(format!("{}{}", t("❌ 服务器返回错误: ", "❌ Server error: "), resp.status()));
197238
}
@@ -240,6 +281,9 @@ async fn fetch_adapter_config(command_uuid: &str, token: &str) -> Result<String,
240281
.await
241282
.map_err(|_| t("❌ 服务器连接失败,请稍后再试", "❌ Server connection failed, please try again later").to_string())?;
242283

284+
if resp.status().as_u16() == 403 {
285+
token_expired_exit();
286+
}
243287
if !resp.status().is_success() {
244288
return Err(format!("{}{}", t("❌ 获取配置失败: ", "❌ Failed to fetch config: "), resp.status()));
245289
}
@@ -255,14 +299,7 @@ async fn fetch_adapter_config(command_uuid: &str, token: &str) -> Result<String,
255299
}
256300

257301
async fn upload_adapter(yaml: &str) {
258-
let config = opencli_rs_ai::load_config();
259-
let token = match config.autocli_token {
260-
Some(t) => t,
261-
None => {
262-
eprintln!("{}", t("⏭️ 未配置 Token,跳过上传。运行: opencli-rs auth", "⏭️ No autocli-token configured, skipping upload. Run: opencli-rs auth"));
263-
return;
264-
}
265-
};
302+
let token = require_token();
266303

267304
let api_url = opencli_rs_ai::upload_url();
268305

@@ -288,6 +325,8 @@ async fn upload_adapter(yaml: &str) {
288325
Ok(resp) => {
289326
if resp.status().is_success() {
290327
eprintln!("{}", t("✅ 配置上传成功", "✅ Adapter uploaded successfully"));
328+
} else if resp.status().as_u16() == 403 {
329+
token_expired_exit();
291330
} else {
292331
let status = resp.status();
293332
let body = resp.text().await.unwrap_or_default();
@@ -409,14 +448,7 @@ async fn main() {
409448
} else {
410449
format!("https://{}", raw_url)
411450
};
412-
let config = opencli_rs_ai::load_config();
413-
let token = match &config.autocli_token {
414-
Some(t) => t.clone(),
415-
None => {
416-
eprintln!("{}", t("❌ 未认证,请先运行: opencli-rs auth", "❌ Not authenticated. Run first: opencli-rs auth"));
417-
std::process::exit(1);
418-
}
419-
};
451+
let token = require_token();
420452

421453
match search_existing_adapters(&url, &token).await {
422454
Ok(matches) if !matches.is_empty() => {
@@ -647,15 +679,7 @@ async fn main() {
647679
Ok(page) => {
648680
if use_ai {
649681
// Require token for --ai
650-
let config = opencli_rs_ai::load_config();
651-
let token = match &config.autocli_token {
652-
Some(t) => t.clone(),
653-
None => {
654-
eprintln!("{}", t("❌ 未认证,请先运行: opencli-rs auth", "❌ Not authenticated. Run first: opencli-rs auth"));
655-
let _ = page.close().await;
656-
std::process::exit(1);
657-
}
658-
};
682+
let token = require_token();
659683

660684
// Step 1: Search server for existing adapters
661685
let mut need_ai_generate = false;

0 commit comments

Comments
 (0)