#![deny(missing_docs)]
use crate::{AuthorizationType, RestClient};
use reqwest::StatusCode;
use serde::Deserialize;
use tokio::time::{self, Duration, Instant};
const SCOPE: &str = "spark:all";
const GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:device_code";
#[allow(dead_code)]
pub struct DeviceAuthenticator {
client_id: String,
client_secret: String,
client: RestClient,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
pub struct VerificationToken {
pub user_code: String,
device_code: String,
pub verification_uri: String,
pub verification_uri_complete: String,
interval: u64,
}
#[derive(Deserialize, Debug)]
struct TokenResponse {
access_token: String,
}
pub type Bearer = String;
impl DeviceAuthenticator {
#[must_use]
pub fn new(id: &str, secret: &str) -> Self {
let client = RestClient::new();
Self {
client_id: id.to_string(),
client_secret: secret.to_string(),
client,
}
}
pub async fn verify(&self) -> Result<VerificationToken, crate::Error> {
let params = &[("client_id", self.client_id.as_str()), ("scope", SCOPE)];
let verification_token = self
.client
.api_post_form_urlencoded::<VerificationToken>(
"device/authorize",
params,
None::<()>,
AuthorizationType::None,
)
.await?;
Ok(verification_token)
}
pub async fn wait_for_authentication(
&self,
verification_token: &VerificationToken,
) -> Result<Bearer, crate::Error> {
let params = [
("grant_type", GRANT_TYPE),
("device_code", &verification_token.device_code),
("client_id", &self.client_id),
];
let mut interval = time::interval_at(
Instant::now() + Duration::from_secs(verification_token.interval),
Duration::from_secs(verification_token.interval + 1),
);
loop {
interval.tick().await;
match self
.client
.api_post_form_urlencoded::<TokenResponse>(
"device/token",
params,
None::<()>,
AuthorizationType::Basic {
username: &self.client_id,
password: &self.client_secret,
},
)
.await
{
Ok(token) => return Ok(token.access_token),
Err(e) => match e {
crate::error::Error::StatusText(http_status, _) => {
if http_status != StatusCode::PRECONDITION_REQUIRED {
return Err(crate::Error::Authentication);
}
}
_ => {
return Err(crate::Error::Authentication);
}
},
}
}
}
}