storkit: merge 448_story_send_oauth_login_link_via_chat_when_credentials_are_missing
This commit is contained in:
Generated
+21
-76
@@ -26,7 +26,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.7",
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
@@ -286,15 +286,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
@@ -436,7 +427,7 @@ version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.7",
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -501,12 +492,6 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "const_panic"
|
||||
version = "0.2.15"
|
||||
@@ -620,15 +605,6 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -647,7 +623,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"digest 0.10.7",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
@@ -773,7 +749,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid 0.9.6",
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -846,22 +822,11 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -906,7 +871,7 @@ dependencies = [
|
||||
"ed25519",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1415,7 +1380,7 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1495,15 +1460,6 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a79f2aff40c18ab8615ddc5caa9eb5b96314aef18fe5823090f204ad988e813"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
@@ -2211,7 +2167,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
@@ -2304,7 +2260,7 @@ dependencies = [
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
@@ -2339,7 +2295,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2393,7 +2349,7 @@ dependencies = [
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -2652,7 +2608,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
@@ -2710,7 +2666,7 @@ version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
@@ -3557,7 +3513,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"ruma-common",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
@@ -3605,7 +3561,7 @@ version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -3930,7 +3886,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3941,18 +3897,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.2",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4110,7 +4055,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"serde_yaml",
|
||||
"sha2 0.11.0",
|
||||
"sha2",
|
||||
"strip-ansi-escapes",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -4745,7 +4690,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.7",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
@@ -4845,7 +4790,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"x25519-dalek",
|
||||
|
||||
@@ -616,7 +616,13 @@ pub(super) async fn handle_message(
|
||||
}
|
||||
Err(e) => {
|
||||
slog!("[matrix-bot] LLM error: {e}");
|
||||
let err_msg = format!("Error processing your request: {e}");
|
||||
let err_msg = if let Some(url) = crate::llm::oauth::extract_login_url_from_error(&e) {
|
||||
format!(
|
||||
"Authentication required. [Click here to log in to Claude]({url})"
|
||||
)
|
||||
} else {
|
||||
format!("Error processing your request: {e}")
|
||||
};
|
||||
let _ = msg_tx.send(err_msg.clone());
|
||||
(err_msg, None)
|
||||
}
|
||||
@@ -686,6 +692,24 @@ mod tests {
|
||||
assert_eq!(prompt, "@bob:example.com: What's up?");
|
||||
}
|
||||
|
||||
// -- OAuth login link formatting ----------------------------------------
|
||||
|
||||
#[test]
|
||||
fn oauth_error_produces_login_link() {
|
||||
let err = "OAuth session expired or credentials missing. Please log in: http://localhost:3001/oauth/authorize";
|
||||
let url = crate::llm::oauth::extract_login_url_from_error(err);
|
||||
assert!(url.is_some(), "should extract URL from OAuth error");
|
||||
let msg = format!("Authentication required. [Click here to log in to Claude]({})", url.unwrap());
|
||||
assert!(msg.contains("http://localhost:3001/oauth/authorize"));
|
||||
assert!(msg.contains("[Click here to log in to Claude]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_oauth_error_not_formatted_as_link() {
|
||||
let err = "Some unrelated error";
|
||||
assert!(crate::llm::oauth::extract_login_url_from_error(err).is_none());
|
||||
}
|
||||
|
||||
// -- bot_name / system prompt -------------------------------------------
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -383,7 +383,13 @@ async fn handle_llm_message(ctx: &WhatsAppWebhookContext, sender: &str, user_mes
|
||||
}
|
||||
Err(e) => {
|
||||
slog!("[whatsapp] LLM error: {e}");
|
||||
let err_msg = format!("Error processing your request: {e}");
|
||||
let err_msg = if let Some(url) = crate::llm::oauth::extract_login_url_from_error(&e) {
|
||||
format!(
|
||||
"Authentication required. Log in to Claude here: {url}"
|
||||
)
|
||||
} else {
|
||||
format!("Error processing your request: {e}")
|
||||
};
|
||||
let _ = msg_tx.send(err_msg.clone());
|
||||
(err_msg, None)
|
||||
}
|
||||
@@ -491,6 +497,24 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
// ── OAuth login link formatting ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn whatsapp_oauth_error_produces_plain_text_url() {
|
||||
let err = "OAuth session expired or credentials missing. Please log in: http://localhost:3001/oauth/authorize";
|
||||
let url = crate::llm::oauth::extract_login_url_from_error(err);
|
||||
assert!(url.is_some(), "should extract URL from OAuth error");
|
||||
let msg = format!("Authentication required. Log in to Claude here: {}", url.unwrap());
|
||||
assert!(msg.contains("http://localhost:3001/oauth/authorize"));
|
||||
assert!(!msg.contains('['), "WhatsApp message should not use Markdown link syntax");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whatsapp_non_oauth_error_not_formatted_as_link() {
|
||||
let err = "Some unrelated error occurred during processing";
|
||||
assert!(crate::llm::oauth::extract_login_url_from_error(err).is_none());
|
||||
}
|
||||
|
||||
// ── Allowlist tests ───────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -161,10 +161,43 @@ pub async fn refresh_access_token() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract the OAuth login URL from an error message produced by the Claude Code provider.
|
||||
///
|
||||
/// The provider returns errors like:
|
||||
/// `"OAuth session expired or credentials missing. Please log in: http://localhost:3001/oauth/authorize"`
|
||||
///
|
||||
/// Returns the URL portion when the error indicates missing or expired credentials,
|
||||
/// `None` otherwise.
|
||||
pub fn extract_login_url_from_error(err: &str) -> Option<&str> {
|
||||
let marker = "Please log in: ";
|
||||
let start = err.find(marker)?;
|
||||
Some(err[start + marker.len()..].trim())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extract_login_url_from_oauth_error() {
|
||||
let err = "OAuth session expired or credentials missing. Please log in: http://localhost:3001/oauth/authorize";
|
||||
let url = extract_login_url_from_error(err);
|
||||
assert_eq!(url, Some("http://localhost:3001/oauth/authorize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_login_url_returns_none_for_unrelated_error() {
|
||||
let err = "Some other error occurred";
|
||||
assert!(extract_login_url_from_error(err).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_login_url_with_different_port() {
|
||||
let err = "OAuth session expired or credentials missing. Please log in: http://localhost:3002/oauth/authorize";
|
||||
let url = extract_login_url_from_error(err);
|
||||
assert_eq!(url, Some("http://localhost:3002/oauth/authorize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_credentials_file() {
|
||||
let json = r#"{
|
||||
|
||||
@@ -138,9 +138,11 @@ impl ClaudeCodeProvider {
|
||||
on_token("\n*Refreshing authentication token...*\n");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
Err(_e) => {
|
||||
let port = crate::http::resolve_port();
|
||||
let login_url = format!("http://localhost:{port}/oauth/authorize");
|
||||
return Err(format!(
|
||||
"OAuth session expired. Please run `claude login` to re-authenticate. ({e})"
|
||||
"OAuth session expired or credentials missing. Please log in: {login_url}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user