use actix_web::http::header;
use actix_web::{web, HttpRequest, HttpResponse};
use crate::pages::templates::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>,
token: Uuid,
csrf: Uuid,
last_error: Option<&'a str>,
is_game_pwd: bool,
}
#[derive(serde_derive::Deserialize)]
pub struct FormData {
login: Option<String>,
password: String,
password_copy: String,
token: Option<Uuid>,
csrf: Uuid,
}
#[actix_web::get("/reset-game-pwd-{token}.html")]
pub async fn get_reset_game_pwd(
request: HttpRequest,
token: web::Path<String>,
data: web::Data<WebData<'_>>,
data_ro: web::Data<DataBaseRo>,
) -> HttpResponse {
let jar = request_to_jar(request);
let user = jar
.private(&data.cookies_key)
.get("auth")
.map(|x| x.value().to_string());
let token = match Uuid::parse_str(&token.into_inner()) {
Ok(t) => t,
Err(e) => {
log::warn!("Incorrect token: {}", e);
return HttpResponse::NotFound().body("Not found");
}
};
let dbclient = 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.prepare("select player_name, last_error, is_game_pwd from auth.reset_tokens where token = $1 and NOW() < create_ts + interval '1 day' 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.query_opt(&stmt, &[&token]).await {
Ok(rows) => rows,
Err(e) => {
log::error!("Pool RO query error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let row = match rows {
Some(row) => row,
None => {
return HttpResponse::NotFound().body("Not found");
}
};
let player_name = row.get::<_, &str>(0);
let last_error = row.get::<_, Option<&str>>(1);
let is_game_pwd = row.get::<_, bool>(2);
let csrf = Uuid::new_v4();
{
let mut cache = data.cache_reset_game_pwd.lock().await;
cache.insert(
csrf,
(token, player_name.to_string(), is_game_pwd),
std::time::Duration::from_secs(data.cache_duration_sec),
);
}
let body = match data.handlebars.render(
RESET_GAME_PWD,
&PageData {
common_auth_info: CommonAuthInfo {
user: user.map(Into::into),
},
token,
csrf,
last_error,
is_game_pwd,
},
) {
Ok(b) => b,
Err(e) => {
log::error!("Render reset-game-pwd error: {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
insert_security_headers(HttpResponse::Ok()).body(body)
}
#[actix_web::post("/reset-game-pwd.do")]
pub async fn post_reset_game_pwd(
request: HttpRequest,
form: web::Form<FormData>,
data: web::Data<WebData<'_>>,
data_rw: web::Data<DataBaseRw>,
) -> HttpResponse {
let cached_data = {
let mut cache = data.cache_reset_game_pwd.lock().await;
cache.remove(&form.csrf)
};
let (cached_token, cached_login, is_game_pwd) = match cached_data {
Some(d) => d,
None => {
log::warn!("Unknown data for CSRF: {}", form.csrf);
return HttpResponse::BadRequest().body("Incorrect");
}
};
if cached_token.is_nil() {
if form.token.is_some() {
log::warn!("Expected nil token for CSRF: {}", form.csrf);
return HttpResponse::BadRequest().body("Incorrect");
}
} else if form.token != Some(cached_token) {
log::warn!("Mismatch token for CSRF: {}", form.csrf);
return HttpResponse::BadRequest().body("Incorrect");
}
let jar = request_to_jar(request);
let form_login = jar
.private(&data.cookies_key)
.get("auth")
.map(|x| std::borrow::Cow::Owned(x.value().to_string()))
.or(form.login.as_ref().map(std::borrow::Cow::Borrowed))
.map(|x| x.to_ascii_lowercase());
let dbclient = 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 pass_match = form.password == form.password_copy;
let login_match = form_login == Some(cached_login.to_ascii_lowercase());
if pass_match && login_match {
if !cached_token.is_nil() {
let stmt = match dbclient
.prepare("delete from auth.reset_tokens where token = $1 and player_name = $2;")
.await
{
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RW statement delete error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let deleted = match dbclient
.execute(&stmt, &[&cached_token, &cached_login])
.await
{
Ok(c) => c,
Err(e) => {
log::error!("Pool RW execute delete error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
if deleted == 0 {
log::error!("Not delete error: {}", cached_token);
return HttpResponse::BadRequest().body("Incorrect");
}
}
let stmt = match dbclient.prepare(if is_game_pwd { "update auth.users set game_password = crypt($1, gen_salt('bf', 8)) where player_name = $2;" } else { "update auth.users set web_password = crypt($1, gen_salt('bf', 8)) where player_name = $2;" }).await {
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RW statement upd pwd error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let changed = match dbclient
.execute(&stmt, &[&form.password, &cached_login])
.await
{
Ok(c) => c,
Err(e) => {
log::error!("Pool RW execute upd pwd error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
if changed > 0 {
HttpResponse::Found()
.append_header((header::LOCATION, "index.html"))
.finish()
} else {
log::error!("Not update pwd error: {}", cached_token);
HttpResponse::BadRequest().body("Incorrect")
}
} else {
let stmt = match dbclient.prepare("update auth.reset_tokens set last_error = $1 where token = $2 and player_name = $3;").await {
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RW statement error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let changed = match dbclient
.execute(
&stmt,
&[
&if login_match {
"Passwords mismatch"
} else {
"Login mismatch"
},
&cached_token,
&cached_login,
],
)
.await
{
Ok(c) => c,
Err(e) => {
log::error!("Pool RW execute error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
if changed > 0 {
HttpResponse::Found()
.append_header((
header::LOCATION,
format!("reset-game-pwd-{}.html", cached_token),
))
.finish()
} else {
log::error!("Not set error: {}", cached_token);
HttpResponse::BadRequest().body("Incorrect")
}
}
}