Auth with user token

This commit is contained in:
Franklin 2023-09-21 13:13:10 -04:00
parent a117b55ff4
commit 51d88246cd
11 changed files with 124 additions and 73 deletions

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS "user" (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS token ( CREATE TABLE IF NOT EXISTS "token" (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
user_id INT NOT NULL, user_id INT NOT NULL,
auth_token TEXT NOT NULL, auth_token TEXT NOT NULL,

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS credential ( CREATE TABLE IF NOT EXISTS "credential" (
user_id INT NOT NULL, user_id INT NOT NULL,
credential_type VARCHAR NOT NULL, credential_type VARCHAR NOT NULL,
credential VARCHAR NOT NULL, credential VARCHAR NOT NULL,

View File

@ -1,41 +1,36 @@
use crate::domain::credential::Credential; use crate::domain::credential::Credential;
use crate::dto::credential::CredentialDto; use crate::dto::credential::CredentialDto;
use chrono::Utc; use chrono::Utc;
use sqlx::{Error, PgPool}; use sqlx::{Error, PgConnection, PgPool};
pub async fn insert_credentials( pub async fn insert_credential(
conn: &PgPool, conn: &mut PgConnection,
credentials: Vec<CredentialDto>, credential_dto: CredentialDto,
user_id: &i32, user_id: &i32,
) -> Result<Vec<Credential>, Error> { ) -> Result<Credential, Error> {
let insert_query_base = r#"INSERT INTO credential let insert_query_base = r#"INSERT INTO "credential"
(user_id, credential_type, credential, validated, time_created, last_updated) (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"#; VALUES ($1, $2, $3, $4, $5, $5) RETURNING user_id, credential_type, credential, validated, time_created, last_updated"#;
let mut persisted_credentials = Vec::new(); sqlx::query_as(insert_query_base)
for credential_dto in credentials {
let persisted_credential: Credential = sqlx::query_as(insert_query_base)
.bind(user_id) .bind(user_id)
.bind(credential_dto.credential_type) .bind(credential_dto.credential_type)
.bind(credential_dto.credential) .bind(credential_dto.credential)
.bind(false) .bind(false)
.bind(Utc::now()) .bind(Utc::now())
.fetch_one(conn) .fetch_one(conn)
.await?; .await
persisted_credentials.push(persisted_credential);
}
Ok(persisted_credentials)
} }
pub async fn fetch_user_credentials( pub async fn fetch_user_credentials(
conn: &PgPool, conn: &mut PgConnection,
user_id: &i32, user_id: &i32,
) -> Result<Vec<Credential>, Error> { ) -> Result<Vec<Credential>, 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( pub async fn get_credential(
conn: &PgPool, conn: &mut PgConnection,
credential: String, credential: String,
) -> Result<Option<Credential>, Error> { ) -> Result<Option<Credential>, 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
} }

View File

@ -1,8 +1,8 @@
use crate::domain::token::Token; use crate::domain::token::Token;
use chrono::Utc; use chrono::Utc;
use sqlx::{Error, PgPool}; use sqlx::{Error, PgConnection, PgPool};
pub async fn insert_token(conn: &PgPool, token: Token) -> Result<Token, Error> { pub async fn insert_token(conn: &mut PgConnection, token: Token) -> Result<Token, Error> {
sqlx::query_as(r#"INSERT INTO token ( sqlx::query_as(r#"INSERT INTO token (
user_id, auth_token, refresh_token, time_created, last_updated) VALUES ($1, $2, $3, $4, $4) RETURNING *;"#) 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) .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<Token, Error> {
} }
pub async fn update_token( pub async fn update_token(
conn: &PgPool, conn: &mut PgConnection,
token_id: &i32, token_id: &i32,
refresh_token: String, refresh_token: String,
new_auth_token: String, new_auth_token: String,
@ -28,7 +28,7 @@ pub async fn update_token(
.await .await
} }
pub async fn remove_token(conn: &PgPool, token_id: &i32) -> Result<Option<Token>, Error> { pub async fn remove_token(conn: &mut PgConnection, token_id: &i32) -> Result<Option<Token>, Error> {
sqlx::query_as(r#"DELETE FROM token WHERE id = $1 RETURNING *;"#) sqlx::query_as(r#"DELETE FROM token WHERE id = $1 RETURNING *;"#)
.bind(token_id) .bind(token_id)
.fetch_optional(conn) .fetch_optional(conn)
@ -36,7 +36,7 @@ pub async fn remove_token(conn: &PgPool, token_id: &i32) -> Result<Option<Token>
} }
pub async fn validate_user_token( pub async fn validate_user_token(
conn: &PgPool, conn: &mut PgConnection,
user_id: &i32, user_id: &i32,
auth_token: String, auth_token: String,
) -> Result<Option<Token>, Error> { ) -> Result<Option<Token>, Error> {

View File

@ -1,10 +1,10 @@
use crate::domain::user::User; 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<User, sqlx::Error> { pub async fn insert_user(conn: &mut PgConnection, user: User) -> Result<User, sqlx::Error> {
sqlx::query_as( sqlx::query_as(
r#" 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 *; VALUES ($1, $2, $3, $4, $4) RETURNING *;
"#, "#,
) )
@ -16,10 +16,10 @@ pub async fn insert_user(conn: &PgPool, user: User) -> Result<User, sqlx::Error>
.await .await
} }
pub async fn get_user_with_id(conn: &PgPool, user_id: &i32) -> Result<Option<User>, sqlx::Error> { pub async fn get_user_with_id(conn: &mut PgConnection, user_id: &i32) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT * FROM user where id = $1; SELECT * FROM "user" where id = $1;
"#, "#,
) )
.bind(user_id) .bind(user_id)
@ -27,10 +27,10 @@ pub async fn get_user_with_id(conn: &PgPool, user_id: &i32) -> Result<Option<Use
.await .await
} }
pub async fn update_user(conn: &PgPool, user: User) -> Result<User, sqlx::Error> { pub async fn update_user(conn: &mut PgConnection, user: User) -> Result<User, sqlx::Error> {
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE user SET UPDATE "user" SET
name = $2, password = $3, salt = $4, last_updated = $5 name = $2, password = $3, salt = $4, last_updated = $5
WHERE id = $1 RETURNING *; WHERE id = $1 RETURNING *;
"#, "#,
@ -44,10 +44,10 @@ pub async fn update_user(conn: &PgPool, user: User) -> Result<User, sqlx::Error>
.await .await
} }
pub async fn delete_user(conn: &PgPool, user_id: &i32) -> Result<Option<User>, sqlx::Error> { pub async fn delete_user(conn: &mut PgConnection, user_id: &i32) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as( sqlx::query_as(
r#" r#"
DELETE FROM user where id = $1 RETURNING *; DELETE FROM "user" where id = $1 RETURNING *;
"#, "#,
) )
.bind(user_id) .bind(user_id)

View File

@ -0,0 +1,2 @@
pub const AUTH_TOKEN_EXPIRATION_TIME_MILLIS: i64 = 604800000; // 7 Days

View File

@ -1,2 +1,3 @@
pub mod error_messages; pub mod error_messages;
pub mod variable_lengths; pub mod variable_lengths;
pub mod expirations;

View File

@ -1,2 +1,2 @@
mod token; pub mod token;
mod user; pub mod user;

View File

@ -1,19 +1,20 @@
use std::error::Error;
use chrono::Utc; use chrono::Utc;
use log::{error, log}; use log::{debug, error, log};
use tokio::task::JoinError; use sqlx::{Error, Postgres, Transaction};
use crate::dao::credential::{get_credential, insert_credentials}; use sqlx::pool::PoolConnection;
use crate::dao::token::insert_token; use crate::dao::credential::{get_credential, insert_credential};
use crate::dao::user::insert_user; use crate::dao::token::{insert_token, validate_user_token};
use crate::domain::credential::Credential; use crate::dao::user::{get_user_with_id, insert_user};
use crate::domain::token::Token; use crate::domain::token::Token;
use crate::domain::user::User; use crate::domain::user::User;
use crate::dto::token::AuthenticateUserDto;
use crate::dto::users::UserRegisterPayload; 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::utils::hasher::{generate_multiple_random_token_with_rng, hash_password};
use crate::validation::user_validator::validate_user_for_creation; use crate::validation::user_validator::validate_user_for_creation;
pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) -> Result<Token, Vec<ErrorResource>> { pub async fn register_user<'a>(transaction: &mut Transaction<'a, Postgres>, user: UserRegisterPayload) -> Result<Token, Vec<ErrorResource<'a>>> {
let mut error_resources: Vec<ErrorResource> = Vec::new(); let mut error_resources: Vec<ErrorResource> = Vec::new();
// Validate user // Validate user
validate_user_for_creation(&user, &mut error_resources); 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() { for credential_dto in user.credentials.iter() {
match get_credential( match get_credential(
&db_conn, transaction,
credential_dto.credential.clone(), credential_dto.credential.clone(),
) )
.await .await
@ -38,7 +39,7 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) ->
} }
} }
Err(e) => { Err(e) => {
error!("{}", e); error!("1{}", e);
error_resources.push(("ERROR.DATABASE_ERROR", "")); error_resources.push(("ERROR.DATABASE_ERROR", ""));
} }
}; };
@ -60,33 +61,33 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) ->
}; };
let persisted_user; let persisted_user;
// Insert user in DB // Insert user in DB
match insert_user(&db_conn, user_to_insert).await{ match insert_user(transaction, user_to_insert).await{
Ok(user) => { Ok(user) => {
persisted_user = user; persisted_user = user;
}, },
Err(e) => { Err(e) => {
error!("{}", e); error!("2{}", e);
error_resources.push(("ERROR.DATABASE_ERROR", "")); error_resources.push(("ERROR.DATABASE_ERROR", ""));
return Err(error_resources); return Err(error_resources);
}}; }};
// Insert Credentials // Insert Credentials
match insert_credentials(db_conn, user.credentials, &persisted_user.id).await { for credential in user.credentials {
match insert_credential(transaction, credential, &persisted_user.id).await {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("{}", e); error!("3{}", e);
error_resources.push(("ERROR.DATABASE_ERROR", "")); error_resources.push(("ERROR.DATABASE_ERROR", ""));
return Err(error_resources); return Err(error_resources);
} }
}; };
}
// Create token and send it back. // Create token and send it back.
let tokens: Vec<String> = match generate_multiple_random_token_with_rng(2).await { let tokens: Vec<String> = match generate_multiple_random_token_with_rng(2).await {
Ok(tokens) => tokens, Ok(tokens) => tokens,
Err(e) => { Err(e) => {
error!("{}", e); error!("4{}", e);
error_resources.push(("ERROR.JOIN_ERROR", "")); error_resources.push(("ERROR.JOIN_ERROR", ""));
return Err(error_resources); return Err(error_resources);
} }
@ -116,7 +117,7 @@ pub async fn register_user(db_conn: &sqlx::PgPool, user: UserRegisterPayload) ->
}; };
// Insert token in DB // 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) => {
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<User, Vec<ErrorResource>> {
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)
}
}
}

