just_auth/
facebook.rs

1//! https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow
2use crate::{
3    auth_server_builder, error::Result, AuthAction, AuthConfig, AuthUrlProvider, AuthUser,
4    GenericAuthAction,
5};
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator};
10use std::collections::HashMap;
11
12pub struct AuthorizationServer {
13    config: AuthConfig,
14}
15
16auth_server_builder!();
17
18impl AuthUrlProvider for AuthorizationServer {
19    type AuthRequest = AuthRequest;
20
21    type TokenRequest = GetTokenRequest;
22
23    type UserInfoRequest = GetUserInfoRequest;
24
25    fn authorize_url(request: Self::AuthRequest) -> Result<String> {
26        let query = serde_urlencoded::to_string(request)?;
27        Ok(format!(
28            "https://www.facebook.com/v21.0/dialog/oauth?response_type=token&{query}"
29        ))
30    }
31
32    fn access_token_url(request: Self::TokenRequest) -> Result<String> {
33        let query = serde_urlencoded::to_string(request)?;
34        Ok(format!(
35            "https://graph.facebook.com/v21.0/oauth/access_token?grant_type=authorization_code&{query}"
36        ))
37    }
38
39    /// https://developers.facebook.com/docs/graph-api/overview#me
40    fn user_info_url(request: Self::UserInfoRequest) -> Result<String> {
41        let query = serde_urlencoded::to_string(request)?;
42        Ok(format!("https://graph.facebook.com/me?{query}"))
43    }
44}
45
46#[async_trait]
47impl AuthAction for AuthorizationServer {
48    type AuthCallback = AuthCallback;
49    type AuthToken = TokenResponse;
50    type AuthUser = UserInfoResponse;
51
52    async fn get_access_token(&self, callback: Self::AuthCallback) -> Result<Self::AuthToken> {
53        let AuthConfig {
54            client_id,
55            client_secret,
56            redirect_uri,
57            ..
58        } = &self.config;
59        let access_token_url = Self::access_token_url(GetTokenRequest {
60            client_id: client_id.to_string(),
61            client_secret: client_secret.clone().expect("client_secret is empty"),
62            code: callback.code,
63            redirect_uri: redirect_uri.to_string(),
64        })?;
65        Ok(reqwest::get(access_token_url).await?.json().await?)
66    }
67
68    async fn get_user_info(&self, token: Self::AuthToken) -> Result<Self::AuthUser> {
69        let user_info_url = Self::user_info_url(GetUserInfoRequest {
70            access_token: token.access_token,
71        })?;
72        Ok(reqwest::get(user_info_url).await?.json().await?)
73    }
74}
75
76#[async_trait]
77impl GenericAuthAction for AuthorizationServer {
78    async fn authorize<S: Into<String> + Send>(&self, state: S) -> Result<String> {
79        let AuthConfig {
80            client_id,
81            redirect_uri,
82            scope,
83            ..
84        } = &self.config;
85        Self::authorize_url(AuthRequest {
86            client_id: client_id.to_string(),
87            redirect_uri: redirect_uri.to_string(),
88            state: Some(state.into()),
89            scope: scope.clone().unwrap_or_default(),
90            ..Default::default()
91        })
92    }
93
94    async fn login<S: Into<String> + Send>(&self, callback: S) -> Result<AuthUser> {
95        let callback: AuthCallback = serde_urlencoded::from_str(&callback.into())?;
96        let token = self.get_access_token(callback).await?;
97        let user = self.get_user_info(token.clone()).await?;
98        Ok(AuthUser {
99            user_id: user.id,
100            name: user.name,
101            access_token: token.access_token,
102            refresh_token: token.token_type,
103            expires_in: token.expires_in,
104            extra: user.extra,
105        })
106    }
107}
108
109#[serde_as]
110#[derive(Debug, Default, Serialize, Deserialize)]
111pub struct AuthRequest {
112    client_id: String,
113    redirect_uri: String,
114    #[serde_as(as = "StringWithSeparator::<CommaSeparator, String>")]
115    scope: Vec<String>,
116    state: Option<String>,
117    display: Option<String>,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121pub struct AuthCallback {
122    code: String,
123    state: String,
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127pub struct GetTokenRequest {
128    client_id: String,
129    client_secret: String,
130    code: String,
131    redirect_uri: String,
132}
133
134#[derive(Debug, Serialize, Deserialize)]
135pub struct RefreshTokenRequest {
136    grant_type: String,
137    appid: String,
138    refresh_token: String,
139}
140
141#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct TokenResponse {
143    pub access_token: String,
144    pub expires_in: i64,
145    pub token_type: String,
146}
147
148#[derive(Debug, Default, Serialize, Deserialize)]
149pub struct GetUserInfoRequest {
150    access_token: String,
151}
152
153#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct UserInfoResponse {
155    pub id: String,
156    pub name: String,
157    #[serde(flatten)]
158    pub extra: HashMap<String, Value>,
159}