From 51d88246cdf3b419a9ad8286f250504ebb5d05bc Mon Sep 17 00:00:00 2001 From: Franklin Date: Thu, 21 Sep 2023 13:13:10 -0400 Subject: [PATCH] Auth with user token --- migrations/{1_user.sql => 100_user.sql} | 2 +- migrations/{2_token.sql => 101_token.sql} | 2 +- .../{3_credential.sql => 102_credential.sql} | 2 +- src/dao/credential.rs | 43 ++++---- src/dao/token.rs | 10 +- src/dao/user.rs | 18 +-- src/resources/expirations.rs | 2 + src/resources/mod.rs | 1 + src/service/mod.rs | 4 +- src/service/user.rs | 103 +++++++++++++----- src/validation/user_validator.rs | 10 +- 11 files changed, 124 insertions(+), 73 deletions(-) rename migrations/{1_user.sql => 100_user.sql} (82%) rename migrations/{2_token.sql => 101_token.sql} (82%) rename migrations/{3_credential.sql => 102_credential.sql} (86%) create mode 100644 src/resources/expirations.rs diff --git a/migrations/1_user.sql b/migrations/100_user.sql similarity index 82% rename from migrations/1_user.sql rename to migrations/100_user.sql index 1865a74..503ffce 100644 --- a/migrations/1_user.sql +++ b/migrations/100_user.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS user ( +CREATE TABLE IF NOT EXISTS "user" ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, password TEXT NOT NULL, diff --git a/migrations/2_token.sql b/migrations/101_token.sql similarity index 82% rename from migrations/2_token.sql rename to migrations/101_token.sql index 0e14799..786deae 100644 --- a/migrations/2_token.sql +++ b/migrations/101_token.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS token ( +CREATE TABLE IF NOT EXISTS "token" ( id SERIAL PRIMARY KEY, user_id INT NOT NULL, auth_token TEXT NOT NULL, diff --git a/migrations/3_credential.sql b/migrations/102_credential.sql similarity index 86% rename from migrations/3_credential.sql rename to migrations/102_credential.sql index 1c5a8d3..39b2e01 100644 --- a/migrations/3_credential.sql +++ b/migrations/102_credential.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS credential ( +CREATE TABLE IF NOT EXISTS "credential" ( user_id INT NOT NULL, credential_type VARCHAR NOT NULL, credential VARCHAR NOT NULL, diff --git a/src/dao/credential.rs b/src/dao/credential.rs index f4e0516..5b913e8 100644 --- a/src/dao/credential.rs +++ b/src/dao/credential.rs @@ -1,41 +1,36 @@ use crate::domain::credential::Credential; use crate::dto::credential::CredentialDto; use chrono::Utc; -use sqlx::{Error, PgPool}; +use sqlx::{Error, PgConnection, PgPool}; -pub async fn insert_credentials( - conn: &PgPool, - credentials: Vec, +pub async fn insert_credential( + conn: &mut PgConnection, + credential_dto: CredentialDto, user_id: &i32, -) -> Result, Error> { - let insert_query_base = r#"INSERT INTO credential +) -> Result { + let insert_query_base = r#"INSERT INTO "credential" (user_id, credential_type, credential, validated, time_created, last_updated) - VALUES ($1, $2, $3, $4, $5, $5) RETURNING user_id, credential_type as "credential_type: _", credential, validated, time_created, last_updated"#; - let mut persisted_credentials = Vec::new(); - for credential_dto in credentials { - let persisted_credential: Credential = sqlx::query_as(insert_query_base) - .bind(user_id) - .bind(credential_dto.credential_type) - .bind(credential_dto.credential) - .bind(false) - .bind(Utc::now()) - .fetch_one(conn) - .await?; - persisted_credentials.push(persisted_credential); - } - Ok(persisted_credentials) + VALUES ($1, $2, $3, $4, $5, $5) RETURNING user_id, credential_type, credential, validated, time_created, last_updated"#; + sqlx::query_as(insert_query_base) + .bind(user_id) + .bind(credential_dto.credential_type) + .bind(credential_dto.credential) + .bind(false) + .bind(Utc::now()) + .fetch_one(conn) + .await } pub async fn fetch_user_credentials( - conn: &PgPool, + conn: &mut PgConnection, user_id: &i32, ) -> Result, Error> { - sqlx::query_as(r#"SELECT user_id, credential_type as "credential_type: _", credential, validated, time_created, last_updated FROM credential WHERE user_id = $1 "#).bind(user_id).fetch_all(conn).await + sqlx::query_as(r#"SELECT user_id, credential_type, credential, validated, time_created, last_updated FROM "credential" WHERE user_id = $1 "#).bind(user_id).fetch_all(conn).await } pub async fn get_credential( - conn: &PgPool, + conn: &mut PgConnection, credential: String, ) -> Result, Error> { - sqlx::query_as(r#"SELECT user_id, credential_type as "credential_type: _", credential, validated, time_created, last_updated FROM credential WHERE credential = $1"#).bind(credential).fetch_optional(conn).await + sqlx::query_as(r#"SELECT user_id, credential_type, credential, validated, time_created, last_updated FROM "credential" WHERE credential = $1"#).bind(credential).fetch_optional(conn).await } diff --git a/src/dao/token.rs b/src/dao/token.rs index 525cbc8..c5459be 100644 --- a/src/dao/token.rs +++ b/src/dao/token.rs @@ -1,8 +1,8 @@ use crate::domain::token::Token; use chrono::Utc; -use sqlx::{Error, PgPool}; +use sqlx::{Error, PgConnection, PgPool}; -pub async fn insert_token(conn: &PgPool, token: Token) -> Result { +pub async fn insert_token(conn: &mut PgConnection, token: Token) -> Result { sqlx::query_as(r#"INSERT INTO token ( user_id, auth_token, refresh_token, time_created, last_updated) VALUES ($1, $2, $3, $4, $4) RETURNING *;"#) .bind(token.user_id).bind(token.auth_token).bind(token.refresh_token).bind(token.time_created) @@ -10,7 +10,7 @@ pub async fn insert_token(conn: &PgPool, token: Token) -> Result { } pub async fn update_token( - conn: &PgPool, + conn: &mut PgConnection, token_id: &i32, refresh_token: String, new_auth_token: String, @@ -28,7 +28,7 @@ pub async fn update_token( .await } -pub async fn remove_token(conn: &PgPool, token_id: &i32) -> Result, Error> { +pub async fn remove_token(conn: &mut PgConnection, token_id: &i32) -> Result, Error> { sqlx::query_as(r#"DELETE FROM token WHERE id = $1 RETURNING *;"#) .bind(token_id) .fetch_optional(conn) @@ -36,7 +36,7 @@ pub async fn remove_token(conn: &PgPool, token_id: &i32) -> Result } pub async fn validate_user_token( - conn: &PgPool, + conn: &mut PgConnection, user_id: &i32, auth_token: String, ) -> Result, Error> { diff --git a/src/dao/user.rs b/src/dao/user.rs index 9a14e13..76187a8 100644 --- a/src/dao/user.rs +++ b/src/dao/user.rs @@ -1,10 +1,10 @@ use crate::domain::user::User; -use sqlx::{query_as, query_as_with, PgPool}; +use sqlx::{PgConnection, PgPool, Postgres, Transaction}; -pub async fn insert_user(conn: &PgPool, user: User) -> Result { +pub async fn insert_user(conn: &mut PgConnection, user: User) -> Result { sqlx::query_as( r#" - INSERT INTO user (name, password, salt, time_created, last_updated) + INSERT INTO "user" (name, password, salt, time_created, last_updated) VALUES ($1, $2, $3, $4, $4) RETURNING *; "#, ) @@ -16,10 +16,10 @@ pub async fn insert_user(conn: &PgPool, user: User) -> Result .await } -pub async fn get_user_with_id(conn: &PgPool, user_id: &i32) -> Result, sqlx::Error> { +pub async fn get_user_with_id(conn: &mut PgConnection, user_id: &i32) -> Result, sqlx::Error> { sqlx::query_as( r#" - SELECT * FROM user where id = $1; + SELECT * FROM "user" where id = $1; "#, ) .bind(user_id) @@ -27,10 +27,10 @@ pub async fn get_user_with_id(conn: &PgPool, user_id: &i32) -> Result Result { +pub async fn update_user(conn: &mut PgConnection, user: User) -> Result { sqlx::query_as( r#" - UPDATE user SET + UPDATE "user" SET name = $2, password = $3, salt = $4, last_updated = $5 WHERE id = $1 RETURNING *; "#, @@ -44,10 +44,10 @@ pub async fn update_user(conn: &PgPool, user: User) -> Result .await } -pub async fn delete_user(conn: &PgPool, user_id: &i32) -> Result, sqlx::Error> { +pub async fn delete_user(conn: &mut PgConnection, user_id: &i32) -> Result, sqlx::Error> { sqlx::query_as( r#" - DELETE FROM user where id = $1 RETURNING *; + DELETE FROM "user" where id = $1 RETURNING *; "#, ) .bind(user_id) diff --git a/src/resources/expirations.rs b/src/resources/expirations.rs new file mode 100644 index 0000000..b1ca140 --- /dev/null +++ b/src/resources/expirations.rs @@ -0,0 +1,2 @@ + +pub const AUTH_TOKEN_EXPIRATION_TIME_MILLIS: i64 = 604800000; // 7 Days \ No newline at end of file diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 77ec44a..0c7b354 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,2 +1,3 @@ pub mod error_messages; pub mod variable_lengths; +pub mod expirations; diff --git a/src/service/mod.rs b/src/service/mod.rs index c26caff..87a7595 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,2 +1,2 @@ -mod token; -mod user; +pub mod token; +pub mod user; diff --git a/src/service/user.rs b/src/service/user.rs index d65d73b..1c323f9 100644 --- a/src/service/user.rs +++ b/src/service/user.rs @@ -1,19 +1,20 @@ -use std::error::Error; use chrono::Utc; -use log::{error, log}; -use tokio::task::JoinError; -use crate::dao::credential::{get_credential, insert_credentials}; -use crate::dao::token::insert_token; -use crate::dao::user::insert_user; -use crate::domain::credential::Credential; +use log::{debug, error, log}; +use sqlx::{Error, Postgres, Transaction}; +use sqlx::pool::PoolConnection; +use crate::dao::credential::{get_credential, insert_credential}; +use crate::dao::token::{insert_token, validate_user_token}; +use crate::dao::user::{get_user_with_id, insert_user}; use crate::domain::token::Token; use crate::domain::user::User; +use crate::dto::token::AuthenticateUserDto; use crate::dto::users::UserRegisterPayload; -use crate::resources::error_messages::{ERROR_TOKEN_NOT_CREATED, ERROR_TOO_MANY_CREDENTIALS, ERROR_USER_ALREADY_EXISTS, ErrorResource}; +use crate::resources::error_messages::{ERROR_EXPIRED_TOKEN, ERROR_INCORRECT_TOKEN, ERROR_TOKEN_NOT_CREATED, ERROR_TOO_MANY_CREDENTIALS, ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST, ErrorResource}; +use crate::resources::expirations::AUTH_TOKEN_EXPIRATION_TIME_MILLIS; use crate::utils::hasher::{generate_multiple_random_token_with_rng, hash_password}; use crate::validation::user_validator::validate_user_for_creation; -pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> Result> { +pub async fn register_user<'a>(transaction: &mut Transaction<'a, Postgres>, user: UserRegisterPayload) -> Result>> { let mut error_resources: Vec = Vec::new(); // Validate user validate_user_for_creation(&user, &mut error_resources); @@ -23,7 +24,7 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> } for credential_dto in user.credentials.iter() { match get_credential( - &db_conn, + transaction, credential_dto.credential.clone(), ) .await @@ -38,7 +39,7 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> } } Err(e) => { - error!("{}", e); + error!("1{}", e); error_resources.push(("ERROR.DATABASE_ERROR", "")); } }; @@ -60,33 +61,33 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> }; let persisted_user; - // Insert user in DB - match insert_user(&db_conn, user_to_insert).await{ + match insert_user(transaction, user_to_insert).await{ Ok(user) => { persisted_user = user; }, Err(e) => { - error!("{}", e); + error!("2{}", e); error_resources.push(("ERROR.DATABASE_ERROR", "")); return Err(error_resources); }}; // Insert Credentials - match insert_credentials(db_conn, user.credentials, &persisted_user.id).await { - Ok(_) => {} - Err(e) => { - error!("{}", e); - error_resources.push(("ERROR.DATABASE_ERROR", "")); - return Err(error_resources); - } - }; - + for credential in user.credentials { + match insert_credential(transaction, credential, &persisted_user.id).await { + Ok(_) => {} + Err(e) => { + error!("3{}", e); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + return Err(error_resources); + } + }; + } // Create token and send it back. let tokens: Vec = match generate_multiple_random_token_with_rng(2).await { Ok(tokens) => tokens, Err(e) => { - error!("{}", e); + error!("4{}", e); error_resources.push(("ERROR.JOIN_ERROR", "")); return Err(error_resources); } @@ -116,7 +117,7 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> }; // Insert token in DB - match insert_token(&db_conn, token_to_insert).await { + match insert_token(transaction, token_to_insert).await { Ok(persisted_token) => { Ok(persisted_token) }, @@ -127,3 +128,55 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> } } } +pub async fn authenticate_user(db_conn: &sqlx::PgPool, user: AuthenticateUserDto) -> Result> { + let mut error_resources = Vec::new(); + let mut conn = match db_conn.acquire().await { + Ok(conn) => conn, + Err(error) => { + error!("{:?}", error); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + return Err(error_resources); + } + }; + let persisted_user = match get_user_with_id(&mut conn, &user.id).await { + Ok(persisted_user_opt) => match persisted_user_opt { + None => { + error_resources.push(ERROR_USER_DOES_NOT_EXIST); + return Err(error_resources); + }, + Some(persisted_user) => persisted_user + }, + Err(error) => { + error!("{:?}", error); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + return Err(error_resources); + } + }; + + match validate_user_token(&mut conn, &user.id, user.auth_token).await { + Ok(persisted_token_opt) => match persisted_token_opt { + None => { + error_resources.push(ERROR_INCORRECT_TOKEN); + Err(error_resources) + }, + Some(persisted_token) => { + // Check if persisted_token expired + if Utc::now().timestamp_millis() - persisted_token.last_updated.timestamp_millis() > AUTH_TOKEN_EXPIRATION_TIME_MILLIS { + // Expired + debug!("Expired token: {:?}", persisted_token); + error_resources.push(ERROR_EXPIRED_TOKEN); + Err(error_resources) + } else { + // Not expired + Ok(persisted_user) + } + } + }, + Err(error) => { + error!("{:?}", error); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + Err(error_resources) + } + } +} + diff --git a/src/validation/user_validator.rs b/src/validation/user_validator.rs index 88b9da8..c6d3907 100644 --- a/src/validation/user_validator.rs +++ b/src/validation/user_validator.rs @@ -17,14 +17,14 @@ fn validate_user_email(email: &String) -> bool { && email.contains('@') && email.contains('.') } -fn validate_user_phone_number(email: &String) -> bool { - email.len() >= CredentialType::get_max_length(&CredentialType::PhoneNumber) - && email.len() <= CredentialType::get_min_length(&CredentialType::PhoneNumber) +fn validate_user_phone_number(phone_number: &String) -> bool { + phone_number.len() <= CredentialType::get_max_length(&CredentialType::PhoneNumber) + && phone_number.len() >= CredentialType::get_min_length(&CredentialType::PhoneNumber) } fn validate_user_username(username: &String) -> bool { - username.len() >= CredentialType::get_max_length(&CredentialType::PhoneNumber) - && username.len() <= CredentialType::get_min_length(&CredentialType::PhoneNumber) + username.len() >= CredentialType::get_max_length(&CredentialType::Username) + && username.len() <= CredentialType::get_min_length(&CredentialType::Username) } fn validate_user_name(name: &String) -> bool { name.len() >= MIN_NAME_LENGTH.into() && name.len() <= MAX_NAME_LENGTH.into()