View File

@ -17,14 +17,14 @@ fn validate_user_email(email: &String) -> bool {
&& email.contains('@') && email.contains('@')
&& email.contains('.') && email.contains('.')
} }
fn validate_user_phone_number(email: &String) -> bool { fn validate_user_phone_number(phone_number: &String) -> bool {
email.len() >= CredentialType::get_max_length(&CredentialType::PhoneNumber) phone_number.len() <= CredentialType::get_max_length(&CredentialType::PhoneNumber)
&& email.len() <= CredentialType::get_min_length(&CredentialType::PhoneNumber) && phone_number.len() >= CredentialType::get_min_length(&CredentialType::PhoneNumber)
} }
fn validate_user_username(username: &String) -> bool { fn validate_user_username(username: &String) -> bool {
username.len() >= CredentialType::get_max_length(&CredentialType::PhoneNumber) username.len() >= CredentialType::get_max_length(&CredentialType::Username)
&& username.len() <= CredentialType::get_min_length(&CredentialType::PhoneNumber) && username.len() <= CredentialType::get_min_length(&CredentialType::Username)
} }
fn validate_user_name(name: &String) -> bool { fn validate_user_name(name: &String) -> bool {
name.len() >= MIN_NAME_LENGTH.into() && name.len() <= MAX_NAME_LENGTH.into() name.len() >= MIN_NAME_LENGTH.into() && name.len() <= MAX_NAME_LENGTH.into()