添加Google账户授权

This commit is contained in:
liyunjia 2024-05-26 09:47:48 +08:00
parent 53695b468e
commit d00d07d46e
10 changed files with 165 additions and 73 deletions

3
Cargo.lock generated
View File

@ -479,6 +479,9 @@ dependencies = [
"library",
"serde",
"sqlx",
"tracing",
"tracing-appender",
"tracing-subscriber",
"validator",
]

View File

@ -1,17 +1,17 @@
use axum::Json;
use axum_extra::extract::WithRejection;
use domain::dto::account::AccountRegister;
use domain::dto::account::AuthenticateGooleAccountReq;
use library::resp::{rejection::IRejection, response::{ResErr, ResOK, ResResult}};
use validator::Validate;
pub async fn create(
WithRejection(Json(req), _): IRejection<Json<AccountRegister>>
pub async fn authenticate_google(
WithRejection(Json(req), _): IRejection<Json<AuthenticateGooleAccountReq>>
) -> ResResult<ResOK<()>> {
if let Err(err) = req.validate() {
return Err(ResErr::ErrParams(Some(err.to_string())));
}
service::account::register(req).await?;
service::account::authticate_google(req).await?;
Ok(ResOK(None))
}

View File

@ -9,7 +9,7 @@ pub(crate) fn init() -> Router {
let open = Router::new().route("/", get(|| async { "hello" }));
let auth = Router::new()
.route("/game_accounts", post(controller::account::create))
.route("/account/google", post(controller::account::authenticate_google))
.route("/feedback", post(controller::feedback::add_feedback).get(controller::feedback::get_feedback_list_by_page));
Router::new()

View File

@ -10,5 +10,8 @@ serde = { workspace = true, features = ["derive"] }
sqlx = { workspace = true, features = ["postgres", "uuid", "macros", "sqlx-macros", "chrono", "time", "sqlx-postgres"] }
validator = { workspace = true, features = ["derive"] }
chrono = { workspace = true, features = ["serde"]}
tracing = { workspace = true }
tracing-appender = { workspace = true }
tracing-subscriber = { workspace = true, features = ["json"] }
library = { path = "../library" }

View File

@ -2,17 +2,7 @@ use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Debug, Validate, Deserialize, Serialize)]
pub struct AccountRegister {
#[validate(required(message = "用户名称不能为空"), length(min = 1, message = "用户名称不能为空"))]
pub username: Option<String>,
#[validate(required(message = "电子邮箱不能为空"), length(min = 1, message = "电子邮箱不能为空"))]
pub email: Option<String>,
#[validate(required(message = "平台ID不能为空"), length(min = 1, message = "平台ID不能为空"))]
pub platform_id: Option<String>,
#[validate(required(message = "用户类型不能为空"), length(min = 1, message = "用户类型不能为空"))]
pub user_type: Option<String>,
#[validate(required(message = "用户所属区域不能为空"), length(min = 1, message = "用户所属区域不能为空"))]
pub country_code: Option<String>,
pub struct AuthenticateGooleAccountReq {
#[validate(required(message = "用户ID Token不能为空"), length(min = 1, message = "用户ID Token不能为空"))]
pub id_token: Option<String>,
}

View File

@ -1,7 +1,6 @@
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use sqlx::{Error, PgPool};
use sqlx::types::{chrono, JsonValue};
use sqlx::types::uuid::Bytes;
#[derive(Debug, Clone, Default)]
pub struct Account {
@ -32,35 +31,91 @@ pub struct Account {
}
impl Account {
// pub async fn find_by_platform_id(platform_id: &str, db_pool: &PgPool) -> Result<Account, sqlx::Error> {
// sqlx::query_as!(
// Account,
// r#"select * from account where platform_id = $1"#,
// platform_id
// )
// .fetch_one(db_pool)
// .await
// }
// pub async fn add_account_info(player_info: &mut Account, db_pool: &PgPool) -> Result<Account, sqlx::Error> {
// sqlx::query_as!(
// Account,
// r#"
// insert into account
// (username, email, platform_id, user_type, country_code, created_at, updated_at)
// values
// ($1, $2, $3, $4, $5, $6, $7) returning *"#,
// player_info.username,
// player_info.email,
// player_info.platform_id,
// player_info.user_type,
// player_info.country_code,
// player_info.created_at,
// player_info.updated_at
// ).fetch_one(db_pool).await
// }
pub async fn find_by_apple_id(apple_id: &str, db_pool: &PgPool) -> Result<Option<Account>, Error> {
match sqlx::query_as!(
Account,
r#"select * from account where apple_id = $1"#,
apple_id
)
.fetch_one(db_pool)
.await {
Ok(account) => {
return Ok(Some(account));
}
Err(Error::RowNotFound) => {
return Err(Error::RowNotFound);
}
Err(e) => {
tracing::error!("find_by_apple_id error: {:?}", e);
return Err(e);
}
}
}
pub async fn add_google_account(&self, db_pool: &PgPool) -> Result<Account, sqlx::Error> {
pub async fn find_by_custom_id(custom_id: &str, db_pool: &PgPool) -> Result<Option<Account>, Error> {
match sqlx::query_as!(
Account,
r#"select * from account where custom_id = $1"#,
custom_id
)
.fetch_one(db_pool)
.await {
Ok(account) => {
return Ok(Some(account));
}
Err(Error::RowNotFound) => {
return Err(Error::RowNotFound);
}
Err(e) => {
tracing::error!("find_by_custom_id error: {:?}", e);
return Err(e);
}
}
}
pub async fn find_by_facebook_id(facebook_id: &str, db_pool: &PgPool) -> Result<Option<Account>, Error> {
match sqlx::query_as!(
Account,
r#"select * from account where facebook_id = $1"#,
facebook_id
)
.fetch_one(db_pool)
.await {
Ok(account) => {
return Ok(Some(account));
}
Err(Error::RowNotFound) => {
return Err(Error::RowNotFound);
}
Err(e) => {
tracing::error!("find_by_facebook_id error: {:?}", e);
return Err(e);
}
}
}
pub async fn find_by_google_id(google_id: &str, db_pool: &PgPool) -> Result<Option<Account>, Error> {
match sqlx::query_as!(
Account,
r#"select * from account where google_id = $1"#,
google_id
)
.fetch_one(db_pool)
.await {
Ok(account) => {
return Ok(Some(account));
}
Err(Error::RowNotFound) => {
return Ok(None);
}
Err(e) => {
tracing::error!("find_by_google_id error: {:?}", e);
return Err(e);
}
}
}
pub async fn save_google_account(&self, db_pool: &PgPool) -> Result<Account, Error> {
sqlx::query_as!(
Account,
r#"

View File

@ -35,6 +35,8 @@ pub enum ResErr {
ErrData(Option<String>),
ErrService(Option<String>),
ErrSocial(Option<String>),
ErrAccountDisable(Option<String>),
ErrSqlx(Option<String>)
}
use ResErr::*;
@ -106,6 +108,20 @@ impl IntoResponse for ResErr {
None => Status::<()>::Err(code, String::from("社交业务异常")),
}
}
ErrAccountDisable(msg) => {
let code = 90000;
match msg {
Some(v) => Status::<()>::Err(code, v),
None => Status::<()>::Err(code, String::from("账户已禁用")),
}
}
ErrSqlx(msg) => {
let code = 90000;
match msg {
Some(v) => Status::<()>::Err(code, v),
None => Status::<()>::Err(code, String::from("账户已禁用")),
}
}
};
Json(status.to_reply()).into_response()
@ -124,7 +140,9 @@ impl Display for ResErr {
| ErrSystem(message)
| ErrData(message)
| ErrService(message)
| ErrSocial(message) => write!(f, "{:?}", message),
| ErrSocial(message)
| ErrAccountDisable(message)
| ErrSqlx(message) => write!(f, "{:?}", message),
}
}
}
@ -147,6 +165,12 @@ impl From<&str> for ResErr {
}
}
impl From<sqlx::Error> for ResErr {
fn from(value: sqlx::Error) -> Self {
ErrSqlx(Some(value.to_string()))
}
}
impl Default for ResErr {
fn default() -> Self {
ErrSystem(None)

View File

@ -112,7 +112,7 @@ async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, St
}
// 验证ID Token并考虑kid匹配
async fn verify_id_token(id_token: &str) -> SocialResult<GoogleJwtProfile> {
pub async fn verify_id_token(id_token: &str) -> SocialResult<GoogleJwtProfile> {
// 获取并解析公钥
let public_keys = fetch_and_parse_google_public_keys().await?;

View File

@ -5,21 +5,21 @@ use crate::config;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
sub: i64, // 用户ID
sub: String, // 用户ID
exp: i64, // Token过期时间戳
}
pub fn generate_token(sub: i64) -> String {
pub fn generate_token(sub: &str) -> String {
let claim = Claims {
sub,
sub: sub.to_string(),
exp: (Utc::now() + TimeDelta::try_seconds(config!().jwt.expires).unwrap()).timestamp(),
};
generate(claim)
}
pub fn generate_refresh_token(sub: i64) -> String {
pub fn generate_refresh_token(sub: &str) -> String {
let claim = Claims {
sub,
sub: sub.to_string(),
exp: (Utc::now() + TimeDelta::try_seconds(config!().jwt.refresh_expires).unwrap()).timestamp(),
};
generate(claim)

View File

@ -1,26 +1,43 @@
use domain::dto::account::AccountRegister;
use chrono::Utc;
use domain::dto::account::AuthenticateGooleAccountReq;
use domain::entities::account::Account;
use library::db;
use library::resp::response::ResErr::{ErrPerm, ErrSystem};
use library::resp::response::{ResOK, ResResult};
use library::{db, token};
use library::resp::response::ResErr::ErrPerm;
use library::resp::response::{ResErr, ResOK, ResResult};
use library::social::google;
pub async fn register(req: AccountRegister) -> ResResult<ResOK<()>> {
match Account::add_google_account(
&mut Account {
username: req.username.unwrap(),
email: req.email,
..Default::default()
}, db!()
).await {
Ok(player_info) => {
tracing::info!("添加账户成功: {:?}", player_info)
pub async fn authticate_google(req: AuthenticateGooleAccountReq) -> ResResult<ResOK<(String, String)>> {
let verify_result = google::verify_id_token(&req.id_token.unwrap()).await.map_err(|err| {
tracing::error!(error = ?err, "校验Google Token失败");
ErrPerm(None)
})?;
let account = Account::find_by_google_id(&verify_result.aud, db!()).await?;
let account = match account {
None => {
tracing::info!("账户不存在, {:?}", verify_result);
Account::save_google_account(
&Account {
username: verify_result.name,
google_id: Some(verify_result.aud),
email: Some(verify_result.email),
display_name: Some(verify_result.given_name),
avatar_url: Some(verify_result.picture),
..Default::default()
}, db!()).await?
}
Err(err) => {
tracing::error!(error = ?err, "添加账号失败");
return Err(ErrSystem(None));
Some(account) => {
tracing::info!("账户已存在, {:?}", account);
if account.disable_time > Utc::now() {
tracing::error!("账户已禁用");
return Err(ResErr::system("账户已禁用"));
}
account
}
}
};
Ok(ResOK(None))
}
return Ok(ResOK(Some((
token::generate_token(&account.id),
token::generate_refresh_token(&account.id),
))));
}