use secrecy::{ExposeSecret, Secret};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
use url::Url;
#[derive(Debug, Clone)]
pub struct AuthorizationResponse {
pub device_code: Secret<String>,
pub user_code: String,
pub verification_uri: Url,
pub verification_uri_complete: Option<Url>,
pub expires_in: u64,
pub interval: u64,
}
impl AuthorizationResponse {
pub fn device_code(&self) -> &str {
self.device_code.expose_secret()
}
pub fn user_code(&self) -> &str {
&self.user_code
}
pub fn verification_uri(&self) -> &Url {
&self.verification_uri
}
pub fn verification_uri_complete(&self) -> Option<&Url> {
self.verification_uri_complete.as_ref()
}
pub fn expires_in(&self) -> Duration {
Duration::from_secs(self.expires_in)
}
pub fn poll_interval(&self) -> Duration {
Duration::from_secs(self.interval)
}
#[cfg(feature = "qr-codes")]
pub fn generate_qr_code(&self) -> Result<String, crate::error::DeviceFlowError> {
use qrcode::{render::unicode, QrCode};
let uri = self
.verification_uri_complete
.as_ref()
.unwrap_or(&self.verification_uri);
let code = QrCode::new(uri.as_str())?;
Ok(code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build())
}
}
#[derive(Debug, Clone)]
pub struct TokenResponse {
pub access_token: Secret<String>,
pub token_type: String,
pub expires_in: Option<u64>,
pub refresh_token: Option<Secret<String>>,
pub scope: Option<String>,
pub issued_at: OffsetDateTime,
}
impl TokenResponse {
pub fn access_token(&self) -> &str {
self.access_token.expose_secret()
}
pub fn refresh_token(&self) -> Option<&str> {
self.refresh_token
.as_ref()
.map(|t| t.expose_secret().as_str())
}
pub fn is_expired(&self) -> bool {
if let Some(expires_in) = self.expires_in {
let expiry = self.issued_at + time::Duration::seconds(expires_in as i64);
OffsetDateTime::now_utc() >= expiry
} else {
false
}
}
pub fn expires_at(&self) -> Option<OffsetDateTime> {
self.expires_in
.map(|expires_in| self.issued_at + time::Duration::seconds(expires_in as i64))
}
pub fn remaining_lifetime(&self) -> Option<Duration> {
self.expires_at().map(|expires_at| {
let remaining = expires_at - OffsetDateTime::now_utc();
Duration::from_secs(remaining.whole_seconds().max(0) as u64)
})
}
pub fn expires_within(&self, duration: Duration) -> bool {
if let Some(remaining) = self.remaining_lifetime() {
remaining <= duration
} else {
false
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorResponse {
pub error: String,
pub error_description: Option<String>,
pub error_uri: Option<Url>,
}
#[derive(Debug, Clone, Serialize)]
pub struct DeviceAuthorizationRequest {
pub client_id: String,
pub scope: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct DeviceTokenRequest {
pub grant_type: String,
pub device_code: String,
pub client_id: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct RefreshTokenRequest {
pub grant_type: String,
pub refresh_token: String,
pub client_id: String,
pub scope: Option<String>,
}
impl<'de> Deserialize<'de> for AuthorizationResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct AuthorizationResponseHelper {
device_code: String,
user_code: String,
verification_uri: Url,
verification_uri_complete: Option<Url>,
expires_in: u64,
interval: u64,
}
let helper = AuthorizationResponseHelper::deserialize(deserializer)?;
Ok(AuthorizationResponse {
device_code: Secret::new(helper.device_code),
user_code: helper.user_code,
verification_uri: helper.verification_uri,
verification_uri_complete: helper.verification_uri_complete,
expires_in: helper.expires_in,
interval: helper.interval,
})
}
}
impl<'de> Deserialize<'de> for TokenResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct TokenResponseHelper {
access_token: String,
token_type: String,
expires_in: Option<u64>,
refresh_token: Option<String>,
scope: Option<String>,
}
let helper = TokenResponseHelper::deserialize(deserializer)?;
Ok(TokenResponse {
access_token: Secret::new(helper.access_token),
token_type: helper.token_type,
expires_in: helper.expires_in,
refresh_token: helper.refresh_token.map(Secret::new),
scope: helper.scope,
issued_at: OffsetDateTime::now_utc(),
})
}
}