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,
name VARCHAR(255) 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,
user_id INT 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,
credential_type VARCHAR NOT NULL,
credential VARCHAR NOT NULL,

View File

@ -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<CredentialDto>,
pub async fn insert_credential(
conn: &mut PgConnection,
credential_dto: CredentialDto,
user_id: &i32,
) -> Result<Vec<Credential>, Error> {
let insert_query_base = r#"INSERT INTO credential
) -> Result<Credential, Error> {
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<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(
conn: &PgPool,
conn: &mut PgConnection,
credential: String,
) -> 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 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 (
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<Token, Error> {
}
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<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 *;"#)
.bind(token_id)
.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(
conn: &PgPool,
conn: &mut PgConnection,
user_id: &i32,
auth_token: String,
) -> Result<Option<Token>, Error> {

View File

@ -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<User, sqlx::Error> {
pub async fn insert_user(conn: &mut PgConnection, user: User) -> Result<User, sqlx::Error> {
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<User, sqlx::Error>
.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(
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<Option<Use
.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(
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<User, sqlx::Error>
.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(
r#"
DELETE FROM user where id = $1 RETURNING *;
DELETE FROM "user" where id = $1 RETURNING *;
"#,
)
.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 variable_lengths;
pub mod expirations;

View File

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

View File

@ -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<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();
// 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<String> = 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<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('.')
}
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()