use std::str::FromStr;
use crate::commons::timestamp_millis;
use crate::prelude2::*;
use crate::services::otpcode_service::models_extend::OptCodeRenewParam;
use actix_http::header::CONTENT_ENCODING;
use actix_http::ContentEncoding;
use actix_web_flash_messages::IncomingFlashMessages;
use actix_web_flash_messages::Level;
use qrcode::render::svg;
use qrcode::QrCode;
use regex::Regex;
use crate::commons::validatorr::validation_flatten;
use crate::core::auth0;
use crate::core::auth0::{Requestor, UserId};
use crate::core::flash_messages::flash_message;
use crate::services::user_account_service::enums::UserGender;
use crate::services::user_account_service::models::UserIdentifierInfo;
use crate::services::user_account_service::models::UserProfileInfo;
use crate::services::user_account_service::models::{UserAccountInfo, UserChangePasswdForm};
use crate::services::user_account_service::{query_user_record, update_passwd};
use crate::services::user_account_service::update_user_account_settings;
use crate::services::user_account_service::update_user_identifier;
use crate::services::user_account_service::update_user_profile;
use crate::services::otpcode_service::OtpCodeRecord;
pub async fn user_info(
app_state: web::Data<AppContext>,
user_id: web::ReqData<UserId>,
request: HttpRequest,
) -> impl Responder {
let user_id = user_id.into_inner();
let user_record = match query_user_record(app_state.mysql(), &user_id.0).await {
Ok(user_record) => Some(user_record),
Err(e) => {
log::error!("query_user_record: user_id={}, error={:?}", user_id, e);
None
}
};
request.json(200, R::ok(user_record))
}
pub async fn user_profile_form(
app_state: web::Data<AppContext>,
user_id: web::ReqData<UserId>,
request: HttpRequest,
flash_messages: IncomingFlashMessages,
) -> impl Responder {
let user_id = user_id.into_inner();
let user_record = match query_user_record(app_state.mysql(), &user_id.0).await {
Ok(user_record) => Some(user_record),
Err(e) => {
log::error!("query_user_record: user_id={}, error={:?}", user_id, e);
None
}
};
let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);
let mut ctx = tera::Context::new();
ctx.insert("__flash_message", &_flash_html);
if user_record.is_some() {
ctx.insert("USER_PROFILE", &user_record);
}
request.render(200, "account_setting/user_profile.html", ctx)
}
pub async fn user_profile_update(
app_state: web::Data<AppContext>,
form: web::Form<UserProfileInfo>,
user_id: web::ReqData<UserId>,
_request: HttpRequest,
) -> Result<HttpResponse> {
if let Some(err) = validation_flatten(&form.0) {
return _request.json(200, R::invalid(err));
}
let user_id = user_id.into_inner();
let response = crate::utils::see_other("/your-profile#user_details");
match update_user_profile(app_state.mysql(), &user_id.0, &form.0).await {
Ok(result) => {
if result {
flash_message("个人信息更新成功", Level::Success);
Ok(response)
} else {
flash_message("个人信息更新失败", Level::Error);
Ok(response)
}
}
Err(e) => {
log::error!("user_profile: user_id={}, error={:?}", user_id, e);
flash_message(&format!("个人信息更新失败: {:?}", e), Level::Error);
Ok(response)
}
}
}
pub async fn user_identifier_form(
app_state: web::Data<AppContext>,
user_id: web::ReqData<UserId>,
request: HttpRequest,
flash_messages: IncomingFlashMessages,
) -> impl Responder {
let user_id = user_id.into_inner();
let user_record = match query_user_record(app_state.mysql(), &user_id.0).await {
Ok(user_record) => Some(user_record),
Err(e) => {
log::error!("query_user_record: user_id={}, error={:?}", user_id, e);
None
}
};
let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);
let mut ctx = tera::Context::new();
ctx.insert("__flash_message", &_flash_html);
if user_record.is_some() {
ctx.insert("USER_IDENTIFIER", &user_record);
}
request.render(200, "account_setting/user_identifier.html", ctx)
}
pub async fn user_identifier_update(
app_state: web::Data<AppContext>,
form: web::Form<UserIdentifierInfo>,
user_id: web::ReqData<UserId>,
_request: HttpRequest,
) -> Result<HttpResponse> {
let response = crate::utils::see_other("/user-identifier#user_details");
if let Some(err) = validation_flatten(&form.0) {
flash_message(&format!("无效参数!{}", err), Level::Warning);
return Ok(response);
}
if UserGender::from_str(&form.0.gender).is_err() {
flash_message("无效参数!", Level::Warning);
return Ok(response);
}
let user_id = user_id.into_inner();
match update_user_identifier(app_state.mysql(), &user_id.0, &form.0).await {
Ok(result) => {
if result {
flash_message("你的身份认证信息更新成功", Level::Success);
Ok(response)
} else {
flash_message("你的身份认证信息更新失败", Level::Error);
Ok(response)
}
}
Err(e) => {
log::error!("user_profile: user_id={}, error={:?}", user_id, e);
flash_message(&format!("你的身份认证信息更新失败: {:?}", e), Level::Error);
Ok(response)
}
}
}
pub async fn user_account_form(
app_state: web::Data<AppContext>,
user_id: web::ReqData<UserId>,
flash_messages: IncomingFlashMessages,
request: HttpRequest,
) -> impl Responder {
let user_id = user_id.into_inner();
let user_record = match query_user_record(app_state.mysql(), &user_id.0).await {
Ok(user_record) => Some(user_record),
Err(e) => {
log::error!("query_user_record: user_id={}, error={:?}", user_id, e);
None
}
};
let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);
let mut ctx = tera::Context::new();
ctx.insert("__flash_message", &_flash_html);
if user_record.is_some() {
ctx.insert("USER_ACCOUNT", &user_record);
}
request.render(200, "account_setting/user_account.html", ctx)
}
pub async fn user_account_update(
app_state: web::Data<AppContext>,
form: web::Form<UserAccountInfo>,
_request: HttpRequest,
user_id: web::ReqData<UserId>,
) -> Result<HttpResponse> {
let response = crate::utils::see_other("/user-account#user_details");
if let Some(err) = validation_flatten(&form.0) {
flash_message(&format!("无效参数!{}", err), Level::Warning);
return Ok(response);
}
let user_id = user_id.into_inner();
match update_user_account_settings(app_state.mysql(), &user_id.0, &form.0).await {
Ok(result) => {
if result {
flash_message("你的账户设置信息更新成功", Level::Success);
Ok(response)
} else {
flash_message("你的账户设置信息更新失败", Level::Error);
Ok(response)
}
}
Err(e) => {
log::error!("user_profile: user_id={}, error={:?}", user_id, e);
flash_message(&format!("你的账户设置信息更新失败: {:?}", e), Level::Error);
Ok(response)
}
}
}
pub async fn payments_form(
_app_state: web::Data<AppContext>,
_user_id: web::ReqData<UserId>,
_flash_messages: IncomingFlashMessages,
request: HttpRequest,
) -> impl Responder {
let mut ctx = tera::Context::new();
ctx.insert("__receive_code", &crate::commons::random_token());
request.render(200, "account_setting/payments.html", ctx)
}
pub async fn payments_receive_code(
_app_state: web::Data<AppContext>,
_user_id: web::ReqData<UserId>,
qrcode: actix_web::web::Path<String>,
) -> impl Responder {
let qrcode = qrcode.as_str().as_bytes();
let image3 = QrCode::new(qrcode)
.unwrap()
.render()
.min_dimensions(200, 200)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build();
HttpResponse::Ok()
.content_type("image/svg+xml")
.append_header((CONTENT_ENCODING, ContentEncoding::Identity))
.append_header((
"Cache-Control",
"no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0",
))
.body(image3)
}
pub async fn payments_payment_code(
_app_state: web::Data<AppContext>,
_user_id: web::ReqData<UserId>,
_qrcode: actix_web::web::Path<String>,
) -> impl Responder {
let s = crate::commons::random_token();
let qrcode = s.as_bytes();
let image3 = QrCode::new(qrcode)
.unwrap()
.render()
.min_dimensions(200, 200)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build();
HttpResponse::Ok()
.content_type("image/svg+xml")
.append_header((CONTENT_ENCODING, ContentEncoding::Identity))
.append_header((
"Cache-Control",
"no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0",
))
.body(image3)
}
pub async fn user_notifications_form(
flash_messages: IncomingFlashMessages,
request: HttpRequest,
) -> impl Responder {
let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);
let mut ctx = tera::Context::new();
ctx.insert("__flash_message", &_flash_html);
request.render(200, "account_setting/user_notifications.html", ctx)
}
pub async fn user_notifications_update(
_request: HttpRequest,
_user_id: web::ReqData<UserId>,
) -> Result<HttpResponse> {
let response = crate::utils::see_other("/user-notifications#user_details");
crate::core::flash_messages::send_flash_message("您的系统通知设置更新成功");
Ok(response)
}
pub async fn user_keys_form(
flash_messages: IncomingFlashMessages,
request: HttpRequest,
) -> impl Responder {
let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);
let mut ctx = tera::Context::new();
ctx.insert("__flash_message", &_flash_html);
request.render(200, "account_setting/user_keys.html", ctx)
}
pub async fn user_keys_update(
_request: HttpRequest,
_user_id: web::ReqData<UserId>,
) -> Result<HttpResponse> {
let response = crate::utils::see_other("/user-keys#user_details");
crate::core::flash_messages::send_flash_message("你的秘钥更新成功");
Ok(response)
}
pub async fn change_passwd(
_form: web::Form<UserChangePasswdForm>,
context: web::Data<AppContext>,
_request: HttpRequest,
_requestor: web::ReqData<Requestor>,
) -> Result<HttpResponse> {
if let Some(err) = validation_flatten(&_form.0) {
return _request.json(200, R::invalid(err));
}
if _form.0.new_passwd != _form.0.confirm {
return _request.json(200, R::failed(413, "确认密码有误"));
}
if _form.0.new_passwd == _form.0.current {
return _request.json(200, R::failed(413, "新密码有误"));
}
let credentials = auth0::Credentials {
username: _requestor.user().user_name.clone(),
password: _form.0.current.clone(),
};
let user_id = auth0::validate_credentials(&credentials, context.mysql()).await;
if let Some(user_id) = user_id {
let succeed =
update_passwd(context.mysql_client(), &user_id.0, &_form.0.new_passwd).await?;
return _request.json(200, R::ok(succeed));
}
_request.json(200, R::failed(400, "密码有误"))
}
pub async fn query_opt_code(
request: HttpRequest,
user_id: web::ReqData<UserId>,
context: web::Data<AppContext>,
) -> impl Responder {
let list = OtpCodeRecord::query_by_user_id(context.mysql(), &user_id.into_inner().0).await?;
request.json(200, R::ok(list))
}
#[rustfmt::skip]
pub async fn opt_code_renew(
request: HttpRequest,
form: actix_web::web::Form<OptCodeRenewParam>,
user_id: web::ReqData<UserId>,
context: web::Data<AppContext>,
) -> impl Responder {
let row = OtpCodeRecord::query_by_id(context.mysql(), &form.0.id, &form.0.name, &user_id.into_inner().0).await?;
if let Some(row) = row {
let mut record = OtpCodeRecord::from_row(row)?;
let f = |record: &OtpCodeRecord| {
let mut result = indexmap::IndexMap::<String, String>::new();
result.insert("id".to_string(), record.id.to_owned().to_string());
result.insert("name".to_string(), record.name.to_owned());
result.insert("code".to_string(), record.code.to_string());
result.insert("expired_at".to_string(), record.expired_at.to_string());
result.insert("seconds".to_string(), record.period.to_string());
result.to_owned()
};
if (timestamp_millis() as u64) < record.expired_at {
return request.json(200, R::ok(f(&record)));
}
if OtpCodeRecord::re_new_code(context.mysql(), &mut record).await? {
return request.json(200, R::ok(f(&record)));
}
return request.json(200, R::failed(400, ""));
}
request.json(200, R::failed(400, "Invalid id or name"))
}
#[rustfmt::skip]
pub async fn import_opt_code(
body: web::Bytes,
request: HttpRequest,
user_id: web::ReqData<UserId>,
context: web::Data<AppContext>,
) -> impl Responder {
let otp_code_line = match crate::commons::bytes_to_string(body.to_vec()) {
Ok(val) => val,
Err(e) => return request.json(200, R::failed(500, e.to_string())),
};
#[rustfmt::skip]
let re: Regex = Regex::new(r"^otpauth://totp/(?P<app>[\w\W]+):(?P<name>[\w\W]+)\?algorithm=SHA1&digits=(?P<digits>\d+)&issuer=(?P<issuer>[\w\W]+)&period=(?P<period>\d+)&secret=(?P<secret>[\w\W]+)$").unwrap();
if let Some(captures) = re.captures(&otp_code_line) {
let mut new_record = OtpCodeRecord::default();
if let Some(_app) = captures.name("app") {
new_record.app = _app.as_str().to_string();
}
if let Some(_name) = captures.name("name") {
new_record.name = _name.as_str().to_string();
}
if let Some(_digits) = captures.name("digits") {
new_record.digits = crate::commons::string_to_number(_digits.as_str()).unwrap();
}
if let Some(_issuer) = captures.name("issuer") {
new_record.issuer = _issuer.as_str().to_string();
}
if let Some(_period) = captures.name("period") {
new_record.period = crate::commons::string_to_number(_period.as_str()).unwrap();
}
if let Some(secret) = captures.name("secret") {
new_record.key = secret.as_str().to_string();
}
let row = OtpCodeRecord::query_by_name(context.mysql(), &new_record.name, &new_record.key)
.await?;
let f = |record: &OtpCodeRecord| {
let mut result = indexmap::IndexMap::<String, String>::new();
result.insert("id".to_string(), record.id.to_owned().to_string());
result.insert("name".to_string(), record.name.to_owned());
result.insert("code".to_string(), record.code.to_string());
result.insert("expired_at".to_string(), record.expired_at.to_string());
result.insert("seconds".to_string(), record.period.to_string());
result.to_owned()
};
if let Some(_row) = row {
let mut _record = OtpCodeRecord::from_row(_row)?;
if (timestamp_millis() as u64) < _record.expired_at {
return request.json(200, R::ok(f(&_record)));
} else {
if OtpCodeRecord::re_new_code(context.mysql(), &mut _record).await? {
return request.json(200, R::ok(f(&_record)));
}
}
} else {
new_record.issuer = "OneAuthenticator".to_string();
new_record.user_id = user_id.into_inner().0;
new_record.code = crate::commons::random_number(6);
new_record.expired_at = (timestamp_millis() + ((new_record.period * 1000) as i64)) as u64;
new_record.created_at = timestamp_millis() as u64;
new_record.updated_at = timestamp_millis() as u64;
new_record.id = OtpCodeRecord::insert(context.mysql(), &new_record).await?;
return request.json(200, R::ok(f(&new_record)));
}
} else {
return request.json(200, R::failed(413, "bad format"));
}
request.json(200, R::ok(HashMap::<String, String>::new()))
}