1use 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 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}