captcha repository is removed

This commit is contained in:
병준 박 2022-08-06 04:20:17 +00:00
parent fb1b451959
commit 81ac84043b
11 changed files with 102 additions and 333 deletions

4
.vscode/launch.json vendored
View File

@ -23,6 +23,8 @@
"URL_DATABASE": "postgresql://beteran:qwer5795QWER@192.168.50.200:25432/beteran", "URL_DATABASE": "postgresql://beteran:qwer5795QWER@192.168.50.200:25432/beteran",
"URL_BROKER": "nats://192.168.50.200:4222", "URL_BROKER": "nats://192.168.50.200:4222",
"QUEUE_BROKER": "bet.beteran", "QUEUE_BROKER": "bet.beteran",
"CAPTCHA_SALT": "qwer5795QWER",
"PASSWORD_SALT": "qwer5795QWER",
}, },
"args": [], "args": [],
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
@ -47,6 +49,8 @@
"URL_DATABASE": "postgresql://beteran:qwer5795QWER@192.168.50.200:25432/beteran", "URL_DATABASE": "postgresql://beteran:qwer5795QWER@192.168.50.200:25432/beteran",
"URL_BROKER": "nats://192.168.50.200:4222", "URL_BROKER": "nats://192.168.50.200:4222",
"QUEUE_BROKER": "bet.beteran", "QUEUE_BROKER": "bet.beteran",
"CAPTCHA_SALT": "qwer5795QWER",
"PASSWORD_SALT": "qwer5795QWER",
}, },
"args": [], "args": [],
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"

View File

@ -28,7 +28,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio-cron-scheduler = { version = "0" } tokio-cron-scheduler = { version = "0" }
uuid = { version = "0", features = ["serde", "v4", "v5"] } uuid = { version = "0", features = ["serde", "v4", "v5"] }
beteran-protobuf-rust = { git = "https://gitlab.loafle.net/bet/beteran-protobuf-rust.git", tag = "v0.1.22-snapshot" } 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.2-snapshot" } beteran-common-rust = { git = "https://gitlab.loafle.net/bet/beteran-common-rust.git", tag = "v0.1.3-snapshot" }
[build-dependencies] [build-dependencies]

View File

@ -1,3 +0,0 @@
DROP UNIQUE INDEX uidx_captchas_token;
DROP TABLE captchas;

View File

@ -1,12 +0,0 @@
CREATE TABLE IF NOT EXISTS captchas (
id UUID DEFAULT uuid_generate_v4(),
token TEXT NOT NULL,
security_code TEXT NOT NULL,
expires_at BIGINT NOT NULL DEFAULT (extract(epoch from now()) * 1000),
created_at BIGINT NOT NULL DEFAULT (extract(epoch from now()) * 1000),
PRIMARY KEY (id)
);
-- index
CREATE UNIQUE INDEX uidx_captchas_token ON captchas (token);

View File

@ -34,6 +34,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
None => "".to_string(), None => "".to_string(),
}; };
let captcha_salt = match env::var_os("CAPTCHA_SALT") {
Some(v) => v.into_string().unwrap(),
None => "".to_string(),
};
let password_salt = match env::var_os("PASSWORD_SALT") {
Some(v) => v.into_string().unwrap(),
None => "".to_string(),
};
let manager = ConnectionManager::<PgConnection>::new(url_db); let manager = ConnectionManager::<PgConnection>::new(url_db);
let pool = Pool::builder() let pool = Pool::builder()
.max_size(4) .max_size(4)
@ -50,6 +59,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
connection_server_broker.clone(), connection_server_broker.clone(),
queue_server_broker.clone(), queue_server_broker.clone(),
pool.clone(), pool.clone(),
captcha_salt.clone(),
password_salt.clone(),
); );
println!("Server service [beteran-server-service] is started"); println!("Server service [beteran-server-service] is started");

View File

@ -1,9 +0,0 @@
//!
//!
///
pub mod models;
///
pub mod repository;
///
pub mod schema;

View File

@ -1,37 +0,0 @@
use super::schema::captchas;
use beteran_common_rust as bcr;
///
#[derive(Eq, Hash, Identifiable, Queryable, PartialEq, Debug, Clone)]
#[table_name = "captchas"]
pub struct Captcha {
///
pub id: uuid::Uuid,
///
pub security_code: String,
///
pub expires_at: i64,
///
pub created_at: i64,
}
///
#[derive(Insertable, Debug, Clone)]
#[table_name = "captchas"]
pub struct NewCaptcha {
///
pub security_code: String,
///
pub expires_at: i64,
}
///
#[derive(Debug, Clone)]
pub struct FindAll {
///
pub expires_at: Option<i64>,
///
pub pagination: Option<bcr::models::pagination::Pagination>,
///
pub sorts: Option<Vec<bcr::models::pagination::Sort>>,
}

