use std::collections::HashMap;
use crate::types::{
CibaAuthResponse, CibaAuthenticationExtras, CibaGrantResponse, GrantExtras, GrantParams,
OidcClientError, OidcHttpClient, OidcReturnType,
};
use super::{validate_id_token_params::ValidateIdTokenParams, Client};
pub struct CibaHandle {
client: Client,
extras: Option<CibaAuthenticationExtras>,
expires_at: u64,
interval: u64,
response: CibaAuthResponse,
last_requested: u64,
pub(crate) now: fn() -> u64,
}
impl CibaHandle {
pub fn new(
client: Client,
res: CibaAuthResponse,
extras: Option<CibaAuthenticationExtras>,
) -> Self {
let now = client.now;
Self {
client,
extras,
expires_at: now() + res.get_expires_in(),
interval: res.get_interval().unwrap_or(5),
response: res,
last_requested: 0,
now,
}
}
pub fn expired(&self) -> bool {
self.expires_in() == 0
}
pub fn interval(&self) -> u64 {
self.interval
}
pub fn expires_in(&self) -> u64 {
let now = (self.now)();
if now >= self.expires_at {
return 0;
}
self.expires_at - now
}
pub fn increase_interval(&mut self, by: u64) {
self.interval += by;
}
pub fn response(&self) -> &CibaAuthResponse {
&self.response
}
pub async fn grant_async<T: OidcHttpClient>(
&mut self,
http_client: &T,
) -> OidcReturnType<CibaGrantResponse> {
if self.expired() {
return Err(Box::new(OidcClientError::new_rp_error(
"auth_req_id has expired",
None,
)));
}
if ((self.now)() - self.last_requested) < self.interval {
return Ok(CibaGrantResponse::Debounced);
}
let extras = GrantExtras {
client_assertion_payload: self
.extras
.as_ref()
.and_then(|x| x.client_assertion_payload.to_owned()),
dpop: self.extras.as_ref().and_then(|x| x.dpop.as_ref()),
..Default::default()
};
let mut body = HashMap::new();
if let Some(eb) = &self.extras.as_ref().and_then(|x| x.exchange_body.clone()) {
for (k, v) in eb {
body.insert(k.to_owned(), v.to_owned());
}
}
body.insert(
"grant_type".to_string(),
"urn:openid:params:grant-type:ciba".to_owned(),
);
body.insert(
"auth_req_id".to_string(),
self.response.auth_req_id.to_owned(),
);
self.last_requested = (self.now)();
let mut token_set = match self
.client
.grant_async(
http_client,
GrantParams {
body,
extras,
retry: true,
},
)
.await
{
Ok(t) => t,
Err(e) => {
match e.as_ref() {
OidcClientError::OPError(sbe, _) => {
if sbe.error == "slow_down" {
self.increase_interval(5);
return Ok(CibaGrantResponse::SlowDown);
}
if sbe.error == "authorization_pending" {
return Ok(CibaGrantResponse::AuthorizationPending);
}
if sbe.error == "expired_token" {
return Ok(CibaGrantResponse::ExpiredToken);
}
if sbe.error == "access_denied" {
return Ok(CibaGrantResponse::AccessDenied);
}
return Err(e);
}
OidcClientError::Error(_, _)
| OidcClientError::TypeError(_, _)
| OidcClientError::RPError(_, _) => return Err(e),
};
}
};
if token_set.get_id_token().is_some() {
token_set = self.client.decrypt_id_token(token_set)?;
token_set = self
.client
.validate_id_token_async(
ValidateIdTokenParams::new(token_set, "ciba", http_client)
.auth_req_id(self.response.auth_req_id.clone()),
)
.await?;
}
Ok(CibaGrantResponse::Successful(Box::new(token_set)))
}
}