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

@ -17,3 +17,5 @@ __Content - JSON:__
### 401 - Error: Unauthorized
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},
Argon2,
};
use base64::{Engine, engine::general_purpose::STANDARD as Base64Engine};
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
use sha2::{Digest, Sha256};
use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};

View File

@ -13,7 +13,9 @@ async fn register(
Ok(resp) => match resp {
data::RegisterResponse::Success => HttpResponse::Ok().finish(),
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(),
},
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")]
async fn tokens_delete(
data: web::Data<ApiState>,

View File

@ -73,6 +73,19 @@ pub enum DeleteResponse {
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)]
pub struct TokenDeleteRequest {
pub token: String,

View File

@ -1,7 +1,7 @@
use crate::{
accounts::Account,
api::account::data,
security::{is_sql_injection, AlphaExt, meet_password_criteria},
security::{is_sql_injection, meet_password_criteria, AlphaExt},
tokens::{AuthToken, VerificationToken},
};
use anyhow::Result;
@ -52,11 +52,15 @@ pub async fn register(
)?;
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) {
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?;
@ -172,6 +176,31 @@ pub async fn delete(
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(
pool: &PgPool,
auth: String,

View File

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

View File

@ -1,11 +1,15 @@
use crate::api::ApiState;
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 log::error;
#[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 {
Ok(resp) => match resp {
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")]
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() {
if let Some(id) = query.id {
data::InfoRequest::Id(id)
@ -51,8 +57,11 @@ async fn info(data: web::Data<ApiState>, query: web::Query<data::ProjectIdQuery>
}
#[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() {
if let Some(id) = query.id {
data::DeleteRequest::Id(id)

View File

@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct ProjectIdQuery {
pub name: Option<String>,

View File

@ -1,3 +1,5 @@
use crate::accounts::Account;
use crate::projects::ProjectMemberFlags;
use crate::{
api::project::data,
projects::Project,
@ -6,8 +8,6 @@ use crate::{
};
use anyhow::{Error, Result};
use sqlx::PgPool;
use crate::accounts::Account;
use crate::projects::ProjectMemberFlags;
pub async fn create(
pool: &PgPool,
@ -46,22 +46,28 @@ pub async fn info(pool: &PgPool, request: data::InfoRequest) -> Result<data::Inf
}
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,
None => return Ok(data::InfoResponse::NotFound)
None => return Ok(data::InfoResponse::NotFound),
};
let mut members = Vec::new();
members.reserve(project.members.len());
for &id in project.members.iter() {
members.push((match Account::from_id(pool, id.id()).await? {
Some(m) => m.username,
None => return Err(Error::msg(format!("The account with id {} was not found", id.id()))),
},
id.id(),
id.flags()));
members.push((
match Account::from_id(pool, id.id()).await? {
Some(m) => m.username,
None => {
return Err(Error::msg(format!(
"The account with id {} was not found",
id.id()
)))
}
},
id.id(),
id.flags(),
));
}
Ok(data::InfoResponse::Success(data::InfoSuccess {
@ -94,14 +100,16 @@ pub async fn delete(
}
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,
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);
}

View File

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

View File

@ -6,14 +6,18 @@ pub fn is_sql_injection(string: &String) -> bool {
}
pub fn meet_password_criteria(string: &String) -> bool {
if string.len() < 12 ||
!string.chars().any(|c| c.is_numeric()) ||
!string.chars().any(|c| c.is_lowercase()) ||
!string.chars().any(|c| c.is_uppercase()) ||
!string.chars().any(|c|{
if string.len() < 12
|| !string.chars().any(|c| c.is_numeric())
|| !string.chars().any(|c| c.is_lowercase())
|| !string.chars().any(|c| c.is_uppercase())
|| !string.chars().any(|c| {
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;
}
true