siwa_async/auth/
auth_token.rs

1//! Generate and Validate Tokens
2use hyper::StatusCode;
3use serde::{self, Deserialize, Serialize};
4
5use crate::{
6	common::constants::{
7		APPLE_AUTH_TOKEN_ENDPOINT, APPLE_REVOKE_TOKENS_ENDPOINT,
8	},
9	error::{Error, Result},
10	object::{AppleClientSecretPayload, AuthError},
11};
12
13/// <https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens>
14/// <https://developer.apple.com/documentation/sign_in_with_apple/revoke_tokens>
15#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
16pub struct AuthTokenRequest {
17	/// Bundle id
18	client_id: String,
19	/// From [AppleClientSecretPayload][crate::object::AppleClientSecretPayload]
20	client_secret: String,
21	/// Authorization code
22	code: Option<String>,
23	/// [AuthGrantType]
24	grant_type: AuthGrantType,
25	/// Refresh token
26	refresh_token: Option<String>,
27	/// Redirect uri for web or when you provided it when SIWA
28	redirect_uri: Option<String>,
29}
30
31impl AuthTokenRequest {
32	/// - client_id: Bundle id
33	/// - client_secret: From [AppleClientSecretPayload][crate::object::AppleClientSecretPayload]
34	pub fn new(
35		client_id: String,
36		client_secret: AppleClientSecretPayload,
37		redirect_uri: Option<String>,
38	) -> Result<Self> {
39		Ok(Self {
40			client_id,
41			client_secret: client_secret.encode()?,
42			redirect_uri,
43			code: None,
44			refresh_token: None,
45			grant_type: AuthGrantType::AuthorizationCode,
46		})
47	}
48	pub async fn validate_auth_code(
49		&self,
50		auth_code: &str,
51	) -> Result<AuthTokenResponse> {
52		let mut request = self.clone();
53		request.grant_type = AuthGrantType::AuthorizationCode;
54		request.code = Some(auth_code.to_owned());
55		request.refresh_token = None;
56		request.validate().await
57	}
58
59	pub async fn validate_refresh_token(
60		&self,
61		refresh_token: &str,
62	) -> Result<AuthTokenResponse> {
63		let mut request = self.clone();
64		request.grant_type = AuthGrantType::RefreshToken;
65		request.refresh_token = Some(refresh_token.to_owned());
66		request.code = None;
67		request.validate().await
68	}
69
70	async fn validate(&self) -> Result<AuthTokenResponse> {
71		let client = reqwest::Client::new();
72		let res = client
73			.post(APPLE_AUTH_TOKEN_ENDPOINT)
74			.form(self)
75			.send()
76			.await
77			.map_err(|e| Error::HttpError(format!("{:?}", e)))?;
78		match &res.status() {
79			&StatusCode::OK => {
80				Ok(res.json::<AuthTokenResponse>().await?)
81			}
82			_ => {
83				Err(Error::BadRequest(res.json::<AuthError>().await?))
84			}
85		}
86	}
87
88	pub async fn revoke_refresh_token(
89		&self,
90		refresh_token: String,
91	) -> Result<()> {
92		self.revoke(refresh_token, &AuthTokenType::RefreshToken)
93			.await
94	}
95
96	pub async fn revoke_access_token(
97		&self,
98		access_token: String,
99	) -> Result<()> {
100		self.revoke(access_token, &AuthTokenType::AccessToken).await
101	}
102
103	async fn revoke(
104		&self,
105		token: String,
106		token_type: &AuthTokenType,
107	) -> Result<()> {
108		let client = reqwest::Client::new();
109		let params = [
110			("client_id", self.client_id.clone()),
111			("client_secret", self.client_secret.clone()),
112			("token", token),
113			(
114				"token_type_hint",
115				serde_json::ser::to_string(token_type).unwrap(),
116			),
117		];
118		let res = client
119			.post(APPLE_REVOKE_TOKENS_ENDPOINT)
120			.form(&params)
121			.send()
122			.await
123			.map_err(|e| Error::HttpError(format!("{:?}", e)))?;
124
125		match &res.status() {
126			&StatusCode::OK => Ok(()),
127			_ => Err(Error::BadRequest(
128				res.json::<AuthError>().await.unwrap(),
129			)),
130		}
131	}
132}
133
134#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
135enum AuthGrantType {
136	/// "authorization_code"
137	#[serde(rename = "authorization_code")]
138	AuthorizationCode,
139
140	/// "refresh_token"
141	#[serde(rename = "refresh_token")]
142	RefreshToken,
143}
144
145/// <https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse>
146#[derive(Debug, Deserialize, Serialize, PartialEq)]
147pub struct AuthTokenResponse {
148	pub access_token: String,
149	pub expires_in: u64,
150	pub id_token: String,
151	/// This is not present when the request is `refresh_token`
152	pub refresh_token: Option<String>,
153	/// Always "Bearer"
154	pub token_type: String,
155}
156
157#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
158pub enum AuthTokenType {
159	#[serde(rename = "refresh_token")]
160	RefreshToken,
161	#[serde(rename = "access_token")]
162	AccessToken,
163}