Skip to content

Commit 5040a1a

Browse files
committed
feat(auth): warn when sign-in falls back off the preferred port
1 parent f808220 commit 5040a1a

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

crates/llm_stream/src/auth/flow.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub fn login(config_dir: &Path) -> Result<TokenSet> {
2323
let state = random_state();
2424

2525
let listener = listener::bind()?;
26+
if let Some(notice) = listener::fallback_notice(listener.port()?) {
27+
eprintln!("{notice}");
28+
}
2629
let redirect_uri = listener.redirect_uri()?;
2730

2831
let url = oauth::authorize_url(&AuthorizeParams {

crates/llm_stream/src/auth/listener.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ pub fn bind() -> Result<CallbackListener> {
2222
Ok(CallbackListener { listener })
2323
}
2424

25+
/// The line an operator needs when the preferred port was taken.
26+
///
27+
/// `None` when the listener got port 1455. Otherwise a sentence naming the
28+
/// conflict, because the failure it predicts is otherwise unreadable: if
29+
/// `OpenAI` pins the redirect URI to 1455, the authorization server rejects the
30+
/// sign-in with a generic `invalid redirect_uri` and nothing on screen connects
31+
/// that to a port another process is holding.
32+
#[must_use]
33+
pub fn fallback_notice(port: u16) -> Option<String> {
34+
(port != PREFERRED_PORT).then(|| {
35+
format!(
36+
"warning: port {PREFERRED_PORT} was busy, so sign-in is listening on port {port}. \
37+
If the browser reports an invalid redirect URI, close whatever holds \
38+
port {PREFERRED_PORT} (a running codex, or another llm-stream --login) and try again."
39+
)
40+
})
41+
}
42+
2543
impl CallbackListener {
2644
pub fn port(&self) -> Result<u16> {
2745
Ok(self.listener.local_addr()?.port())
@@ -104,6 +122,23 @@ mod tests {
104122
use super::*;
105123
use std::io::Write;
106124

125+
#[test]
126+
fn the_preferred_port_needs_no_notice() {
127+
assert_eq!(fallback_notice(PREFERRED_PORT), None);
128+
}
129+
130+
#[test]
131+
fn a_fallback_port_names_both_ports_and_the_symptom() {
132+
let Some(notice) = fallback_notice(54321) else {
133+
panic!("a fallback port must produce a notice");
134+
};
135+
assert!(notice.contains("1455"), "got: {notice}");
136+
assert!(notice.contains("54321"), "got: {notice}");
137+
// Without this the operator sees an opaque browser error and has no
138+
// reason to suspect a port conflict.
139+
assert!(notice.contains("invalid redirect URI"), "got: {notice}");
140+
}
141+
107142
#[test]
108143
fn extracts_the_query_from_a_request_line() {
109144
assert_eq!(

0 commit comments

Comments
 (0)