Registration and fixes

This commit is contained in:
2024-08-04 09:48:41 +03:00
parent 94bb1371fa
commit b6c71ee35b
11 changed files with 244 additions and 52 deletions

View File

@ -10,6 +10,7 @@ use axum_extra::{
headers::{authorization::Bearer, Authorization},
TypedHeader,
};
use chrono::{TimeDelta, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize};
@ -122,6 +123,13 @@ pub struct Claims {
}
impl Claims {
pub fn new(user_id: i32) -> Self {
Self {
user_id,
exp: (Utc::now() + TimeDelta::days(30)).timestamp(),
}
}
pub fn encode(self) -> Result<Token, Error> {
let access_token = encode(
&Header::new(jsonwebtoken::Algorithm::HS256),

View File

@ -10,16 +10,19 @@ pub async fn create_user(
user_email: &str,
hashed_password: &[u8],
pool: &Pool,
) -> sqlx::Result<i32> {
let id = sqlx::query!(
) -> sqlx::Result<Option<i32>> {
let Some(record) = sqlx::query!(
"INSERT INTO users(username, email, hashed_password) VALUES ($1, $2, $3) RETURNING user_id",
user_name,
user_email,
hashed_password
)
.fetch_one(pool)
.fetch_optional(pool)
.await?
.user_id;
else {
return Ok(None);
};
let id = record.user_id;
sqlx::query!(
"INSERT INTO folders(owner_id, folder_name) VALUES ($1, $2)",
id,
@ -27,7 +30,7 @@ pub async fn create_user(
)
.execute(pool)
.await?;
Ok(id)
Ok(Some(id))
}
/// Deletes the user and returns the files that must be deleted

View File

@ -1,5 +1,4 @@
use axum::Form;
use chrono::TimeDelta;
use crate::{
auth::{authenticate_user, Error, Token},
@ -12,24 +11,13 @@ pub struct Params {
password: String,
}
fn get_exp() -> i64 {
let mut time = chrono::Utc::now();
time += TimeDelta::days(30);
time.timestamp()
}
pub async fn post(
State(state): State<AppState>,
State(pool): State<Pool>,
Form(payload): Form<Params>,
) -> Result<Json<Token>, Error> {
let user_id = authenticate_user(&payload.username, &payload.password, &state.pool)
let user_id = authenticate_user(&payload.username, &payload.password, &pool)
.await
.map_err(|_| Error::WrongCredentials)?
.ok_or(Error::WrongCredentials)?;
Claims {
user_id,
exp: get_exp(),
}
.encode()
.map(Json)
Claims::new(user_id).encode().map(Json)
}

View File

@ -1 +1,2 @@
pub mod auth_post;
pub mod register;

View File

@ -0,0 +1,68 @@
use axum::Form;
use axum_extra::either::Either;
use itertools::Itertools;
use validator::{Validate, ValidationError};
use crate::{
auth::{Error, HashedBytes, Token},
prelude::*,
};
#[derive(Deserialize, Debug, Validate)]
pub struct Params {
#[validate(length(min = 3, max = 10))]
username: String,
#[validate(email)]
email: String,
#[validate(length(min = 6), custom(function = "validate_password"))]
password: String,
}
fn validate_password(password: &str) -> Result<(), ValidationError> {
let mut has_lower = false;
let mut has_upper = false;
let mut has_number = false;
let mut has_special = false;
for char in password.chars() {
if char.is_lowercase() {
has_lower = true;
} else if char.is_uppercase() {
has_upper = true;
} else if char.is_ascii_digit() {
has_number = true;
} else {
has_special = true;
}
}
let error_msgs = [has_lower, has_upper, has_number, has_special]
.into_iter()
.zip(["No lower", "No upper", "No numbers", "No special"])
.filter_map(|(param, msg)| (!param).then_some(msg));
let msg = error_msgs.format(" ").to_string();
if !msg.is_empty() {
return Err(ValidationError::new("invalid_password").with_message(msg.into()));
}
Ok(())
}
pub async fn register(
State(pool): State<Pool>,
Form(params): Form<Params>,
) -> Result<Json<Token>, Either<(StatusCode, String), Error>> {
params
.validate()
.map_err(|err| Either::E1((StatusCode::BAD_REQUEST, err.to_string())))?;
let password = HashedBytes::hash_bytes(params.password.as_bytes()).as_bytes();
let Some(id) = db::users::create_user(&params.username, &params.email, &password, &pool)
.await
.handle_internal()
.map_err(|status| Either::E1((status, String::new())))?
else {
return Err(Either::E1((
StatusCode::BAD_REQUEST,
"Either the user name or the email are taken".to_owned(),
)));
};
let token = Claims::new(id).encode().map_err(Either::E2)?;
Ok(Json(token))
}

View File

@ -22,17 +22,11 @@ impl From<db::folder::FolderWithoutParentId> for FolderStructure {
}
}
#[derive(Debug, Serialize)]
pub struct Response {
folder_id: Uuid,
structure: FolderStructure,
}
pub async fn structure(
Query(params): Query<Params>,
State(pool): State<Pool>,
claims: Claims,
) -> Result<Json<Response>, StatusCode> {
) -> Result<Json<FolderStructure>, StatusCode> {
let folder_id = db::folder::process_id(params.folder_id, claims.user_id, &pool)
.await
.handle_internal()?
@ -41,15 +35,12 @@ pub async fn structure(
.await
.handle_internal()?
.ok_or(StatusCode::NOT_FOUND)?;
let mut response = Response {
folder_id,
structure: folder.into(),
};
let mut stack = vec![&mut response.structure];
let mut response: FolderStructure = folder.into();
let mut stack = vec![&mut response];
while let Some(folder) = stack.pop() {
let (files, folders) = try_join!(
db::file::get_files(folder_id, &pool).try_collect(),
db::folder::get_folders(folder_id, claims.user_id, &pool)
db::file::get_files(folder.folder_base.folder_id, &pool).try_collect(),
db::folder::get_folders(folder.folder_base.folder_id, claims.user_id, &pool)
.map_ok(Into::into)
.try_collect()
)

View File

@ -1,8 +1,12 @@
use validator::Validate;
use crate::prelude::*;
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Validate)]
pub struct Params {
#[validate(email)]
username: String,
#[validate(email)]
email: String,
}
@ -10,9 +14,13 @@ pub async fn put(
State(pool): State<Pool>,
claims: Claims,
Json(params): Json<Params>,
) -> Result<Json<db::users::UserInfo>, StatusCode> {
) -> Result<Json<db::users::UserInfo>, (StatusCode, String)> {
params
.validate()
.map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()))?;
let info = db::users::update(claims.user_id, &params.username, &params.email, &pool)
.await
.handle_internal()?;
.handle_internal()
.map_err(|status| (status, String::new()))?;
Ok(Json(info))
}

View File

@ -121,6 +121,7 @@ fn app(state: AppState) -> Router {
)
.route("/users/current", get(users::get::current))
.route("/users/search", get(users::search::search))
.route("/register", post(authorization::register::register))
.route("/authorize", post(authorization::auth_post::post))
.layer(middleware)
.with_state(state)