View File

@ -1,135 +0,0 @@
//!
//!
use super::{models, schema::captchas};
use beteran_common_rust as bcr;
use diesel::prelude::*;
use diesel::result::Error;
///
pub struct Repository {}
impl std::fmt::Debug for Repository {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Repository of captchas").finish()
}
}
impl Default for Repository {
fn default() -> Self {
Self::new()
}
}
impl Repository {
///
pub fn new() -> Repository {
Repository {}
}
///
pub fn insert(
&self,
conn: &diesel::PgConnection,
new_captcha: &models::NewCaptcha,
) -> Result<models::Captcha, Error> {
let captcha = diesel::insert_into(captchas::table)
.values(new_captcha)
.get_result::<models::Captcha>(conn)?;
Ok(captcha)
}
///
pub fn select(
&self,
conn: &diesel::PgConnection,
id: uuid::Uuid,
) -> Result<Option<models::Captcha>, Error> {
match captchas::table.find(id).first::<models::Captcha>(conn) {
Ok(m) => Ok(Some(m)),
Err(e) => match e {
diesel::result::Error::NotFound => Ok(None),
_ => Err(e),
},
}
}
///
pub fn select_all_count(
&self,
conn: &diesel::PgConnection,
find_all: models::FindAll,
) -> Result<i64, Error> {
use captchas::dsl;
let mut q = captchas::table.into_boxed();
if let Some(sp) = find_all.expires_at {
q = q.filter(dsl::expires_at.lt(sp));
}
q.count().get_result(conn)
}
///
pub fn select_all(
&self,
conn: &diesel::PgConnection,
find_all: models::FindAll,
) -> Result<Vec<models::Captcha>, Error> {
use captchas::dsl;
let mut q = captchas::table.into_boxed();
if let Some(sp) = find_all.expires_at {
q = q.filter(dsl::expires_at.lt(sp));
}
if let Some(p) = find_all.pagination {
let page = p.page.unwrap_or(1);
if let Some(page_size) = p.page_size {
q = q.offset(((page - 1) * page_size) as i64);
q = q.limit(page_size as i64);
}
}
if let Some(orderbys) = find_all.sorts {
for s in orderbys {
match s {
bcr::models::pagination::Sort::ASC(property) => match property.as_str() {
"expires_at" => {
q = q.order_by(dsl::expires_at.asc());
}
"created_at" => {
q = q.order_by(dsl::created_at.asc());
}
_ => {}
},
bcr::models::pagination::Sort::DESC(property) => match property.as_str() {
"expires_at" => {
q = q.order_by(dsl::expires_at.desc());
}
"created_at" => {
q = q.order_by(dsl::created_at.desc());
}
_ => {}
},
};
}
}
q.load::<models::Captcha>(conn)
}
///
pub fn delete_expired(&self, conn: &diesel::PgConnection) -> Result<u64, Error> {
use captchas::dsl;
let now = chrono::Utc::now().timestamp();
diesel::delete(dsl::captchas.filter(dsl::expires_at.le(now)))
.execute(conn)
.map(|c| c as u64)
}
}

View File

@ -1,16 +0,0 @@
//!
//!
table! {
///
captchas(id) {
///
id -> Uuid,
///
security_code -> Text,
///
expires_at -> BigInt,
///
created_at -> BigInt,
}
}

View File

@ -1,4 +1,3 @@
pub mod captcha;
pub mod member; pub mod member;
pub mod member_class; pub mod member_class;
pub mod member_level; pub mod member_level;

View File

