1use crate::{auth_server_builder, AuthUser, GenericAuthAction};
3use crate::{error::Result, AuthAction, AuthConfig, AuthUrlProvider};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use serde_with::DisplayFromStr;
8use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator};
9use std::collections::HashMap;
10
11pub struct AuthorizationServer {
12 config: AuthConfig,
13}
14
15auth_server_builder!();
16
17impl AuthUrlProvider for AuthorizationServer {
18 type AuthRequest = AuthRequest;
19
20 type TokenRequest = GetTokenRequest;
21
22 type UserInfoRequest = GetUserInfoRequest;
23
24 fn authorize_url(request: Self::AuthRequest) -> Result<String> {
25 let query = serde_urlencoded::to_string(request)?;
26 Ok(format!(
27 "https://api.weibo.com/oauth2/authorize?response_type=code&{query}"
28 ))
29 }
30
31 fn access_token_url(request: Self::TokenRequest) -> Result<String> {
32 let query = serde_urlencoded::to_string(request)?;
33 Ok(format!(
34 "https://api.weibo.com/oauth2/access_token?grant_type=authorization_code&{query}"
35 ))
36 }
37
38 fn user_info_url(request: Self::UserInfoRequest) -> Result<String> {
39 let query = serde_urlencoded::to_string(request)?;
40 Ok(format!(
41 "https://api.weibo.com/2/eps/user/info.json?{query}"
42 ))
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 uid: token.uid,
72 })?;
73 Ok(reqwest::get(user_info_url).await?.json().await?)
74 }
75}
76
77#[async_trait]
78impl GenericAuthAction for AuthorizationServer {
79 async fn authorize<S: Into<String> + Send>(&self, state: S) -> Result<String> {
80 let AuthConfig {
81 client_id,
82 redirect_uri,
83 scope,
84 ..
85 } = &self.config;
86 Self::authorize_url(AuthRequest {
87 client_id: client_id.to_string(),
88 redirect_uri: redirect_uri.to_string(),
89 state: Some(state.into()),
90 scope: scope
91 .clone()
92 .or_else(|| Some(vec!["email".into()]))
93 .expect("scope is empty"),
94 ..Default::default()
95 })
96 }
97
98 async fn login<S: Into<String> + Send>(&self, callback: S) -> Result<AuthUser> {
99 let callback: AuthCallback = serde_urlencoded::from_str(&callback.into())?;
100 let token = self.get_access_token(callback).await?;
101 let user = self.get_user_info(token.clone()).await?;
102 Ok(AuthUser {
103 user_id: user.uid,
104 name: user.nickname,
105 access_token: token.access_token,
106 refresh_token: "".to_string(),
107 expires_in: token.expires_in,
108 extra: user.extra,
109 })
110 }
111}
112
113#[serde_as]
114#[derive(Debug, Default, Serialize, Deserialize)]
115pub struct AuthRequest {
116 client_id: String,
117 redirect_uri: String,
118 #[serde_as(as = "StringWithSeparator::<CommaSeparator, String>")]
119 scope: Vec<String>,
120 state: Option<String>,
121 display: Option<String>,
122 forcelogin: Option<bool>,
123 language: Option<String>,
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127pub struct AuthCallback {
128 code: String,
129 state: Option<String>,
130}
131
132#[derive(Debug, Serialize, Deserialize)]
133pub struct GetTokenRequest {
134 client_id: String,
135 client_secret: String,
136 code: String,
137 redirect_uri: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct TokenResponse {
142 access_token: String,
143 remind_in: i64,
144 expires_in: i64,
145 uid: i64,
146}
147
148#[serde_as]
149#[derive(Debug, Serialize, Deserialize)]
150pub struct GetUserInfoRequest {
151 access_token: String,
152 #[serde_as(as = "DisplayFromStr")]
153 uid: i64,
154}
155
156#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct UserInfoResponse {
159 pub uid: String,
160 pub nickname: String,
161 #[serde(flatten)]
162 pub extra: HashMap<String, Value>,
163}