use actix_web::http::header;
use actix_web::{web, HttpRequest, HttpResponse};
use crate::pages::templates::QUERY_RESET_GAME_PWD;
use crate::pages::{insert_security_headers, request_to_jar, CommonAuthInfo};
use crate::{DataBaseRo, DataBaseRw, WebData};
use uuid::Uuid;
#[derive(serde_derive::Serialize)]
struct PageData<'a> {
common_auth_info: CommonAuthInfo<'a>,
csrf: Uuid,
}
#[derive(serde_derive::Deserialize)]
pub struct FormData {
login: String,
contact_type: String,
contact: String,
password_type: String,
csrf: Uuid,
}
pub async fn query_reset_game_pwd(
request: HttpRequest,
data: web::Data<WebData<'_>>,
) -> HttpResponse {
let jar = request_to_jar(request);
let user = jar
.private(&data.cookies_key)
.get("auth")
.map(|x| x.value().to_string());
let csrf = Uuid::new_v4();
{
let mut cache = data.cache_query_reset_game_pwd.lock().await;
cache.insert(
csrf,
(),
std::time::Duration::from_secs(data.cache_duration_sec),
);
}
let body = match data.handlebars.render(
QUERY_RESET_GAME_PWD,
&PageData {
common_auth_info: CommonAuthInfo {
user: user.map(Into::into),
},
csrf,
},
) {
Ok(b) => b,
Err(e) => {
log::error!("Render error: {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
insert_security_headers(HttpResponse::Ok()).body(body)
}
async fn send_email(
mailer: &lettre::AsyncSmtpTransport<lettre::Tokio1Executor>,
mailer_from: &lettre::message::Mailbox,
token_url: String,
to: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let email = lettre::Message::builder()
.from(mailer_from.clone())
.to(to.parse()?)
.subject("FreeOrion reset game password link")
.body(format!(
"You requested change for FreeOrion game password.\n\
Use next link {} to enter new password. Link is active for 24 hours.\n\
Don't send it to other people.",
token_url
))?;
use lettre::AsyncTransport;
mailer.send(email).await?;
Ok(())
}
async fn send_xmpp(
xmpp_url: &str,
token_url: String,
to: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let client = awc::Client::default();
client
.post(xmpp_url)
.insert_header(("X-XMPP-To", to))
.send_body(format!(
"You requested change for FreeOrion game password.\n\
Use next link {} to enter new password. Link is active for 24 hours.\n\
Don't send it to other people.",
token_url
))
.await?;
Ok(())
}
enum Protocol {
Email,
Xmpp,
}
#[actix_web::post("/query-reset-game-pwd.do")]
pub async fn post_query_reset_game_pwd(
form: web::Form<FormData>,
data: web::Data<WebData<'_>>,
data_ro: web::Data<DataBaseRo>,
data_rw: web::Data<DataBaseRw>,
) -> HttpResponse {
let cached_data = {
let mut cache = data.cache_query_reset_game_pwd.lock().await;
cache.remove(&form.csrf)
};
if cached_data.is_none() {
log::warn!("Unknown data for CSRF: {}", form.csrf);
return HttpResponse::BadRequest().body("Incorrect");
}
let protocol = match form.contact_type.as_str() {
"email" => Protocol::Email,
"xmpp" => Protocol::Xmpp,
_ => {
log::warn!("Unknown data for contact type: {}", form.contact_type);
return HttpResponse::BadRequest().body("Incorrect");
}
};
let is_game_pwd = match form.password_type.as_str() {
"game" => true,
"web" => false,
_ => {
log::warn!("Unknown data for password type: {}", form.password_type);
return HttpResponse::BadRequest().body("Incorrect");
}
};
let dbclient_ro = match data_ro.0.get().await {
Ok(c) => c,
Err(e) => {
log::error!("Pool RO error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let stmt = match dbclient_ro.prepare("select 1 from auth.users u inner join auth.contacts c on u.player_name = c.player_name and c.is_active = true and c.delete_ts is null where u.player_name = $1 and c.protocol = ($2::text)::auth.contact_protocol and c.address = $3 limit 1").await {
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RO statement error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let rows = match dbclient_ro
.query_opt(&stmt, &[&form.login, &form.contact_type, &form.contact])
.await
{
Ok(rows) => rows,
Err(e) => {
log::error!("Pool RO query error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
if rows.is_none() {
return HttpResponse::NotFound().body("Not found");
}
let dbclient_rw = match data_rw.0.get().await {
Ok(c) => c,
Err(e) => {
log::error!("Pool RW error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let stmt = match dbclient_rw
.prepare("insert into auth.reset_tokens values ($1, $2, NULL, NOW(), NULL, $3);")
.await
{
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RW statement error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let token = Uuid::new_v4();
let inserted = match dbclient_rw
.execute(&stmt, &[&form.login, &token, &is_game_pwd])
.await
{
Ok(c) => c,
Err(e) => {
log::error!("Pool RW execute insert error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
if inserted == 0 {
log::error!("Pool RW execute insert row error");
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
let token_url = format!(
"{}://{}/reset-game-pwd-{}.html",
data.base_proto, data.base_domain, token
);
match protocol {
Protocol::Email => {
if let Err(e) =
send_email(&data.mailer, &data.mailer_from, token_url, &form.contact).await
{
log::error!("Cann't send email: {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
}
Protocol::Xmpp => {
if let Err(e) = send_xmpp(&data.xmpp_url, token_url, &form.contact).await {
log::error!("Cann't send xmpp: {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
}
};
HttpResponse::Found()
.append_header((header::LOCATION, "index.html"))
.finish()
}