use super::token::TokenExchangeForm;
use crate::error::{Error, Result};
#[derive(Clone, Debug)]
pub struct PendingAuthorization {
verifier: String,
state: String,
client_id: String,
redirect_uri: String,
}
impl PendingAuthorization {
pub fn new(
verifier: impl Into<String>,
state: impl Into<String>,
client_id: impl Into<String>,
redirect_uri: impl Into<String>,
) -> Self {
Self {
verifier: verifier.into(),
state: state.into(),
client_id: client_id.into(),
redirect_uri: redirect_uri.into(),
}
}
pub fn state(&self) -> &str {
&self.state
}
pub fn exchange_form(self, redirect_url: &url::Url) -> Result<TokenExchangeForm> {
let mut code = None;
let mut state = None;
let mut error = None;
let mut error_description = None;
for (key, value) in redirect_url.query_pairs() {
match key.as_ref() {
"code" => code = Some(value.into_owned()),
"state" => state = Some(value.into_owned()),
"error" => error = Some(value.into_owned()),
"error_description" => error_description = Some(value.into_owned()),
_ => {}
}
}
let returned_state = state.unwrap_or_default();
if !constant_time_eq(returned_state.as_bytes(), self.state.as_bytes()) {
return Err(Error::OAuthStateMismatch);
}
if let Some(error) = error {
return Err(Error::OAuth {
error,
error_description,
});
}
let code = code.ok_or_else(|| {
Error::Response("authorization result has neither a code nor an error".to_string())
})?;
Ok(TokenExchangeForm::new(
code,
self.verifier,
self.redirect_uri,
self.client_id,
))
}
pub async fn complete(
self,
api: &crate::client::LichessApi<reqwest::Client>,
redirect_url: &url::Url,
) -> Result<super::AccessToken> {
let form = self.exchange_form(redirect_url)?;
api.obtain_access_token(form).await
}
}
fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
if left.len() != right.len() {
return false;
}
left.iter()
.zip(right)
.fold(0u8, |acc, (l, r)| acc | (l ^ r))
== 0
}