siwa-async 0.5.1

Sign In With Apple Validation in Async Rust
Documentation
//! Decoded Identity Token
use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_bool_from_anything;

use crate::common::{
	constants::APPLE_ISSUER,
	error::{Error, Result},
	jwk::fetch_apple_keys,
};

/// # Identity Token From Apple Server
/// Details: <https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple>
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct AppleIdentityToken {
	/// Token issuer: <https://appleid.apple.com>
	pub iss: String,
	/// User unique ID
	pub sub: String,
	/// client_id (Bundle ID)
	pub aud: String,
	/// Issued at. Seconds since linux epoch.
	pub iat: u64,
	/// Expiration time. Seconds since linux epoch.
	pub exp: u64,
	/// nonce value
	pub nonce: Option<String>,
	/// If nonce was used
	#[serde(
		deserialize_with = "deserialize_bool_from_anything",
		default
	)]
	pub nonce_supported: bool,
	/// User email
	pub email: Option<String>,
	/// Always true
	#[serde(
		deserialize_with = "deserialize_bool_from_anything",
		default = "default_true"
	)]
	pub email_verified: bool,
	/// If the email is the proxy
	#[serde(
		deserialize_with = "deserialize_bool_from_anything",
		default
	)]
	pub is_private_email: bool,
	pub real_user_status: Option<u8>,
	pub transfer_sub: Option<String>,
	pub auth_time: u64,
}

fn default_true() -> bool {
	true
}

impl AppleIdentityToken {
	pub async fn decode(
		id_token: &str,
		client_id: &str,
		validate_exp: bool,
	) -> Result<Self> {
		let header = Self::decode_header(id_token)?;
		let decoding_key =
			Self::find_decoding_key(&header.kid.unwrap()).await?;

		let mut val = jsonwebtoken::Validation::new(header.alg);
		val.validate_exp = validate_exp;
		val.set_audience(&[client_id]);
		val.set_issuer(&[APPLE_ISSUER]);

		let token_data = jsonwebtoken::decode::<AppleIdentityToken>(
			id_token,
			&decoding_key,
			&val,
		)?;

		Ok(token_data.claims)
	}

	fn decode_header(token: &str) -> Result<jsonwebtoken::Header> {
		let header = jsonwebtoken::decode_header(token)?;

		match &header.kid {
			Some(_) => Ok(header),
			None => Err(Error::KidNotFound),
		}
	}

	async fn find_decoding_key(
		kid: &String,
	) -> Result<jsonwebtoken::DecodingKey> {
		let pubkeys = fetch_apple_keys().await?;

		match pubkeys.get(kid) {
			Some(key) => {
				let decoding_key =
					jsonwebtoken::DecodingKey::from_rsa_components(
						&key.n, &key.e,
					)?;
				Ok(decoding_key)
			}
			None => Err(Error::KeyNotFound),
		}
	}
}