refactor(treewide)/feat(api): implemented /account/id - ran rustfmt over the codebase

This commit is contained in:
antifallobst 2023-09-10 14:14:29 +02:00
parent 2d7d66bed4
commit 27aadf46d5
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
11 changed files with 148 additions and 65 deletions

View File

@ -16,4 +16,6 @@ __Content - JSON:__
| username | The projects unique name. | | username | The projects unique name. |
### 401 - Error: Unauthorized ### 401 - Error: Unauthorized
The provided access token doesn't allow you to perform this operation. The provided access token doesn't allow you to perform this operation.
### 403 - Error: Forbidden
Blocked for security reasons.

View File

@ -3,7 +3,7 @@ use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2, Argon2,
}; };
use base64::{Engine, engine::general_purpose::STANDARD as Base64Engine}; use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};

View File

@ -13,7 +13,9 @@ async fn register(
Ok(resp) => match resp { Ok(resp) => match resp {
data::RegisterResponse::Success => HttpResponse::Ok().finish(), data::RegisterResponse::Success => HttpResponse::Ok().finish(),
data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)), data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)),
data::RegisterResponse::Unprocessable(b) => HttpResponse::UnprocessableEntity().json(web::Json(b)), data::RegisterResponse::Unprocessable(b) => {
HttpResponse::UnprocessableEntity().json(web::Json(b))
}
data::RegisterResponse::Blocked => HttpResponse::Forbidden().finish(), data::RegisterResponse::Blocked => HttpResponse::Forbidden().finish(),
}, },
Err(e) => { Err(e) => {
@ -84,6 +86,21 @@ async fn delete(
} }
} }
#[get("/account/id")]
async fn id(data: web::Data<ApiState>, auth: BearerAuth) -> impl Responder {
match handlers::id(&data.pool, auth.token().to_string()).await {
Ok(resp) => match resp {
data::IdResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
data::IdResponse::Unauthorized => HttpResponse::Unauthorized().finish(),
data::IdResponse::Blocked => HttpResponse::Forbidden().finish(),
},
Err(e) => {
error!("While handling delete request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}
#[delete("/account/tokens")] #[delete("/account/tokens")]
async fn tokens_delete( async fn tokens_delete(
data: web::Data<ApiState>, data: web::Data<ApiState>,

View File

@ -73,6 +73,19 @@ pub enum DeleteResponse {
Unauthorized, Unauthorized,
} }
#[derive(Debug, Serialize)]
pub struct IdSuccess {
pub id: i64,
pub username: String,
}
#[derive(Debug, Serialize)]
pub enum IdResponse {
Success(IdSuccess),
Unauthorized,
Blocked,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct TokenDeleteRequest { pub struct TokenDeleteRequest {
pub token: String, pub token: String,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
accounts::Account, accounts::Account,
api::account::data, api::account::data,
security::{is_sql_injection, AlphaExt, meet_password_criteria}, security::{is_sql_injection, meet_password_criteria, AlphaExt},
tokens::{AuthToken, VerificationToken}, tokens::{AuthToken, VerificationToken},
}; };
use anyhow::Result; use anyhow::Result;
@ -52,11 +52,15 @@ pub async fn register(
)?; )?;
if !email_regex.is_match(&request.email) { if !email_regex.is_match(&request.email) {
return Ok(data::RegisterResponse::Unprocessable(data::RegisterUnprocessable::Email)); return Ok(data::RegisterResponse::Unprocessable(
data::RegisterUnprocessable::Email,
));
} }
if !meet_password_criteria(&request.password) { if !meet_password_criteria(&request.password) {
return Ok(data::RegisterResponse::Unprocessable(data::RegisterUnprocessable::Password)); return Ok(data::RegisterResponse::Unprocessable(
data::RegisterUnprocessable::Password,
));
} }
let account = Account::new(pool, &request.username, &request.email, &request.password).await?; let account = Account::new(pool, &request.username, &request.email, &request.password).await?;
@ -172,6 +176,31 @@ pub async fn delete(
Ok(data::DeleteResponse::Success) Ok(data::DeleteResponse::Success)
} }
pub async fn id(pool: &PgPool, auth: String) -> Result<data::IdResponse> {
if !auth.is_alpha() {
return Ok(data::IdResponse::Blocked);
}
let token = match AuthToken::check(pool, &auth).await? {
Some(t) => t,
None => return Ok(data::IdResponse::Unauthorized),
};
let account = match Account::from_id(pool, token.account.id()).await? {
Some(a) => a,
None => {
return Err(anyhow::Error::msg(
"Failed to get account data. Account not found in database",
))
}
};
Ok(data::IdResponse::Success(data::IdSuccess {
id: account.id.id(),
username: account.username,
}))
}
pub async fn token_delete( pub async fn token_delete(
pool: &PgPool, pool: &PgPool,
auth: String, auth: String,

View File

@ -91,6 +91,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
.service(account::calls::verify) .service(account::calls::verify)
.service(account::calls::authenticate) .service(account::calls::authenticate)
.service(account::calls::delete) .service(account::calls::delete)
.service(account::calls::id)
.service(account::calls::tokens_delete) .service(account::calls::tokens_delete)
.service(account::calls::tokens_get) .service(account::calls::tokens_get)
.service(project::calls::create) .service(project::calls::create)

View File

@ -1,11 +1,15 @@
use crate::api::ApiState;
use crate::api::project::{data, handlers}; use crate::api::project::{data, handlers};
use actix_web::{get, post, web, HttpResponse, Responder, delete}; use crate::api::ApiState;
use actix_web::{delete, get, post, web, HttpResponse, Responder};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use log::error; use log::error;
#[post("/project/create")] #[post("/project/create")]
async fn create(data: web::Data<ApiState>, auth: BearerAuth, body: web::Json<data::CreateRequest>) -> impl Responder { async fn create(
data: web::Data<ApiState>,
auth: BearerAuth,
body: web::Json<data::CreateRequest>,
) -> impl Responder {
match handlers::create(&data.pool, auth.token().to_string(), body.into_inner()).await { match handlers::create(&data.pool, auth.token().to_string(), body.into_inner()).await {
Ok(resp) => match resp { Ok(resp) => match resp {
data::CreateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), data::CreateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
@ -21,8 +25,10 @@ async fn create(data: web::Data<ApiState>, auth: BearerAuth, body: web::Json<dat
} }
#[get("/project/info")] #[get("/project/info")]
async fn info(data: web::Data<ApiState>, query: web::Query<data::ProjectIdQuery>) -> impl Responder { async fn info(
data: web::Data<ApiState>,
query: web::Query<data::ProjectIdQuery>,
) -> impl Responder {
let request = if query.id.is_none() || query.name.is_none() { let request = if query.id.is_none() || query.name.is_none() {
if let Some(id) = query.id { if let Some(id) = query.id {
data::InfoRequest::Id(id) data::InfoRequest::Id(id)
@ -51,8 +57,11 @@ async fn info(data: web::Data<ApiState>, query: web::Query<data::ProjectIdQuery>
} }
#[delete("/project/delete")] #[delete("/project/delete")]
async fn delete(data: web::Data<ApiState>, auth: BearerAuth, query: web::Query<data::ProjectIdQuery>) -> impl Responder { async fn delete(
data: web::Data<ApiState>,
auth: BearerAuth,
query: web::Query<data::ProjectIdQuery>,
) -> impl Responder {
let request = if query.id.is_none() || query.name.is_none() { let request = if query.id.is_none() || query.name.is_none() {
if let Some(id) = query.id { if let Some(id) = query.id {
data::DeleteRequest::Id(id) data::DeleteRequest::Id(id)

View File

@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ProjectIdQuery { pub struct ProjectIdQuery {
pub name: Option<String>, pub name: Option<String>,
@ -60,4 +59,4 @@ pub enum DeleteResponse {
Unauthorized, Unauthorized,
Blocked, Blocked,
NotFound, NotFound,
} }

View File

@ -1,3 +1,5 @@
use crate::accounts::Account;
use crate::projects::ProjectMemberFlags;
use crate::{ use crate::{
api::project::data, api::project::data,
projects::Project, projects::Project,
@ -6,8 +8,6 @@ use crate::{
}; };
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use sqlx::PgPool; use sqlx::PgPool;
use crate::accounts::Account;
use crate::projects::ProjectMemberFlags;
pub async fn create( pub async fn create(
pool: &PgPool, pool: &PgPool,
@ -26,7 +26,7 @@ pub async fn create(
if is_sql_injection(&request.name) || is_sql_injection(&request.description) { if is_sql_injection(&request.name) || is_sql_injection(&request.description) {
return Ok(data::CreateResponse::Blocked); return Ok(data::CreateResponse::Blocked);
} }
if let Some(_) = Project::from_name(pool, &request.name).await? { if let Some(_) = Project::from_name(pool, &request.name).await? {
return Ok(data::CreateResponse::Conflict); return Ok(data::CreateResponse::Conflict);
} }
@ -46,22 +46,28 @@ pub async fn info(pool: &PgPool, request: data::InfoRequest) -> Result<data::Inf
} }
Project::from_name(pool, &name).await? Project::from_name(pool, &name).await?
} }
data::InfoRequest::Id(id) => Project::from_id(pool, id).await? data::InfoRequest::Id(id) => Project::from_id(pool, id).await?,
} } {
{
Some(p) => p, Some(p) => p,
None => return Ok(data::InfoResponse::NotFound) None => return Ok(data::InfoResponse::NotFound),
}; };
let mut members = Vec::new(); let mut members = Vec::new();
members.reserve(project.members.len()); members.reserve(project.members.len());
for &id in project.members.iter() { for &id in project.members.iter() {
members.push((match Account::from_id(pool, id.id()).await? { members.push((
Some(m) => m.username, match Account::from_id(pool, id.id()).await? {
None => return Err(Error::msg(format!("The account with id {} was not found", id.id()))), Some(m) => m.username,
}, None => {
id.id(), return Err(Error::msg(format!(
id.flags())); "The account with id {} was not found",
id.id()
)))
}
},
id.id(),
id.flags(),
));
} }
Ok(data::InfoResponse::Success(data::InfoSuccess { Ok(data::InfoResponse::Success(data::InfoSuccess {
@ -94,18 +100,20 @@ pub async fn delete(
} }
Project::from_name(pool, &name).await? Project::from_name(pool, &name).await?
} }
data::DeleteRequest::Id(id) => Project::from_id(pool, id).await? data::DeleteRequest::Id(id) => Project::from_id(pool, id).await?,
} } {
{
Some(p) => p, Some(p) => p,
None => return Ok(data::DeleteResponse::NotFound) None => return Ok(data::DeleteResponse::NotFound),
}; };
if !project.member_has_flag(&token.account, ProjectMemberFlags::Owner).await { if !project
.member_has_flag(&token.account, ProjectMemberFlags::Owner)
.await
{
return Ok(data::DeleteResponse::Unauthorized); return Ok(data::DeleteResponse::Unauthorized);
} }
project.delete(pool).await?; project.delete(pool).await?;
Ok(data::DeleteResponse::Success) Ok(data::DeleteResponse::Success)
} }

View File

@ -53,15 +53,17 @@ impl Project {
.fetch_one(pool) .fetch_one(pool)
.await .await
{ {
Ok(row) => { Ok(row) => Ok(Some(Self {
Ok(Some(Self { id: row.id,
id: row.id, name: row.name,
name: row.name, description: row.description,
description: row.description, created: row.created,
created: row.created, members: row
members: row.members.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(), .members
})) .iter()
}, .map(|&raw_id| raw_id.into())
.collect::<Vec<ID>>(),
})),
Err(sqlx::Error::RowNotFound) => Ok(None), Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Error::new(e)), Err(e) => Err(Error::new(e)),
} }
@ -72,15 +74,17 @@ impl Project {
.fetch_one(pool) .fetch_one(pool)
.await .await
{ {
Ok(row) => { Ok(row) => Ok(Some(Self {
Ok(Some(Self { id: row.id,
id: row.id, name: row.name,
name: row.name, description: row.description,
description: row.description, created: row.created,
created: row.created, members: row
members: row.members.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(), .members
})) .iter()
}, .map(|&raw_id| raw_id.into())
.collect::<Vec<ID>>(),
})),
Err(sqlx::Error::RowNotFound) => Ok(None), Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Error::new(e)), Err(e) => Err(Error::new(e)),
} }
@ -99,12 +103,9 @@ impl Project {
} }
pub async fn delete(&self, pool: &PgPool) -> Result<()> { pub async fn delete(&self, pool: &PgPool) -> Result<()> {
sqlx::query!( sqlx::query!(r#"DELETE FROM Projects WHERE id = $1;"#, self.id)
r#"DELETE FROM Projects WHERE id = $1;"#, .execute(pool)
self.id .await?;
)
.execute(pool)
.await?;
Ok(()) Ok(())
} }

View File

@ -6,14 +6,18 @@ pub fn is_sql_injection(string: &String) -> bool {
} }
pub fn meet_password_criteria(string: &String) -> bool { pub fn meet_password_criteria(string: &String) -> bool {
if string.len() < 12 || if string.len() < 12
!string.chars().any(|c| c.is_numeric()) || || !string.chars().any(|c| c.is_numeric())
!string.chars().any(|c| c.is_lowercase()) || || !string.chars().any(|c| c.is_lowercase())
!string.chars().any(|c| c.is_uppercase()) || || !string.chars().any(|c| c.is_uppercase())
!string.chars().any(|c|{ || !string.chars().any(|c| {
let x = c as u8; let x = c as u8;
(x >= 32 && x <= 47) || (x >= 58 && x <= 64) || (x >= 91 && x <= 96) || (x >= 123 && x <= 126) (x >= 32 && x <= 47)
}) { || (x >= 58 && x <= 64)
|| (x >= 91 && x <= 96)
|| (x >= 123 && x <= 126)
})
{
return false; return false;
} }
true true