From 34df22994188b5c3b25be7dc18baa5722669cf9f Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 18 Aug 2024 02:15:10 -0400 Subject: [PATCH] feat: return cookie in response --- crates/core/src/auth/error.rs | 2 + crates/core/src/auth/service.rs | 4 +- crates/core/src/user/error.rs | 3 + crates/core/src/user/service.rs | 8 ++- .../modules/auth/mutation/token_create.rs | 4 +- .../modules/user/mutation/user_register.rs | 3 +- crates/test/src/modules/auth/mod.rs | 1 + crates/test/src/modules/auth/token_create.rs | 63 +++++++++++++++++++ crates/test/src/modules/mod.rs | 1 + crates/test/src/modules/post/post_create.rs | 4 +- crates/test/src/modules/user/user_update.rs | 2 +- 11 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 crates/test/src/modules/auth/mod.rs create mode 100644 crates/test/src/modules/auth/token_create.rs diff --git a/crates/core/src/auth/error.rs b/crates/core/src/auth/error.rs index 28de3709..c358cc06 100644 --- a/crates/core/src/auth/error.rs +++ b/crates/core/src/auth/error.rs @@ -4,6 +4,8 @@ pub type Result = std::result::Result; #[derive(Clone, Debug, Error, PartialEq, Eq)] pub enum AuthError { + #[error("Verify Password Error. {0}")] + VerifyPasswordError(String), #[error("Failed to sign token")] SignTokenError, #[error("Failed to encode JWT")] diff --git a/crates/core/src/auth/service.rs b/crates/core/src/auth/service.rs index 850dcee7..264d0470 100644 --- a/crates/core/src/auth/service.rs +++ b/crates/core/src/auth/service.rs @@ -83,10 +83,10 @@ impl AuthService { Ok(token_data.claims) } - pub fn validate_password(&self, encoded: &str, raw: &str) -> bool { + pub fn validate_password(&self, encoded: &str, raw: &str) -> Result { let raw = raw.as_bytes(); - verify_encoded(encoded, raw).unwrap() + verify_encoded(encoded, raw).map_err(|err| AuthError::VerifyPasswordError(err.to_string())) } pub fn parse_jwt(&self, jwt: &str) -> Result { diff --git a/crates/core/src/user/error.rs b/crates/core/src/user/error.rs index 8db0d808..a313fbe1 100644 --- a/crates/core/src/user/error.rs +++ b/crates/core/src/user/error.rs @@ -1,5 +1,6 @@ use thiserror::Error; +use crate::auth::error::AuthError; use crate::image::error::ImageError; use super::model::EmailError; @@ -11,6 +12,8 @@ pub type Result = std::result::Result; #[derive(Clone, Debug, Error, PartialEq, Eq)] pub enum UserError { + #[error("Authentication Error. {0}")] + AuthError(#[from] AuthError), #[error("The username is invalid")] UsernameError(UsernameError), #[error("The username {0} is already taken")] diff --git a/crates/core/src/user/service.rs b/crates/core/src/user/service.rs index cee94537..ed5c2c5c 100644 --- a/crates/core/src/user/service.rs +++ b/crates/core/src/user/service.rs @@ -34,7 +34,7 @@ pub struct CreateUserDto { pub surname: String, pub username: Username, pub email: Email, - pub password: Password, + pub password: String, // FIXME: Use a secret based ds } pub struct UploadAvatarDto { @@ -65,6 +65,8 @@ impl UserService

