use hyper::StatusCode;
use serde::{self, Deserialize, Serialize};
use crate::{
common::constants::{
APPLE_AUTH_TOKEN_ENDPOINT, APPLE_REVOKE_TOKENS_ENDPOINT,
},
error::{Error, Result},
object::{AppleClientSecretPayload, AuthError},
};
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub struct AuthTokenRequest {
client_id: String,
client_secret: String,
code: Option<String>,
grant_type: AuthGrantType,
refresh_token: Option<String>,
redirect_uri: Option<String>,
}
impl AuthTokenRequest {
pub fn new(
client_id: String,
client_secret: AppleClientSecretPayload,
redirect_uri: Option<String>,
) -> Result<Self> {
Ok(Self {
client_id,
client_secret: client_secret.encode()?,
redirect_uri,
code: None,
refresh_token: None,
grant_type: AuthGrantType::AuthorizationCode,
})
}
pub async fn validate_auth_code(
&self,
auth_code: &str,
) -> Result<AuthTokenResponse> {
let mut request = self.clone();
request.grant_type = AuthGrantType::AuthorizationCode;
request.code = Some(auth_code.to_owned());
request.refresh_token = None;
request.validate().await
}
pub async fn validate_refresh_token(
&self,
refresh_token: &str,
) -> Result<AuthTokenResponse> {
let mut request = self.clone();
request.grant_type = AuthGrantType::RefreshToken;
request.refresh_token = Some(refresh_token.to_owned());
request.code = None;
request.validate().await
}
async fn validate(&self) -> Result<AuthTokenResponse> {
let client = reqwest::Client::new();
let res = client
.post(APPLE_AUTH_TOKEN_ENDPOINT)
.form(self)
.send()
.await
.map_err(|e| Error::HttpError(format!("{:?}", e)))?;
match &res.status() {
&StatusCode::OK => {
Ok(res.json::<AuthTokenResponse>().await?)
}
_ => {
Err(Error::BadRequest(res.json::<AuthError>().await?))
}
}
}
pub async fn revoke_refresh_token(
&self,
refresh_token: String,
) -> Result<()> {
self.revoke(refresh_token, &AuthTokenType::RefreshToken)
.await
}
pub async fn revoke_access_token(
&self,
access_token: String,
) -> Result<()> {
self.revoke(access_token, &AuthTokenType::AccessToken).await
}
async fn revoke(
&self,
token: String,
token_type: &AuthTokenType,
) -> Result<()> {
let client = reqwest::Client::new();
let params = [
("client_id", self.client_id.clone()),
("client_secret", self.client_secret.clone()),
("token", token),
(
"token_type_hint",
serde_json::ser::to_string(token_type).unwrap(),
),
];
let res = client
.post(APPLE_REVOKE_TOKENS_ENDPOINT)
.form(¶ms)
.send()
.await
.map_err(|e| Error::HttpError(format!("{:?}", e)))?;
match &res.status() {
&StatusCode::OK => Ok(()),
_ => Err(Error::BadRequest(
res.json::<AuthError>().await.unwrap(),
)),
}
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
enum AuthGrantType {
#[serde(rename = "authorization_code")]
AuthorizationCode,
#[serde(rename = "refresh_token")]
RefreshToken,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct AuthTokenResponse {
pub access_token: String,
pub expires_in: u64,
pub id_token: String,
pub refresh_token: Option<String>,
pub token_type: String,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub enum AuthTokenType {
#[serde(rename = "refresh_token")]
RefreshToken,
#[serde(rename = "access_token")]
AccessToken,
}