matrix_sdk/authentication/qrcode/
oauth_client.rs1use std::pin::Pin;
16
17use futures_core::Future;
18use mas_oidc_client::types::{
19 oidc::VerifiedProviderMetadata,
20 scope::{MatrixApiScopeToken, ScopeToken},
21};
22use oauth2::{
23 basic::BasicClient, ClientId, DeviceAuthorizationUrl, EndpointNotSet, EndpointSet,
24 HttpClientError, HttpRequest, Scope, StandardDeviceAuthorizationResponse, TokenResponse,
25 TokenUrl,
26};
27use vodozemac::Curve25519PublicKey;
28
29use super::DeviceAuthorizationOauthError;
30use crate::{authentication::oidc::OidcSessionTokens, http_client::HttpClient};
31
32pub(super) struct OauthClient {
37 inner: BasicClient<EndpointNotSet, EndpointSet, EndpointNotSet, EndpointNotSet, EndpointSet>,
39 http_client: HttpClient,
40}
41
42impl OauthClient {
43 pub(super) fn new(
44 client_id: String,
45 server_metadata: &VerifiedProviderMetadata,
46 http_client: HttpClient,
47 ) -> Result<Self, DeviceAuthorizationOauthError> {
48 let client_id = ClientId::new(client_id);
49
50 let token_endpoint = TokenUrl::from_url(server_metadata.token_endpoint().clone());
51
52 let device_authorization_endpoint = server_metadata
56 .device_authorization_endpoint
57 .clone()
58 .map(DeviceAuthorizationUrl::from_url)
59 .ok_or(DeviceAuthorizationOauthError::NoDeviceAuthorizationEndpoint)?;
60
61 let oauth2_client = BasicClient::new(client_id)
62 .set_token_uri(token_endpoint)
63 .set_device_authorization_url(device_authorization_endpoint);
64
65 Ok(Self { inner: oauth2_client, http_client })
66 }
67
68 pub(super) async fn request_device_authorization(
69 &self,
70 device_id: Curve25519PublicKey,
71 ) -> Result<StandardDeviceAuthorizationResponse, DeviceAuthorizationOauthError> {
72 let scopes = [
73 ScopeToken::MatrixApi(MatrixApiScopeToken::Full),
74 ScopeToken::try_with_matrix_device(device_id.to_base64()).expect(
75 "We should be able to create a scope token from a \
76 Curve25519 public key encoded as base64",
77 ),
78 ]
79 .into_iter()
80 .map(|scope| Scope::new(scope.to_string()));
81
82 let details: StandardDeviceAuthorizationResponse = self
83 .inner
84 .exchange_device_code()
85 .add_scopes(scopes)
86 .request_async(&self.http_client)
87 .await?;
88
89 Ok(details)
90 }
91
92 pub(super) async fn wait_for_tokens(
93 &self,
94 details: &StandardDeviceAuthorizationResponse,
95 ) -> Result<OidcSessionTokens, DeviceAuthorizationOauthError> {
96 let response = self
97 .inner
98 .exchange_device_access_token(details)
99 .request_async(&self.http_client, tokio::time::sleep, None)
100 .await?;
101
102 let tokens = OidcSessionTokens {
103 access_token: response.access_token().secret().to_owned(),
104 refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
105 latest_id_token: None,
106 };
107
108 Ok(tokens)
109 }
110}
111
112impl<'c> oauth2::AsyncHttpClient<'c> for HttpClient {
113 type Error = HttpClientError<reqwest::Error>;
114
115 type Future =
116 Pin<Box<dyn Future<Output = Result<oauth2::HttpResponse, Self::Error>> + Send + Sync + 'c>>;
117
118 fn call(&'c self, request: HttpRequest) -> Self::Future {
119 Box::pin(async move {
120 let response = self.inner.call(request).await?;
121
122 Ok(response)
123 })
124 }
125}