style: cargo clippy and format
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful

This commit is contained in:
Radical 2025-05-02 15:20:22 +02:00
parent 5d0d666094
commit 97f7595cc5
10 changed files with 289 additions and 159 deletions

View file

@ -1,13 +1,16 @@
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{error, post, web, Error, HttpResponse}; use actix_web::{Error, HttpResponse, error, post, web};
use argon2::{PasswordHash, PasswordVerifier}; use argon2::{PasswordHash, PasswordVerifier};
use futures::StreamExt;
use log::error; use log::error;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::StreamExt;
use crate::{crypto::{generate_access_token, generate_refresh_token}, Data}; use crate::{
Data,
crypto::{generate_access_token, generate_refresh_token},
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct LoginInformation { struct LoginInformation {
@ -25,7 +28,10 @@ pub struct Response {
const MAX_SIZE: usize = 262_144; const MAX_SIZE: usize = 262_144;
#[post("/login")] #[post("/login")]
pub async fn response(mut payload: web::Payload, data: web::Data<Data>) -> Result<HttpResponse, Error> { pub async fn response(
mut payload: web::Payload,
data: web::Data<Data>,
) -> Result<HttpResponse, Error> {
let mut body = web::BytesMut::new(); let mut body = web::BytesMut::new();
while let Some(chunk) = payload.next().await { while let Some(chunk) = payload.next().await {
let chunk = chunk?; let chunk = chunk?;
@ -51,45 +57,82 @@ pub async fn response(mut payload: web::Payload, data: web::Data<Data>) -> Resul
} }
if email_regex.is_match(&login_information.username) { if email_regex.is_match(&login_information.username) {
if let Ok(row) = sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE email = $1").bind(login_information.username).fetch_one(&data.pool).await { if let Ok(row) =
sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE email = $1")
.bind(login_information.username)
.fetch_one(&data.pool)
.await
{
let (uuid, password): (String, String) = row; let (uuid, password): (String, String) = row;
return Ok(login(data.clone(), uuid, login_information.password, password, login_information.device_name).await) return Ok(login(
data.clone(),
uuid,
login_information.password,
password,
login_information.device_name,
)
.await);
} }
return Ok(HttpResponse::Unauthorized().finish()) return Ok(HttpResponse::Unauthorized().finish());
} else if username_regex.is_match(&login_information.username) { } else if username_regex.is_match(&login_information.username) {
if let Ok(row) = sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE username = $1").bind(login_information.username).fetch_one(&data.pool).await { if let Ok(row) =
sqlx::query_as("SELECT CAST(uuid as VARCHAR), password FROM users WHERE username = $1")
.bind(login_information.username)
.fetch_one(&data.pool)
.await
{
let (uuid, password): (String, String) = row; let (uuid, password): (String, String) = row;
return Ok(login(data.clone(), uuid, login_information.password, password, login_information.device_name).await) return Ok(login(
data.clone(),
uuid,
login_information.password,
password,
login_information.device_name,
)
.await);
} }
return Ok(HttpResponse::Unauthorized().finish()) return Ok(HttpResponse::Unauthorized().finish());
} }
Ok(HttpResponse::Unauthorized().finish()) Ok(HttpResponse::Unauthorized().finish())
} }
async fn login(data: actix_web::web::Data<Data>, uuid: String, request_password: String, database_password: String, device_name: String) -> HttpResponse { async fn login(
data: actix_web::web::Data<Data>,
uuid: String,
request_password: String,
database_password: String,
device_name: String,
) -> HttpResponse {
if let Ok(parsed_hash) = PasswordHash::new(&database_password) { if let Ok(parsed_hash) = PasswordHash::new(&database_password) {
if data.argon2.verify_password(request_password.as_bytes(), &parsed_hash).is_ok() { if data
.argon2
.verify_password(request_password.as_bytes(), &parsed_hash)
.is_ok()
{
let refresh_token = generate_refresh_token(); let refresh_token = generate_refresh_token();
let access_token = generate_access_token(); let access_token = generate_access_token();
if refresh_token.is_err() { if refresh_token.is_err() {
error!("{}", refresh_token.unwrap_err()); error!("{}", refresh_token.unwrap_err());
return HttpResponse::InternalServerError().finish() return HttpResponse::InternalServerError().finish();
} }
let refresh_token = refresh_token.unwrap(); let refresh_token = refresh_token.unwrap();
if access_token.is_err() { if access_token.is_err() {
error!("{}", access_token.unwrap_err()); error!("{}", access_token.unwrap_err());
return HttpResponse::InternalServerError().finish() return HttpResponse::InternalServerError().finish();
} }
let access_token = access_token.unwrap(); let access_token = access_token.unwrap();
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
if let Err(error) = sqlx::query(&format!("INSERT INTO refresh_tokens (token, uuid, created, device_name) VALUES ($1, '{}', $2, $3 )", uuid)) if let Err(error) = sqlx::query(&format!("INSERT INTO refresh_tokens (token, uuid, created, device_name) VALUES ($1, '{}', $2, $3 )", uuid))
.bind(&refresh_token) .bind(&refresh_token)
@ -114,10 +157,10 @@ async fn login(data: actix_web::web::Data<Data>, uuid: String, request_password:
return HttpResponse::Ok().json(Response { return HttpResponse::Ok().json(Response {
access_token, access_token,
refresh_token, refresh_token,
}) });
} }
return HttpResponse::Unauthorized().finish() return HttpResponse::Unauthorized().finish();
} }
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()

View file

@ -1,13 +1,16 @@
use std::{str::FromStr, time::{SystemTime, UNIX_EPOCH}}; use std::{
str::FromStr,
time::{SystemTime, UNIX_EPOCH},
};
use actix_web::{web, HttpResponse, Scope}; use actix_web::{HttpResponse, Scope, web};
use log::error; use log::error;
use sqlx::Postgres; use sqlx::Postgres;
use uuid::Uuid; use uuid::Uuid;
mod register;
mod login; mod login;
mod refresh; mod refresh;
mod register;
mod revoke; mod revoke;
pub fn web() -> Scope { pub fn web() -> Scope {
@ -18,24 +21,33 @@ pub fn web() -> Scope {
.service(revoke::res) .service(revoke::res)
} }
pub async fn check_access_token<'a>(access_token: String, pool: &'a sqlx::Pool<Postgres>) -> Result<Uuid, HttpResponse> { pub async fn check_access_token(
match sqlx::query_as("SELECT CAST(uuid as VARCHAR), created FROM access_tokens WHERE token = $1") access_token: String,
pool: &sqlx::Pool<Postgres>,
) -> Result<Uuid, HttpResponse> {
match sqlx::query_as(
"SELECT CAST(uuid as VARCHAR), created FROM access_tokens WHERE token = $1",
)
.bind(&access_token) .bind(&access_token)
.fetch_one(&*pool) .fetch_one(pool)
.await { .await
{
Ok(row) => { Ok(row) => {
let (uuid, created): (String, i64) = row; let (uuid, created): (String, i64) = row;
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let lifetime = current_time - created; let lifetime = current_time - created;
if lifetime > 3600 { if lifetime > 3600 {
return Err(HttpResponse::Unauthorized().finish()) return Err(HttpResponse::Unauthorized().finish());
} }
Ok(Uuid::from_str(&uuid).unwrap()) Ok(Uuid::from_str(&uuid).unwrap())
}, }
Err(error) => { Err(error) => {
error!("{}", error); error!("{}", error);
Err(HttpResponse::InternalServerError().finish()) Err(HttpResponse::InternalServerError().finish())

View file

@ -1,10 +1,13 @@
use std::time::{SystemTime, UNIX_EPOCH}; use actix_web::{Error, HttpResponse, error, post, web};
use actix_web::{error, post, web, Error, HttpResponse}; use futures::StreamExt;
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::StreamExt; use std::time::{SystemTime, UNIX_EPOCH};
use crate::{crypto::{generate_access_token, generate_refresh_token}, Data}; use crate::{
Data,
crypto::{generate_access_token, generate_refresh_token},
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct RefreshRequest { struct RefreshRequest {
@ -33,15 +36,24 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
let refresh_request = serde_json::from_slice::<RefreshRequest>(&body)?; let refresh_request = serde_json::from_slice::<RefreshRequest>(&body)?;
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
if let Ok(row) = sqlx::query_as("SELECT CAST(uuid as VARCHAR), created FROM refresh_tokens WHERE token = $1").bind(&refresh_request.refresh_token).fetch_one(&data.pool).await { if let Ok(row) =
sqlx::query_as("SELECT CAST(uuid as VARCHAR), created FROM refresh_tokens WHERE token = $1")
.bind(&refresh_request.refresh_token)
.fetch_one(&data.pool)
.await
{
let (uuid, created): (String, i64) = row; let (uuid, created): (String, i64) = row;
if let Err(error) = sqlx::query("DELETE FROM access_tokens WHERE refresh_token = $1") if let Err(error) = sqlx::query("DELETE FROM access_tokens WHERE refresh_token = $1")
.bind(&refresh_request.refresh_token) .bind(&refresh_request.refresh_token)
.execute(&data.pool) .execute(&data.pool)
.await { .await
{
error!("{}", error); error!("{}", error);
} }
@ -51,14 +63,18 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
if let Err(error) = sqlx::query("DELETE FROM refresh_tokens WHERE token = $1") if let Err(error) = sqlx::query("DELETE FROM refresh_tokens WHERE token = $1")
.bind(&refresh_request.refresh_token) .bind(&refresh_request.refresh_token)
.execute(&data.pool) .execute(&data.pool)
.await { .await
{
error!("{}", error); error!("{}", error);
} }
return Ok(HttpResponse::Unauthorized().finish()) return Ok(HttpResponse::Unauthorized().finish());
} }
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let mut refresh_token = refresh_request.refresh_token; let mut refresh_token = refresh_request.refresh_token;
@ -67,23 +83,24 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
if new_refresh_token.is_err() { if new_refresh_token.is_err() {
error!("{}", new_refresh_token.unwrap_err()); error!("{}", new_refresh_token.unwrap_err());
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
let new_refresh_token = new_refresh_token.unwrap(); let new_refresh_token = new_refresh_token.unwrap();
match sqlx::query("UPDATE refresh_tokens SET token = $1, created = $2 WHERE token = $3") match sqlx::query("UPDATE refresh_tokens SET token = $1, created = $2 WHERE token = $3")
.bind(&new_refresh_token) .bind(&new_refresh_token)
.bind(&current_time) .bind(current_time)
.bind(&refresh_token) .bind(&refresh_token)
.execute(&data.pool) .execute(&data.pool)
.await { .await
{
Ok(_) => { Ok(_) => {
refresh_token = new_refresh_token; refresh_token = new_refresh_token;
}, }
Err(error) => { Err(error) => {
error!("{}", error); error!("{}", error);
}, }
} }
} }
@ -91,7 +108,7 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
if access_token.is_err() { if access_token.is_err() {
error!("{}", access_token.unwrap_err()); error!("{}", access_token.unwrap_err());
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
let access_token = access_token.unwrap(); let access_token = access_token.unwrap();
@ -108,8 +125,8 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
return Ok(HttpResponse::Ok().json(Response { return Ok(HttpResponse::Ok().json(Response {
refresh_token, refresh_token,
access_token access_token,
})) }));
} }
Ok(HttpResponse::Unauthorized().finish()) Ok(HttpResponse::Unauthorized().finish())

View file

@ -1,15 +1,21 @@
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{error, post, web, Error, HttpResponse}; use actix_web::{Error, HttpResponse, error, post, web};
use argon2::{
PasswordHasher,
password_hash::{SaltString, rand_core::OsRng},
};
use futures::StreamExt;
use log::error; use log::error;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::StreamExt;
use uuid::Uuid; use uuid::Uuid;
use argon2::{password_hash::{rand_core::OsRng, SaltString}, PasswordHasher};
use crate::{crypto::{generate_access_token, generate_refresh_token}, Data};
use super::login::Response; use super::login::Response;
use crate::{
Data,
crypto::{generate_access_token, generate_refresh_token},
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct AccountInformation { struct AccountInformation {
@ -70,68 +76,76 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
let email_regex = Regex::new(r"[-A-Za-z0-9!#$%&'*+/=?^_`{|}~]+(?:\.[-A-Za-z0-9!#$%&'*+/=?^_`{|}~]+)*@(?:[A-Za-z0-9](?:[-A-Za-z0-9]*[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[-A-Za-z0-9]*[A-Za-z0-9])?").unwrap(); let email_regex = Regex::new(r"[-A-Za-z0-9!#$%&'*+/=?^_`{|}~]+(?:\.[-A-Za-z0-9!#$%&'*+/=?^_`{|}~]+)*@(?:[A-Za-z0-9](?:[-A-Za-z0-9]*[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[-A-Za-z0-9]*[A-Za-z0-9])?").unwrap();
if !email_regex.is_match(&account_information.email) { if !email_regex.is_match(&account_information.email) {
return Ok(HttpResponse::Forbidden().json( return Ok(HttpResponse::Forbidden().json(ResponseError {
ResponseError {
email_valid: false, email_valid: false,
..Default::default() ..Default::default()
} }));
))
} }
// FIXME: This regex doesnt seem to be working // FIXME: This regex doesnt seem to be working
let username_regex = Regex::new(r"[a-zA-Z0-9.-_]").unwrap(); let username_regex = Regex::new(r"[a-zA-Z0-9.-_]").unwrap();
if !username_regex.is_match(&account_information.identifier) || account_information.identifier.len() < 3 || account_information.identifier.len() > 32 { if !username_regex.is_match(&account_information.identifier)
return Ok(HttpResponse::Forbidden().json( || account_information.identifier.len() < 3
ResponseError { || account_information.identifier.len() > 32
{
return Ok(HttpResponse::Forbidden().json(ResponseError {
gorb_id_valid: false, gorb_id_valid: false,
..Default::default() ..Default::default()
} }));
))
} }
// Password is expected to be hashed using SHA3-384 // Password is expected to be hashed using SHA3-384
let password_regex = Regex::new(r"[0-9a-f]{96}").unwrap(); let password_regex = Regex::new(r"[0-9a-f]{96}").unwrap();
if !password_regex.is_match(&account_information.password) { if !password_regex.is_match(&account_information.password) {
return Ok(HttpResponse::Forbidden().json( return Ok(HttpResponse::Forbidden().json(ResponseError {
ResponseError {
password_hashed: false, password_hashed: false,
..Default::default() ..Default::default()
} }));
))
} }
let salt = SaltString::generate(&mut OsRng); let salt = SaltString::generate(&mut OsRng);
if let Ok(hashed_password) = data.argon2.hash_password(account_information.password.as_bytes(), &salt) { if let Ok(hashed_password) = data
.argon2
.hash_password(account_information.password.as_bytes(), &salt)
{
// TODO: Check security of this implementation // TODO: Check security of this implementation
return Ok(match sqlx::query(&format!("INSERT INTO users (uuid, username, password, email) VALUES ( '{}', $1, $2, $3 )", uuid)) return Ok(
match sqlx::query(&format!(
"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 // FIXME: Password has no security currently, either from a client or server perspective
.bind(hashed_password.to_string()) .bind(hashed_password.to_string())
.bind(account_information.email) .bind(account_information.email)
.execute(&data.pool) .execute(&data.pool)
.await { .await
{
Ok(_out) => { Ok(_out) => {
let refresh_token = generate_refresh_token(); let refresh_token = generate_refresh_token();
let access_token = generate_access_token(); let access_token = generate_access_token();
if refresh_token.is_err() { if refresh_token.is_err() {
error!("{}", refresh_token.unwrap_err()); error!("{}", refresh_token.unwrap_err());
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
let refresh_token = refresh_token.unwrap(); let refresh_token = refresh_token.unwrap();
if access_token.is_err() { if access_token.is_err() {
error!("{}", access_token.unwrap_err()); error!("{}", access_token.unwrap_err());
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
let access_token = access_token.unwrap(); let access_token = access_token.unwrap();
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
if let Err(error) = sqlx::query(&format!("INSERT INTO refresh_tokens (token, uuid, created, device_name) VALUES ($1, '{}', $2, $3 )", uuid)) if let Err(error) = sqlx::query(&format!("INSERT INTO refresh_tokens (token, uuid, created, device_name) VALUES ($1, '{}', $2, $3 )", uuid))
.bind(&refresh_token) .bind(&refresh_token)
@ -153,32 +167,37 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish())
} }
HttpResponse::Ok().json( HttpResponse::Ok().json(Response {
Response {
access_token, access_token,
refresh_token, refresh_token,
})
} }
)
},
Err(error) => { Err(error) => {
let err_msg = error.as_database_error().unwrap().message(); let err_msg = error.as_database_error().unwrap().message();
match err_msg { match err_msg {
err_msg if err_msg.contains("unique") && err_msg.contains("username_key") => HttpResponse::Forbidden().json(ResponseError { err_msg
if err_msg.contains("unique") && err_msg.contains("username_key") =>
{
HttpResponse::Forbidden().json(ResponseError {
gorb_id_available: false, gorb_id_available: false,
..Default::default() ..Default::default()
}), })
err_msg if err_msg.contains("unique") && err_msg.contains("email_key") => HttpResponse::Forbidden().json(ResponseError { }
err_msg if err_msg.contains("unique") && err_msg.contains("email_key") => {
HttpResponse::Forbidden().json(ResponseError {
email_available: false, email_available: false,
..Default::default() ..Default::default()
}), })
}
_ => { _ => {
error!("{}", err_msg); error!("{}", err_msg);
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
} }
} }
}
}, },
}) );
} }
Ok(HttpResponse::InternalServerError().finish()) Ok(HttpResponse::InternalServerError().finish())

View file

@ -1,10 +1,10 @@
use actix_web::{error, post, web, Error, HttpResponse}; use actix_web::{Error, HttpResponse, error, post, web};
use argon2::{PasswordHash, PasswordVerifier}; use argon2::{PasswordHash, PasswordVerifier};
use futures::{StreamExt, future};
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::{future, StreamExt};
use crate::{api::v1::auth::check_access_token, Data}; use crate::{Data, api::v1::auth::check_access_token};
#[derive(Deserialize)] #[derive(Deserialize)]
struct RevokeRequest { struct RevokeRequest {
@ -20,9 +20,7 @@ struct Response {
impl Response { impl Response {
fn new(deleted: bool) -> Self { fn new(deleted: bool) -> Self {
Self { Self { deleted }
deleted
}
} }
} }
@ -44,18 +42,21 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
let authorized = check_access_token(revoke_request.access_token, &data.pool).await; let authorized = check_access_token(revoke_request.access_token, &data.pool).await;
if authorized.is_err() { if let Err(error) = authorized {
return Ok(authorized.unwrap_err()) return Ok(error);
} }
let uuid = authorized.unwrap(); let uuid = authorized.unwrap();
let database_password_raw = sqlx::query_scalar(&format!("SELECT password FROM users WHERE uuid = '{}'", uuid)) let database_password_raw = sqlx::query_scalar(&format!(
"SELECT password FROM users WHERE uuid = '{}'",
uuid
))
.fetch_one(&data.pool) .fetch_one(&data.pool)
.await; .await;
if database_password_raw.is_err() { if let Err(error) = database_password_raw {
error!("{}", database_password_raw.unwrap_err()); error!("{}", error);
return Ok(HttpResponse::InternalServerError().json(Response::new(false))); return Ok(HttpResponse::InternalServerError().json(Response::new(false)));
} }
@ -63,25 +64,32 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
let hashed_password_raw = PasswordHash::new(&database_password); let hashed_password_raw = PasswordHash::new(&database_password);
if hashed_password_raw.is_err() { if let Err(error) = hashed_password_raw {
error!("{}", hashed_password_raw.unwrap_err()); error!("{}", error);
return Ok(HttpResponse::InternalServerError().json(Response::new(false))); return Ok(HttpResponse::InternalServerError().json(Response::new(false)));
} }
let hashed_password = hashed_password_raw.unwrap(); let hashed_password = hashed_password_raw.unwrap();
if data.argon2.verify_password(revoke_request.password.as_bytes(), &hashed_password).is_err() { if data
return Ok(HttpResponse::Unauthorized().finish()) .argon2
.verify_password(revoke_request.password.as_bytes(), &hashed_password)
.is_err()
{
return Ok(HttpResponse::Unauthorized().finish());
} }
let tokens_raw = sqlx::query_scalar(&format!("SELECT token FROM refresh_tokens WHERE uuid = '{}' AND device_name = $1", uuid)) let tokens_raw = sqlx::query_scalar(&format!(
"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) .fetch_all(&data.pool)
.await; .await;
if tokens_raw.is_err() { if tokens_raw.is_err() {
error!("{:?}", tokens_raw); error!("{:?}", tokens_raw);
return Ok(HttpResponse::InternalServerError().json(Response::new(false))) return Ok(HttpResponse::InternalServerError().json(Response::new(false)));
} }
let tokens: Vec<String> = tokens_raw.unwrap(); let tokens: Vec<String> = tokens_raw.unwrap();
@ -89,33 +97,44 @@ pub async fn res(mut payload: web::Payload, data: web::Data<Data>) -> Result<Htt
let mut access_tokens_delete = vec![]; let mut access_tokens_delete = vec![];
let mut refresh_tokens_delete = vec![]; let mut refresh_tokens_delete = vec![];
for token in tokens { for token in tokens {
access_tokens_delete.push(sqlx::query("DELETE FROM access_tokens WHERE refresh_token = $1") access_tokens_delete.push(
sqlx::query("DELETE FROM access_tokens WHERE refresh_token = $1")
.bind(token.clone()) .bind(token.clone())
.execute(&data.pool)); .execute(&data.pool),
);
refresh_tokens_delete.push(sqlx::query("DELETE FROM refresh_tokens WHERE token = $1") refresh_tokens_delete.push(
sqlx::query("DELETE FROM refresh_tokens WHERE token = $1")
.bind(token.clone()) .bind(token.clone())
.execute(&data.pool)); .execute(&data.pool),
);
} }
let results_access_tokens = future::join_all(access_tokens_delete).await; let results_access_tokens = future::join_all(access_tokens_delete).await;
let results_refresh_tokens = future::join_all(refresh_tokens_delete).await; let results_refresh_tokens = future::join_all(refresh_tokens_delete).await;
let access_tokens_errors: Vec<&Result<sqlx::postgres::PgQueryResult, sqlx::Error>> = results_access_tokens.iter().filter(|r| r.is_err()).collect(); let access_tokens_errors: Vec<&Result<sqlx::postgres::PgQueryResult, sqlx::Error>> =
let refresh_tokens_errors: Vec<&Result<sqlx::postgres::PgQueryResult, sqlx::Error>> = results_refresh_tokens.iter().filter(|r| r.is_err()).collect(); results_access_tokens
.iter()
.filter(|r| r.is_err())
.collect();
let refresh_tokens_errors: Vec<&Result<sqlx::postgres::PgQueryResult, sqlx::Error>> =
results_refresh_tokens
.iter()
.filter(|r| r.is_err())
.collect();
if !access_tokens_errors.is_empty() && !refresh_tokens_errors.is_empty() { if !access_tokens_errors.is_empty() && !refresh_tokens_errors.is_empty() {
error!("{:?}", access_tokens_errors); error!("{:?}", access_tokens_errors);
error!("{:?}", refresh_tokens_errors); error!("{:?}", refresh_tokens_errors);
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} else if !access_tokens_errors.is_empty() { } else if !access_tokens_errors.is_empty() {
error!("{:?}", access_tokens_errors); error!("{:?}", access_tokens_errors);
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} else if !refresh_tokens_errors.is_empty() { } else if !refresh_tokens_errors.is_empty() {
error!("{:?}", refresh_tokens_errors); error!("{:?}", refresh_tokens_errors);
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
Ok(HttpResponse::Ok().json(Response::new(true))) Ok(HttpResponse::Ok().json(Response::new(true)))

View file

@ -1,7 +1,7 @@
use actix_web::{Scope, web}; use actix_web::{Scope, web};
mod stats;
mod auth; mod auth;
mod stats;
mod user; mod user;
pub fn web() -> Scope { pub fn web() -> Scope {

View file

@ -18,10 +18,13 @@ struct Response {
#[get("/stats")] #[get("/stats")]
pub async fn res(data: web::Data<Data>) -> impl Responder { pub async fn res(data: web::Data<Data>) -> impl Responder {
let accounts; let accounts;
if let Ok(users) = sqlx::query("SELECT uuid FROM users").fetch_all(&data.pool).await { if let Ok(users) = sqlx::query("SELECT uuid FROM users")
.fetch_all(&data.pool)
.await
{
accounts = users.len(); accounts = users.len();
} else { } else {
return HttpResponse::InternalServerError().finish() return HttpResponse::InternalServerError().finish();
} }
let response = Response { let response = Response {

View file

@ -1,10 +1,10 @@
use actix_web::{error, post, web, Error, HttpResponse}; use actix_web::{Error, HttpResponse, error, post, web};
use futures::StreamExt;
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::StreamExt;
use uuid::Uuid; use uuid::Uuid;
use crate::{api::v1::auth::check_access_token, Data}; use crate::{Data, api::v1::auth::check_access_token};
#[derive(Deserialize)] #[derive(Deserialize)]
struct AuthenticationRequest { struct AuthenticationRequest {
@ -21,7 +21,11 @@ struct Response {
const MAX_SIZE: usize = 262_144; const MAX_SIZE: usize = 262_144;
#[post("/user/{uuid}")] #[post("/user/{uuid}")]
pub async fn res(mut payload: web::Payload, path: web::Path<(String,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> { pub async fn res(
mut payload: web::Payload,
path: web::Path<(String,)>,
data: web::Data<Data>,
) -> Result<HttpResponse, Error> {
let mut body = web::BytesMut::new(); let mut body = web::BytesMut::new();
while let Some(chunk) = payload.next().await { while let Some(chunk) = payload.next().await {
let chunk = chunk?; let chunk = chunk?;
@ -38,8 +42,8 @@ pub async fn res(mut payload: web::Payload, path: web::Path<(String,)>, data: we
let authorized = check_access_token(authentication_request.access_token, &data.pool).await; let authorized = check_access_token(authentication_request.access_token, &data.pool).await;
if authorized.is_err() { if let Err(error) = authorized {
return Ok(authorized.unwrap_err()) return Ok(error);
} }
let mut uuid = authorized.unwrap(); let mut uuid = authorized.unwrap();
@ -48,23 +52,29 @@ pub async fn res(mut payload: web::Payload, path: web::Path<(String,)>, data: we
let requested_uuid = Uuid::parse_str(&request); let requested_uuid = Uuid::parse_str(&request);
if requested_uuid.is_err() { if requested_uuid.is_err() {
return Ok(HttpResponse::BadRequest().json(r#"{ "error": "UUID is invalid!" }"#)) return Ok(HttpResponse::BadRequest().json(r#"{ "error": "UUID is invalid!" }"#));
} }
uuid = requested_uuid.unwrap() uuid = requested_uuid.unwrap()
} }
let row = sqlx::query_as(&format!(
let row = sqlx::query_as(&format!("SELECT username, display_name FROM users WHERE uuid = '{}'", uuid)) "SELECT username, display_name FROM users WHERE uuid = '{}'",
uuid
))
.fetch_one(&data.pool) .fetch_one(&data.pool)
.await; .await;
if row.is_err() { if let Err(error) = row {
error!("{}", row.unwrap_err()); error!("{}", error);
return Ok(HttpResponse::InternalServerError().finish()) return Ok(HttpResponse::InternalServerError().finish());
} }
let (username, display_name): (String, Option<String>) = row.unwrap(); let (username, display_name): (String, Option<String>) = row.unwrap();
Ok(HttpResponse::Ok().json(Response { uuid: uuid.to_string(), username, display_name: display_name.unwrap_or_default() })) Ok(HttpResponse::Ok().json(Response {
uuid: uuid.to_string(),
username,
display_name: display_name.unwrap_or_default(),
}))
} }

View file

@ -4,11 +4,11 @@ use hex::encode;
pub fn generate_access_token() -> Result<String, getrandom::Error> { pub fn generate_access_token() -> Result<String, getrandom::Error> {
let mut buf = [0u8; 16]; let mut buf = [0u8; 16];
fill(&mut buf)?; fill(&mut buf)?;
Ok(encode(&buf)) Ok(encode(buf))
} }
pub fn generate_refresh_token() -> Result<String, getrandom::Error> { pub fn generate_refresh_token() -> Result<String, getrandom::Error> {
let mut buf = [0u8; 32]; let mut buf = [0u8; 32];
fill(&mut buf)?; fill(&mut buf)?;
Ok(encode(&buf)) Ok(encode(buf))
} }

View file

@ -29,7 +29,12 @@ struct Data {
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Error> { async fn main() -> Result<(), Error> {
SimpleLogger::new().with_level(log::LevelFilter::Info).with_colors(true).env().init().unwrap(); SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.with_colors(true)
.env()
.init()
.unwrap();
let args = Args::parse(); let args = Args::parse();
let config = ConfigBuilder::load(args.config).await?.build(); let config = ConfigBuilder::load(args.config).await?.build();
@ -42,7 +47,8 @@ async fn main() -> Result<(), Error> {
TODO: Figure out if a table should be used here and if not then what. TODO: Figure out if a table should be used here and if not then what.
Also figure out if these should be different types from what they currently are and if we should add more "constraints" Also figure out if these should be different types from what they currently are and if we should add more "constraints"
*/ */
sqlx::raw_sql(r#" sqlx::raw_sql(
r#"
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
uuid uuid PRIMARY KEY UNIQUE NOT NULL, uuid uuid PRIMARY KEY UNIQUE NOT NULL,
username varchar(32) UNIQUE NOT NULL, username varchar(32) UNIQUE NOT NULL,
@ -67,7 +73,8 @@ async fn main() -> Result<(), Error> {
uuid uuid NOT NULL REFERENCES users(uuid), uuid uuid NOT NULL REFERENCES users(uuid),
created int8 NOT NULL created int8 NOT NULL
) )
"#) "#,
)
.execute(&pool) .execute(&pool)
.await?; .await?;