{ } pub async fn register(&self, dto: CreateUserDto) -> Result { + let password_hash = Password::hash(&dto.password)?; + self.repository .insert(InsertUserDto { id: User::pxid()?.to_string(), @@ -72,7 +74,7 @@ impl UserService

{ surname: dto.surname, username: dto.username.to_string(), email: dto.email.to_string(), - password_hash: dto.password.to_string(), + password_hash, }) .await } @@ -125,7 +127,7 @@ impl UserService

{ if let Some(password_hash) = maybe_password_hash { if self .auth_service - .validate_password(&password_hash, password) + .validate_password(&password_hash, password)? { return Ok(true); } diff --git a/crates/server/src/graphql/modules/auth/mutation/token_create.rs b/crates/server/src/graphql/modules/auth/mutation/token_create.rs index 69c9ba1b..df011693 100644 --- a/crates/server/src/graphql/modules/auth/mutation/token_create.rs +++ b/crates/server/src/graphql/modules/auth/mutation/token_create.rs @@ -1,5 +1,6 @@ use async_graphql::{Context, Result, SimpleObject}; +use axum::http::header::SET_COOKIE; use serde::{Deserialize, Serialize}; use crate::context::SharedContext; @@ -26,6 +27,7 @@ impl TokenCreate { if credentials_ok { if let Some(user) = context.services.user.find_by_email(&email).await? { let token = context.services.auth.sign_token(user.id)?; + ctx.insert_http_header(SET_COOKIE, format!("Set-Cookie={token}")); return Ok(Self { token: Some(AccessToken { @@ -35,7 +37,6 @@ impl TokenCreate { }); } } - Ok(Self { token: None, error: Some(AuthError { @@ -45,6 +46,7 @@ impl TokenCreate { }) } Err(err) => { + println!("{:?}", err); tracing::error!(%err, "Failed to verify credentials"); Ok(Self { diff --git a/crates/server/src/graphql/modules/user/mutation/user_register.rs b/crates/server/src/graphql/modules/user/mutation/user_register.rs index 4163f9a1..8988f6b8 100644 --- a/crates/server/src/graphql/modules/user/mutation/user_register.rs +++ b/crates/server/src/graphql/modules/user/mutation/user_register.rs @@ -29,13 +29,12 @@ impl UserRegister { pub async fn exec(ctx: &Context<'_>, input: UserRegisterInput) -> Result { let context = ctx.data_unchecked::(); let username = Username::from_str(&input.username)?; - let password = Password::from_str(&input.password)?; let dto = CreateUserDto { name: input.name, surname: input.surname, username, email: input.email.into_inner(), - password, + password: input.password.clone(), }; match context.services.user.register(dto).await { diff --git a/crates/test/src/modules/auth/mod.rs b/crates/test/src/modules/auth/mod.rs new file mode 100644 index 00000000..fba1933e --- /dev/null +++ b/crates/test/src/modules/auth/mod.rs @@ -0,0 +1 @@ +mod token_create; diff --git a/crates/test/src/modules/auth/token_create.rs b/crates/test/src/modules/auth/token_create.rs new file mode 100644 index 00000000..da5d9cb7 --- /dev/null +++ b/crates/test/src/modules/auth/token_create.rs @@ -0,0 +1,63 @@ +use std::str::FromStr; + +use async_graphql::{Request, Variables}; +use serde_json::json; +use townhall::user::{ + model::{Email, Password, Username}, + service::CreateUserDto, +}; + +use crate::TestUtil; + +#[tokio::test] +async fn authenticates_successfully() { + let test_util = TestUtil::new_cleared().await; + let (context, schema) = test_util.parts(); + let mutation: &str = " + mutation TokenCreate($email: String!, $password: String!) { + tokenCreate(email: $email, password: $password) { + token { + accessToken + } + error { + code + message + } + } + } + "; + let password = String::from("TestingPassword1234"); + let user = context + .services + .user + .register(CreateUserDto { + name: "John".into(), + surname: "Appleseed".into(), + username: Username::from_str("jappleseed").unwrap(), + email: Email::from_str("j.appleseed@mail.com").unwrap(), + password: password.clone(), + }) + .await + .unwrap(); + + let result = schema + .execute( + Request::new(mutation).variables(Variables::from_json(json!({ + "email": "j.appleseed@mail.com", + "password": password.clone(), + }))), + ) + .await; + + println!("{:#?}", result); + + let set_cookie = result.http_headers.get("Set-Cookie"); + + assert!(set_cookie.is_some()); + + let data = result.data.into_json().unwrap(); + let access_token = &data["tokenCreate"]["token"]["accessToken"]; + + assert!(!access_token.to_string().is_empty()); + assert_eq!(set_cookie.unwrap().to_str().unwrap(), access_token.to_string()); +} diff --git a/crates/test/src/modules/mod.rs b/crates/test/src/modules/mod.rs index edaa3cb3..507c38bd 100644 --- a/crates/test/src/modules/mod.rs +++ b/crates/test/src/modules/mod.rs @@ -1,2 +1,3 @@ +mod auth; mod post; mod user; diff --git a/crates/test/src/modules/post/post_create.rs b/crates/test/src/modules/post/post_create.rs index 3ab0e78d..65ed2177 100644 --- a/crates/test/src/modules/post/post_create.rs +++ b/crates/test/src/modules/post/post_create.rs @@ -45,7 +45,7 @@ async fn create_post() { let (context, schema) = test_util.parts(); let username = Username::from_str("john_appleseed").unwrap(); let email = Email::from_str("john_appleseed@whizzes.io").unwrap(); - let password = Password::from_str("Root$1234").unwrap(); + let password = String::from("Root$1234"); let user = context .services .user @@ -93,7 +93,7 @@ async fn creates_post_with_parent() { let (context, schema) = test_util.parts(); let username = Username::from_str("john_appleseed").unwrap(); let email = Email::from_str("john_appleseed@whizzes.io").unwrap(); - let password = Password::from_str("Root$1234").unwrap(); + let password = String::from("Root$1234"); let user = context .services .user diff --git a/crates/test/src/modules/user/user_update.rs b/crates/test/src/modules/user/user_update.rs index da2e28a8..21957309 100644 --- a/crates/test/src/modules/user/user_update.rs +++ b/crates/test/src/modules/user/user_update.rs @@ -21,7 +21,7 @@ async fn updates_a_user() { surname: "Madu".into(), email: Email::from_str("augustine@gmail.com").unwrap(), username: Username::from_str("Cudi").unwrap(), - password: Password::from_str("Password12##$$").unwrap(), + password: String::from("Password12##$$"), }) .await .unwrap();