matrix_sdk/authentication/oauth/
auth_code_builder.rs1use std::borrow::Cow;
16
17use oauth2::{
18 basic::BasicClient as OAuthClient, AuthUrl, CsrfToken, PkceCodeChallenge, RedirectUrl, Scope,
19};
20use ruma::{
21 api::client::discovery::get_authorization_server_metadata::msc2965::Prompt, OwnedDeviceId,
22 UserId,
23};
24use tracing::{info, instrument};
25use url::Url;
26
27use super::{ClientRegistrationData, OAuth, OAuthError};
28use crate::{authentication::oauth::AuthorizationValidationData, Result};
29
30#[allow(missing_debug_implementations)]
35pub struct OAuthAuthCodeUrlBuilder {
36 oauth: OAuth,
37 registration_data: Option<ClientRegistrationData>,
38 scopes: Vec<Scope>,
39 device_id: OwnedDeviceId,
40 redirect_uri: Url,
41 prompt: Option<Vec<Prompt>>,
42 login_hint: Option<String>,
43}
44
45impl OAuthAuthCodeUrlBuilder {
46 pub(super) fn new(
47 oauth: OAuth,
48 scopes: Vec<Scope>,
49 device_id: OwnedDeviceId,
50 redirect_uri: Url,
51 registration_data: Option<ClientRegistrationData>,
52 ) -> Self {
53 Self {
54 oauth,
55 registration_data,
56 scopes,
57 device_id,
58 redirect_uri,
59 prompt: None,
60 login_hint: None,
61 }
62 }
63
64 pub fn prompt(mut self, prompt: Vec<Prompt>) -> Self {
72 self.prompt = Some(prompt);
73 self
74 }
75
76 pub fn user_id_hint(mut self, user_id: &UserId) -> Self {
81 self.login_hint = Some(format!("mxid:{user_id}"));
82 self
83 }
84
85 #[instrument(target = "matrix_sdk::client", skip_all)]
95 pub async fn build(self) -> Result<OAuthAuthorizationData, OAuthError> {
96 let Self { oauth, registration_data, scopes, device_id, redirect_uri, prompt, login_hint } =
97 self;
98
99 let server_metadata = oauth.server_metadata().await?;
100
101 oauth.use_registration_data(&server_metadata, registration_data.as_ref()).await?;
102
103 let data = oauth.data().expect("OAuth 2.0 data should be set after registration");
104 info!(
105 issuer = server_metadata.issuer.as_str(),
106 ?scopes,
107 "Authorizing scope via the OAuth 2.0 Authorization Code flow"
108 );
109
110 let auth_url = AuthUrl::from_url(server_metadata.authorization_endpoint.clone());
111
112 let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
113 let redirect_uri = RedirectUrl::from_url(redirect_uri);
114
115 let client = OAuthClient::new(data.client_id.clone()).set_auth_uri(auth_url);
116 let mut request = client
117 .authorize_url(CsrfToken::new_random)
118 .add_scopes(scopes)
119 .set_pkce_challenge(pkce_challenge)
120 .set_redirect_uri(Cow::Borrowed(&redirect_uri));
121
122 if let Some(prompt) = prompt {
123 let prompt_str = prompt.iter().map(Prompt::as_str).collect::<Vec<_>>().join(" ");
125 request = request.add_extra_param("prompt", prompt_str);
126 }
127
128 if let Some(login_hint) = login_hint {
129 request = request.add_extra_param("login_hint", login_hint);
130 }
131
132 let (url, state) = request.url();
133
134 data.authorization_data.lock().await.insert(
135 state.clone(),
136 AuthorizationValidationData { server_metadata, device_id, redirect_uri, pkce_verifier },
137 );
138
139 Ok(OAuthAuthorizationData { url, state })
140 }
141}
142
143#[derive(Debug, Clone)]
145#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
146pub struct OAuthAuthorizationData {
147 pub url: Url,
149 pub state: CsrfToken,
152}
153
154#[cfg(feature = "uniffi")]
155#[matrix_sdk_ffi_macros::export]
156impl OAuthAuthorizationData {
157 pub fn login_url(&self) -> String {
159 self.url.to_string()
160 }
161}