use anyhow::{bail, Context, Result};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::{Digest, Sha256};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use url::Url;
const API: &str = "https://openrouter.ai/api/v1/auth/keys";
fn random_token() -> Result<String> {
let mut bytes = [0u8; 32];
getrandom::getrandom(&mut bytes).map_err(|_| anyhow::anyhow!("OS randomness unavailable"))?;
Ok(URL_SAFE_NO_PAD.encode(bytes))
}
fn authorization_url(verifier: &str, callback: Option<&str>) -> Result<Url> {
let mut url = Url::parse("https://openrouter.ai/auth")?;
{
let mut params = url.query_pairs_mut();
params.append_pair(
"code_challenge",
&URL_SAFE_NO_PAD.encode(Sha256::digest(verifier)),
);
params.append_pair("code_challenge_method", "S256");
params.append_pair("key_label", "Supercode");
if let Some(callback) = callback {
params.append_pair("callback_url", callback);
}
}
Ok(url)
}
pub async fn authorize(headless: bool) -> Result<String> {
let verifier = random_token()?;
let code = if headless {
let url = authorization_url(&verifier, None)?;
eprintln!("Open this URL, authorize Supercode, then paste the authorization code:\n{url}");
crate::hidden_input::read_hidden_line().context("reading authorization code")?
} else {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.context("opening OAuth callback listener; use --oauth --headless over SSH")?;
let path = format!("/callback/{}", random_token()?);
let callback = format!("http://127.0.0.1:{}{path}", listener.local_addr()?.port());
let url = authorization_url(&verifier, Some(&callback))?;
eprintln!("Authorize Supercode in your browser:\n{url}");
open_browser(url.as_str());
tokio::time::timeout(Duration::from_secs(600), receive_callback(&listener, &path))
.await
.context("OpenRouter sign-in timed out; run login again")??
};
if code.trim().is_empty() || code.len() > 4096 {
bail!("invalid authorization code");
}
exchange(API, code.trim(), &verifier).await
}
fn open_browser(url: &str) {
#[cfg(target_os = "macos")]
let command = Some(("open", vec![url]));
#[cfg(target_os = "linux")]
let command = Some(("xdg-open", vec![url]));
#[cfg(target_os = "windows")]
let command = Some(("rundll32", vec!["url.dll,FileProtocolHandler", url]));
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
let command: Option<(&str, Vec<&str>)> = None;
if let Some((program, args)) = command {
let _ = std::process::Command::new(program)
.args(args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
}
}
fn callback_code(target: &str, path: &str) -> Result<Option<String>> {
if !target.starts_with('/') || target.starts_with("//") {
return Ok(None);
}
let url = Url::parse(&format!("http://localhost{target}"))?;
if url.path() != path {
return Ok(None);
}
if url.query_pairs().any(|(name, _)| name == "error") {
bail!("OpenRouter authorization was declined");
}
let codes: Vec<_> = url
.query_pairs()
.filter(|(name, _)| name == "code")
.map(|(_, code)| code.into_owned())
.collect();
if codes.len() != 1 || codes[0].is_empty() || codes[0].len() > 4096 {
bail!("OpenRouter callback has no unique authorization code");
}
Ok(Some(codes[0].clone()))
}
async fn receive_callback(listener: &tokio::net::TcpListener, path: &str) -> Result<String> {
loop {
let (mut stream, _) = listener.accept().await?;
let mut bytes = Vec::new();
let read = tokio::time::timeout(Duration::from_secs(5), async {
let mut byte = [0u8; 1];
while bytes.len() < 8192 {
if stream.read(&mut byte).await? == 0 {
break;
}
bytes.push(byte[0]);
if bytes.ends_with(b"\r\n\r\n") {
break;
}
}
Ok::<_, std::io::Error>(())
})
.await;
if !matches!(read, Ok(Ok(()))) {
continue;
}
let request = String::from_utf8_lossy(&bytes);
let mut line = request
.lines()
.next()
.unwrap_or_default()
.split_whitespace();
let method = line.next();
let target = line.next().unwrap_or_default();
let result = if method == Some("GET") {
callback_code(target, path)
} else {
Ok(None)
};
let (status, message) = match &result {
Ok(Some(_)) => (
"200 OK",
"Authorization received. Return to Supercode to finish signing in.",
),
Ok(None) => ("404 Not Found", "Unknown callback."),
Err(_) => (
"400 Bad Request",
"Authorization failed. Return to Supercode.",
),
};
let response = format!("HTTP/1.1 {status}\r\nContent-Type: text/plain\r\nCache-Control: no-store\r\nReferrer-Policy: no-referrer\r\nConnection: close\r\nContent-Length: {}\r\n\r\n{message}", message.len());
let _ = stream.write_all(response.as_bytes()).await;
if let Some(code) = result? {
return Ok(code);
}
}
}
async fn exchange(endpoint: &str, code: &str, verifier: &str) -> Result<String> {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.timeout(Duration::from_secs(30))
.build()?;
let response = client
.post(endpoint)
.json(&serde_json::json!({
"code": code, "code_verifier": verifier, "code_challenge_method": "S256"
}))
.send()
.await
.context("OpenRouter authorization exchange failed")?;
if !response.status().is_success() {
bail!(
"OpenRouter authorization exchange returned HTTP {}; run login again",
response.status()
);
}
let body: serde_json::Value = response
.json()
.await
.context("invalid OpenRouter authorization response")?;
let key = body
.get("key")
.and_then(|value| value.as_str())
.filter(|key| {
(8..=4096).contains(&key.len())
&& key
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
})
.context("OpenRouter returned no valid API key")?;
Ok(key.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pkce_uses_rfc7636_s256_vector() {
let url = authorization_url("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", None).unwrap();
assert!(url
.query_pairs()
.any(|(k, v)| k == "code_challenge"
&& v == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"));
assert!(!url.query_pairs().any(|(k, _)| k == "callback_url"));
assert_eq!(random_token().unwrap().len(), 43);
}
#[test]
fn callback_requires_exact_attempt_and_unique_code() {
assert_eq!(
callback_code("/callback/wrong?code=secret", "/callback/right").unwrap(),
None
);
assert_eq!(
callback_code("/callback/right?code=a%2Bb", "/callback/right").unwrap(),
Some("a+b".into())
);
assert!(callback_code("/callback/right?code=a&code=b", "/callback/right").is_err());
assert!(callback_code("/callback/right?error=denied", "/callback/right").is_err());
}
#[tokio::test]
async fn callback_listener_accepts_code_without_reflecting_it() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let task =
tokio::spawn(async move { receive_callback(&listener, "/callback/attempt").await });
let response = reqwest::get(format!("http://{addr}/callback/attempt?code=private-code"))
.await
.unwrap();
assert!(!response.text().await.unwrap().contains("private-code"));
assert_eq!(task.await.unwrap().unwrap(), "private-code");
}
#[test]
fn login_flags_reject_ambiguous_credential_sources() {
use clap::Parser;
assert!(
crate::Cli::try_parse_from(["supercode", "login", "--oauth", "--headless"]).is_ok()
);
assert!(crate::Cli::try_parse_from(["supercode", "login", "--headless"]).is_err());
assert!(crate::Cli::try_parse_from([
"supercode",
"login",
"--oauth",
"--api-key",
"secret"
])
.is_err());
assert!(crate::Cli::try_parse_from([
"supercode",
"login",
"--oauth",
"--base-url",
"https://other.example"
])
.is_err());
}
#[tokio::test]
#[ignore = "requires the owned OpenRouter World"]
async fn world_exchange_is_single_use_and_key_reaches_model_api() {
let endpoint =
std::env::var("OPENROUTER_TWIN_URL").expect("run through the OpenRouter World");
let verifier = random_token().unwrap();
let client = reqwest::Client::new();
let result: serde_json::Value = client.post(format!("{endpoint}/api/v1/auth/authorize"))
.json(&serde_json::json!({ "callback_url": "http://localhost/callback",
"code_challenge": URL_SAFE_NO_PAD.encode(Sha256::digest(&verifier)), "code_challenge_method": "S256" }))
.send().await.unwrap().error_for_status().unwrap().json().await.unwrap();
let code = result["data"]["code"].as_str().unwrap();
let url = format!("{endpoint}/api/v1/auth/keys");
assert!(exchange(&url, code, "wrong-verifier").await.is_err());
let key = exchange(&url, code, &verifier).await.unwrap();
assert!(exchange(&url, code, &verifier).await.is_err());
let reply = client.post(format!("{endpoint}/api/v1/chat/completions"))
.bearer_auth(key).json(&serde_json::json!({"model":"openai/gpt-4o","messages":[{"role":"user","content":"synthetic credential probe"}]}))
.send().await.unwrap();
assert!(reply.status().is_success());
}
}