Compare commits

..

No commits in common. "0614c4cad06c5191ed45e3aca6a02fbb3e839ff4" and "cd3ab9b6bc540b84c8787bc86ef3107b6daaa7aa" have entirely different histories.

7 changed files with 130 additions and 90 deletions

121
Cargo.lock generated
View File

@ -107,7 +107,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -220,7 +220,7 @@ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -642,7 +642,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -939,6 +939,7 @@ checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
"serde",
] ]
[[package]] [[package]]
@ -1326,7 +1327,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -1383,6 +1384,30 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.86"
@ -1416,6 +1441,8 @@ dependencies = [
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"utoipa",
"utoipauto",
"uuid", "uuid",
] ]
@ -1670,7 +1697,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -1884,7 +1911,7 @@ dependencies = [
"quote", "quote",
"sqlx-core", "sqlx-core",
"sqlx-macros-core", "sqlx-macros-core",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -1907,7 +1934,7 @@ dependencies = [
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres", "sqlx-postgres",
"sqlx-sqlite", "sqlx-sqlite",
"syn", "syn 2.0.72",
"tempfile", "tempfile",
"tokio", "tokio",
"url", "url",
@ -2039,6 +2066,16 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.72" version = "2.0.72"
@ -2092,7 +2129,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -2176,7 +2213,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -2273,7 +2310,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]
@ -2372,6 +2409,64 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utoipa"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
"indexmap",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"regex",
"syn 2.0.72",
"uuid",
]
[[package]]
name = "utoipauto"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4713aabc5ed18aabcd594345b48983b112c0b5dab3d24754352e7f5cf924da03"
dependencies = [
"utoipauto-macro",
]
[[package]]
name = "utoipauto-core"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17e82ab96c5a55263b5bed151b8426410d93aa909a453acdbd4b6792b5af7d64"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "utoipauto-macro"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8338dc3c9526011ffaa2aa6bd60ddfda9d49d2123108690755c6e34844212"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"utoipauto-core",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.10.0" version = "1.10.0"
@ -2433,7 +2528,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2455,7 +2550,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2670,7 +2765,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.72",
] ]
[[package]] [[package]]

View File

@ -51,4 +51,6 @@ tracing-subscriber = { version = "0.3", features = [
"parking_lot", "parking_lot",
"env-filter", "env-filter",
] } ] }
utoipa = { version = "4", features = ["axum_extras", "uuid", "chrono"] }
utoipauto = "0.1"
uuid = { version = "1", features = ["serde", "v7"] } uuid = { version = "1", features = ["serde", "v7"] }

View File

