|
| 1 | +use async_trait::async_trait; |
| 2 | +use lazy_static::lazy_static; |
| 3 | +use serde::Deserialize; |
| 4 | +use url::Url; |
| 5 | + |
| 6 | +use crate::auth::oauth::providers::{OAuthProviderError, OAuthProviderFactory}; |
| 7 | +use crate::auth::oauth::{OAuthClientSettings, OAuthProvider, OAuthUser}; |
| 8 | +use crate::auth::AuthError; |
| 9 | +use crate::config::proto::{OAuthProviderConfig, OAuthProviderId}; |
| 10 | + |
| 11 | +#[derive(Default, Deserialize, Debug)] |
| 12 | +struct FacebookUserPictureData { |
| 13 | + url: String, |
| 14 | +} |
| 15 | + |
| 16 | +#[derive(Default, Deserialize, Debug)] |
| 17 | +struct FacebookUserPicture { |
| 18 | + data: FacebookUserPictureData, |
| 19 | +} |
| 20 | + |
| 21 | +#[derive(Default, Deserialize, Debug)] |
| 22 | +struct FacebookUser { |
| 23 | + id: String, |
| 24 | + email: String, |
| 25 | + // name: Option<String>, |
| 26 | + picture: Option<FacebookUserPicture>, |
| 27 | +} |
| 28 | + |
| 29 | +pub(crate) struct FacebookOAuthProvider { |
| 30 | + client_id: String, |
| 31 | + client_secret: String, |
| 32 | +} |
| 33 | + |
| 34 | +impl FacebookOAuthProvider { |
| 35 | + const NAME: &'static str = "facebook"; |
| 36 | + const DISPLAY_NAME: &'static str = "Facebook"; |
| 37 | + |
| 38 | + const AUTH_URL: &'static str = "https://www.facebook.com/v3.2/dialog/oauth"; |
| 39 | + const TOKEN_URL: &'static str = "https://graph.facebook.com/v3.2/oauth/access_token"; |
| 40 | + const USER_API_URL: &'static str = |
| 41 | + "https://graph.facebook.com/me?fields=name,email,picture.type(large)"; |
| 42 | + |
| 43 | + fn new(config: &OAuthProviderConfig) -> Result<Self, OAuthProviderError> { |
| 44 | + let Some(client_id) = config.client_id.clone() else { |
| 45 | + return Err(OAuthProviderError::Missing( |
| 46 | + "Facebook client id".to_string(), |
| 47 | + )); |
| 48 | + }; |
| 49 | + let Some(client_secret) = config.client_secret.clone() else { |
| 50 | + return Err(OAuthProviderError::Missing( |
| 51 | + "Facebook client secret".to_string(), |
| 52 | + )); |
| 53 | + }; |
| 54 | + |
| 55 | + return Ok(Self { |
| 56 | + client_id, |
| 57 | + client_secret, |
| 58 | + }); |
| 59 | + } |
| 60 | + |
| 61 | + pub fn factory() -> OAuthProviderFactory { |
| 62 | + OAuthProviderFactory { |
| 63 | + id: OAuthProviderId::Facebook, |
| 64 | + name: Self::NAME, |
| 65 | + display_name: Self::DISPLAY_NAME, |
| 66 | + factory: Box::new(|config: &OAuthProviderConfig| Ok(Box::new(Self::new(config)?))), |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +#[async_trait] |
| 72 | +impl OAuthProvider for FacebookOAuthProvider { |
| 73 | + fn name(&self) -> &'static str { |
| 74 | + Self::NAME |
| 75 | + } |
| 76 | + fn provider(&self) -> OAuthProviderId { |
| 77 | + OAuthProviderId::Facebook |
| 78 | + } |
| 79 | + fn display_name(&self) -> &'static str { |
| 80 | + Self::DISPLAY_NAME |
| 81 | + } |
| 82 | + |
| 83 | + fn settings(&self) -> Result<OAuthClientSettings, AuthError> { |
| 84 | + lazy_static! { |
| 85 | + static ref AUTH_URL: Url = Url::parse(FacebookOAuthProvider::AUTH_URL).unwrap(); |
| 86 | + static ref TOKEN_URL: Url = Url::parse(FacebookOAuthProvider::TOKEN_URL).unwrap(); |
| 87 | + } |
| 88 | + |
| 89 | + return Ok(OAuthClientSettings { |
| 90 | + auth_url: AUTH_URL.clone(), |
| 91 | + token_url: TOKEN_URL.clone(), |
| 92 | + client_id: self.client_id.clone(), |
| 93 | + client_secret: self.client_secret.clone(), |
| 94 | + }); |
| 95 | + } |
| 96 | + |
| 97 | + fn oauth_scopes(&self) -> Vec<&'static str> { |
| 98 | + return vec!["email"]; |
| 99 | + } |
| 100 | + |
| 101 | + async fn get_user(&self, access_token: String) -> Result<OAuthUser, AuthError> { |
| 102 | + let response = reqwest::Client::new() |
| 103 | + .get(Self::USER_API_URL) |
| 104 | + .bearer_auth(access_token) |
| 105 | + .send() |
| 106 | + .await |
| 107 | + .map_err(|err| AuthError::FailedDependency(err.into()))?; |
| 108 | + |
| 109 | + let user = response |
| 110 | + .json::<FacebookUser>() |
| 111 | + .await |
| 112 | + .map_err(|err| AuthError::FailedDependency(err.into()))?; |
| 113 | + |
| 114 | + return Ok(OAuthUser { |
| 115 | + provider_user_id: user.id, |
| 116 | + provider_id: OAuthProviderId::Facebook, |
| 117 | + email: user.email, |
| 118 | + verified: true, |
| 119 | + avatar: user.picture.map(|p| p.data.url), |
| 120 | + }); |
| 121 | + } |
| 122 | +} |
0 commit comments