Add log in and log out support
Dependencies
- [2]
6CFNBL5LAdd headers for better security - [3]
KDYHFAE3Add and remove cookies - [4]
AAUEQMCPAdd common header block - [5]
HZDCKIXQUse constants for templates - [6]
LWCZDLBIAdd buttons to accept and remove cookies - [7]
WVHXYKCVAdd postgresql pools - [8]
2MPJPGRYPopulate cookies jar - [9]
HDHALX3UAdd Cookies Policy page - [10]
EVP2FSBHSplit index page - [11]
QEK76JYTProcess and log template render error - [*]
65A3LIWUUse handlebars to render index - [*]
TRBYOQBICheck CSRF and user existence - [*]
3HT5CE6SManage TTL duration in config - [*]
4MZ4VIR7Initial commit - [*]
WLWTNO4YCreate form to request game password change link - [*]
DGGFYSEGUse non-escaped template for Atom XML - [*]
S6MX4MFOAdd handlers for accepting and removing cookies
Change contents
- replacement in src/templates/sub/header.html at line 5
Log In Log Out{{#if common_auth_info.user}}Hello, {{ common_auth_info.user }}<form id="logout" action="logout.do" method="post"><input name="submit" type="submit" value="Log Out"></form>{{else}}<a href="login.html">Log In</a>{{/if}} - file addition: login.html[13.12]
<!DOCTYPE html><html><head><meta charset="UTF-8"><link rel="alternate" type="application/rss+xml" href="static/rss.xml" title="Multiplayer FreeOrion server news" /><link rel="stylesheet" type="text/css" href="static/style.css" /><title>Welcome to public multiplayer FreeOrion server!</title></head><body>{{> header}}<div class="content"><h1>Log In</h1><form id="login" action="login.do" method="post"><fieldset><legend>Enter contact data to request password change:</legend><div><label for="login">Username:</label><input name="login" id="login" type="text" placeholder="Username" required autofocus /></div><div><label for="password">Password:</label><input name="password" id="password" type="password" placeholder="Password" required autofocus /></div><input name="csrf" type="hidden" value="{{ csrf }}"><input name="submit" type="submit" value="Log In"></fieldset></form></div>{{> footer}}</body></html> - replacement in src/pages/mod.rs at line 1
use actix_web::HttpResponseBuilder;use actix_web::{cookie, HttpRequest, HttpResponseBuilder}; - edit in src/pages/mod.rs at line 10
pub mod log_in;pub mod log_out; - edit in src/pages/mod.rs at line 25
pub const LOG_IN: &str = "log-in"; - edit in src/pages/mod.rs at line 36
#[derive(serde_derive::Serialize)]pub struct CommonAuthInfo {pub user: Option<String>,} - edit in src/pages/mod.rs at line 46
pub cache_login: Mutex<TtlCache<Uuid, ()>>, - edit in src/pages/mod.rs at line 70
}pub fn request_to_jar(request: HttpRequest) -> cookie::CookieJar {let mut jar = cookie::CookieJar::new();match request.cookies() {Ok(cookies) => {for cookie in &*cookies {jar.add_original(cookie.clone());}}Err(e) => {log::error!("Cann't get cookies from request: {}", e);}}jar - file addition: log_out.rs[5.17]
use actix_web::http::header;use actix_web::{cookie, web, HttpResponse};use crate::WebData;pub async fn log_out(data: web::Data<WebData<'_>>) -> HttpResponse {let mut response = HttpResponse::Found().append_header((header::LOCATION, "index.html")).finish();let mut builder = cookie::Cookie::build("auth", "").path("/");if data.set_cookie_domain {builder = builder.domain(data.base_domain.to_string());}if let Err(e) = response.add_removal_cookie(&builder.finish()) {log::error!("Cann't set cookie {}", e);}response} - file addition: log_in.rs[5.17]
use actix_web::http::header;use actix_web::{cookie, web, HttpRequest, HttpResponse};use uuid::Uuid;use crate::pages::templates::LOG_IN;use crate::pages::{insert_security_headers, request_to_jar};use crate::{DataBaseRo, WebData};#[derive(serde_derive::Serialize)]struct PageData {csrf: Uuid,}#[derive(serde_derive::Deserialize)]pub struct FormData {login: String,password: String,csrf: Uuid,}pub async fn get_log_in(data: web::Data<WebData<'_>>) -> HttpResponse {let csrf = Uuid::new_v4();{let mut cache = data.cache_login.lock().await;cache.insert(csrf,(),std::time::Duration::from_secs(data.cache_duration_sec),);}let body = match data.handlebars.render(LOG_IN, &PageData { csrf }) {Ok(b) => b,Err(e) => {log::error!("Render login error: {}", e);return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());}};insert_security_headers(HttpResponse::Ok()).body(body)}pub async fn post_log_in(request: HttpRequest,form: web::Form<FormData>,data: web::Data<WebData<'_>>,data_ro: web::Data<DataBaseRo>,) -> HttpResponse {let cached_data = {let mut cache = data.cache_login.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 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 web_password = crypt($1, web_password) from auth.users where player_name = $2 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, &[&form.password, &form.login]).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");}};if !row.get::<_, bool>(0) {return HttpResponse::NotFound().body("Not found");}let mut jar = request_to_jar(request);let mut builder = cookie::Cookie::build("auth", form.login.clone()).path("/").secure(true).http_only(true).same_site(cookie::SameSite::Strict).max_age(cookie::time::Duration::weeks(4));if data.set_cookie_domain {builder = builder.domain(data.base_domain.to_string());}jar.private_mut(&data.cookies_key).add(builder.finish());let mut response = HttpResponse::Found().append_header((header::LOCATION, "index.html")).finish();if let Some(c) = jar.get("auth") {if let Err(e) = response.add_cookie(c) {log::error!("Cann't set cookie {}", e);}}response} - replacement in src/pages/index.rs at line 1
use actix_web::{web, HttpResponse};use actix_web::{web, HttpRequest, HttpResponse}; - edit in src/pages/index.rs at line 3
use crate::pages::insert_security_headers; - edit in src/pages/index.rs at line 4
use crate::pages::CommonAuthInfo;use crate::pages::{insert_security_headers, request_to_jar}; - replacement in src/pages/index.rs at line 8
pub async fn index(data: web::Data<WebData<'_>>) -> HttpResponse {let body = match data.handlebars.render(INDEX, &()) {#[derive(serde_derive::Serialize)]struct PageData {pub common_auth_info: CommonAuthInfo,}pub async fn index(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 body = match data.handlebars.render(INDEX,&PageData {common_auth_info: CommonAuthInfo { user },},) { - edit in src/pages/cookies_policy.rs at line 4
use crate::pages::insert_security_headers; - edit in src/pages/cookies_policy.rs at line 5
use crate::pages::{insert_security_headers, request_to_jar}; - edit in src/pages/cookies_policy.rs at line 13[5.935]→[3.34:97](∅→∅),[3.97]→[5.156:470](∅→∅),[5.156]→[5.156:470](∅→∅),[5.470]→[3.98:108](∅→∅),[3.108]→[5.470:471](∅→∅),[5.470]→[5.470:471](∅→∅)
fn request_to_jar(request: HttpRequest) -> cookie::CookieJar {let mut jar = cookie::CookieJar::new();match request.cookies() {Ok(cookies) => {for cookie in &*cookies {jar.add_original(cookie.clone());}}Err(e) => {log::error!("Cann't get cookies from request: {}", e);}}jar} - edit in src/main.rs at line 16
use pages::log_in::{get_log_in, post_log_in};use pages::log_out::log_out; - edit in src/main.rs at line 174
handlebars.register_template_string(templates::LOG_IN, include_str!("templates/login.html")).expect("cookies policy template"); - edit in src/main.rs at line 217
cache_login: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)), - edit in src/main.rs at line 261[5.1858][19.451]
.route("/login.html", web::get().to(get_log_in)) - edit in src/main.rs at line 264[19.607][5.2328]
.route("/logout.do", web::post().to(log_out)).route("/login.do", web::post().to(post_log_in))