fix: extract matrix password from backtick code span (real conduit format)
The live conduit admin-room reply (observed directly in #admins) is: Successfully reset the password for user @x:server: `<password>` The delimiter is ': ' after the user id, NOT ' to:' — so the ' to: ' marker added earlier never matched the real format, and extract_new_password returned None for every reply, timing out auto-recovery for every agent. conduit always renders the password as a backtick code span, so anchor on that directly: take the content of the first backtick pair when the message is a password-reset success. Guard against a code-spanned matrix user id in an error message (a real password has no whitespace and isn't @localpart:server). Prose markers stay as a fallback for hypothetical non-code-span builds. Tests cover the exact live format and the code-spanned-user-id error case.
This commit is contained in:
parent
da52afcf53
commit
2d8a68e4d1
1 changed files with 44 additions and 0 deletions
|
|
@ -365,6 +365,32 @@ fn extract_new_password(bot_message: &str) -> Option<String> {
|
|||
// ASCII lowercase: same byte length as the original, so positions from
|
||||
// `lower.find(marker)` are valid byte indices into `bot_message`.
|
||||
let lower = bot_message.to_ascii_lowercase();
|
||||
|
||||
// Primary strategy: the conduit/tuwunel admin bot always renders the new
|
||||
// password as a backtick code span. The live reply observed in the admin
|
||||
// room is:
|
||||
// "Successfully reset the password for user @x:server: `<password>`"
|
||||
// (note the delimiter is ": " after the user id, NOT " to:" — the prose
|
||||
// wording varies between builds, so anchoring on the code span is the
|
||||
// robust extraction). Take the content of the first backtick pair when the
|
||||
// message is a password-reset success. Guard against grabbing a code-spanned
|
||||
// matrix user id ("@x:server") from an error message: a real password has
|
||||
// no whitespace and isn't a `@localpart:server` id.
|
||||
if lower.contains("password")
|
||||
&& let Some(open) = bot_message.find('`')
|
||||
{
|
||||
let after = &bot_message[open + 1..];
|
||||
if let Some(close) = after.find('`') {
|
||||
let pw = &after[..close];
|
||||
if !pw.is_empty()
|
||||
&& !pw.contains(char::is_whitespace)
|
||||
&& !(pw.starts_with('@') && pw.contains(':'))
|
||||
{
|
||||
return Some(pw.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for marker in &[
|
||||
// Explicit "is:" variants (most common in conduwuit / tuwunel):
|
||||
"new password is: ",
|
||||
|
|
@ -503,6 +529,24 @@ mod extract_new_password_tests {
|
|||
assert_eq!(extract_new_password(msg).as_deref(), Some("hunter2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conduit_live_admin_room_format() {
|
||||
// The ACTUAL reply observed in the live #admins room — the delimiter
|
||||
// is ": " after the user id (no " to:"), password in a code span.
|
||||
let msg = "Successfully reset the password for user @triage:pr1ma.darkest.space: `hVfa6TpvIKnADoEJNWn9saHoI`";
|
||||
assert_eq!(
|
||||
extract_new_password(msg).as_deref(),
|
||||
Some("hVfa6TpvIKnADoEJNWn9saHoI")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codespan_userid_in_error_not_mistaken_for_password() {
|
||||
// An error that code-spans the user id must not yield it as a password.
|
||||
let msg = "Failed to reset password for `@sock:pr1ma.darkest.space` — user not found";
|
||||
assert_eq!(extract_new_password(msg), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_new_password_colon() {
|
||||
let msg = "New password: P@ssword1";
|
||||
|
|
|
|||
Loading…
Reference in a new issue