From 1a00d065fe9efaa2046223bc39c7a2a9f7d004f6 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Tue, 5 May 2026 20:06:38 +0800 Subject: [PATCH] {"schema":"maestro/commit/1","summary":"Use device code for ChatGPT login","authority":"manual"} --- Cargo.lock | 82 +----- apps/voxit/src/main.rs | 71 +----- packages/voxit-core/Cargo.toml | 2 - packages/voxit-core/src/auth.rs | 439 ++------------------------------ 4 files changed, 29 insertions(+), 565 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 016faf4..0c6181c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,12 +225,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -771,23 +765,6 @@ dependencies = [ "libc", ] -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.0", -] - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "clipboard-win" version = "5.4.1" @@ -960,15 +937,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -1775,7 +1743,6 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2190,12 +2157,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.8.1" @@ -3647,7 +3608,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand", "ring", "rustc-hash 2.1.1", "rustls", @@ -3695,18 +3656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" -dependencies = [ - "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", + "rand_core", ] [[package]] @@ -3716,7 +3666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -3728,12 +3678,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" - [[package]] name = "range-alloc" version = "0.1.4" @@ -4166,7 +4110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -4177,7 +4121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -4557,18 +4501,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -4882,7 +4814,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand", "rustls", "rustls-pki-types", "sha1", @@ -5088,13 +5020,11 @@ dependencies = [ "keyring", "objc2-foundation 0.3.2", "objc2-local-authentication", - "rand 0.10.0", "reqwest", "security-framework-sys", "serde", "serde_json", "sha2", - "tiny_http", "tokio", "tokio-tungstenite", "tracing", diff --git a/apps/voxit/src/main.rs b/apps/voxit/src/main.rs index 314b7c6..5644b6c 100644 --- a/apps/voxit/src/main.rs +++ b/apps/voxit/src/main.rs @@ -107,7 +107,6 @@ enum AuthEvent { SignedIn(AuthRecord), StatusChecked(AuthStatus), DeviceCodeInfo { user_code: String, verification_uri: String }, - Status(String), Failed(String), } @@ -384,10 +383,6 @@ impl VoxitApp { self.status = "Device code shown in panel. Open the URL and enter the code.".to_string(); }, - AuthEvent::Status(message) => { - self.auth_busy = true; - self.status = message; - }, AuthEvent::Failed(err) => { self.device_code_user_code = None; self.device_code_verification_uri = None; @@ -505,69 +500,26 @@ impl VoxitApp { self.auth_busy = true; self.device_code_user_code = None; self.device_code_verification_uri = None; - self.status = "Starting browser login...".to_string(); + self.status = "Starting ChatGPT device code login...".to_string(); let tx = self.auth_event_tx.clone(); #[cfg(target_os = "macos")] let _ = voxit_macos::activate_current_application(); thread::spawn(move || { - let event = match auth::sign_in_with_chatgpt() { + let event = match auth::sign_in_with_chatgpt(|user_code, verification_uri| { + let _ = tx.send(AuthEvent::DeviceCodeInfo { + user_code: user_code.to_string(), + verification_uri: verification_uri.to_string(), + }); + }) { Ok(record) => AuthEvent::SignedIn(record), - Err(err) if is_browser_login_timeout_error(&err) => { - let _ = tx.send(AuthEvent::Status( - "Browser login timed out. Switching to device-code login.".to_string(), - )); - - match auth::sign_in_with_device_code_with_progress( - |user_code, verification_uri| { - let _ = tx.send(AuthEvent::DeviceCodeInfo { - user_code: user_code.to_string(), - verification_uri: verification_uri.to_string(), - }); - }, - ) { - Ok(record) => AuthEvent::SignedIn(record), - Err(device_code_err) => AuthEvent::Failed(format!( - "browser timeout: {err}; device code login failed: {device_code_err}" - )), - } - }, Err(err) => AuthEvent::Failed(err), }; let _ = tx.send(event); }); } - fn start_sign_in_with_device_code(&mut self) { - if self.auth_busy { - return; - } - - self.auth_busy = true; - self.device_code_user_code = None; - self.device_code_verification_uri = None; - self.status = "Starting device code login...".to_string(); - - let tx = self.auth_event_tx.clone(); - #[cfg(target_os = "macos")] - let _ = voxit_macos::activate_current_application(); - - thread::spawn(move || { - let event = - match auth::sign_in_with_device_code_with_progress(|user_code, verification_uri| { - let _ = tx.send(AuthEvent::DeviceCodeInfo { - user_code: user_code.to_string(), - verification_uri: verification_uri.to_string(), - }); - }) { - Ok(record) => AuthEvent::SignedIn(record), - Err(err) => AuthEvent::Failed(err), - }; - let _ = tx.send(event); - }); - } - fn sign_out(&mut self) { self.device_code_user_code = None; self.device_code_verification_uri = None; @@ -1009,9 +961,6 @@ impl VoxitApp { if ui.add_enabled(can_auth, Button::new("Sign in with ChatGPT")).clicked() { self.start_sign_in_with_chatgpt(); } - if ui.add_enabled(can_auth, Button::new("Device code login")).clicked() { - self.start_sign_in_with_device_code(); - } } }); } @@ -1297,12 +1246,6 @@ impl App for VoxitApp { } } -fn is_browser_login_timeout_error(message: &str) -> bool { - let message = message.to_lowercase(); - - message.contains("browser login timeout") || message.contains("callback timeout") -} - fn ensure_app_data_dir(app_root: &Path) -> Result<()> { fs::create_dir_all(app_root).map_err(|err| { crate::prelude::eyre!("Failed to create app data directory {}: {err}", app_root.display()) diff --git a/packages/voxit-core/Cargo.toml b/packages/voxit-core/Cargo.toml index 3d820a5..829fccf 100644 --- a/packages/voxit-core/Cargo.toml +++ b/packages/voxit-core/Cargo.toml @@ -20,12 +20,10 @@ futures-util = { version = "0.3", optional = true } hound = { version = "3.5" } http = { version = "1.3", optional = true } keyring = { version = "3.6", features = ["apple-native"] } -rand = { version = "0.10" } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "rustls"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } sha2 = { version = "0.10" } -tiny_http = { version = "0.12" } tokio = { version = "1.47", features = ["rt-multi-thread", "time"], optional = true } tokio-tungstenite = { version = "0.28", default-features = false, features = ["connect", "rustls-tls-native-roots"], optional = true } tracing = { version = "0.1" } diff --git a/packages/voxit-core/src/auth.rs b/packages/voxit-core/src/auth.rs index a8ce2b1..84af22a 100644 --- a/packages/voxit-core/src/auth.rs +++ b/packages/voxit-core/src/auth.rs @@ -282,7 +282,7 @@ use std::{ io::{self, Error, ErrorKind, Read as _, Write as _}, os::unix::fs::{OpenOptionsExt as _, PermissionsExt as _}, path::{Path, PathBuf}, - string::{String, ToString}, + string::String, sync::{Condvar, Mutex, OnceLock, RwLock, mpsc, mpsc::RecvTimeoutError}, thread, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, @@ -291,24 +291,16 @@ use std::{ use base64::Engine; use directories::ProjectDirs; use keyring::Entry; -use rand::RngExt as _; -use reqwest::blocking::Client; +use reqwest::blocking::{Client, Response}; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest, Sha256}; -use tiny_http::{Header, Request, Server}; -use url::{Url, form_urlencoded}; +use url::form_urlencoded; type AuthResult = std::result::Result; const CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann"; const DEFAULT_ISSUER: &str = "https://auth.openai.com"; -const DEFAULT_PORT: u16 = 1_455; -const FALLBACK_PORT: u16 = 1_457; -const REDIRECT_URI_PATH: &str = "/auth/callback"; -const CODEX_OAUTH_ORIGINATOR: &str = "codex_cli_rs"; -const CODEX_OAUTH_SCOPE: &str = - "openid profile email offline_access api.connectors.read api.connectors.invoke"; const REFRESH_TOKEN_URL: &str = "https://auth.openai.com/oauth/token"; const TOKEN_REFRESH_SKEW_SECS: u64 = 60; const KEYRING_SERVICE: &str = "Voxit Auth"; @@ -382,12 +374,6 @@ struct StoredAuth { tokens: Option, } -#[derive(Debug)] -struct PkceCodes { - code_verifier: String, - code_challenge: String, -} - #[derive(Debug)] struct DeviceCode { device_auth_id: String, @@ -442,9 +428,12 @@ pub fn sign_out() -> AuthResult<()> { Ok(()) } -/// Start browser login flow and store OAuth credentials. -pub fn sign_in_with_chatgpt() -> AuthResult { - sign_in_with_chatgpt_browser() +/// Start ChatGPT device-code login and store OAuth credentials. +pub fn sign_in_with_chatgpt(on_device_code: F) -> AuthResult +where + F: Fn(&str, &str), +{ + sign_in_with_device_code_with_progress(on_device_code) } /// Start device-code flow and store OAuth credentials. @@ -467,9 +456,8 @@ where let _ = webbrowser::open(&verification_uri); let login_code = poll_device_code(&device_code)?; let redirect_uri = format!("{DEFAULT_ISSUER}/deviceauth/callback"); - let pkce = PkceCodes { code_verifier: login_code.code_verifier, code_challenge: String::new() }; let tokens = exchange_authorization_code( - &pkce, + &login_code.code_verifier, &login_code.authorization_code, &redirect_uri, DEFAULT_ISSUER, @@ -506,26 +494,6 @@ pub(crate) fn chatgpt_auth_context() -> AuthResult { Err("not signed in with ChatGPT".to_string()) } -fn sign_in_with_chatgpt_browser() -> AuthResult { - let pkce = generate_pkce(); - let state = generate_state(); - let server = bind_callback_server()?; - let redirect_uri = browser_redirect_uri(server_port(&server)?); - let authorize_url = - build_authorize_url(&redirect_uri, &pkce.code_challenge, &state, DEFAULT_ISSUER); - - webbrowser::open(&authorize_url) - .map_err(|_| "failed to open browser for ChatGPT login".to_string())?; - - wait_for_callback(server, &state, &pkce, &redirect_uri, DEFAULT_ISSUER) -} - -fn browser_redirect_uri(port: u16) -> String { - // Codex OSS uses http://localhost:/auth/callback for browser OAuth redirect URI. - // Aligning here avoids auth.openai.com rejecting 127.0.0.1 redirect URIs for this client id. - format!("http://localhost:{port}{REDIRECT_URI_PATH}") -} - fn valid_tokens_or_none(auth: Option) -> AuthResult> { let auth = match auth { Some(auth) => auth, @@ -642,7 +610,7 @@ fn poll_device_code(device_code: &DeviceCode) -> AuthResult { } fn exchange_authorization_code( - pkce: &PkceCodes, + code_verifier: &str, authorization_code: &str, redirect_uri: &str, issuer: &str, @@ -652,7 +620,7 @@ fn exchange_authorization_code( url_encode(authorization_code), url_encode(redirect_uri), url_encode(CLIENT_ID), - url_encode(&pkce.code_verifier), + url_encode(code_verifier), ); let response = post_form(&format!("{issuer}/oauth/token"), &form)?; let parsed: Value = @@ -1229,305 +1197,6 @@ fn clear_auth_file(base: &Path) -> AuthResult<()> { } } -fn bind_callback_server() -> AuthResult { - let primary = format!("127.0.0.1:{DEFAULT_PORT}"); - - match Server::http(&primary) { - Ok(server) => Ok(server), - Err(primary_err) => { - let fallback = format!("127.0.0.1:{FALLBACK_PORT}"); - - tracing::warn!( - error = %primary_err, - primary_port = DEFAULT_PORT, - fallback_port = FALLBACK_PORT, - "auth callback primary port unavailable; trying fallback" - ); - - Server::http(&fallback).map_err(|fallback_err| { - format!( - "failed to bind local callback server on {primary} or {fallback}: {fallback_err}" - ) - }) - }, - } -} - -fn server_port(server: &Server) -> AuthResult { - server - .server_addr() - .to_ip() - .map(|addr| addr.port()) - .ok_or_else(|| "failed to resolve local callback server port".to_string()) -} - -fn wait_for_callback( - server: Server, - expected_state: &str, - pkce: &PkceCodes, - redirect_uri: &str, - issuer: &str, -) -> AuthResult { - let start = Instant::now(); - let timeout = Duration::from_secs(180); - - loop { - let request = match server.recv_timeout(Duration::from_millis(200)) { - Ok(Some(request)) => request, - Ok(None) => { - if start.elapsed() > timeout { - return Err("browser login timeout".to_string()); - } - - continue; - }, - Err(err) => return Err(format!("auth callback wait failed: {err}")), - }; - - match handle_callback_request(request, expected_state, pkce, redirect_uri, issuer)? { - Some(record) => return Ok(record), - None => - if start.elapsed() > timeout { - return Err("browser login timeout".to_string()); - }, - } - } -} - -fn handle_callback_request( - request: Request, - expected_state: &str, - pkce: &PkceCodes, - redirect_uri: &str, - issuer: &str, -) -> AuthResult> { - let full_url = format!("http://localhost{}", request.url()); - let parsed = match Url::parse(&full_url) { - Ok(parsed) => parsed, - Err(err) => { - respond_error(request, 400, &format!("bad request: {err}")); - - return Err("callback url parse failed".to_string()); - }, - }; - - if parsed.path() != REDIRECT_URI_PATH { - respond_text(request, 404, "not found"); - - return Ok(None); - } - - let params: HashMap = parsed.query_pairs().into_owned().collect(); - - if let Some(error) = params.get("error").filter(|v| !v.is_empty()) { - let details = params.get("error_description").map_or_else(String::new, ToString::to_string); - let message = if details.is_empty() { - format!("oauth callback error: {error}") - } else { - format!("oauth callback error: {error} ({details})") - }; - - respond_error(request, 400, &message); - - return Err(message); - } - - let state = params.get("state").map_or("", String::as_str); - - if state != expected_state { - let message = "state mismatch".to_string(); - - respond_error(request, 400, &message); - - return Err(message); - } - - let record = match params.get("code") { - Some(code) => { - let tokens = match exchange_authorization_code(pkce, code, redirect_uri, issuer) { - Ok(tokens) => tokens, - Err(err) => { - let message = format!("oauth token exchange failed: {err}"); - - respond_error(request, 400, &message); - - return Err(message); - }, - }; - - if let Err(err) = store_tokens(&tokens) { - let message = format!("oauth token save failed: {err}"); - - respond_error(request, 500, &message); - - return Err(message); - } - - respond_html(request, 200, &success_redirect_page_html()); - - AuthRecord { account_id: tokens.account_id } - }, - None => { - let message = "missing authorization code".to_string(); - - respond_error(request, 400, &message); - - return Err(message); - }, - }; - - Ok(Some(record)) -} - -fn respond_text(request: Request, status_code: u16, body: &str) { - let response = tiny_http::Response::from_string(body).with_status_code(status_code); - let _ = request.respond(response); -} - -fn respond_html(request: Request, status_code: u16, body: &str) { - let response = tiny_http::Response::from_string(body) - .with_status_code(status_code) - .with_header( - Header::from_bytes("Content-Type", "text/html; charset=utf-8") - .expect("valid Content-Type header"), - ) - .with_header( - Header::from_bytes("Cache-Control", "no-store").expect("valid Cache-Control header"), - ); - let _ = request.respond(response); -} - -fn respond_error(request: Request, status_code: u16, message: &str) { - let body = format!( - r#"

{}

Close this window and retry from Voxit.

"#, - html_escape(message) - ); - - respond_html(request, status_code, &body); -} - -fn success_redirect_page_html() -> String { - // Browsers often block `window.close()` unless the window was opened by script. - // Still attempt auto-close to match common OAuth UX; always show a manual close instruction. - r#" - - - - - Voxit Sign-in - - - -
-
-

Signed in

-

- You may return to Voxit. This window will try to close automatically in - 7s. -

-

If it doesn’t close automatically, please close this window manually.

-
You can safely close this page.
-
-
- - - -"# - .to_string() -} - -fn build_authorize_url( - redirect_uri: &str, - code_challenge: &str, - state: &str, - issuer: &str, -) -> String { - let mut url = Url::parse(&format!("{issuer}/oauth/authorize")).unwrap_or_else(|_| { - Url::parse("https://auth.openai.com/oauth/authorize") - .unwrap_or_else(|_| Url::parse("https://example.com").expect("valid fallback url")) - }); - - url.query_pairs_mut().append_pair("response_type", "code"); - url.query_pairs_mut().append_pair("client_id", CLIENT_ID); - url.query_pairs_mut().append_pair("redirect_uri", redirect_uri); - url.query_pairs_mut().append_pair("scope", CODEX_OAUTH_SCOPE); - url.query_pairs_mut().append_pair("code_challenge", code_challenge); - url.query_pairs_mut().append_pair("code_challenge_method", "S256"); - url.query_pairs_mut().append_pair("id_token_add_organizations", "true"); - url.query_pairs_mut().append_pair("codex_cli_simplified_flow", "true"); - url.query_pairs_mut().append_pair("state", state); - url.query_pairs_mut().append_pair("originator", CODEX_OAUTH_ORIGINATOR); - - url.to_string() -} - fn post_json(url: &str, body: &str) -> AuthResult { let client = Client::builder() .timeout(Duration::from_secs(120)) @@ -1558,7 +1227,7 @@ fn post_form(url: &str, body: &str) -> AuthResult { parse_response(response, url) } -fn post_raw(url: &str, body: &[u8]) -> AuthResult { +fn post_raw(url: &str, body: &[u8]) -> AuthResult { let client = Client::builder() .timeout(Duration::from_secs(120)) .build() @@ -1573,7 +1242,7 @@ fn post_raw(url: &str, body: &[u8]) -> AuthResult { Ok(response) } -fn parse_response(response: reqwest::blocking::Response, context: &str) -> AuthResult { +fn parse_response(response: Response, context: &str) -> AuthResult { if !response.status().is_success() { let status = response.status(); let body = response.text().unwrap_or_else(|_| "".to_string()); @@ -1676,28 +1345,6 @@ fn is_token_expired(created_at_unix: u64, expires_in: Option) -> bool { now >= created_at_unix.saturating_add(ttl) } -fn generate_pkce() -> PkceCodes { - let mut bytes = [0_u8; 64]; - let mut rng = rand::rng(); - - rng.fill(&mut bytes); - - let code_verifier = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes); - let challenge = Sha256::digest(code_verifier.as_bytes()); - let code_challenge = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(challenge); - - PkceCodes { code_verifier, code_challenge } -} - -fn generate_state() -> String { - let mut bytes = [0_u8; 32]; - let mut rng = rand::rng(); - - rng.fill(&mut bytes); - - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes) -} - fn now_unix() -> u64 { SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs()) } @@ -1725,23 +1372,6 @@ fn url_encode(value: &str) -> String { form_urlencoded::byte_serialize(value.as_bytes()).collect::() } -fn html_escape(raw: &str) -> String { - let mut out = String::new(); - - for ch in raw.chars() { - match ch { - '&' => out.push_str("&"), - '<' => out.push_str("<"), - '>' => out.push_str(">"), - '"' => out.push_str("""), - '\'' => out.push_str("'"), - _ => out.push(ch), - } - } - - out -} - #[cfg(test)] mod tests { use std::{ @@ -1751,10 +1381,8 @@ mod tests { }; use crate::auth::{ - self, AUTH_FILE_FALLBACK_ENV, CLIENT_ID, CODEX_OAUTH_ORIGINATOR, CODEX_OAUTH_SCOPE, - DEFAULT_ISSUER, DEFAULT_PORT, HashMap, KEYCHAIN_BACKEND_ENV, KEYRING_VERIFY_ENABLED_ENV, - KeychainBackend, REDIRECT_URI_PATH, StoredAuth, TEST_FORCE_KEYRING_ERROR_ENV, TokenData, - Url, + self, AUTH_FILE_FALLBACK_ENV, KEYCHAIN_BACKEND_ENV, KEYRING_VERIFY_ENABLED_ENV, + KeychainBackend, StoredAuth, TEST_FORCE_KEYRING_ERROR_ENV, TokenData, }; static TEST_MUTEX: Mutex<()> = Mutex::new(()); @@ -1779,41 +1407,6 @@ mod tests { } } - #[test] - fn browser_redirect_uri_matches_codex() { - assert_eq!( - auth::browser_redirect_uri(DEFAULT_PORT), - format!("http://localhost:{DEFAULT_PORT}{REDIRECT_URI_PATH}") - ); - } - - #[test] - fn authorize_url_includes_expected_codex_params() { - let url = auth::build_authorize_url( - &auth::browser_redirect_uri(DEFAULT_PORT), - "challenge123", - "state123", - DEFAULT_ISSUER, - ); - let parsed = Url::parse(&url).expect("valid authorize url"); - let params: HashMap = parsed.query_pairs().into_owned().collect(); - - assert_eq!(parsed.path(), "/oauth/authorize"); - assert_eq!(params.get("response_type").map(String::as_str), Some("code")); - assert_eq!(params.get("client_id").map(String::as_str), Some(CLIENT_ID)); - assert_eq!( - params.get("redirect_uri").map(String::as_str), - Some(auth::browser_redirect_uri(DEFAULT_PORT).as_str()) - ); - assert_eq!(params.get("scope").map(String::as_str), Some(CODEX_OAUTH_SCOPE)); - assert_eq!(params.get("code_challenge").map(String::as_str), Some("challenge123")); - assert_eq!(params.get("code_challenge_method").map(String::as_str), Some("S256")); - assert_eq!(params.get("id_token_add_organizations").map(String::as_str), Some("true")); - assert_eq!(params.get("codex_cli_simplified_flow").map(String::as_str), Some("true")); - assert_eq!(params.get("state").map(String::as_str), Some("state123")); - assert_eq!(params.get("originator").map(String::as_str), Some(CODEX_OAUTH_ORIGINATOR)); - } - #[test] fn status_and_access_token_use_in_memory_cache() { let _guard = TEST_MUTEX.lock().unwrap();