- Split library (db/crypto/service) and MCP/Web/OAuth binary - Add deploy examples and CI/docs updates Made-with: Cursor
67 lines
1.8 KiB
Rust
67 lines
1.8 KiB
Rust
use anyhow::{Context, Result};
|
|
use serde::Deserialize;
|
|
|
|
use super::{OAuthConfig, OAuthUserInfo};
|
|
|
|
#[derive(Deserialize)]
|
|
struct TokenResponse {
|
|
access_token: String,
|
|
#[allow(dead_code)]
|
|
token_type: String,
|
|
#[allow(dead_code)]
|
|
id_token: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct UserInfo {
|
|
sub: String,
|
|
email: Option<String>,
|
|
name: Option<String>,
|
|
picture: Option<String>,
|
|
}
|
|
|
|
/// Exchange authorization code for tokens and fetch user profile.
|
|
pub async fn exchange_code(
|
|
client: &reqwest::Client,
|
|
config: &OAuthConfig,
|
|
code: &str,
|
|
) -> Result<OAuthUserInfo> {
|
|
let token_resp: TokenResponse = client
|
|
.post("https://oauth2.googleapis.com/token")
|
|
.form(&[
|
|
("code", code),
|
|
("client_id", &config.client_id),
|
|
("client_secret", &config.client_secret),
|
|
("redirect_uri", &config.redirect_uri),
|
|
("grant_type", "authorization_code"),
|
|
])
|
|
.send()
|
|
.await
|
|
.context("failed to exchange Google code")?
|
|
.error_for_status()
|
|
.context("Google token endpoint error")?
|
|
.json()
|
|
.await
|
|
.context("failed to parse Google token response")?;
|
|
|
|
let user: UserInfo = client
|
|
.get("https://openidconnect.googleapis.com/v1/userinfo")
|
|
.bearer_auth(&token_resp.access_token)
|
|
.send()
|
|
.await
|
|
.context("failed to fetch Google userinfo")?
|
|
.error_for_status()
|
|
.context("Google userinfo endpoint error")?
|
|
.json()
|
|
.await
|
|
.context("failed to parse Google userinfo")?;
|
|
|
|
Ok(OAuthUserInfo {
|
|
provider: "google".to_string(),
|
|
provider_id: user.sub,
|
|
email: user.email,
|
|
name: user.name,
|
|
avatar_url: user.picture,
|
|
})
|
|
}
|