1use {
2 crate::client_credentials::{ClientCredentials, base_url_builder as tenant_base_url_builder},
3 reqwest::Url,
4 serde::{Deserialize, Serialize},
5 smbcloud_model::error_codes::{ErrorCode, ErrorResponse},
6 smbcloud_network::environment::Environment,
7 uuid::Uuid,
8};
9
10const TENANT_APPLE_AUTHORIZE_PATH: &str = "v1/client/oauth/apple/authorize";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AppleAuthorizationRequest {
14 pub authorize_url: String,
15 pub redirect_uri: String,
16 pub state: String,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AppleAuthSession {
21 pub access_token: String,
22 pub refresh_token: Option<String>,
23 pub email: Option<String>,
24 pub name: Option<String>,
25 pub provider: String,
26 pub provider_account_id: String,
27 pub state: Option<String>,
28}
29
30pub fn build_authorization_request_with_client(
31 env: Environment,
32 client: ClientCredentials<'_>,
33 redirect_uri: String,
34 state: Option<String>,
35) -> Result<AppleAuthorizationRequest, ErrorResponse> {
36 let state = state.unwrap_or_else(|| Uuid::new_v4().to_string());
37 let mut url_builder = tenant_base_url_builder(env, client);
38
39 url_builder
40 .add_route(TENANT_APPLE_AUTHORIZE_PATH)
41 .add_param("redirect_uri", &redirect_uri)
42 .add_param("state", &state);
43
44 Ok(AppleAuthorizationRequest {
45 authorize_url: url_builder.build(),
46 redirect_uri,
47 state,
48 })
49}
50
51pub fn parse_callback_url(
52 callback_url: &str,
53 expected_state: Option<&str>,
54) -> Result<AppleAuthSession, ErrorResponse> {
55 let url = Url::parse(callback_url).map_err(|err| ErrorResponse::Error {
56 error_code: ErrorCode::ParseError,
57 message: err.to_string(),
58 })?;
59
60 let mut access_token = None;
61 let mut refresh_token = None;
62 let mut email = None;
63 let mut name = None;
64 let mut provider = None;
65 let mut provider_account_id = None;
66 let mut state = None;
67 let mut error = None;
68
69 for (key, value) in url.query_pairs() {
70 match key.as_ref() {
71 "access_token" => access_token = Some(value.into_owned()),
72 "refresh_token" => refresh_token = Some(value.into_owned()),
73 "email" => email = Some(value.into_owned()),
74 "name" => name = Some(value.into_owned()),
75 "provider" => provider = Some(value.into_owned()),
76 "provider_account_id" => provider_account_id = Some(value.into_owned()),
77 "state" => state = Some(value.into_owned()),
78 "error" => error = Some(value.into_owned()),
79 _ => {}
80 }
81 }
82
83 if let Some(error) = error {
84 return Err(ErrorResponse::Error {
85 error_code: ErrorCode::InvalidParams,
86 message: error,
87 });
88 }
89
90 if let Some(expected_state) = expected_state {
91 if state.as_deref() != Some(expected_state) {
92 return Err(ErrorResponse::Error {
93 error_code: ErrorCode::InvalidParams,
94 message: "Apple callback state mismatch.".to_string(),
95 });
96 }
97 }
98
99 Ok(AppleAuthSession {
100 access_token: required_param("access_token", access_token)?,
101 refresh_token,
102 email,
103 name,
104 provider: provider.unwrap_or_else(|| "apple".to_string()),
105 provider_account_id: required_param("provider_account_id", provider_account_id)?,
106 state,
107 })
108}
109
110fn required_param(
111 name: &str,
112 value: Option<String>,
113) -> Result<String, ErrorResponse> {
114 value.ok_or_else(|| ErrorResponse::Error {
115 error_code: ErrorCode::InvalidParams,
116 message: format!("Missing `{name}` in Apple callback URL."),
117 })
118}