diff --git a/src/api/v1/auth/login.rs b/src/api/v1/auth/login.rs index b0bee13..bc6af8c 100644 --- a/src/api/v1/auth/login.rs +++ b/src/api/v1/auth/login.rs @@ -1,7 +1,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{post, web, Error, HttpResponse}; +use actix_web::{error, post, web, Error, HttpResponse}; use argon2::{PasswordHash, PasswordVerifier}; +use futures::StreamExt; use log::error; use serde::Deserialize; @@ -18,11 +19,25 @@ struct LoginInformation { device_name: String, } +const MAX_SIZE: usize = 262_144; + #[post("/login")] pub async fn response( - login_information: web::Json, + mut payload: web::Payload, data: web::Data, ) -> Result { + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + + let login_information = serde_json::from_slice::(&body)?; + if !PASSWORD_REGEX.is_match(&login_information.password) { return Ok(HttpResponse::Forbidden().json(r#"{ "password_hashed": false }"#)); } @@ -30,7 +45,7 @@ pub async fn response( if EMAIL_REGEX.is_match(&login_information.username) { let row = sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE email = $1") - .bind(&login_information.username) + .bind(login_information.username) .fetch_one(&data.pool) .await; @@ -52,15 +67,15 @@ pub async fn response( return Ok(login( data.clone(), uuid, - login_information.password.clone(), + login_information.password, password, - login_information.device_name.clone(), + login_information.device_name, ) .await); } else if USERNAME_REGEX.is_match(&login_information.username) { let row = sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE username = $1") - .bind(&login_information.username) + .bind(login_information.username) .fetch_one(&data.pool) .await; @@ -82,9 +97,9 @@ pub async fn response( return Ok(login( data.clone(), uuid, - login_information.password.clone(), + login_information.password, password, - login_information.device_name.clone(), + login_information.device_name, ) .await); } diff --git a/src/api/v1/auth/register.rs b/src/api/v1/auth/register.rs index 6c1db45..a56dd0e 100644 --- a/src/api/v1/auth/register.rs +++ b/src/api/v1/auth/register.rs @@ -1,10 +1,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{Error, HttpResponse, post, web}; +use actix_web::{Error, HttpResponse, error, post, web}; use argon2::{ PasswordHasher, password_hash::{SaltString, rand_core::OsRng}, }; +use futures::StreamExt; use log::error; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -53,8 +54,21 @@ impl Default for ResponseError { } } +const MAX_SIZE: usize = 262_144; + #[post("/register")] -pub async fn res(account_information: web::Json, data: web::Data) -> Result { +pub async fn res(mut payload: web::Payload, data: web::Data) -> Result { + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + let account_information = serde_json::from_slice::(&body)?; + let uuid = Uuid::now_v7(); if !EMAIL_REGEX.is_match(&account_information.email) { @@ -93,9 +107,10 @@ pub async fn res(account_information: web::Json, data: web:: "INSERT INTO users (uuid, username, password, email) VALUES ( '{}', $1, $2, $3 )", uuid )) - .bind(&account_information.identifier) + .bind(account_information.identifier) + // FIXME: Password has no security currently, either from a client or server perspective .bind(hashed_password.to_string()) - .bind(&account_information.email) + .bind(account_information.email) .execute(&data.pool) .await { @@ -125,7 +140,7 @@ pub async fn res(account_information: web::Json, data: web:: if let Err(error) = sqlx::query(&format!("INSERT INTO refresh_tokens (token, uuid, created_at, device_name) VALUES ($1, '{}', $2, $3 )", uuid)) .bind(&refresh_token) .bind(current_time) - .bind(&account_information.device_name) + .bind(account_information.device_name) .execute(&data.pool) .await { error!("{}", error); diff --git a/src/api/v1/auth/revoke.rs b/src/api/v1/auth/revoke.rs index 97d05a5..9ebbb30 100644 --- a/src/api/v1/auth/revoke.rs +++ b/src/api/v1/auth/revoke.rs @@ -1,6 +1,6 @@ -use actix_web::{Error, HttpRequest, HttpResponse, post, web}; +use actix_web::{Error, HttpRequest, HttpResponse, error, post, web}; use argon2::{PasswordHash, PasswordVerifier}; -use futures::{future}; +use futures::{StreamExt, future}; use log::error; use serde::{Deserialize, Serialize}; @@ -23,11 +23,12 @@ impl Response { } } -// TODO: Should maybe be a delete request? +const MAX_SIZE: usize = 262_144; + #[post("/revoke")] pub async fn res( req: HttpRequest, - revoke_request: web::Json, + mut payload: web::Payload, data: web::Data, ) -> Result { let headers = req.headers(); @@ -38,6 +39,18 @@ pub async fn res( return Ok(error); } + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + + let revoke_request = serde_json::from_slice::(&body)?; + let authorized = check_access_token(auth_header.unwrap(), &data.pool).await; if let Err(error) = authorized { @@ -81,7 +94,7 @@ pub async fn res( "SELECT token FROM refresh_tokens WHERE uuid = '{}' AND device_name = $1", uuid )) - .bind(&revoke_request.device_name) + .bind(revoke_request.device_name) .fetch_all(&data.pool) .await;