From 4e9328d50abed04fb1ff24f54fc3f9ab6f8dcb5a Mon Sep 17 00:00:00 2001 From: holmofy Date: Mon, 7 Oct 2024 21:18:21 +0800 Subject: [PATCH] feat: :sparkles: generic login & flattern extra --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/baidu.rs | 30 +++++++++++++--------- src/facebook.rs | 33 ++++++++++++++++-------- src/github.rs | 57 ++++++++++++++---------------------------- src/lib.rs | 17 ++++++++++--- src/qq.rs | 62 +++++++++++++++++++++++++++++++--------------- src/twitter.rs | 36 ++++++++++++++++----------- src/wechat_open.rs | 31 ++++++++++++++++------- src/weibo.rs | 33 ++++++++++++++---------- 10 files changed, 181 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6f3ab0..826b228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,7 +539,7 @@ dependencies = [ [[package]] name = "just-auth" -version = "0.1.1" +version = "0.1.2" dependencies = [ "async-trait", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 78d88b3..8b09769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ description = "just for oauth2 login" edition = "2021" license = "MIT" name = "just-auth" -version = "0.1.1" +version = "0.1.2" [dependencies] async-trait = "0.1" diff --git a/src/baidu.rs b/src/baidu.rs index af7ca0a..d31fe3d 100644 --- a/src/baidu.rs +++ b/src/baidu.rs @@ -1,9 +1,11 @@ //! https://openauth.baidu.com/doc/doc.html use crate::error::Result; -use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider, AuthUser}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::{formats::SpaceSeparator, serde_as, StringWithSeparator}; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -60,6 +62,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.openid, + name: user.username.unwrap_or_default(), + access_token: token.access_token, + refresh_token: token.refresh_token, + expires_in: token.expires_in, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -166,16 +181,7 @@ pub struct GetUserInfoRequest { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { pub openid: String, - pub unionid: String, - pub userid: Option, - pub securemobile: Option, pub username: Option, - pub portrait: Option, - pub userdetail: Option, - pub birthday: Option, - pub marriage: Option, - pub sex: Option, - pub blood: Option, - pub is_bind_mobile: Option, - pub is_realname: Option, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/facebook.rs b/src/facebook.rs index b56c7b9..56b5264 100644 --- a/src/facebook.rs +++ b/src/facebook.rs @@ -1,8 +1,12 @@ //! https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow -use crate::{auth_server_builder, error::Result, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{ + auth_server_builder, error::Result, AuthAction, AuthConfig, AuthUrlProvider, AuthUser, +}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator}; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -31,6 +35,7 @@ impl AuthUrlProvider for AuthorizationServer { )) } + /// https://developers.facebook.com/docs/graph-api/overview#me fn user_info_url(request: Self::UserInfoRequest) -> Result { let query = serde_urlencoded::to_string(request)?; Ok(format!("https://graph.facebook.com/me?{query}")) @@ -59,6 +64,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.id, + name: user.name, + access_token: token.access_token, + refresh_token: token.token_type, + expires_in: token.expires_in, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -129,13 +147,8 @@ pub struct GetUserInfoRequest { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { - pub openid: String, - pub nickname: String, - pub sex: i64, - pub province: String, - pub city: String, - pub country: String, - pub headimgurl: String, - pub privilege: Vec, - pub unionid: String, + pub id: String, + pub name: String, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/github.rs b/src/github.rs index 44196af..d2f6939 100644 --- a/src/github.rs +++ b/src/github.rs @@ -1,10 +1,12 @@ //! https://docs.github.com/zh/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps use crate::error::Result; -use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider, AuthUser}; use async_trait::async_trait; use reqwest::header::ACCEPT; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::{formats::SpaceSeparator, serde_as, StringWithSeparator}; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -59,6 +61,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.id.to_string(), + name: user.name, + access_token: token.access_token, + refresh_token: token.token_type, + expires_in: i64::MAX, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -130,45 +145,11 @@ pub struct TokenResponse { #[derive(Debug, Serialize, Deserialize)] pub struct GetUserInfoRequest {} +/// https://docs.github.com/en/rest/users/users #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { - pub login: String, pub id: i64, - pub node_id: String, - pub avatar_url: String, - pub gravatar_id: String, - pub url: String, - pub html_url: String, - pub followers_url: String, - pub following_url: String, - pub gists_url: String, - pub starred_url: String, - pub subscriptions_url: String, - pub organizations_url: String, - pub repos_url: String, - pub events_url: String, - pub received_events_url: String, - #[serde(rename = "type")] - pub type_field: String, - pub site_admin: bool, pub name: String, - pub company: String, - pub blog: String, - pub location: String, - pub email: String, - pub hireable: bool, - pub bio: String, - pub twitter_username: String, - pub public_repos: i64, - pub public_gists: i64, - pub followers: i64, - pub following: i64, - pub created_at: String, - pub updated_at: String, - pub private_gists: i64, - pub total_private_repos: i64, - pub owned_private_repos: i64, - pub disk_usage: i64, - pub collaborators: i64, - pub two_factor_authentication: bool, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/lib.rs b/src/lib.rs index 740b574..496181c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,11 @@ pub mod weibo; mod utils; +use std::collections::HashMap; + use crate::error::Result; use async_trait::async_trait; +use serde_json::Value; pub struct AuthConfig { client_id: String, @@ -96,12 +99,18 @@ pub trait AuthAction { async fn authorize + Send>(&self, state: S) -> Result; - async fn login(&self, callback: Self::AuthCallback) -> Result { - let token = self.get_access_token(callback).await?; - self.get_user_info(token).await - } + async fn login(&self, callback: Self::AuthCallback) -> Result; async fn get_access_token(&self, callback: Self::AuthCallback) -> Result; async fn get_user_info(&self, token: Self::AuthToken) -> Result; } + +pub struct AuthUser { + pub user_id: String, + pub name: String, + pub access_token: String, + pub refresh_token: String, + pub expires_in: i64, + pub extra: HashMap, +} diff --git a/src/qq.rs b/src/qq.rs index de013f5..ea7a3e4 100644 --- a/src/qq.rs +++ b/src/qq.rs @@ -1,7 +1,11 @@ //! https://wikinew.open.qq.com/index.html#/iwiki/901251864 -use crate::{auth_server_builder, error::Result, utils, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{ + auth_server_builder, error::Result, utils, AuthAction, AuthConfig, AuthUrlProvider, AuthUser, +}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -56,6 +60,27 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let AuthConfig { client_id, .. } = &self.config; + let token = self.get_access_token(callback).await?; + let access_token = token.access_token; + let open_id = self.get_open_id(&access_token).await?; + let user_info_url = Self::user_info_url(GetUserInfoRequest { + openid: open_id.openid.clone(), + access_token: access_token.clone(), + oauth_consumer_key: client_id.to_string(), + })?; + let user: Self::AuthUser = reqwest::get(user_info_url).await?.json().await?; + Ok(AuthUser { + user_id: open_id.openid, + name: user.nickname, + access_token: access_token, + refresh_token: token.refresh_token, + expires_in: token.expires_in.into(), + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -76,6 +101,18 @@ impl AuthAction for AuthorizationServer { async fn get_user_info(&self, token: Self::AuthToken) -> Result { let AuthConfig { client_id, .. } = &self.config; let access_token = token.access_token; + let value = self.get_open_id(&access_token).await?; + let user_info_url = Self::user_info_url(GetUserInfoRequest { + openid: value.openid, + access_token: access_token, + oauth_consumer_key: client_id.to_string(), + })?; + Ok(reqwest::get(user_info_url).await?.json().await?) + } +} + +impl AuthorizationServer { + async fn get_open_id(&self, access_token: &str) -> Result { let jsonp = reqwest::get(format!( "https://graph.qq.com/oauth2.0/me?access_token={access_token}" )) @@ -84,13 +121,7 @@ impl AuthAction for AuthorizationServer { .await?; let json = utils::substr_between(&jsonp, "callback(", ");").expect("jsonp response is valid"); - let value: OpenIdResp = serde_json::from_str(json)?; - let user_info_url = Self::user_info_url(GetUserInfoRequest { - openid: value.openid, - access_token: access_token, - oauth_consumer_key: client_id.to_string(), - })?; - Ok(reqwest::get(user_info_url).await?.json().await?) + Ok(serde_json::from_str(json)?) } } @@ -162,20 +193,11 @@ pub struct GetUserInfoRequest { openid: String, } +/// https://wiki.connect.qq.com/get_user_info #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserInfoResponse { - pub ret: i64, - pub msg: String, pub nickname: String, - pub figureurl: String, - #[serde(rename = "figureurl_1")] - pub figureurl_1: String, - #[serde(rename = "figureurl_2")] - pub figureurl_2: String, - #[serde(rename = "figureurl_qq_1")] - pub figureurl_qq_1: String, - #[serde(rename = "figureurl_qq_2")] - pub figureurl_qq_2: String, - pub gender: String, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/twitter.rs b/src/twitter.rs index 2011467..9d32bbf 100644 --- a/src/twitter.rs +++ b/src/twitter.rs @@ -1,10 +1,13 @@ //! https://developer.x.com/en/docs/authentication/oauth-2-0/authorization-code //! https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token //! https://developer.x.com/en/docs/x-api/users/lookup/api-reference/get-users-me +use std::collections::HashMap; + use crate::error::Result; -use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{auth_server_builder, AuthAction, AuthConfig, AuthUrlProvider, AuthUser}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::{ formats::{CommaSeparator, SpaceSeparator}, serde_as, StringWithSeparator, @@ -65,6 +68,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.id, + name: user.name, + access_token: token.access_token, + refresh_token: token.token_type, + expires_in: i64::MAX, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -160,19 +176,11 @@ pub struct GetUserInfoRequest { user_fields: Vec, } +/// https://developer.x.com/en/docs/x-api/users/lookup/api-reference/get-users-me #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { - pub openid: String, - pub unionid: String, - pub userid: Option, - pub securemobile: Option, - pub username: Option, - pub portrait: Option, - pub userdetail: Option, - pub birthday: Option, - pub marriage: Option, - pub sex: Option, - pub blood: Option, - pub is_bind_mobile: Option, - pub is_realname: Option, + pub id: String, + pub name: String, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/wechat_open.rs b/src/wechat_open.rs index a22359b..6345239 100644 --- a/src/wechat_open.rs +++ b/src/wechat_open.rs @@ -1,9 +1,13 @@ //! 微信开放平台 //! https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html -use crate::{auth_server_builder, error::Result, AuthAction, AuthConfig, AuthUrlProvider}; +use crate::{ + auth_server_builder, error::Result, AuthAction, AuthConfig, AuthUrlProvider, AuthUser, +}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator}; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -69,6 +73,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.unionid, + name: user.nickname, + access_token: token.access_token, + refresh_token: token.refresh_token, + expires_in: token.expires_in, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -148,15 +165,11 @@ pub struct GetUserInfoRequest { lang: Option, } +/// https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { - pub openid: String, - pub nickname: String, - pub sex: i64, - pub province: String, - pub city: String, - pub country: String, - pub headimgurl: String, - pub privilege: Vec, pub unionid: String, + pub nickname: String, + #[serde(flatten)] + pub extra: HashMap, } diff --git a/src/weibo.rs b/src/weibo.rs index 4c07dce..a2a99c7 100644 --- a/src/weibo.rs +++ b/src/weibo.rs @@ -1,10 +1,12 @@ //! https://open.weibo.com/wiki/授权机制说明 -use crate::auth_server_builder; +use crate::{auth_server_builder, AuthUser}; use crate::{error::Result, AuthAction, AuthConfig, AuthUrlProvider}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use serde_json::Value; use serde_with::DisplayFromStr; use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator}; +use std::collections::HashMap; pub struct AuthorizationServer { config: AuthConfig, @@ -66,6 +68,19 @@ impl AuthAction for AuthorizationServer { }) } + async fn login(&self, callback: Self::AuthCallback) -> Result { + let token = self.get_access_token(callback).await?; + let user = self.get_user_info(token.clone()).await?; + Ok(AuthUser { + user_id: user.uid, + name: user.nickname, + access_token: token.access_token, + refresh_token: "".to_string(), + expires_in: token.expires_in, + extra: user.extra, + }) + } + async fn get_access_token(&self, callback: Self::AuthCallback) -> Result { let AuthConfig { client_id, @@ -118,7 +133,7 @@ pub struct GetTokenRequest { redirect_uri: String, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TokenResponse { access_token: String, remind_in: i64, @@ -134,19 +149,11 @@ pub struct GetUserInfoRequest { uid: i64, } +/// https://open.weibo.com/wiki/获取用户基本信息 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInfoResponse { - pub subscribe: i64, pub uid: String, pub nickname: String, - pub sex: i64, - pub language: String, - pub city: String, - pub province: String, - pub country: String, - pub headimgurl: String, - pub headimgurl_large: String, - pub headimgurl_hd: String, - pub follow: String, - pub subscribe_time: i64, + #[serde(flatten)] + pub extra: HashMap, }