Skip to content

Commit 0baf8d1

Browse files
committed
Add untested Facebook and Microsoft oauth providers.
1 parent 23b4d5c commit 0baf8d1

File tree

7 files changed

+247
-12
lines changed

7 files changed

+247
-12
lines changed

proto/config.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ enum OAuthProviderId {
3131
DISCORD = 10;
3232
GITLAB = 11;
3333
GOOGLE = 12;
34+
FACEBOOK = 13;
35+
MICROSOFT = 14;
3436
}
3537

3638
message OAuthProviderConfig {

trailbase-core/src/auth/oauth/providers/discord.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl DiscordOAuthProvider {
3131
));
3232
};
3333

34-
return Ok(DiscordOAuthProvider {
34+
return Ok(Self {
3535
client_id,
3636
client_secret,
3737
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
}

trailbase-core/src/auth/oauth/providers/gitlab.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl GitlabOAuthProvider {
3131
));
3232
};
3333

34-
return Ok(GitlabOAuthProvider {
34+
return Ok(Self {
3535
client_id,
3636
client_secret,
3737
});
@@ -40,25 +40,23 @@ impl GitlabOAuthProvider {
4040
pub fn factory() -> OAuthProviderFactory {
4141
OAuthProviderFactory {
4242
id: OAuthProviderId::Gitlab,
43-
name: GitlabOAuthProvider::NAME,
44-
display_name: GitlabOAuthProvider::DISPLAY_NAME,
45-
factory: Box::new(|config: &OAuthProviderConfig| {
46-
Ok(Box::new(GitlabOAuthProvider::new(config)?))
47-
}),
43+
name: Self::NAME,
44+
display_name: Self::DISPLAY_NAME,
45+
factory: Box::new(|config: &OAuthProviderConfig| Ok(Box::new(Self::new(config)?))),
4846
}
4947
}
5048
}
5149

5250
#[async_trait]
5351
impl OAuthProvider for GitlabOAuthProvider {
5452
fn name(&self) -> &'static str {
55-
GitlabOAuthProvider::NAME
53+
Self::NAME
5654
}
5755
fn provider(&self) -> OAuthProviderId {
5856
OAuthProviderId::Gitlab
5957
}
6058
fn display_name(&self) -> &'static str {
61-
GitlabOAuthProvider::DISPLAY_NAME
59+
Self::DISPLAY_NAME
6260
}
6361

6462
fn settings(&self) -> Result<OAuthClientSettings, AuthError> {
@@ -81,7 +79,7 @@ impl OAuthProvider for GitlabOAuthProvider {
8179

8280
async fn get_user(&self, access_token: String) -> Result<OAuthUser, AuthError> {
8381
let response = reqwest::Client::new()
84-
.get(GitlabOAuthProvider::USER_API_URL)
82+
.get(Self::USER_API_URL)
8583
.bearer_auth(access_token)
8684
.send()
8785
.await

trailbase-core/src/auth/oauth/providers/google.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub(crate) struct GoogleOAuthProvider {
1515

1616
impl GoogleOAuthProvider {
1717
const NAME: &'static str = "google";
18-
const DISPLAY_NAME: &'static str = "google";
18+
const DISPLAY_NAME: &'static str = "Google";
1919

2020
const AUTH_URL: &'static str = "https://accounts.google.com/o/oauth2/auth";
2121
const TOKEN_URL: &'static str = "https://accounts.google.com/o/oauth2/token";
@@ -31,7 +31,7 @@ impl GoogleOAuthProvider {
3131
));
3232
};
3333

34-
return Ok(GoogleOAuthProvider {
34+
return Ok(Self {
3535
client_id,
3636
client_secret,
3737
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 MicrosoftUser {
13+
id: String,
14+
mail: String,
15+
}
16+
17+
pub(crate) struct MicrosoftOAuthProvider {
18+
client_id: String,
19+
client_secret: String,
20+
}
21+
22+
impl MicrosoftOAuthProvider {
23+
const NAME: &'static str = "microsoft";
24+
const DISPLAY_NAME: &'static str = "Microsoft";
25+
26+
const AUTH_URL: &'static str = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
27+
const TOKEN_URL: &'static str = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
28+
const USER_API_URL: &'static str = "https://graph.microsoft.com/v1.0/me";
29+
30+
fn new(config: &OAuthProviderConfig) -> Result<Self, OAuthProviderError> {
31+
let Some(client_id) = config.client_id.clone() else {
32+
return Err(OAuthProviderError::Missing(
33+
"Microsoft client id".to_string(),
34+
));
35+
};
36+
let Some(client_secret) = config.client_secret.clone() else {
37+
return Err(OAuthProviderError::Missing(
38+
"Microsoft client secret".to_string(),
39+
));
40+
};
41+
42+
return Ok(Self {
43+
client_id,
44+
client_secret,
45+
});
46+
}
47+
48+
pub fn factory() -> OAuthProviderFactory {
49+
OAuthProviderFactory {
50+
id: OAuthProviderId::Microsoft,
51+
name: Self::NAME,
52+
display_name: Self::DISPLAY_NAME,
53+
factory: Box::new(|config: &OAuthProviderConfig| Ok(Box::new(Self::new(config)?))),
54+
}
55+
}
56+
}
57+
58+
#[async_trait]
59+
impl OAuthProvider for MicrosoftOAuthProvider {
60+
fn name(&self) -> &'static str {
61+
Self::NAME
62+
}
63+
fn provider(&self) -> OAuthProviderId {
64+
OAuthProviderId::Microsoft
65+
}
66+
fn display_name(&self) -> &'static str {
67+
Self::DISPLAY_NAME
68+
}
69+
70+
fn settings(&self) -> Result<OAuthClientSettings, AuthError> {
71+
lazy_static! {
72+
static ref AUTH_URL: Url = Url::parse(MicrosoftOAuthProvider::AUTH_URL).unwrap();
73+
static ref TOKEN_URL: Url = Url::parse(MicrosoftOAuthProvider::TOKEN_URL).unwrap();
74+
}
75+
76+
return Ok(OAuthClientSettings {
77+
auth_url: AUTH_URL.clone(),
78+
token_url: TOKEN_URL.clone(),
79+
client_id: self.client_id.clone(),
80+
client_secret: self.client_secret.clone(),
81+
});
82+
}
83+
84+
fn oauth_scopes(&self) -> Vec<&'static str> {
85+
return vec!["User.Read"];
86+
}
87+
88+
async fn get_user(&self, access_token: String) -> Result<OAuthUser, AuthError> {
89+
let response = reqwest::Client::new()
90+
.get(Self::USER_API_URL)
91+
.bearer_auth(access_token)
92+
.send()
93+
.await
94+
.map_err(|err| AuthError::FailedDependency(err.into()))?;
95+
96+
let user = response
97+
.json::<MicrosoftUser>()
98+
.await
99+
.map_err(|err| AuthError::FailedDependency(err.into()))?;
100+
101+
return Ok(OAuthUser {
102+
provider_user_id: user.id,
103+
provider_id: OAuthProviderId::Microsoft,
104+
email: user.mail,
105+
verified: true,
106+
avatar: None,
107+
});
108+
}
109+
}

trailbase-core/src/auth/oauth/providers/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
mod discord;
2+
mod facebook;
23
mod gitlab;
34
mod google;
5+
mod microsoft;
46

57
#[cfg(test)]
68
pub(crate) mod test;
@@ -54,6 +56,8 @@ lazy_static! {
5456
discord::DiscordOAuthProvider::factory(),
5557
gitlab::GitlabOAuthProvider::factory(),
5658
google::GoogleOAuthProvider::factory(),
59+
facebook::FacebookOAuthProvider::factory(),
60+
microsoft::MicrosoftOAuthProvider::factory(),
5761
#[cfg(test)]
5862
test::TestOAuthProvider::factory(),
5963
];

0 commit comments

Comments
 (0)