diff --git a/api/src/db/user.rs b/api/src/db/user.rs index e71b62c..855e3f8 100644 --- a/api/src/db/user.rs +++ b/api/src/db/user.rs @@ -53,30 +53,32 @@ pub async fn insert_new_user( .bind(name) .fetch_one(pool).await .map_err(|err| { - error!(%err, record = ?new_user, "Cannot insert new user record"); + error!(?err, record = ?new_user, "Cannot insert new user record"); AppError::from(err) }) } #[derive(Debug, FromRow)] -pub struct UserIdAndHashedPassword { +pub struct UserAndHashedPassword { pub id: i32, + pub username: String, + pub name: String, pub password: String, } pub async fn get_username_and_password_by_username( pool: &DbPool, username: String, -) -> Result { - sqlx::query_as::<_, UserIdAndHashedPassword>( - "SELECT id, password FROM public.user WHERE username = $1;", +) -> Result { + sqlx::query_as::<_, UserAndHashedPassword>( + "SELECT id, username, name, password FROM public.user WHERE username = $1;", ) .bind(username) .fetch_one(pool) .await .map_err(|err| { - error!(%err, "Unable to find user"); + error!(?err, "Unable to find user"); AppError::from(err) }) } @@ -87,7 +89,7 @@ pub async fn verify_user(pool: &DbPool, user_id: i32) -> Result<(), AppError> { .execute(pool) .await .map_err(|err| { - error!(%err, user_id, "Error verifying user"); + error!(?err, user_id, "Error verifying user"); AppError::from(err) }) .map(|_| ()) diff --git a/api/src/models/error.rs b/api/src/models/error.rs index 4b8293c..7a49838 100644 --- a/api/src/models/error.rs +++ b/api/src/models/error.rs @@ -81,6 +81,7 @@ impl From for AppError { impl From for AppError { fn from(other: SqlxError) -> Self { match &other { + SqlxError::RowNotFound => ErrorKind::NoDbRecordFound, SqlxError::Database(db_err) => { if let Some(err_code) = db_err.code() { map_db_error_code_to_error_kind(err_code, db_err) @@ -137,6 +138,7 @@ impl Display for AppError { f, "Cannot retrieve session: missing required field: {field}" ), + ErrorKind::NoDbRecordFound => write!(f, "No database record found"), ErrorKind::NoSessionFound => write!(f, "No session found"), ErrorKind::Sqlx(err) => write!(f, "{err}"), ErrorKind::TokenKey => write!( @@ -161,6 +163,7 @@ enum ErrorKind { InvalidToken, MissingEnvironmentVariables(Vec<&'static str>), MissingSessionField(&'static str), + NoDbRecordFound, NoSessionFound, Sqlx(SqlxError), TokenKey, @@ -181,6 +184,10 @@ impl IntoResponse for AppError { StatusCode::UNAUTHORIZED, ApiResponse::new_with_error(self).into_json_response(), ), + &ErrorKind::NoDbRecordFound => ( + StatusCode::NOT_FOUND, + ApiResponse::new_with_error(self).into_json_response(), + ), _ => ( StatusCode::INTERNAL_SERVER_ERROR, ApiResponse::new_with_error(self).into_json_response(), diff --git a/api/src/requests/auth/login/handler.rs b/api/src/requests/auth/login/handler.rs index 08f0aea..5ca0a21 100644 --- a/api/src/requests/auth/login/handler.rs +++ b/api/src/requests/auth/login/handler.rs @@ -4,13 +4,21 @@ use axum::{ response::{IntoResponse, Response}, Json, }; +use http::StatusCode; +use pasetors::{keys::SymmetricKey, version4::V4}; use tracing::debug; use crate::{ - db::{get_username_and_password_by_username, DbPool, UserIdAndHashedPassword}, - models::AppError, - requests::AppState, - services::verify_password, + db::{get_username_and_password_by_username, DbPool, UserAndHashedPassword}, + models::{ApiResponse, AppError}, + requests::{ + auth::login::models::{AuthLoginResponse, AuthLoginTokenData}, + AppState, + }, + services::{ + auth_token::{generate_access_token, generate_auth_token}, + verify_password, + }, }; use super::models::AuthLoginRequest; @@ -21,19 +29,50 @@ pub async fn auth_login_post_handler( Json(body): Json, ) -> Result { let pool = state.db_pool(); - auth_login_request(pool, body).await + let token_key = state.env().token_key(); + auth_login_request(pool, token_key, body).await } -async fn auth_login_request(pool: &DbPool, body: AuthLoginRequest) -> Result { +async fn auth_login_request( + pool: &DbPool, + token_key: &SymmetricKey, + body: AuthLoginRequest, +) -> Result { debug!(?body); let AuthLoginRequest { username, password } = body; - let UserIdAndHashedPassword { - id: _id, + let UserAndHashedPassword { + id: user_id, + username, + name, password: hashed_password, } = get_username_and_password_by_username(pool, username).await?; verify_password(password, hashed_password)?; - Ok(().into_response()) + let (access_token, _access_token_id, access_token_expiration) = + generate_access_token(token_key, user_id); + + let (auth_token, _auth_token_id, auth_token_expiration) = + generate_auth_token(token_key, user_id); + + let response = AuthLoginResponse { + user_id, + username, + name, + access: AuthLoginTokenData { + token: access_token, + expiration: access_token_expiration, + }, + auth: AuthLoginTokenData { + token: auth_token, + expiration: auth_token_expiration, + }, + }; + + Ok(( + StatusCode::OK, + ApiResponse::new(response).into_json_response(), + ) + .into_response()) } diff --git a/api/src/requests/auth/login/models/mod.rs b/api/src/requests/auth/login/models/mod.rs index bfbbdc6..b8be632 100644 --- a/api/src/requests/auth/login/models/mod.rs +++ b/api/src/requests/auth/login/models/mod.rs @@ -2,4 +2,4 @@ mod request; mod response; pub use request::*; -// pub use response::*; +pub use response::*; diff --git a/api/src/requests/auth/login/models/response.rs b/api/src/requests/auth/login/models/response.rs index 64693db..4eb8270 100644 --- a/api/src/requests/auth/login/models/response.rs +++ b/api/src/requests/auth/login/models/response.rs @@ -6,6 +6,8 @@ use serde::Serialize; #[serde(rename_all = "camelCase")] pub struct AuthLoginResponse { pub user_id: i32, + pub username: String, + pub name: String, pub access: AuthLoginTokenData, pub auth: AuthLoginTokenData, }