From 03b4e92d3a352d7e79beccaf8abb792456870afb Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 18 May 2021 00:57:53 -0400 Subject: [PATCH] session info handler --- debug/curl/session_info.py | 41 ++++++++++ src/endpoints/users/session_info.rs | 114 ++++++++++++++++++++++++++++ src/main.rs | 6 ++ src/util/session_checker.rs | 35 +++++++-- 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100755 debug/curl/session_info.py diff --git a/debug/curl/session_info.py b/debug/curl/session_info.py new file mode 100755 index 0000000000..7326a74da1 --- /dev/null +++ b/debug/curl/session_info.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import requests +import re + +login_url = 'http://localhost:12345/login' +session_info_url = 'http://localhost:12345/session' + + +r = requests.get(session_info_url) + +print("===== Session Info (logged out) =====") +print(r.content) + +payload = { + 'username_or_email': 'echelon', + 'password': 'testing', +} + +r = requests.post(login_url, json=payload) + +print("===== Login =====") +print('Status: {}'.format(r.status_code)) +print(r.content) +for k, v in r.headers.items(): + print(' {}: {}'.format(k, v)) + +# NB: May not be set due to cookie domain: +#r.cookies['session'] + +raw_set_cookie_header = r.headers['set-cookie'] +m = re.match(r'session=([^;\s]+);', raw_set_cookie_header) +session_cookie = m.group(1) + +cookies = { 'session': session_cookie } + +r = requests.get(session_info_url, cookies=cookies) + +print("===== Session Info (logged in) =====") +print(r.content) + diff --git a/src/endpoints/users/session_info.rs b/src/endpoints/users/session_info.rs index e69de29bb2..1d632cc38a 100644 --- a/src/endpoints/users/session_info.rs +++ b/src/endpoints/users/session_info.rs @@ -0,0 +1,114 @@ +use actix_http::Error; +use actix_http::http::header; +use actix_web::dev::HttpResponseBuilder; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::{Responder, web, HttpResponse, error, HttpRequest, HttpMessage}; +use crate::endpoints::users::create_account::CreateAccountError::{BadInput, ServerError, UsernameTaken, EmailTaken}; +use crate::server_state::ServerState; +use crate::util::ip_address::get_request_ip; +use crate::util::tokens::random_token; +use derive_more::{Display, Error}; +use log::{info, warn, log}; +use regex::Regex; +use sqlx::error::DatabaseError; +use sqlx::error::Error::Database; +use sqlx::mysql::MySqlDatabaseError; +use std::sync::Arc; +use crate::validations::username::validate_username; +use crate::validations::passwords::validate_passwords; +use actix_web::cookie::Cookie; +use sqlx::MySqlPool; +use crate::AnyhowResult; +use crate::common_queries::sessions::create_session_for_user; +use crate::endpoints::users::login::LoginSuccessResponse; +use crate::util::session_checker::SessionRecord; + +#[derive(Serialize)] +pub struct UserInfo { + pub user_token: String, +} + +#[derive(Serialize)] +pub struct SessionInfoSuccessResponse { + pub success: bool, + pub logged_in: bool, + pub user: Option, +} + +#[derive(Serialize)] +pub struct ErrorResponse { + pub success: bool, + pub error_reason: String, +} + +#[derive(Debug, Display)] +pub enum SessionInfoError { + ServerError, +} + +impl ResponseError for SessionInfoError { + fn status_code(&self) -> StatusCode { + match *self { + SessionInfoError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + let error_reason = match self { + SessionInfoError::ServerError => "server error".to_string(), + }; + + let response = ErrorResponse { + success: false, + error_reason, + }; + + let body = match serde_json::to_string(&response) { + Ok(json) => json, + Err(_) => "{}".to_string(), + }; + + HttpResponseBuilder::new(self.status_code()) + .set_header(header::CONTENT_TYPE, "application/json") + .body(body) + } +} + +pub async fn session_info_handler( + http_request: HttpRequest, + server_state: web::Data>) -> Result +{ + let maybe_session = server_state.session_checker.maybe_get_session(&http_request, &server_state.mysql_pool) + .await + .map_err(|e| { + warn!("Session checker error: {:?}", e); + SessionInfoError::ServerError + })?; + + let mut logged_in = false; + let mut user_info = None; + + match maybe_session { + None => {} + Some(session_data) => { + logged_in = true; + user_info = Some(UserInfo { + user_token: session_data.user_token.clone(), + }); + } + } + + let response = SessionInfoSuccessResponse { + success: true, + logged_in, + user: user_info, + }; + + let body = serde_json::to_string(&response) + .map_err(|e| SessionInfoError::ServerError)?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)) +} diff --git a/src/main.rs b/src/main.rs index 9145213b27..84432345c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use crate::endpoints::users::login::login_handler; use crate::util::cookies::CookieManager; use crate::endpoints::users::logout::logout_handler; use crate::util::session_checker::SessionChecker; +use crate::endpoints::users::session_info::session_info_handler; const DEFAULT_BIND_ADDRESS : &'static str = "0.0.0.0:12345"; const DEFAULT_RUST_LOG: &'static str = "debug,actix_web=info"; @@ -136,6 +137,11 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> .route(web::post().to(logout_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service( + web::resource("/session") + .route(web::get().to(session_info_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) .service( web::scope("/foo") .service( diff --git a/src/util/session_checker.rs b/src/util/session_checker.rs index 1ff6c6f1ff..92739e40c0 100644 --- a/src/util/session_checker.rs +++ b/src/util/session_checker.rs @@ -1,7 +1,10 @@ -use crate::util::cookies::CookieManager; -use sqlx::MySqlPool; -use crate::AnyhowResult; use actix_web::HttpRequest; +use anyhow::anyhow; +use crate::AnyhowResult; +use crate::util::cookies::CookieManager; +use log::{info, warn}; +use sqlx::MySqlPool; +use sqlx::error::Error::RowNotFound; #[derive(Clone)] pub struct SessionChecker { @@ -9,8 +12,8 @@ pub struct SessionChecker { } pub struct SessionRecord { - session_token: String, - user_token: String, + pub session_token: String, + pub user_token: String, } pub struct SessionUserRecord { @@ -38,7 +41,7 @@ impl SessionChecker { }; // NB: Lookup failure is Err(RowNotFound). - let session_record = sqlx::query_as!( + let maybe_session_record = sqlx::query_as!( SessionRecord, r#" SELECT @@ -51,9 +54,25 @@ AND deleted_at IS NULL session_token.to_string(), ) .fetch_one(pool) - .await?; // TODO: This will return error if it doesn't exist + .await; // TODO: This will return error if it doesn't exist - Ok(Some(session_record)) + match maybe_session_record { + Ok(session_record) => { + Ok(Some(session_record)) + }, + Err(err) => { + match err { + RowNotFound => { + warn!("Valid cookie; invalid session: {}", session_token); + Ok(None) + }, + _ => { + warn!("Session query error: {:?}", err); + Err(anyhow!("session query error: {:?}", err)) + } + } + } + } } /*pub async fn maybe_get_user(request: &HttpRequest, pool: &MySqlPool)