@ -1,8 +1,6 @@
//! //!
//! //!
use std::str::FromStr;
use super::super::super::repositories; use super::super::super::repositories;
use beteran_common_rust as bcr; use beteran_common_rust as bcr;
use beteran_protobuf_rust as bpr; use beteran_protobuf_rust as bpr;
@ -13,30 +11,34 @@ use diesel::{
use prost::Message; use prost::Message;
/// ///
pub struct Service { pub struct Service<'a> {
connection_broker: nats::asynk::Connection, connection_broker: nats::asynk::Connection,
queue_broker: String, queue_broker: String,
pool: Pool<ConnectionManager<PgConnection>>, pool: Pool<ConnectionManager<PgConnection>>,
member_repository: repositories::member::repository::Repository, member_repository: repositories::member::repository::Repository,
member_site_repository: repositories::member_site::repository::Repository, member_site_repository: repositories::member_site::repository::Repository,
member_session_repository: repositories::member_session::repository::Repository, member_session_repository: repositories::member_session::repository::Repository,
captcha_repository: repositories::captcha::repository::Repository, argon2_config: argon2::Config<'a>,
captcha_salt: String,
password_salt: String,
} }
impl std::fmt::Debug for Service { impl std::fmt::Debug for Service<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Service of service.member.service.identity") f.debug_struct("Service of service.member.service.identity")
.finish() .finish()
} }
} }
impl Service { impl Service<'_> {
/// ///
pub fn new( pub fn new(
connection_broker: nats::asynk::Connection, connection_broker: nats::asynk::Connection,
queue_broker: String, queue_broker: String,
pool: Pool<ConnectionManager<PgConnection>>, pool: Pool<ConnectionManager<PgConnection>>,
) -> Service { captcha_salt: String,
password_salt: String,
) -> Service<'static> {
Service { Service {
connection_broker, connection_broker,
queue_broker, queue_broker,
@ -44,7 +46,19 @@ impl Service {
member_repository: repositories::member::repository::Repository::new(), member_repository: repositories::member::repository::Repository::new(),
member_site_repository: repositories::member_site::repository::Repository::new(), member_site_repository: repositories::member_site::repository::Repository::new(),
member_session_repository: repositories::member_session::repository::Repository::new(), member_session_repository: repositories::member_session::repository::Repository::new(),
captcha_repository: repositories::captcha::repository::Repository::new(), argon2_config: argon2::Config {
variant: argon2::Variant::Argon2i,
version: argon2::Version::Version13,
mem_cost: 65536,
time_cost: 10,
lanes: 4,
thread_mode: argon2::ThreadMode::Parallel,
secret: &[],
ad: &[],
hash_length: 32,
},
captcha_salt,
password_salt,
} }
} }
@ -288,23 +302,23 @@ impl Service {
} }
}; };
let site_url = match client.site_url { // let site_url = match client.site_url {
Some(site_url) => site_url, // Some(site_url) => site_url,
None => { // None => {
return Err(bcr::error::rpc::Error::InvalidParams( // return Err(bcr::error::rpc::Error::InvalidParams(
bcr::error::rpc::InvalidParams { // bcr::error::rpc::InvalidParams {
message: "invalid site_url information".to_string(), // message: "invalid site_url information".to_string(),
detail: bcr::error::rpc::InvalidParamsDetail { // detail: bcr::error::rpc::InvalidParamsDetail {
location: "request".to_string(), // location: "request".to_string(),
param: "client.site_url".to_string(), // param: "client.site_url".to_string(),
value: "".to_string(), // value: "".to_string(),
error_type: bcr::error::rpc::InvalidParamsType::Required, // error_type: bcr::error::rpc::InvalidParamsType::Required,
message: "".to_string(), // message: "".to_string(),
}, // },
}, // },
)); // ));
} // }
}; // };
let conn = self.pool.get().map_err(|e| { let conn = self.pool.get().map_err(|e| {
bcr::error::rpc::Error::Server(bcr::error::rpc::Server { bcr::error::rpc::Error::Server(bcr::error::rpc::Server {
@ -314,36 +328,37 @@ impl Service {
}) })
})?; })?;
match self // match self
.member_site_repository // .member_site_repository
.select_by_url(&conn, &site_url) // .select_by_url(&conn, &site_url)
.map_err(|e| { // .map_err(|e| {
bcr::error::rpc::Error::Server(bcr::error::rpc::Server { // bcr::error::rpc::Error::Server(bcr::error::rpc::Server {
code: bpr::protobuf::rpc::Error::SERVER_00, // code: bpr::protobuf::rpc::Error::SERVER_00,
message: format!("server {}", e), // message: format!("server {}", e),
data: None, // data: None,
}) // })
})? { // })? {
Some(ms) => ms, // Some(ms) => ms,
None => { // None => {
return Err(bcr::error::rpc::Error::InvalidParams( // return Err(bcr::error::rpc::Error::InvalidParams(
bcr::error::rpc::InvalidParams { // bcr::error::rpc::InvalidParams {
message: "invalid site_url information".to_string(), // message: "invalid site_url information".to_string(),
detail: bcr::error::rpc::InvalidParamsDetail { // detail: bcr::error::rpc::InvalidParamsDetail {
location: "request".to_string(), // location: "request".to_string(),
param: "client.site_url".to_string(), // param: "client.site_url".to_string(),
value: "".to_string(), // value: "".to_string(),
error_type: bcr::error::rpc::InvalidParamsType::None, // error_type: bcr::error::rpc::InvalidParamsType::None,
message: "".to_string(), // message: "".to_string(),
}, // },
}, // },
)); // ));
} // }
}; // };
let mut c = captcha::Captcha::new(); let mut c = captcha::Captcha::new();
let c = c let c = c
.add_chars(5)
.apply_filter(captcha::filters::Noise::new(0.1)) .apply_filter(captcha::filters::Noise::new(0.1))
.view(220, 120); .view(220, 120);
@ -359,30 +374,19 @@ impl Service {
}; };
let security_code = c.chars_as_string(); let security_code = c.chars_as_string();
let expires_at = (chrono::Utc::now() + chrono::Duration::hours(2)).timestamp(); let security_code_hash = argon2::hash_encoded(
security_code.as_bytes(),
let new_captcha = repositories::captcha::models::NewCaptcha { self.captcha_salt.as_bytes(),
security_code, &self.argon2_config,
expires_at, )
}; .unwrap();
let inserted_captcha = self
.captcha_repository
.insert(&conn, &new_captcha)
.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 message
.respond( .respond(
bpr::ss::member::identity::CaptchaResponse { bpr::ss::member::identity::CaptchaResponse {
error: None, error: None,
result: Some(bpr::ss::member::identity::captcha_response::Result { result: Some(bpr::ss::member::identity::captcha_response::Result {
token: inserted_captcha.id.to_string(), security_code_hash,
image: image_as_base64, image: image_as_base64,
}), }),
} }
@ -487,65 +491,28 @@ impl Service {
} }
}; };
let captcha_id = uuid::Uuid::from_str(req.token.as_str()).map_err(|e| { let security_code_hash = req.security_code_hash;
bcr::error::rpc::Error::InvalidParams(bcr::error::rpc::InvalidParams {
message: "invalid captcha token".to_string(),
detail: bcr::error::rpc::InvalidParamsDetail {
location: "request".to_string(),
param: "token".to_string(),
value: "".to_string(),
error_type: bcr::error::rpc::InvalidParamsType::None,
message: e.to_string(),
},
})
})?;
let security_code = req.security_code; let security_code = req.security_code;
let username = req.username; let username = req.username;
let password = req.password; let password = req.password;
let captcha = match self let security_code_matches =
.captcha_repository argon2::verify_encoded(security_code_hash.as_str(), security_code.as_bytes()).map_err(
.select(&conn, captcha_id) |e| {
.map_err(|e| { bcr::error::rpc::Error::InvalidParams(bcr::error::rpc::InvalidParams {
bcr::error::rpc::Error::Server(bcr::error::rpc::Server { message: "invalid security_code".to_string(),
code: bpr::protobuf::rpc::Error::SERVER_00,
message: format!("server {}", e),
data: None,
})
})? {
Some(c) => c,
None => {
return Err(bcr::error::rpc::Error::InvalidParams(
bcr::error::rpc::InvalidParams {
message: "invalid captcha token".to_string(),
detail: bcr::error::rpc::InvalidParamsDetail { detail: bcr::error::rpc::InvalidParamsDetail {
location: "request".to_string(), location: "request".to_string(),
param: "token".to_string(), param: "security_code".to_string(),
value: "".to_string(), value: security_code.clone(),
error_type: bcr::error::rpc::InvalidParamsType::None, error_type: bcr::error::rpc::InvalidParamsType::None,
message: "".to_string(), message: e.to_string(),
}, },
}, })
));
}
};
if !captcha.expires_at < chrono::Utc::now().timestamp() {
return Err(bcr::error::rpc::Error::InvalidParams(
bcr::error::rpc::InvalidParams {
message: "invalid captcha token".to_string(),
detail: bcr::error::rpc::InvalidParamsDetail {
location: "request".to_string(),
param: "token".to_string(),
value: "".to_string(),
error_type: bcr::error::rpc::InvalidParamsType::None,
message: "captcha token is expired".to_string(),
},
}, },
)); )?;
}
if !captcha.security_code.eq(&security_code) { if !security_code_matches {
return Err(bcr::error::rpc::Error::InvalidParams( return Err(bcr::error::rpc::Error::InvalidParams(
bcr::error::rpc::InvalidParams { bcr::error::rpc::InvalidParams {
message: "invalid security_code".to_string(), message: "invalid security_code".to_string(),