diff --git a/Cargo.toml b/Cargo.toml index 2b36b75..dc147bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tokio-cron-scheduler = { version = "0" } uuid = { version = "0", features = ["serde", "v4", "v5"] } -beteran-protobuf-rust = { git = "https://gitlab.loafle.net/bet/beteran-protobuf-rust.git", tag = "v0.1.23-snapshot" } -beteran-common-rust = { git = "https://gitlab.loafle.net/bet/beteran-common-rust.git", tag = "v0.1.3-snapshot" } +beteran-protobuf-rust = { git = "https://gitlab.loafle.net/bet/beteran-protobuf-rust.git", tag = "v0.1.24-snapshot" } +beteran-common-rust = { git = "https://gitlab.loafle.net/bet/beteran-common-rust.git", tag = "v0.1.4-snapshot" } [build-dependencies] diff --git a/src/compositions/member/composition.rs b/src/compositions/member/composition.rs new file mode 100644 index 0000000..0455f42 --- /dev/null +++ b/src/compositions/member/composition.rs @@ -0,0 +1,118 @@ +//! +//! + +use diesel::{result::Error, sql_query, RunQueryDsl}; + +use super::models; + +pub struct Composition {} + +impl std::fmt::Debug for Composition { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Composition of members").finish() + } +} + +impl Default for Composition { + fn default() -> Self { + Self::new() + } +} + +impl Composition { + /// + pub fn new() -> Composition { + Composition {} + } + + /// + pub fn select( + &self, + conn: &diesel::PgConnection, + id: uuid::Uuid, + ) -> Result, Error> { + match sql_query( + " + SELECT + m.id as m_id, + m.site_id as m_site_id, + m.member_class_id as m_member_class_id, + m.member_level_id as m_member_level_id, + m.username as m_username, + m.password as m_password, + m.nickname as m_nickname, + m.mobile_phone_number as m_mobile_phone_number, + m.state as m_state, + m.state_changed_at as m_state_changed_at, + m.referrer_member_id as m_referrer_member_id, + m.referred_count as m_referred_count, + m.last_signined_ip as m_last_signined_ip, + m.last_signined_at as m_last_signined_at, + m.created_at as m_created_at, + m.updated_at as m_updated_at, + m.deleted_at as m_deleted_at, + + s.id as s_id, + s.url as s_url, + s.show as s_show, + s.can_use as s_can_use, + s.created_at as s_created_at, + s.updated_at as s_updated_at, + s.deleted_at as s_deleted_at, + + mc.id as mc_id, + mc.parent_id as mc_parent_id, + mc.name as mc_name, + mc.created_at as mc_created_at, + mc.updated_at as mc_updated_at, + mc.deleted_at as mc_deleted_at, + + ml.id as ml_id, + ml.name as ml_name, + ml.sort_order as ml_sort_order, + ml.created_at as ml_created_at, + ml.updated_at as ml_updated_at, + ml.deleted_at as ml_deleted_at, + + _m.id as _m_id, + _m.member_site_id as _m_member_site_id, + _m.member_class_id as _m_member_class_id, + _m.member_level_id as _m_member_level_id, + _m.username as _m_username, + _m.password as _m_password, + _m.nickname as _m_nickname, + _m.mobile_phone_number as _m_mobile_phone_number, + _m.state as _m_state, + _m.state_changed_at as _m_state_changed_at, + _m.referrer_member_id as _m_referrer_member_id, + _m.referred_count as _m_referred_count, + _m.last_signined_ip as _m_last_signined_ip, + _m.last_signined_at as _m_last_signined_at, + _m.created_at as _m_created_at, + _m.updated_at as _m_updated_at, + _m.deleted_at as _m_deleted_at + + FROM members as m + JOIN sites s + ON s.id = m.site_id + JOIN member_classes mc + ON mc.id = m.member_class_id + JOIN member_levels ml + ON ml.id = m.member_level_id + JOIN members _m + ON _m.id = m.referrer_member_id + WHERE + m.id = $1 + ", + ) + .bind::(id) + .get_result::(conn) + { + Ok(m) => Ok(Some(m)), + Err(e) => match e { + diesel::result::Error::NotFound => Ok(None), + _ => Err(e), + }, + } + } +} diff --git a/src/compositions/member/mod.rs b/src/compositions/member/mod.rs new file mode 100644 index 0000000..e0b4e90 --- /dev/null +++ b/src/compositions/member/mod.rs @@ -0,0 +1,7 @@ +//! +//! + +/// +pub mod composition; +/// +pub mod models; diff --git a/src/compositions/member/models.rs b/src/compositions/member/models.rs new file mode 100644 index 0000000..dd81383 --- /dev/null +++ b/src/compositions/member/models.rs @@ -0,0 +1,113 @@ +//! +//! +use crate::repositories::{ + member::models::Member as _Member, member::schema::MemberState as _MemberState, + member_class::models::MemberClass as _MemberClass, + member_level::models::MemberLevel as _MemberLevel, site::models::Site as _Site, +}; +use diesel::deserialize::QueryableByName; + +/// +#[derive(Eq, Hash, PartialEq, Debug, Clone)] +pub struct Member { + /// + pub id: uuid::Uuid, + /// + pub site: _Site, + /// + pub member_class: _MemberClass, + /// + pub member_level: _MemberLevel, + /// + pub username: String, + /// + pub password: String, + /// + pub nickname: String, + /// + pub mobile_phone_number: Option, + /// + pub state: _MemberState, + /// + pub state_changed_at: Option, + /// + pub referrer_member: Option<_Member>, + /// + pub referred_count: i64, + /// + pub last_signined_ip: Option, + /// + pub last_signined_at: Option, + /// + pub created_at: i64, + /// + pub updated_at: i64, + /// + pub deleted_at: Option, +} + +impl QueryableByName for Member { + fn build>(row: &R) -> diesel::deserialize::Result { + let referrer_member = _Member { + id: row.get("_m_id")?, + site_id: row.get("_m_site_id")?, + member_class_id: row.get("_m_member_class_id")?, + member_level_id: row.get("_m_member_level_id")?, + username: row.get("_m_username")?, + password: row.get("_m_password")?, + nickname: row.get("_m_nickname")?, + mobile_phone_number: row.get("_m_mobile_phone_number")?, + state: row.get("_m_state")?, + state_changed_at: row.get("_m_state_changed_at")?, + referrer_member_id: row.get("_m_referrer_member_id")?, + referred_count: row.get("_m_referred_count")?, + last_signined_ip: row.get("_m_last_signined_ip")?, + last_signined_at: row.get("_m_last_signined_at")?, + created_at: row.get("_m_created_at")?, + updated_at: row.get("_m_updated_at")?, + deleted_at: row.get("_m_deleted_at")?, + }; + + Ok(Member { + id: row.get("m_id")?, + site: _Site { + id: row.get("s_id")?, + url: row.get("s_url")?, + show: row.get("s_show")?, + can_use: row.get("s_can_use")?, + created_at: row.get("s_created_at")?, + updated_at: row.get("s_updated_at")?, + deleted_at: row.get("s_deleted_at")?, + }, + member_class: _MemberClass { + id: row.get("mc_id")?, + parent_id: row.get("mc_parent_id")?, + name: row.get("mc_name")?, + created_at: row.get("mc_created_at")?, + updated_at: row.get("mc_updated_at")?, + deleted_at: row.get("mc_deleted_at")?, + }, + member_level: _MemberLevel { + id: row.get("ml_id")?, + name: row.get("ml_name")?, + sort_order: row.get("ml_sort_order")?, + created_at: row.get("ml_created_at")?, + updated_at: row.get("ml_updated_at")?, + deleted_at: row.get("ml_deleted_at")?, + }, + username: row.get("m_username")?, + password: row.get("m_password")?, + nickname: row.get("m_nickname")?, + mobile_phone_number: row.get("m_mobile_phone_number")?, + state: row.get("m_state")?, + state_changed_at: row.get("m_state_changed_at")?, + referrer_member: None, + referred_count: row.get("m_referred_count")?, + last_signined_ip: row.get("m_last_signined_ip")?, + last_signined_at: row.get("m_last_signined_at")?, + created_at: row.get("m_created_at")?, + updated_at: row.get("m_updated_at")?, + deleted_at: row.get("m_deleted_at")?, + }) + } +} diff --git a/src/compositions/mod.rs b/src/compositions/mod.rs index e69de29..55c6a43 100644 --- a/src/compositions/mod.rs +++ b/src/compositions/mod.rs @@ -0,0 +1,2 @@ +pub mod member; +pub mod site; diff --git a/src/compositions/site/composition.rs b/src/compositions/site/composition.rs new file mode 100644 index 0000000..ed2a59f --- /dev/null +++ b/src/compositions/site/composition.rs @@ -0,0 +1,62 @@ +//! +//! + +use super::models; +use crate::repositories; +use diesel::{result::Error, sql_query, RunQueryDsl}; + +pub struct Composition { + site_repository: repositories::site::repository::Repository, +} + +impl std::fmt::Debug for Composition { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Composition of members").finish() + } +} + +impl Default for Composition { + fn default() -> Self { + Self::new() + } +} + +impl Composition { + /// + pub fn new() -> Composition { + Composition { + site_repository: repositories::site::repository::Repository::new(), + } + } + + /// + pub fn select_by_url( + &self, + conn: &diesel::PgConnection, + url: Option, + site_id: uuid::Uuid, + ) -> Result, Error> { + let site_url = match url { + Some(site_url) => site_url, + None => { + return Ok(None); + } + }; + let ms = match self.site_repository.select(conn, site_id)? { + Some(s) => s, + None => { + return Ok(None); + } + }; + + if ms.url.eq("*") { + return Ok(Some(ms)); + } + + if !ms.url.eq(&site_url) { + return Ok(None); + } + + Ok(Some(ms)) + } +} diff --git a/src/compositions/site/mod.rs b/src/compositions/site/mod.rs new file mode 100644 index 0000000..e0b4e90 --- /dev/null +++ b/src/compositions/site/mod.rs @@ -0,0 +1,7 @@ +//! +//! + +/// +pub mod composition; +/// +pub mod models; diff --git a/src/compositions/site/models.rs b/src/compositions/site/models.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/compositions/site/models.rs @@ -0,0 +1 @@ + diff --git a/src/repositories/site/repository.rs b/src/repositories/site/repository.rs index ba25644..1f0347f 100644 --- a/src/repositories/site/repository.rs +++ b/src/repositories/site/repository.rs @@ -27,10 +27,18 @@ impl Repository { } /// - pub fn select(&self, conn: &diesel::PgConnection, id: uuid::Uuid) -> Result { - sites::table - .find(id as uuid::Uuid) - .first::(conn) + pub fn select( + &self, + conn: &diesel::PgConnection, + id: uuid::Uuid, + ) -> Result, Error> { + match sites::table.find(id).first::(conn) { + Ok(m) => Ok(Some(m)), + Err(e) => match e { + diesel::result::Error::NotFound => Ok(None), + _ => Err(e), + }, + } } /// diff --git a/src/services/identity/service.rs b/src/services/identity/service.rs index 3b849f9..9209b24 100644 --- a/src/services/identity/service.rs +++ b/src/services/identity/service.rs @@ -1,6 +1,7 @@ //! //! +use super::super::super::compositions; use super::super::super::repositories; use beteran_common_rust as bcr; use beteran_protobuf_rust as bpr; @@ -16,8 +17,9 @@ pub struct Service<'a> { queue_broker: String, pool: Pool>, member_repository: repositories::member::repository::Repository, - member_site_repository: repositories::site::repository::Repository, member_session_repository: repositories::member_session::repository::Repository, + site_repository: repositories::site::repository::Repository, + site_composition: compositions::site::composition::Composition, argon2_config: argon2::Config<'a>, captcha_salt: String, password_salt: String, @@ -44,8 +46,9 @@ impl Service<'_> { queue_broker, pool, member_repository: repositories::member::repository::Repository::new(), - member_site_repository: repositories::site::repository::Repository::new(), member_session_repository: repositories::member_session::repository::Repository::new(), + site_repository: repositories::site::repository::Repository::new(), + site_composition: compositions::site::composition::Composition::new(), argon2_config: argon2::Config::default(), captcha_salt, password_salt, @@ -62,6 +65,38 @@ impl Service<'_> { .map(|_| ()) } + fn check_site( + &self, + conn: &diesel::PgConnection, + url: Option, + site_id: uuid::Uuid, + ) -> Result { + match self + .site_composition + .select_by_url(conn, url, site_id) + .map_err(|e| { + bcr::error::rpc::Error::Server(bcr::error::rpc::Server { + code: bpr::protobuf::rpc::Error::SERVER_00, + message: format!("server {}", e), + data: None, + }) + })? { + Some(s) => Ok(s), + None => Err(bcr::error::rpc::Error::InvalidParams( + bcr::error::rpc::InvalidParams { + message: "invalid site_url information".to_string(), + detail: bcr::error::rpc::InvalidParamsDetail { + location: "request".to_string(), + param: "client.site_url".to_string(), + value: "".to_string(), + error_type: bcr::error::rpc::InvalidParamsType::None, + message: "".to_string(), + }, + }, + )), + } + } + async fn check_username_for_duplication(&self) -> Result<(), Box> { let s = self .connection_broker @@ -274,7 +309,7 @@ impl Service<'_> { }) })?; - let client = match req.client { + let _client = match req.client { Some(c) => c, None => { return Err(bcr::error::rpc::Error::InvalidParams( @@ -292,59 +327,6 @@ impl Service<'_> { } }; - // let site_url = match client.site_url { - // Some(site_url) => site_url, - // None => { - // return Err(bcr::error::rpc::Error::InvalidParams( - // bcr::error::rpc::InvalidParams { - // message: "invalid site_url information".to_string(), - // detail: bcr::error::rpc::InvalidParamsDetail { - // location: "request".to_string(), - // param: "client.site_url".to_string(), - // value: "".to_string(), - // error_type: bcr::error::rpc::InvalidParamsType::Required, - // message: "".to_string(), - // }, - // }, - // )); - // } - // }; - - let conn = self.pool.get().map_err(|e| { - bcr::error::rpc::Error::Server(bcr::error::rpc::Server { - code: bpr::protobuf::rpc::Error::SERVER_00, - message: format!("server {}", e), - data: None, - }) - })?; - - // match self - // .member_site_repository - // .select_by_url(&conn, &site_url) - // .map_err(|e| { - // bcr::error::rpc::Error::Server(bcr::error::rpc::Server { - // code: bpr::protobuf::rpc::Error::SERVER_00, - // message: format!("server {}", e), - // data: None, - // }) - // })? { - // Some(ms) => ms, - // None => { - // return Err(bcr::error::rpc::Error::InvalidParams( - // bcr::error::rpc::InvalidParams { - // message: "invalid site_url information".to_string(), - // detail: bcr::error::rpc::InvalidParamsDetail { - // location: "request".to_string(), - // param: "client.site_url".to_string(), - // value: "".to_string(), - // error_type: bcr::error::rpc::InvalidParamsType::None, - // message: "".to_string(), - // }, - // }, - // )); - // } - // }; - let mut c = captcha::Captcha::new(); let c = c @@ -454,33 +436,6 @@ impl Service<'_> { }) })?; - let ms = match self - .member_site_repository - .select_by_url(&conn, client.site_url()) - .map_err(|e| { - bcr::error::rpc::Error::Server(bcr::error::rpc::Server { - code: bpr::protobuf::rpc::Error::SERVER_00, - message: format!("server {}", e), - data: None, - }) - })? { - Some(ms) => ms, - None => { - return Err(bcr::error::rpc::Error::InvalidParams( - bcr::error::rpc::InvalidParams { - message: "invalid site_url information".to_string(), - detail: bcr::error::rpc::InvalidParamsDetail { - location: "request".to_string(), - param: "client.site_url".to_string(), - value: "".to_string(), - error_type: bcr::error::rpc::InvalidParamsType::None, - message: "".to_string(), - }, - }, - )); - } - }; - let security_code_hash = req.security_code_hash; let security_code = req.security_code; let username = req.username; @@ -544,21 +499,6 @@ impl Service<'_> { } }; - if m.site_id != ms.id { - return Err(bcr::error::rpc::Error::InvalidParams( - bcr::error::rpc::InvalidParams { - message: "invalid site_url".to_string(), - detail: bcr::error::rpc::InvalidParamsDetail { - location: "header".to_string(), - param: "client.site_url".to_string(), - value: client.site_url().to_string(), - error_type: bcr::error::rpc::InvalidParamsType::EqualsTo, - message: "".to_string(), - }, - }, - )); - } - if !(argon2::verify_encoded(&m.password, password.as_bytes()).map_err(|e| { bcr::error::rpc::Error::InvalidParams(bcr::error::rpc::InvalidParams { message: "invalid password".to_string(), @@ -585,6 +525,8 @@ impl Service<'_> { )); } + let _s = self.check_site(&conn, client.site_url.clone(), m.site_id)?; + let expires_at = (chrono::Utc::now() + chrono::Duration::minutes(30)).timestamp(); let session = self .member_session_repository