smbcloud_ascapi_core/
auth.rs1use crate::error::{Error, Result};
2use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
3use serde::Serialize;
4use std::path::Path;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7const AUDIENCE: &str = "appstoreconnect-v1";
8const MAX_TOKEN_LIFETIME_SECS: u64 = 20 * 60;
11
12#[derive(Debug, Clone, Serialize)]
13struct Claims {
14 iss: String,
15 iat: u64,
16 exp: u64,
17 aud: String,
18}
19
20#[derive(Clone)]
25pub struct ApiKey {
26 key_id: String,
27 issuer_id: String,
28 encoding_key: EncodingKey,
29 lifetime_secs: u64,
30}
31
32impl ApiKey {
33 pub fn from_p8_file(
36 key_id: impl Into<String>,
37 issuer_id: impl Into<String>,
38 p8_path: impl AsRef<Path>,
39 ) -> Result<Self> {
40 let path = p8_path.as_ref();
41 let pem = std::fs::read_to_string(path).map_err(|source| Error::PrivateKeyRead {
42 path: path.display().to_string(),
43 source,
44 })?;
45 Self::from_p8_pem(key_id, issuer_id, &pem)
46 }
47
48 pub fn from_p8_pem(
50 key_id: impl Into<String>,
51 issuer_id: impl Into<String>,
52 pem: &str,
53 ) -> Result<Self> {
54 let encoding_key = EncodingKey::from_ec_pem(pem.as_bytes())?;
55 Ok(Self {
56 key_id: key_id.into(),
57 issuer_id: issuer_id.into(),
58 encoding_key,
59 lifetime_secs: MAX_TOKEN_LIFETIME_SECS,
60 })
61 }
62
63 pub fn with_lifetime_secs(mut self, secs: u64) -> Self {
66 self.lifetime_secs = secs.min(MAX_TOKEN_LIFETIME_SECS);
67 self
68 }
69
70 pub fn key_id(&self) -> &str {
71 &self.key_id
72 }
73
74 pub fn lifetime_secs(&self) -> u64 {
75 self.lifetime_secs
76 }
77
78 pub fn token(&self) -> Result<String> {
82 let now = SystemTime::now()
83 .duration_since(UNIX_EPOCH)
84 .unwrap_or_default() .as_secs();
86
87 let claims = Claims {
88 iss: self.issuer_id.clone(),
89 iat: now,
90 exp: now + self.lifetime_secs,
91 aud: AUDIENCE.to_string(),
92 };
93
94 let mut header = Header::new(Algorithm::ES256);
95 header.kid = Some(self.key_id.clone());
96
97 Ok(encode(&header, &claims, &self.encoding_key)?)
98 }
99}