Start the authorization flow. Returns the URL the user should open in a browser. The PKCE verifier and CSRF state are persisted both in-memory and on disk so that `finish_auth` can complete the exchange even after a process restart.
(&self)
| 125 | /// and CSRF state are persisted both in-memory and on disk so that |
| 126 | /// `finish_auth` can complete the exchange even after a process restart. |
| 127 | pub async fn start_auth(&self) -> Result<String, OAuthError> { |
| 128 | let (client_id, client_secret, auth_url, token_url, redirect_url) = self.parsed_config()?; |
| 129 | |
| 130 | let mut client = oauth2::basic::BasicClient::new(client_id) |
| 131 | .set_auth_uri(auth_url) |
| 132 | .set_token_uri(token_url) |
| 133 | .set_redirect_uri(redirect_url); |
| 134 | |
| 135 | if let Some(secret) = client_secret { |
| 136 | client = client.set_client_secret(secret); |
| 137 | } |
| 138 | |
| 139 | let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); |
| 140 | |
| 141 | let mut auth_request = client.authorize_url(CsrfToken::new_random); |
| 142 | for scope in &self.config.scopes { |
| 143 | auth_request = auth_request.add_scope(Scope::new(scope.clone())); |
| 144 | } |
| 145 | |
| 146 | let (url, csrf_state) = auth_request.set_pkce_challenge(pkce_challenge).url(); |
| 147 | |
| 148 | // Persist to disk for cross-restart resilience. |
| 149 | auth::update_code_verifier(&self.mcp_name, pkce_verifier.secret()) |
| 150 | .await |
| 151 | .map_err(OAuthError::Storage)?; |
| 152 | auth::update_oauth_state(&self.mcp_name, csrf_state.secret()) |
| 153 | .await |
| 154 | .map_err(OAuthError::Storage)?; |
| 155 | |
| 156 | // Also keep in memory for the fast path. |
| 157 | { |
| 158 | let mut pending = self.pending.write().await; |
| 159 | *pending = Some(PendingAuth { |
| 160 | pkce_verifier: pkce_verifier.secret().clone(), |
| 161 | csrf_state: csrf_state.secret().clone(), |
| 162 | }); |
| 163 | } |
| 164 | |
| 165 | Ok(url.to_string()) |
| 166 | } |
| 167 | /// Complete the authorization flow by exchanging the authorization code |
| 168 | /// for tokens. |
| 169 | pub async fn finish_auth(&self, code: &str, state: &str) -> Result<(), OAuthError> { |
nothing calls this directly
no test coverage detected