use serde::Deserialize;
use super::jwt::{jwt_account_id, jwt_exp};
use crate::store::{Cred, Secret};
pub const SKEW: u64 = 60;
pub fn is_expired(expires_at: u64, now: u64) -> bool {
now + SKEW >= expires_at
}
#[derive(Clone, PartialEq)]
pub struct Callback {
pub code: String,
pub state: String,
}
pub enum Grant<'a> {
AuthCode {
code: &'a str,
verifier: &'a str,
redirect_uri: &'a str,
},
Device {
device_code: &'a str,
},
Refresh {
refresh_token: &'a Secret,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct TokenResponse {
pub access_token: Secret,
pub refresh_token: Option<Secret>,
pub expires_at: u64,
pub scope: Option<String>,
pub account_id: Option<String>,
}
impl TokenResponse {
pub fn as_cred(
&self,
prior_refresh: &Secret,
prior_scope: &Option<String>,
prior_account_id: &Option<String>,
) -> Cred {
Cred::OAuth2 {
access_token: self.access_token.clone(),
refresh_token: self
.refresh_token
.clone()
.unwrap_or_else(|| prior_refresh.clone()),
expires_at: self.expires_at,
scope: self.scope.clone().or_else(|| prior_scope.clone()),
account_id: self.account_id.clone().or_else(|| prior_account_id.clone()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum AuthError {
Pending,
SlowDown,
Fatal(String),
}
#[derive(Deserialize)]
struct RawToken {
access_token: Option<String>,
refresh_token: Option<String>,
expires_in: Option<u64>,
scope: Option<String>,
id_token: Option<String>,
error: Option<String>,
}
pub fn parse_token_response(bytes: &[u8], now: u64) -> Result<TokenResponse, AuthError> {
let raw: RawToken = serde_json::from_slice(bytes)
.map_err(|e| AuthError::Fatal(format!("malformed token response: {e}")))?;
if let Some(access) = raw.access_token {
let expires_at = match raw.expires_in {
Some(secs) => now.saturating_add(secs),
None => jwt_exp(&access).unwrap_or(now),
};
let account_id = raw.id_token.as_deref().and_then(jwt_account_id);
return Ok(TokenResponse {
access_token: Secret::new(access),
refresh_token: raw.refresh_token.map(Secret::new),
expires_at,
scope: raw.scope,
account_id,
});
}
Err(match raw.error.as_deref() {
Some("authorization_pending") => AuthError::Pending,
Some("slow_down") => AuthError::SlowDown,
Some(other) => AuthError::Fatal(other.to_owned()),
None => AuthError::Fatal("token response had neither access_token nor error".to_owned()),
})
}