use std::{
fmt::Display,
time::{Duration, SystemTime},
};
use jwt::{PKeyWithDigest, SignWithKey, Token};
use openssl::hash::MessageDigest;
use serde::{self, Deserialize, Serialize};
use thiserror::Error;
use crate::error::{Error, Result};
use super::constants::{
APPLE_ISSUER, CLIENT_SECRET_VALID_DURATION_MAX,
};
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct AuthErrorResponse {
pub error: AuthError,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Error)]
pub enum AuthError {
#[serde(rename = "invalid_request")]
InvalidRequest,
#[serde(rename = "invalid_client")]
InvalidClient,
#[serde(rename = "invalid_grant")]
InvalidGrant,
#[serde(rename = "unauthorized_client")]
UnauthorizedClient,
#[serde(rename = "unsupported_grant_type")]
UnsupportedGrantType,
#[serde(rename = "invalid_scope")]
InvalidScope,
}
impl Display for AuthError {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AppleClientSecretPayload {
iss: String,
iat: u64,
exp: u64,
aud: String,
sub: String,
#[serde(skip)]
key_id: String,
#[serde(skip)]
pkey: Option<openssl::pkey::PKey<openssl::pkey::Private>>,
}
impl AppleClientSecretPayload {
pub fn new(
team_id: String,
client_id: String,
issued_at: Option<u64>,
valid_while: Option<Duration>,
key_id: String,
pkey: Option<openssl::pkey::PKey<openssl::pkey::Private>>,
) -> Result<Self> {
let issued_at = issued_at.unwrap_or(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Error::SystemTime)?
.as_secs(),
);
let duration = match valid_while {
Some(duration) => duration,
_ => CLIENT_SECRET_VALID_DURATION_MAX,
}
.as_secs();
let exp = issued_at + duration;
Ok(Self {
aud: APPLE_ISSUER.to_owned(),
exp,
iat: issued_at,
iss: team_id,
sub: client_id,
key_id,
pkey,
})
}
pub fn encode(&self) -> Result<String> {
let pkey = match &self.pkey {
Some(pkey) => pkey,
_ => {
return Err(Error::SignWithKey);
}
};
let pkey = PKeyWithDigest {
digest: MessageDigest::sha256(),
key: pkey.clone(),
};
let header = jwt::Header {
algorithm: jwt::AlgorithmType::Es256,
key_id: Some(self.key_id.clone()),
..Default::default()
};
let client_secret = Token::new(header, self)
.sign_with_key(&pkey)
.map_err(|_| Error::SignWithKey)?
.as_str()
.to_owned();
Ok(client_secret)
}
}
impl TryFrom<AppleClientSecretPayload> for String {
type Error = Error;
fn try_from(value: AppleClientSecretPayload) -> Result<Self> {
value.encode()
}
}