lockbook_server_lib/billing/
app_store_service.rs1use crate::billing::app_store_model::{
2 EncodedNotificationResponseBody, NotificationResponseBody, TransactionInfo,
3};
4use crate::billing::billing_service::AppStoreNotificationError;
5use crate::config::AppleConfig;
6use crate::document_service::DocumentService;
7use crate::{ClientError, ServerError, ServerState};
8use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
9use lb_rs::model::api::{AppStoreAccountState, UnixTimeMillis, UpgradeAccountAppStoreError};
10use libsecp256k1::PublicKey;
11use serde::Serialize;
12use serde::de::DeserializeOwned;
13use tracing::debug;
14use warp::hyper::body::Bytes;
15use x509_parser::error::X509Error;
16use x509_parser::parse_x509_certificate;
17use x509_parser::prelude::X509Certificate;
18
19use super::app_store_client::AppStoreClient;
20use super::google_play_client::GooglePlayClient;
21use super::stripe_client::StripeClient;
22
23const SUBSCRIBED: u16 = 1;
24const EXPIRED: u16 = 2;
25const BILLING_RETRY: u16 = 3;
26const GRACE_PERIOD: u16 = 4;
27
28impl<S, A, G, D> ServerState<S, A, G, D>
29where
30 S: StripeClient,
31 A: AppStoreClient,
32 G: GooglePlayClient,
33 D: DocumentService,
34{
35 pub async fn get_public_key_from_tx(
36 &self, trans: &TransactionInfo,
37 ) -> Result<PublicKey, ServerError<AppStoreNotificationError>> {
38 let public_key: PublicKey = self
39 .index_db
40 .lock()
41 .await
42 .app_store_ids
43 .get()
44 .get(&trans.app_account_token)
45 .ok_or_else(|| {
46 internal!("There is no public_key related to this app_account_token: {:?}", trans)
47 })?
48 .0;
49
50 Ok(public_key)
51 }
52
53 pub async fn verify_details(
54 client: &A, config: &AppleConfig, app_account_token: &str, original_transaction_id: &str,
55 ) -> Result<(UnixTimeMillis, AppStoreAccountState), ServerError<UpgradeAccountAppStoreError>>
56 {
57 let (transaction, transaction_info) = client
58 .get_sub_status(config, original_transaction_id)
59 .await?;
60
61 debug!(?transaction_info.app_account_token, ?app_account_token, "Comparing verified app account token and with unverified");
62 debug!(?transaction.original_transaction_id, ?original_transaction_id, "Comparing verified original transaction id and with unverified");
63 debug!(?transaction.status, "Checking the subscription status.");
64
65 let account_state = match transaction.status {
66 SUBSCRIBED => AppStoreAccountState::Ok,
67 EXPIRED => AppStoreAccountState::Expired,
68 BILLING_RETRY => AppStoreAccountState::FailedToRenew,
69 GRACE_PERIOD => AppStoreAccountState::GracePeriod,
70 _ => return Err(internal!("Unknown subscription status.")),
71 };
72
73 if transaction_info.app_account_token != app_account_token
74 || transaction.original_transaction_id != original_transaction_id
75 {
76 return Err(ClientError(UpgradeAccountAppStoreError::InvalidAuthDetails));
77 }
78
79 Ok((transaction_info.expires_date as UnixTimeMillis, account_state))
80 }
81
82 pub fn decode_verify_notification(
83 config: &AppleConfig, request_body: &Bytes,
84 ) -> Result<NotificationResponseBody, ServerError<AppStoreNotificationError>> {
85 let encoded_resp: EncodedNotificationResponseBody = serde_json::from_slice(request_body)
86 .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
87
88 Self::validate_jwt(config, &encoded_resp.signed_payload)
89 }
90
91 fn validate_jwt<T: Serialize + DeserializeOwned>(
92 config: &AppleConfig, token: &str,
93 ) -> Result<T, ServerError<AppStoreNotificationError>> {
94 let header = decode_header(token)?;
95 let cert_chain: Vec<Vec<u8>> = header
96 .x5c
97 .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?
98 .into_iter()
99 .map(|cert| base64::decode(cert.as_bytes()))
100 .collect::<Result<Vec<Vec<u8>>, base64::DecodeError>>()?;
101
102 let certs: Vec<X509Certificate> = cert_chain
103 .iter()
104 .map(|cert| parse_x509_certificate(cert.as_slice()).map(|(_, cert)| cert))
105 .collect::<Result<Vec<X509Certificate>, x509_parser::nom::Err<X509Error>>>()
106 .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
107
108 for i in 0..certs.len() {
109 if i != certs.len() - 1 {
110 certs[i]
111 .verify_signature(Some(&certs[i + 1].subject_pki))
112 .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
113 } else {
114 certs[i]
115 .verify_signature(Some(
116 &parse_x509_certificate(config.apple_root_cert.as_slice())
117 .map_err(|err| internal!("{:?}", err))?
118 .1
119 .subject_pki,
120 ))
121 .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
122 }
123 }
124
125 let pem = format!(
126 "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----",
127 base64::encode(
128 certs
129 .first()
130 .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?
131 .public_key()
132 .raw
133 )
134 );
135 let key = DecodingKey::from_ec_pem(pem.as_bytes())?;
136
137 let mut validate = Validation::new(Algorithm::ES256);
138 validate.required_spec_claims.remove("exp");
139
140 Ok(decode::<T>(token, &key, &validate)?.claims)
141 }
142
143 pub fn decode_verify_transaction(
144 config: &AppleConfig, encoded_transaction: &str,
145 ) -> Result<TransactionInfo, ServerError<AppStoreNotificationError>> {
146 Self::validate_jwt(config, encoded_transaction)
147 }
148}