just_auth/
weibo.rs

1//! https://open.weibo.com/wiki/授权机制说明
2use 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/// https://open.weibo.com/wiki/获取用户基本信息
157#[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}