添加Google账户授权
This commit is contained in:
parent
53695b468e
commit
d00d07d46e
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -479,6 +479,9 @@ dependencies = [
|
||||
"library",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"validator",
|
||||
]
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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" }
|
@ -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>,
|
||||
}
|
||||
|
@ -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#"
|
||||
|
@ -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)
|
||||
|
@ -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?;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
))));
|
||||
}
|
Loading…
Reference in New Issue
Block a user