siwa_async/common/
object.rs1use std::{
3 fmt::Display,
4 time::{Duration, SystemTime},
5};
6
7use jwt::{PKeyWithDigest, SignWithKey, Token};
8use openssl::hash::MessageDigest;
9use serde::{self, Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::error::{Error, Result};
13
14use super::constants::{
15 APPLE_ISSUER, CLIENT_SECRET_VALID_DURATION_MAX,
16};
17
18#[derive(Debug, Deserialize, Serialize, PartialEq)]
20pub struct AuthErrorResponse {
21 pub error: AuthError,
22}
23
24#[derive(Debug, Deserialize, Serialize, PartialEq, Error)]
25pub enum AuthError {
26 #[serde(rename = "invalid_request")]
27 InvalidRequest,
28 #[serde(rename = "invalid_client")]
29 InvalidClient,
30 #[serde(rename = "invalid_grant")]
31 InvalidGrant,
32 #[serde(rename = "unauthorized_client")]
33 UnauthorizedClient,
34 #[serde(rename = "unsupported_grant_type")]
35 UnsupportedGrantType,
36 #[serde(rename = "invalid_scope")]
37 InvalidScope,
38}
39
40impl Display for AuthError {
41 fn fmt(
42 &self,
43 f: &mut std::fmt::Formatter<'_>,
44 ) -> std::fmt::Result {
45 write!(f, "{:?}", self)
46 }
47}
48
49#[derive(Debug, Deserialize, Serialize, Clone)]
64pub struct AppleClientSecretPayload {
65 iss: String,
66 iat: u64,
67 exp: u64,
69 aud: String,
70 sub: String,
71 #[serde(skip)]
72 key_id: String,
73 #[serde(skip)]
74 pkey: Option<openssl::pkey::PKey<openssl::pkey::Private>>,
75}
76
77impl AppleClientSecretPayload {
78 pub fn new(
85 team_id: String,
86 client_id: String,
87 issued_at: Option<u64>,
88 valid_while: Option<Duration>,
89 key_id: String,
90 pkey: Option<openssl::pkey::PKey<openssl::pkey::Private>>,
91 ) -> Result<Self> {
92 let issued_at = issued_at.unwrap_or(
93 SystemTime::now()
94 .duration_since(SystemTime::UNIX_EPOCH)
95 .map_err(|_| Error::SystemTime)?
96 .as_secs(),
97 );
98
99 let duration = match valid_while {
100 Some(duration) => duration,
101 _ => CLIENT_SECRET_VALID_DURATION_MAX,
102 }
103 .as_secs();
104
105 let exp = issued_at + duration;
106 Ok(Self {
107 aud: APPLE_ISSUER.to_owned(),
108 exp,
109 iat: issued_at,
110 iss: team_id,
111 sub: client_id,
112 key_id,
113 pkey,
114 })
115 }
116
117 pub fn encode(&self) -> Result<String> {
119 let pkey = match &self.pkey {
120 Some(pkey) => pkey,
121 _ => {
122 return Err(Error::SignWithKey);
123 }
124 };
125
126 let pkey = PKeyWithDigest {
127 digest: MessageDigest::sha256(),
128 key: pkey.clone(),
129 };
130
131 let header = jwt::Header {
132 algorithm: jwt::AlgorithmType::Es256,
133 key_id: Some(self.key_id.clone()),
134 ..Default::default()
135 };
136
137 let client_secret = Token::new(header, self)
138 .sign_with_key(&pkey)
139 .map_err(|_| Error::SignWithKey)?
140 .as_str()
141 .to_owned();
142
143 Ok(client_secret)
144 }
145}
146
147impl TryFrom<AppleClientSecretPayload> for String {
148 type Error = Error;
149
150 fn try_from(value: AppleClientSecretPayload) -> Result<Self> {
151 value.encode()
152 }
153}