@ -1,7 +1,7 @@
use std::{array::TryFromSliceError, sync::LazyLock}; use std::{array::TryFromSliceError, sync::LazyLock};
use axum::{ use axum::{
extract::{FromRef, FromRequestParts}, extract::FromRequestParts,
http::{request::Parts, StatusCode}, http::{request::Parts, StatusCode},
response::IntoResponse, response::IntoResponse,
RequestPartsExt, RequestPartsExt,
@ -15,7 +15,7 @@ use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use crate::{db, errors::handle_error, Pool}; use crate::{db, Pool};
pub const HASH_LENGTH: usize = 64; pub const HASH_LENGTH: usize = 64;
pub const SALT_LENGTH: usize = 64; pub const SALT_LENGTH: usize = 64;
@ -46,7 +46,7 @@ pub fn force_init_keys() {
LazyLock::force(&KEYS); LazyLock::force(&KEYS);
} }
/// Hashes the bytes using Scrypt with the given salt /// Hashes the bytes with Scrypt with the given salt
#[must_use] #[must_use]
fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] { fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
let mut hash = [0; HASH_LENGTH]; let mut hash = [0; HASH_LENGTH];
@ -55,7 +55,6 @@ fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
} }
/// Verifieble scrypt hashed bytes /// Verifieble scrypt hashed bytes
#[cfg_attr(test, derive(PartialEq))]
pub struct HashedBytes { pub struct HashedBytes {
pub hash: [u8; HASH_LENGTH], pub hash: [u8; HASH_LENGTH],
pub salt: [u8; SALT_LENGTH], pub salt: [u8; SALT_LENGTH],
@ -141,7 +140,6 @@ impl Claims {
pub enum Error { pub enum Error {
WrongCredentials, WrongCredentials,
TokenCreation, TokenCreation,
Validation,
InvalidToken, InvalidToken,
} }
@ -150,7 +148,6 @@ impl IntoResponse for Error {
let (status, error_message) = match self { let (status, error_message) = match self {
Error::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"), Error::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
Error::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"), Error::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
Error::Validation => (StatusCode::INTERNAL_SERVER_ERROR, "Token validation error"),
Error::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"), Error::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"),
}; };
(status, error_message).into_response() (status, error_message).into_response()
@ -158,53 +155,19 @@ impl IntoResponse for Error {
} }
#[axum::async_trait] #[axum::async_trait]
impl<T> FromRequestParts<T> for Claims impl<T> FromRequestParts<T> for Claims {
where
Pool: FromRef<T>,
T: Sync,
{
type Rejection = Error; type Rejection = Error;
async fn from_request_parts(parts: &mut Parts, state: &T) -> Result<Self, Self::Rejection> { async fn from_request_parts(parts: &mut Parts, _state: &T) -> Result<Self, Self::Rejection> {
let pool = Pool::from_ref(state);
let TypedHeader(Authorization(bearer)) = parts let TypedHeader(Authorization(bearer)) = parts
.extract::<TypedHeader<Authorization<Bearer>>>() .extract::<TypedHeader<Authorization<Bearer>>>()
.await .await
.map_err(|_| Error::InvalidToken)?; .map_err(|_| Error::InvalidToken)?;
let claims: Claims = decode(bearer.token(), &KEYS.decoding_key, &Validation::default()) // Decode the user data
.map_err(|_| Error::InvalidToken)? let token_data =
.claims; decode::<Claims>(bearer.token(), &KEYS.decoding_key, &Validation::default())
match db::users::exists(claims.user_id, &pool).await { .map_err(|_| Error::InvalidToken)?;
Ok(true) => Ok(claims),
Ok(false) => Err(Error::WrongCredentials), Ok(token_data.claims)
Err(err) => {
handle_error(err);
Err(Error::Validation)
}
}
}
}
#[cfg(test)]
mod tests {
use super::HashedBytes;
const PASSWORD: &str = "Password12313#!#4)$*!#";
#[test]
fn test_hash_conversion() {
let bytes = HashedBytes::hash_bytes(PASSWORD.as_bytes());
let bytes2 = HashedBytes::from_bytes(&bytes.as_bytes()).unwrap();
assert!(bytes == bytes2);
}
#[test]
fn test_hash() {
assert!(HashedBytes::hash_bytes(PASSWORD.as_bytes()).verify(PASSWORD.as_bytes()));
}
#[test]
fn test_different_hash() {
assert!(!HashedBytes::hash_bytes(PASSWORD.as_bytes()).verify(b"Different Password"));
} }
} }

View File

@ -61,23 +61,13 @@ pub async fn update(
.await .await
} }
pub async fn exists(user_id: i32, pool: &Pool) -> sqlx::Result<bool> { pub async fn get(user_id: i32, pool: &Pool) -> sqlx::Result<UserInfo> {
sqlx::query!(
"SELECT EXISTS(SELECT user_id FROM users WHERE user_id = $1)",
user_id
)
.fetch_one(pool)
.await
.map(|record| record.exists.unwrap_or(false))
}
pub async fn get(user_id: i32, pool: &Pool) -> sqlx::Result<Option<UserInfo>> {
sqlx::query_as!( sqlx::query_as!(
UserInfo, UserInfo,
"SELECT user_id, username, email FROM users WHERE user_id = $1", "SELECT user_id, username, email FROM users WHERE user_id = $1",
user_id user_id
) )
.fetch_optional(pool) .fetch_one(pool)
.await .await
} }

View File

@ -13,7 +13,7 @@ pub struct Params {
fn get_exp() -> i64 { fn get_exp() -> i64 {
let mut time = chrono::Utc::now(); let mut time = chrono::Utc::now();
time += TimeDelta::days(30); time += TimeDelta::minutes(30);
time.timestamp() time.timestamp()
} }

View File

@ -5,22 +5,12 @@ pub struct Params {
user_id: i32, user_id: i32,
} }
type Response = Result<Json<db::users::UserInfo>, StatusCode>; pub async fn get(
State(pool): State<Pool>,
pub async fn get(State(pool): State<Pool>, Query(params): Query<Params>) -> Response { Query(params): Query<Params>,
) -> Result<Json<db::users::UserInfo>, StatusCode> {
let info = db::users::get(params.user_id, &pool) let info = db::users::get(params.user_id, &pool)
.await .await
.handle_internal()? .handle_internal()?;
.ok_or(StatusCode::NOT_FOUND)?;
Ok(Json(info)) Ok(Json(info))
} }
pub async fn current(state: State<Pool>, claims: Claims) -> Response {
get(
state,
Query(Params {
user_id: claims.user_id,
}),
)
.await
}

View File

@ -42,6 +42,7 @@ async fn create_test_users(pool: &Pool) -> anyhow::Result<()> {
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
// TODO: add utoipa and utoipauto for swagger
let _ = dotenvy::dotenv(); let _ = dotenvy::dotenv();
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
@ -119,7 +120,6 @@ fn app(state: AppState) -> Router {
.delete(users::delete::delete) .delete(users::delete::delete)
.put(users::put::put), .put(users::put::put),
) )
.route("/users/current", get(users::get::current))
.route("/users/search", get(users::search::search)) .route("/users/search", get(users::search::search))
.route("/authorize", post(authorization::auth_post::post)) .route("/authorize", post(authorization::auth_post::post))
.layer(middleware) .layer(middleware)