matrix_sdk/authentication/oidc/
auth_code_builder.rs1use std::borrow::Cow;
16
17use oauth2::{
18 basic::BasicClient as OauthClient, AuthUrl, CsrfToken, PkceCodeChallenge, RedirectUrl, Scope,
19};
20use ruma::{api::client::discovery::get_authorization_server_metadata::msc2965::Prompt, UserId};
21use tracing::{info, instrument};
22use url::Url;
23
24use super::{Oidc, OidcError};
25use crate::{authentication::oidc::AuthorizationValidationData, Result};
26
27#[allow(missing_debug_implementations)]
32pub struct OidcAuthCodeUrlBuilder {
33 oidc: Oidc,
34 scopes: Vec<Scope>,
35 redirect_uri: Url,
36 prompt: Option<Vec<Prompt>>,
37 login_hint: Option<String>,
38}
39
40impl OidcAuthCodeUrlBuilder {
41 pub(super) fn new(oidc: Oidc, scopes: Vec<Scope>, redirect_uri: Url) -> Self {
42 Self { oidc, scopes, redirect_uri, prompt: None, login_hint: None }
43 }
44
45 pub fn prompt(mut self, prompt: Vec<Prompt>) -> Self {
53 self.prompt = Some(prompt);
54 self
55 }
56
57 pub fn user_id_hint(mut self, user_id: &UserId) -> Self {
62 self.login_hint = Some(format!("mxid:{user_id}"));
63 self
64 }
65
66 #[instrument(target = "matrix_sdk::client", skip_all)]
76 pub async fn build(self) -> Result<OidcAuthorizationData, OidcError> {
77 let Self { oidc, scopes, redirect_uri, prompt, login_hint } = self;
78
79 let data = oidc.data().ok_or(OidcError::NotAuthenticated)?;
80 info!(
81 issuer = data.issuer,
82 ?scopes,
83 "Authorizing scope via the OpenID Connect Authorization Code flow"
84 );
85
86 let provider_metadata = oidc.provider_metadata().await?;
87 let auth_url = AuthUrl::from_url(provider_metadata.authorization_endpoint().clone());
88
89 let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
90 let redirect_uri = RedirectUrl::from_url(redirect_uri);
91
92 let client = OauthClient::new(data.client_id.clone()).set_auth_uri(auth_url);
93 let mut request = client
94 .authorize_url(CsrfToken::new_random)
95 .add_scopes(scopes)
96 .set_pkce_challenge(pkce_challenge)
97 .set_redirect_uri(Cow::Borrowed(&redirect_uri));
98
99 if let Some(prompt) = prompt {
100 let prompt_str = prompt.iter().map(Prompt::as_str).collect::<Vec<_>>().join(" ");
102 request = request.add_extra_param("prompt", prompt_str);
103 }
104
105 if let Some(login_hint) = login_hint {
106 request = request.add_extra_param("login_hint", login_hint);
107 }
108
109 let (url, state) = request.url();
110
111 data.authorization_data
112 .lock()
113 .await
114 .insert(state.clone(), AuthorizationValidationData { redirect_uri, pkce_verifier });
115
116 Ok(OidcAuthorizationData { url, state })
117 }
118}
119
120#[derive(Debug, Clone)]
122#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
123pub struct OidcAuthorizationData {
124 pub url: Url,
126 pub state: CsrfToken,
129}
130
131#[cfg(feature = "uniffi")]
132#[matrix_sdk_ffi_macros::export]
133impl OidcAuthorizationData {
134 pub fn login_url(&self) -> String {
136 self.url.to_string()
137 }
138}