oauth2_apple/
authorization_code_grant.rs1use oauth2_client::{
2 authorization_code_grant::provider_ext::ProviderExtAuthorizationCodeGrantOidcSupportType,
3 re_exports::{ClientId, ClientSecret, RedirectUri, Url, UrlParseError},
4 Provider, ProviderExtAuthorizationCodeGrant,
5};
6
7use crate::{AppleScope, AUTHORIZATION_URL, TOKEN_URL};
8
9#[derive(Debug, Clone)]
10pub struct AppleProviderWithAppleJs {
11 client_id: ClientId,
12 client_secret: ClientSecret,
13 redirect_uri: RedirectUri,
14 token_endpoint_url: Url,
16 authorization_endpoint_url: Url,
17}
18impl AppleProviderWithAppleJs {
19 pub fn new(
20 client_id: ClientId,
21 client_secret: ClientSecret,
22 redirect_uri: RedirectUri,
23 ) -> Result<Self, UrlParseError> {
24 Ok(Self {
25 client_id,
26 client_secret,
27 redirect_uri,
28 token_endpoint_url: TOKEN_URL.parse()?,
29 authorization_endpoint_url: AUTHORIZATION_URL.parse()?,
30 })
31 }
32}
33impl Provider for AppleProviderWithAppleJs {
34 type Scope = AppleScope;
35
36 fn client_id(&self) -> Option<&ClientId> {
37 Some(&self.client_id)
38 }
39
40 fn client_secret(&self) -> Option<&ClientSecret> {
41 Some(&self.client_secret)
42 }
43
44 fn token_endpoint_url(&self) -> &Url {
45 &self.token_endpoint_url
46 }
47}
48impl ProviderExtAuthorizationCodeGrant for AppleProviderWithAppleJs {
49 fn redirect_uri(&self) -> Option<&RedirectUri> {
50 Some(&self.redirect_uri)
51 }
52
53 fn oidc_support_type(&self) -> Option<ProviderExtAuthorizationCodeGrantOidcSupportType> {
54 Some(ProviderExtAuthorizationCodeGrantOidcSupportType::Force)
55 }
56
57 fn scopes_default(&self) -> Option<Vec<<Self as Provider>::Scope>> {
58 None
59 }
60
61 fn authorization_endpoint_url(&self) -> &Url {
62 &self.authorization_endpoint_url
63 }
64
65 fn authorization_request_url_modifying(&self, url: &mut Url) {
66 let query_pairs: Vec<_> = url
67 .query_pairs()
68 .map(|(k, v)| (k.to_string(), v.to_string()))
69 .collect::<Vec<_>>();
70 let mut query_pairs_mut = url.query_pairs_mut();
71 query_pairs_mut.clear();
72 for (k, v) in query_pairs {
73 match k.as_str() {
74 "response_type" => {
75 query_pairs_mut.append_pair(k.as_str(), "code id_token");
76 }
77 "scope" => {}
78 _ => {
79 query_pairs_mut.append_pair(k.as_str(), v.as_str());
80 }
81 }
82 }
83 query_pairs_mut.finish();
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 use oauth2_client::{
92 authorization_code_grant::{AccessTokenEndpoint, AuthorizationEndpoint},
93 re_exports::{Endpoint as _, Response},
94 };
95
96 #[test]
97 fn authorization_request() -> Result<(), Box<dyn std::error::Error>> {
98 let provider = AppleProviderWithAppleJs::new(
99 "CLIENT_ID".to_owned(),
100 "CLIENT_SECRET".to_owned(),
101 RedirectUri::new("https://client.example.com/cb")?,
102 )?;
103
104 let request = AuthorizationEndpoint::new(&provider, vec![AppleScope::Email])
105 .configure(|x| x.state = Some("STATE".to_owned()))
106 .render_request()?;
107
108 assert_eq!(request.uri(), "https://appleid.apple.com/auth/authorize?response_type=code+id_token&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb&state=STATE");
109
110 Ok(())
111 }
112
113 #[test]
114 fn access_token_response() -> Result<(), Box<dyn std::error::Error>> {
115 let provider = AppleProviderWithAppleJs::new(
116 "CLIENT_ID".to_owned(),
117 "CLIENT_SECRET".to_owned(),
118 RedirectUri::new("https://client.example.com/cb")?,
119 )?;
120
121 let response_body = include_str!(
122 "../tests/response_body_json_files/access_token_with_authorization_code_grant.json"
123 );
124 let body_ret = AccessTokenEndpoint::new(&provider, "CODE".to_owned())
125 .parse_response(Response::builder().body(response_body.as_bytes().to_vec())?)?;
126
127 match body_ret {
128 Ok(body) => {
129 assert!(body.id_token.is_some());
130 }
131 Err(body) => panic!("{body:?}"),
132 }
133
134 Ok(())
135 }
136}