use super::login::LoginIo;
use super::oauth::{parse_token_response, AuthError, Grant};
use super::refresh::collect_body;
use super::wire::{build_authorize_url, build_token_exchange_request, parse_callback, Pkce};
use super::{auth_error, OAuthConfig};
use crate::canonical::CanonicalError;
use crate::store::{Cred, Secret};
pub(super) fn browser_flow(cfg: &OAuthConfig, io: &mut LoginIo) -> Result<Cred, CanonicalError> {
let port = io
.receiver
.bind(cfg.redirect.port)
.map_err(|e| auth_error(&format!("could not bind loopback listener: {e}")))?;
let redirect_uri = format!("http://{}:{}{}", cfg.redirect.host, port, cfg.redirect.path);
let pkce = Pkce::derive(io.verifier);
let url = build_authorize_url(cfg, &pkce, io.state, &redirect_uri);
io.browser
.open(&url)
.map_err(|e| auth_error(&format!("could not launch browser: {e}")))?;
let query = io
.receiver
.await_query()
.map_err(|e| auth_error(&format!("loopback receiver failed: {e}")))?;
let callback = parse_callback(&query, io.state).map_err(fatal)?;
exchange_auth_code(cfg, io, &callback.code, &pkce.verifier, &redirect_uri)
}
pub(super) fn exchange_auth_code(
cfg: &OAuthConfig,
io: &mut LoginIo,
code: &str,
verifier: &str,
redirect_uri: &str,
) -> Result<Cred, CanonicalError> {
let req = build_token_exchange_request(
cfg,
Grant::AuthCode {
code,
verifier,
redirect_uri,
},
);
let tok = parse_token_response(&collect_body(io.transport.send(req)?)?, io.clock.now())
.map_err(fatal)?;
Ok(tok.as_cred(&Secret::new(""), &None, &None))
}
pub(super) fn fatal(err: AuthError) -> CanonicalError {
let msg = match err {
AuthError::Pending | AuthError::SlowDown => "unexpected poll signal".to_owned(),
AuthError::Fatal(m) => m,
};
auth_error(&format!("login failed: {msg}"))
}