//! //! use super::super::super::compositions; use super::super::super::repositories; use beteran_common_rust as bcr; use beteran_protobuf_rust as bpr; use diesel::{ r2d2::{ConnectionManager, Pool}, PgConnection, }; use prost::Message; /// pub struct Service<'a> { connection_broker: nats::asynk::Connection, queue_broker: String, pool: Pool>, member_repository: repositories::member::repository::Repository, member_session_repository: repositories::member_session::repository::Repository, site_repository: repositories::site::repository::Repository, site_composition: compositions::site::composition::Composition, identity_composition: compositions::identity::composition::Composition, argon2_config: argon2::Config<'a>, captcha_salt: String, password_salt: String, } impl std::fmt::Debug for Service<'_> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("Service of service.member.service.identity") .finish() } } impl Service<'_> { /// pub fn new( connection_broker: nats::asynk::Connection, queue_broker: String, pool: Pool>, captcha_salt: String, password_salt: String, jwt_secret: String, ) -> Service<'static> { Service { connection_broker, queue_broker, pool, member_repository: repositories::member::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(), identity_composition: compositions::identity::composition::Composition::new(jwt_secret), argon2_config: argon2::Config::default(), captcha_salt, password_salt, } } pub async fn subscribe(&self) -> std::result::Result<(), std::boxed::Box> { futures::try_join!( self.check_username_for_duplication(), self.check_nickname_for_duplication(), self.captcha(), self.signin(), ) .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 .queue_subscribe( bpr::ss::member::identity::SUBJECT_CHECK_USERNAME_FOR_DUPLICATION, self.queue_broker.as_str(), ) .await?; while let Some(message) = s.next().await { if let Err(e) = async { let req = bpr::ss::member::identity::CheckUsernameForDuplicationRequest::decode( message.data.as_slice(), ) .map_err(|e| { bcr::error::rpc::Error::InvalidRequest(bcr::error::rpc::InvalidRequest { message: format!("invalid request: {}", e), }) })?; let client = match req.client { Some(c) => c, None => { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid client information".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "client".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, }) })?; let m = self .member_repository .select_by_username(&conn, &req.username) .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, }) })?; message .respond( bpr::ss::member::identity::CheckUsernameForDuplicationResponse { error: None, result: Some( bpr::ss::member::identity::check_username_for_duplication_response::Result { duplicated: m.is_some(), }, ), } .encode_to_vec(), ) .await .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, }) })?; Ok::<(), bcr::error::rpc::Error>(()) } .await { message .respond( bpr::ss::member::identity::CheckUsernameForDuplicationResponse { error: Some(bpr::protobuf::rpc::Error::from(e)), result: None, } .encode_to_vec(), ) .await?; } } Ok(()) } async fn check_nickname_for_duplication(&self) -> Result<(), Box> { let s = self .connection_broker .queue_subscribe( bpr::ss::member::identity::SUBJECT_CHECK_NICKNAME_FOR_DUPLICATION, self.queue_broker.as_str(), ) .await?; while let Some(message) = s.next().await { if let Err(e) = async { let req = bpr::ss::member::identity::CheckNicknameForDuplicationRequest::decode( message.data.as_slice(), ) .map_err(|e| { bcr::error::rpc::Error::InvalidRequest(bcr::error::rpc::InvalidRequest { message: format!("invalid request: {}", e), }) })?; let client = match req.client { Some(c) => c, None => { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid client information".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "client".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, }) })?; let m = self .member_repository .select_by_nickname(&conn, &req.nickname) .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, }) })?; message .respond( bpr::ss::member::identity::CheckNicknameForDuplicationResponse { error: None, result: Some( bpr::ss::member::identity::check_nickname_for_duplication_response::Result { duplicated: m.is_some(), }, ), } .encode_to_vec(), ) .await .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, }) })?; Ok::<(), bcr::error::rpc::Error>(()) } .await { message .respond( bpr::ss::member::identity::CheckNicknameForDuplicationResponse { error: Some(bpr::protobuf::rpc::Error::from(e)), result: None, } .encode_to_vec(), ) .await?; } } Ok(()) } async fn captcha(&self) -> Result<(), Box> { let s = self .connection_broker .queue_subscribe( bpr::ss::member::identity::SUBJECT_CAPTCHA, self.queue_broker.as_str(), ) .await?; while let Some(message) = s.next().await { if let Err(e) = async { let req = bpr::ss::member::identity::CaptchaRequest::decode(message.data.as_slice()) .map_err(|e| { bcr::error::rpc::Error::InvalidRequest(bcr::error::rpc::InvalidRequest { message: format!("invalid request: {}", e), }) })?; let _client = match req.client { Some(c) => c, None => { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid client information".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "client".to_string(), value: "".to_string(), error_type: bcr::error::rpc::InvalidParamsType::Required, message: "".to_string(), }, }, )); } }; let mut c = captcha::Captcha::new(); let c = c .add_chars(5) .apply_filter(captcha::filters::Noise::new(0.1)) .view(220, 60); let image_as_base64 = match c.as_base64() { Some(s) => s, None => { return Err(bcr::error::rpc::Error::Server(bcr::error::rpc::Server { code: bpr::protobuf::rpc::Error::SERVER_00, message: "captcha image encoding error[base64]".to_string(), data: None, })); } }; let security_code = c.chars_as_string(); let security_code_hash = argon2::hash_encoded( security_code.as_bytes(), self.captcha_salt.as_bytes(), &self.argon2_config, ) .unwrap(); message .respond( bpr::ss::member::identity::CaptchaResponse { error: None, result: Some(bpr::ss::member::identity::captcha_response::Result { security_code_hash, image: image_as_base64, }), } .encode_to_vec(), ) .await .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, }) })?; Ok::<(), bcr::error::rpc::Error>(()) } .await { message .respond( bpr::ss::member::identity::CaptchaResponse { error: Some(bpr::protobuf::rpc::Error::from(e)), result: None, } .encode_to_vec(), ) .await?; } } Ok(()) } async fn signin(&self) -> Result<(), Box> { let s = self .connection_broker .queue_subscribe( bpr::ss::member::identity::SUBJECT_SIGNIN, self.queue_broker.as_str(), ) .await?; while let Some(message) = s.next().await { if let Err(e) = async { let req = bpr::ss::member::identity::SigninRequest::decode(message.data.as_slice()) .map_err(|e| { bcr::error::rpc::Error::InvalidRequest(bcr::error::rpc::InvalidRequest { message: format!("invalid request: {}", e), }) })?; let client = match req.client { Some(c) => c, None => { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid client information".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "client".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, }) })?; let security_code_hash = req.security_code_hash; let security_code = req.security_code; let username = req.username; let password = req.password; let security_code_matches = argon2::verify_encoded(security_code_hash.as_str(), security_code.as_bytes()).map_err( |e| { bcr::error::rpc::Error::InvalidParams(bcr::error::rpc::InvalidParams { message: "invalid security_code".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "security_code".to_string(), value: security_code.clone(), error_type: bcr::error::rpc::InvalidParamsType::None, message: e.to_string(), }, }) }, )?; if !security_code_matches { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid security_code".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "token".to_string(), value: security_code.clone(), error_type: bcr::error::rpc::InvalidParamsType::EqualsTo, message: "security_code must equal to captcha".to_string(), }, }, )); } let m = match self .member_repository .select_by_username(&conn, &username) .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(m) => m, None => { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid username".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "username".to_string(), value: username, error_type: bcr::error::rpc::InvalidParamsType::None, 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(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "password".to_string(), value: password.clone(), error_type: bcr::error::rpc::InvalidParamsType::None, message: e.to_string(), }, }) })?) { return Err(bcr::error::rpc::Error::InvalidParams( bcr::error::rpc::InvalidParams { message: "invalid password".to_string(), detail: bcr::error::rpc::InvalidParamsDetail { location: "request".to_string(), param: "password".to_string(), value: password.clone(), error_type: bcr::error::rpc::InvalidParamsType::EqualsTo, message: "".to_string(), }, }, )); } 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 .insert( &conn, &repositories::member_session::models::NewMemberSession { member_id: m.id, ip: client.client_ip.clone(), expires_at, }, ) .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, }) })?; let access_token = self .identity_composition .get_token("Beteran".to_string(), session.id.to_string()) .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, }) })?; message .respond( bpr::ss::member::identity::SigninResponse { error: None, result: Some(bpr::ss::member::identity::signin_response::Result { access_token }), } .encode_to_vec(), ) .await .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, }) })?; Ok::<(), bcr::error::rpc::Error>(()) } .await { message .respond( bpr::ss::member::identity::SigninResponse { error: Some(bpr::protobuf::rpc::Error::from(e)), result: None, } .encode_to_vec(), ) .await?; } } Ok(()) } }