refactor(treewide)/feat(api): implemented /account/id - ran rustfmt over the codebase
This commit is contained in:
parent
2d7d66bed4
commit
27aadf46d5
|
@ -17,3 +17,5 @@ __Content - JSON:__
|
||||||
|
|
||||||
### 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.
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -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((
|
||||||
|
match Account::from_id(pool, id.id()).await? {
|
||||||
Some(m) => m.username,
|
Some(m) => m.username,
|
||||||
None => return Err(Error::msg(format!("The account with id {} was not found", id.id()))),
|
None => {
|
||||||
|
return Err(Error::msg(format!(
|
||||||
|
"The account with id {} was not found",
|
||||||
|
id.id()
|
||||||
|
)))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
id.id(),
|
id.id(),
|
||||||
id.flags()));
|
id.flags(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(data::InfoResponse::Success(data::InfoSuccess {
|
Ok(data::InfoResponse::Success(data::InfoSuccess {
|
||||||
|
@ -94,14 +100,16 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(),
|
members: row
|
||||||
}))
|
.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.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(),
|
members: row
|
||||||
}))
|
.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,10 +103,7 @@ 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;"#,
|
|
||||||
self.id
|
|
||||||
)
|
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue