use log::{debug, error, info, warn};
use super::{AccessToken, AccessTokenResponse, DeviceCode, DeviceCodeResponse, TokenStatus};
use crate::errors::{NetDiskError, NetDiskResult};
use crate::http::HttpClient;
#[derive(Debug, Clone)]
pub struct Authorization {
http_client: HttpClient,
app_key: String,
app_secret: String,
scope: String,
}
impl Authorization {
pub fn new(http_client: HttpClient, app_key: &str, app_secret: &str, scope: &str) -> Self {
Authorization {
http_client,
app_key: app_key.to_string(),
app_secret: app_secret.to_string(),
scope: scope.to_string(),
}
}
pub async fn get_device_code(&self) -> NetDiskResult<DeviceCode> {
let params = [
("response_type", "device_code"),
("client_id", &self.app_key),
("scope", &self.scope),
];
let url = format!(
"https://openapi.baidu.com/oauth/2.0/device/code?{}",
serde_urlencoded::to_string(params).map_err(|e| {
NetDiskError::Unknown {
message: format!("Failed to encode params: {}", e),
}
})?
);
debug!("Requesting device code...");
let response: DeviceCodeResponse = self.http_client.get(&url, None).await?;
info!(
"Device code obtained: user_code={}, verification_url={}",
response.user_code, response.verification_url
);
Ok(response.into())
}
pub async fn request_access_token(
&self,
device_code: &DeviceCode,
) -> NetDiskResult<Option<AccessToken>> {
let params = [
("grant_type", "device_token"),
("code", &device_code.device_code),
("client_id", &self.app_key),
("client_secret", &self.app_secret),
];
let url = format!(
"https://openapi.baidu.com/oauth/2.0/token?{}",
serde_urlencoded::to_string(params).map_err(|e| {
NetDiskError::Unknown {
message: format!("Failed to encode params: {}", e),
}
})?
);
debug!("Polling for device code authorization...");
match self
.http_client
.get::<AccessTokenResponse>(&url, None)
.await
{
Ok(response) => {
let token: AccessToken = response.into();
let status = token.validate();
match status {
TokenStatus::Valid => {
info!(
"Access token obtained successfully, valid for {} seconds",
token.remaining_seconds()
);
}
TokenStatus::ExpiringSoon => {
warn!(
"Access token obtained but will expire soon ({} seconds)",
token.remaining_seconds()
);
}
TokenStatus::Expired => {
error!("Access token obtained but is already expired!");
}
}
Ok(Some(token))
}
Err(NetDiskError::AuthError { description }) => {
if description.contains("authorization_pending")
|| description.contains("slow_down")
{
debug!("Authorization pending or slow_down: {}", description);
Ok(None)
} else if description.contains("invalid_grant") {
error!("Invalid grant or device code expired: {}", description);
Err(NetDiskError::auth_error(&description))
} else {
warn!("Other auth error: {}", description);
Ok(None)
}
}
Err(e) => {
error!("Token request failed: {}", e);
Ok(None)
}
}
}
}