From 68be3fef2888894110ac82f418af7f0b1527dd21 Mon Sep 17 00:00:00 2001 From: Dotan Nahum Date: Mon, 4 Mar 2024 15:24:28 +0200 Subject: [PATCH] improve auth --- examples/demo/src/controllers/auth.rs | 38 +++++++++++-------- examples/demo/src/controllers/dashboard.rs | 7 +++- examples/demo/src/controllers/mysession.rs | 8 +++- examples/demo/src/controllers/notes.rs | 6 +-- examples/demo/src/mailers/auth.rs | 4 +- examples/demo/src/mailers/auth/forgot/html.t | 4 +- examples/demo/src/mailers/auth/forgot/text.t | 2 +- examples/demo/src/mailers/auth/welcome/html.t | 2 +- examples/demo/src/mailers/auth/welcome/text.t | 2 +- examples/demo/src/models/users.rs | 2 +- examples/demo/src/views/auth.rs | 20 +++++++--- examples/demo/src/views/dashboard.rs | 9 ++++- examples/demo/src/views/notes.rs | 2 + examples/demo/tests/requests/notes.rs | 25 ------------ examples/demo/tests/requests/prepare_data.rs | 6 +-- ...can_login_without_verify@auth_request.snap | 2 +- .../can_register@auth_request-2.snap | 2 +- .../can_reset_password@auth_request-2.snap | 4 +- .../can_reset_password@auth_request.snap | 2 +- .../snapshots/get_notes@notes_request.snap | 2 +- .../get_notes_with_filters@notes_request.snap | 2 +- ...et_notes_with_page_size@notes_request.snap | 2 +- ...otes_with_size_and_page@notes_request.snap | 2 +- ...get_notes_withnext_page@notes_request.snap | 2 +- ...ogin_with_valid_password@auth_request.snap | 2 +- src/config.rs | 2 +- src/controller/format.rs | 9 +++++ src/controller/mod.rs | 9 +++++ src/testing.rs | 7 +++- 29 files changed, 107 insertions(+), 79 deletions(-) diff --git a/examples/demo/src/controllers/auth.rs b/examples/demo/src/controllers/auth.rs index da2bc0645..a81e7ab32 100644 --- a/examples/demo/src/controllers/auth.rs +++ b/examples/demo/src/controllers/auth.rs @@ -1,4 +1,4 @@ -use loco_rs::prelude::*; +use loco_rs::{controller::bad_request, prelude::*}; use serde::{Deserialize, Serialize}; use crate::{ @@ -7,7 +7,7 @@ use crate::{ _entities::users, users::{LoginParams, RegisterParams}, }, - views::auth::LoginResponse, + views::auth::UserSession, }; #[derive(Debug, Deserialize, Serialize)] pub struct VerifyParams { @@ -30,7 +30,7 @@ pub struct ResetParams { async fn register( State(ctx): State, Json(params): Json, -) -> Result> { +) -> impl IntoResponse { let res = users::Model::create_with_password(&ctx.db, ¶ms).await; let user = match res { @@ -41,7 +41,7 @@ async fn register( user_email = ¶ms.email, "could not register user", ); - return format::json(()); + return bad_request("could not register user"); } }; @@ -52,7 +52,12 @@ async fn register( AuthMailer::send_welcome(&ctx, &user).await?; - format::json(()) + let jwt_secret = ctx.config.get_jwt_config()?; + + let token = user + .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) + .or_else(|_| unauthorized("unauthorized!"))?; + format::json(UserSession::new(&user, &token)) } /// Verify register user. if the user not verified his email, he can't login to @@ -60,7 +65,7 @@ async fn register( async fn verify( State(ctx): State, Json(params): Json, -) -> Result> { +) -> impl IntoResponse { let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; if user.email_verified_at.is_some() { @@ -71,7 +76,7 @@ async fn verify( tracing::info!(pid = user.pid.to_string(), "user verified"); } - format::json(()) + format::empty_json() } /// In case the user forgot his password this endpoints generate a forgot token @@ -81,11 +86,11 @@ async fn verify( async fn forgot( State(ctx): State, Json(params): Json, -) -> Result> { +) -> impl IntoResponse { let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller - return format::json(()); + return format::empty_json(); }; let user = user @@ -95,30 +100,33 @@ async fn forgot( AuthMailer::forgot_password(&ctx, &user).await?; - format::json(()) + format::empty_json() } /// reset user password by the given parameters -async fn reset(State(ctx): State, Json(params): Json) -> Result> { +async fn reset( + State(ctx): State, + Json(params): Json, +) -> impl IntoResponse { let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller tracing::info!("reset token not found"); - return format::json(()); + return format::empty_json(); }; user.into_active_model() .reset_password(&ctx.db, ¶ms.password) .await?; - format::json(()) + format::empty_json() } /// Creates a user login and returns a token async fn login( State(ctx): State, Json(params): Json, -) -> Result> { +) -> impl IntoResponse { let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; let valid = user.verify_password(¶ms.password); @@ -133,7 +141,7 @@ async fn login( .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) .or_else(|_| unauthorized("unauthorized!"))?; - format::json(LoginResponse::new(&user, &token)) + format::json(UserSession::new(&user, &token)) } pub fn routes() -> Routes { diff --git a/examples/demo/src/controllers/dashboard.rs b/examples/demo/src/controllers/dashboard.rs index 0c41cb0dd..79e9aa8d4 100644 --- a/examples/demo/src/controllers/dashboard.rs +++ b/examples/demo/src/controllers/dashboard.rs @@ -9,9 +9,14 @@ use crate::{initializers::hello_view_engine::HelloView, views}; /// /// This function will return an error if render fails pub async fn render_home(ViewEngine(v): ViewEngine) -> Result { - views::dashboard::home(v) + views::dashboard::home(&v) } +/// Hello +/// +/// # Errors +/// +/// This function will return an error if render fails pub async fn render_hello(ViewEngine(v): ViewEngine) -> Result { // NOTE: v is a hello engine, which always returns 'hello', params dont matter. // it's a funky behavior that we use for demonstrating how easy it is diff --git a/examples/demo/src/controllers/mysession.rs b/examples/demo/src/controllers/mysession.rs index eb3641fdd..85d2eb728 100644 --- a/examples/demo/src/controllers/mysession.rs +++ b/examples/demo/src/controllers/mysession.rs @@ -2,8 +2,12 @@ use axum_session::{Session, SessionNullPool}; use loco_rs::prelude::*; -pub async fn get_session(session: Session) -> Result<()> { - println!("{:#?}", session); +/// Get a session +/// +/// # Errors +/// +/// This function will return an error if result fails +pub async fn get_session(_session: Session) -> Result<()> { format::empty() } diff --git a/examples/demo/src/controllers/notes.rs b/examples/demo/src/controllers/notes.rs index b607975f0..1c380cca7 100644 --- a/examples/demo/src/controllers/notes.rs +++ b/examples/demo/src/controllers/notes.rs @@ -7,11 +7,10 @@ use loco_rs::{ controller::views::pagination::Pager, prelude::*, }; -use sea_orm::{ColumnTrait, Condition}; +use sea_orm::Condition; use serde::{Deserialize, Serialize}; use crate::{ - common, models::_entities::notes::{ActiveModel, Column, Entity, Model}, views::notes::ListResponse, }; @@ -57,10 +56,11 @@ pub async fn list( ) .await?; + /* if let Some(settings) = &ctx.config.settings { let settings = common::settings::Settings::from_json(settings)?; println!("allow list: {:?}", settings.allow_list); - } + }*/ format::render() .cookies(&[ diff --git a/examples/demo/src/mailers/auth.rs b/examples/demo/src/mailers/auth.rs index 30bb1bf2f..28a8991aa 100644 --- a/examples/demo/src/mailers/auth.rs +++ b/examples/demo/src/mailers/auth.rs @@ -29,7 +29,7 @@ impl AuthMailer { locals: json!({ "name": user.name, "verifyToken": user.email_verification_token, - "domain": ctx.config.server.full_url() + "host": ctx.config.server.host }), ..Default::default() }, @@ -53,7 +53,7 @@ impl AuthMailer { locals: json!({ "name": user.name, "resetToken": user.reset_token, - "domain": ctx.config.server.full_url() + "host": ctx.config.server.host }), ..Default::default() }, diff --git a/examples/demo/src/mailers/auth/forgot/html.t b/examples/demo/src/mailers/auth/forgot/html.t index 221dd6020..4662b57c8 100644 --- a/examples/demo/src/mailers/auth/forgot/html.t +++ b/examples/demo/src/mailers/auth/forgot/html.t @@ -1,9 +1,9 @@ -; + Hey {{name}}, Forgot your password? No worries! You can reset it by clicking the link below: - Reset Your Password + Reset Your Password If you didn't request a password reset, please ignore this email. Best regards,
The Loco Team
diff --git a/examples/demo/src/mailers/auth/forgot/text.t b/examples/demo/src/mailers/auth/forgot/text.t index 58c30fd8d..a6ab4968b 100644 --- a/examples/demo/src/mailers/auth/forgot/text.t +++ b/examples/demo/src/mailers/auth/forgot/text.t @@ -1,3 +1,3 @@ Reset your password with this link: -http://localhost/reset#{{resetToken}} +{{host}}/reset/{{resetToken}} diff --git a/examples/demo/src/mailers/auth/welcome/html.t b/examples/demo/src/mailers/auth/welcome/html.t index ae4c41c65..33eca5261 100644 --- a/examples/demo/src/mailers/auth/welcome/html.t +++ b/examples/demo/src/mailers/auth/welcome/html.t @@ -4,7 +4,7 @@ Dear {{name}}, Welcome to Loco! You can now log in to your account. Before you get started, please verify your account by clicking the link below: - + Verify Your Account

Best regards,
The Loco Team

diff --git a/examples/demo/src/mailers/auth/welcome/text.t b/examples/demo/src/mailers/auth/welcome/text.t index 63beefd56..291ba0009 100644 --- a/examples/demo/src/mailers/auth/welcome/text.t +++ b/examples/demo/src/mailers/auth/welcome/text.t @@ -1,4 +1,4 @@ Welcome {{name}}, you can now log in. Verify your account with the link below: - http://localhost/verify#{{verifyToken}} +{{host}}/verify/{{verifyToken}} diff --git a/examples/demo/src/models/users.rs b/examples/demo/src/models/users.rs index 17eda2025..0a1673e3a 100644 --- a/examples/demo/src/models/users.rs +++ b/examples/demo/src/models/users.rs @@ -75,7 +75,7 @@ impl Authenticable for super::_entities::users::Model { } async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { - super::_entities::users::Model::find_by_pid(db, claims_key).await + Self::find_by_pid(db, claims_key).await } } diff --git a/examples/demo/src/views/auth.rs b/examples/demo/src/views/auth.rs index 2240a5087..efbf956fe 100644 --- a/examples/demo/src/views/auth.rs +++ b/examples/demo/src/views/auth.rs @@ -3,21 +3,29 @@ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] -pub struct LoginResponse { +pub struct UserSession { pub token: String, + pub user: UserDetail, +} +#[derive(Debug, Deserialize, Serialize)] +pub struct UserDetail { pub pid: String, + pub email: String, pub name: String, - pub is_verified: bool, + pub last_login: String, } -impl LoginResponse { +impl UserSession { #[must_use] pub fn new(user: &users::Model, token: &String) -> Self { Self { token: token.to_string(), - pid: user.pid.to_string(), - name: user.name.clone(), - is_verified: user.email_verified_at.is_some(), + user: UserDetail { + pid: user.pid.to_string(), + email: user.email.to_string(), + name: user.name.to_string(), + last_login: "n/a".to_string(), + }, } } } diff --git a/examples/demo/src/views/dashboard.rs b/examples/demo/src/views/dashboard.rs index 2d41ad239..bb16214ff 100644 --- a/examples/demo/src/views/dashboard.rs +++ b/examples/demo/src/views/dashboard.rs @@ -1,6 +1,11 @@ use loco_rs::prelude::*; use serde_json::json; -pub fn home(v: impl ViewRenderer) -> Result { - format::render().view(&v, "home/hello.html", json!({})) +/// Home view +/// +/// # Errors +/// +/// This function will return an error if render fails +pub fn home(v: &impl ViewRenderer) -> Result { + format::render().view(v, "home/hello.html", json!({})) } diff --git a/examples/demo/src/views/notes.rs b/examples/demo/src/views/notes.rs index 1c87f0275..83128496f 100644 --- a/examples/demo/src/views/notes.rs +++ b/examples/demo/src/views/notes.rs @@ -6,6 +6,7 @@ use crate::models::_entities::notes; #[derive(Debug, Deserialize, Serialize)] pub struct ListResponse { + id: i32, title: Option, content: Option, } @@ -23,6 +24,7 @@ impl ListResponse { #[must_use] pub fn new(note: ¬es::Model) -> Self { Self { + id: note.id, title: note.title.clone(), content: note.content.clone(), } diff --git a/examples/demo/tests/requests/notes.rs b/examples/demo/tests/requests/notes.rs index 0ee1ac75c..9761cf21c 100644 --- a/examples/demo/tests/requests/notes.rs +++ b/examples/demo/tests/requests/notes.rs @@ -101,31 +101,6 @@ async fn can_get_note() { .await; } -#[tokio::test] -#[serial] -async fn can_get_note_gzip() { - configure_insta!(); - - testing::request::(|request, ctx| async move { - testing::seed::(&ctx.db).await.unwrap(); - - let add_note_request = request.get("notes/1").await; - - with_settings!({ - filters => { - let mut combined_filters = testing::CLEANUP_DATE.to_vec(); - combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); - combined_filters - } - }, { - assert_debug_snapshot!( - (add_note_request.status_code(), add_note_request.text()) - ); - }); - }) - .await; -} - #[tokio::test] #[serial] async fn can_delete_note() { diff --git a/examples/demo/tests/requests/prepare_data.rs b/examples/demo/tests/requests/prepare_data.rs index 695da58b6..9cbe9370a 100644 --- a/examples/demo/tests/requests/prepare_data.rs +++ b/examples/demo/tests/requests/prepare_data.rs @@ -1,5 +1,5 @@ use axum::http::{HeaderName, HeaderValue}; -use blo::{models::users, views::auth::LoginResponse}; +use blo::{models::users, views::auth::UserSession}; use loco_rs::{app::AppContext, TestServer}; const USER_EMAIL: &str = "test@loco.com"; @@ -37,13 +37,13 @@ pub async fn init_user_login(request: &TestServer, ctx: &AppContext) -> LoggedIn })) .await; - let login_response: LoginResponse = serde_json::from_str(&response.text()).unwrap(); + let session: UserSession = serde_json::from_str(&response.text()).unwrap(); LoggedInUser { user: users::Model::find_by_email(&ctx.db, USER_EMAIL) .await .unwrap(), - token: login_response.token, + token: session.token, } } diff --git a/examples/demo/tests/requests/snapshots/can_login_without_verify@auth_request.snap b/examples/demo/tests/requests/snapshots/can_login_without_verify@auth_request.snap index ef54ba671..10a558676 100644 --- a/examples/demo/tests/requests/snapshots/can_login_without_verify@auth_request.snap +++ b/examples/demo/tests/requests/snapshots/can_login_without_verify@auth_request.snap @@ -4,5 +4,5 @@ expression: "(response.status_code(), response.text())" --- ( 200, - "{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"loco\",\"is_verified\":false}", + "{\"token\":\"TOKEN\",\"user\":{\"pid\":\"PID\",\"email\":\"test@loco.com\",\"name\":\"loco\",\"last_login\":\"n/a\"}}", ) diff --git a/examples/demo/tests/requests/snapshots/can_register@auth_request-2.snap b/examples/demo/tests/requests/snapshots/can_register@auth_request-2.snap index 29255296f..429e365cd 100644 --- a/examples/demo/tests/requests/snapshots/can_register@auth_request-2.snap +++ b/examples/demo/tests/requests/snapshots/can_register@auth_request-2.snap @@ -5,6 +5,6 @@ expression: ctx.mailer.unwrap().deliveries() Deliveries { count: 1, messages: [ - "From: System \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\n http://localhost/verify#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\n Verify Your Account\r\n \r\n

Best regards,
The Loco Team

\r\n\r\n\r\n\r\n\r\n--IDENTIFIER--\r\n", + "From: System \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\nhttp://localhost/verify/RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\n http://localhost/verify#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\n Verify Your Account\r\n \r\n

Best regards,
The Loco Team

\r\n\r\n\r\n\r\n\r\n--IDENTIFIER--\r\n", - "From: System \r\nTo: test@loco.com\r\nSubject: Your reset password =?utf-8?b?bGluawo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nReset your password with this link:\r\n\r\nhttp://localhost/reset#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Hey loco,\r\n Forgot your password? No worries! You can reset it by clicking the link b=\r\nelow:\r\n Reset Your Password\r\n If you didn't request a password reset, please ignore this email.\r\n Best regards,
The Loco Team
\r\n\r\n\r\n\r\n\r\n--IDENTIFIER--\r\n", + "From: System \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\nhttp://localhost/verify/RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\nTo: test@loco.com\r\nSubject: Your reset password =?utf-8?b?bGluawo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nReset your password with this link:\r\n\r\nhttp://localhost/reset/RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\n\r\n\r\n Hey loco,\r\n Forgot your password? No worries! You can reset it by clicking the link b=\r\nelow:\r\n R=\r\neset Your Password\r\n If you didn't request a password reset, please ignore this email.\r\n Best regards,
The Loco Team
\r\n\r\n\r\n\r\n\r\n--IDENTIFIER--\r\n", ], } diff --git a/examples/demo/tests/requests/snapshots/can_reset_password@auth_request.snap b/examples/demo/tests/requests/snapshots/can_reset_password@auth_request.snap index be6838d35..83304ccfe 100644 --- a/examples/demo/tests/requests/snapshots/can_reset_password@auth_request.snap +++ b/examples/demo/tests/requests/snapshots/can_reset_password@auth_request.snap @@ -4,5 +4,5 @@ expression: "(reset_response.status_code(), reset_response.text())" --- ( 200, - "null", + "{}", ) diff --git a/examples/demo/tests/requests/snapshots/get_notes@notes_request.snap b/examples/demo/tests/requests/snapshots/get_notes@notes_request.snap index 78fd4a676..e958402e1 100644 --- a/examples/demo/tests/requests/snapshots/get_notes@notes_request.snap +++ b/examples/demo/tests/requests/snapshots/get_notes@notes_request.snap @@ -4,5 +4,5 @@ expression: "(notes.status_code(), notes.text())" --- ( 200, - "{\"results\":[{\"title\":\"Loco note 1\",\"content\":\"content 1\"},{\"title\":\"Loco note 2\",\"content\":\"content 2\"},{\"title\":\"Loco note 3\",\"content\":\"content 3\"},{\"title\":\"Loco note 4\",\"content\":\"content 4\"},{\"title\":\"Loco note 5\",\"content\":\"content 5\"},{\"title\":\"Loco 6\",\"content\":\"content 6\"},{\"title\":\"Loco 7\",\"content\":\"content 7\"},{\"title\":\"Loco 8\",\"content\":\"content 8\"},{\"title\":\"Loco 9\",\"content\":\"content 9\"},{\"title\":\"Loco 10\",\"content\":\"content 10\"}],\"pagination\":{\"page\":1,\"page_size\":10,\"total_pages\":2}}", + "{\"results\":[{\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"content 1\"},{\"id\":ID,\"title\":\"Loco note 2\",\"content\":\"content 2\"},{\"id\":ID,\"title\":\"Loco note 3\",\"content\":\"content 3\"},{\"id\":ID,\"title\":\"Loco note 4\",\"content\":\"content 4\"},{\"id\":ID,\"title\":\"Loco note 5\",\"content\":\"content 5\"},{\"id\":ID,\"title\":\"Loco 6\",\"content\":\"content 6\"},{\"id\":ID,\"title\":\"Loco 7\",\"content\":\"content 7\"},{\"id\":ID,\"title\":\"Loco 8\",\"content\":\"content 8\"},{\"id\":ID,\"title\":\"Loco 9\",\"content\":\"content 9\"},{\"id\":ID,\"title\":\"Loco 10\",\"content\":\"content 10\"}],\"pagination\":{\"page\":1,\"page_size\":10,\"total_pages\":2}}", ) diff --git a/examples/demo/tests/requests/snapshots/get_notes_with_filters@notes_request.snap b/examples/demo/tests/requests/snapshots/get_notes_with_filters@notes_request.snap index d807e05cd..96faf9381 100644 --- a/examples/demo/tests/requests/snapshots/get_notes_with_filters@notes_request.snap +++ b/examples/demo/tests/requests/snapshots/get_notes_with_filters@notes_request.snap @@ -4,5 +4,5 @@ expression: "(notes.status_code(), notes.text())" --- ( 200, - "{\"results\":[{\"title\":\"Loco note 1\",\"content\":\"content 1\"},{\"title\":\"Loco note 2\",\"content\":\"content 2\"}],\"pagination\":{\"page\":1,\"page_size\":2,\"total_pages\":3}}", + "{\"results\":[{\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"content 1\"},{\"id\":ID,\"title\":\"Loco note 2\",\"content\":\"content 2\"}],\"pagination\":{\"page\":1,\"page_size\":2,\"total_pages\":3}}", ) diff --git a/examples/demo/tests/requests/snapshots/get_notes_with_page_size@notes_request.snap b/examples/demo/tests/requests/snapshots/get_notes_with_page_size@notes_request.snap index 3b71a5ffd..785a49325 100644 --- a/examples/demo/tests/requests/snapshots/get_notes_with_page_size@notes_request.snap +++ b/examples/demo/tests/requests/snapshots/get_notes_with_page_size@notes_request.snap @@ -4,5 +4,5 @@ expression: "(notes.status_code(), notes.text())" --- ( 200, - "{\"results\":[{\"title\":\"Loco note 1\",\"content\":\"content 1\"}],\"pagination\":{\"page\":1,\"page_size\":1,\"total_pages\":11}}", + "{\"results\":[{\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"content 1\"}],\"pagination\":{\"page\":1,\"page_size\":1,\"total_pages\":11}}", ) diff --git a/examples/demo/tests/requests/snapshots/get_notes_with_size_and_page@notes_request.snap b/examples/demo/tests/requests/snapshots/get_notes_with_size_and_page@notes_request.snap index 1d921f5f4..429dd3de6 100644 --- a/examples/demo/tests/requests/snapshots/get_notes_with_size_and_page@notes_request.snap +++ b/examples/demo/tests/requests/snapshots/get_notes_with_size_and_page@notes_request.snap @@ -4,5 +4,5 @@ expression: "(notes.status_code(), notes.text())" --- ( 200, - "{\"results\":[{\"title\":\"Loco 6\",\"content\":\"content 6\"},{\"title\":\"Loco 7\",\"content\":\"content 7\"},{\"title\":\"Loco 8\",\"content\":\"content 8\"},{\"title\":\"Loco 9\",\"content\":\"content 9\"},{\"title\":\"Loco 10\",\"content\":\"content 10\"}],\"pagination\":{\"page\":2,\"page_size\":5,\"total_pages\":3}}", + "{\"results\":[{\"id\":ID,\"title\":\"Loco 6\",\"content\":\"content 6\"},{\"id\":ID,\"title\":\"Loco 7\",\"content\":\"content 7\"},{\"id\":ID,\"title\":\"Loco 8\",\"content\":\"content 8\"},{\"id\":ID,\"title\":\"Loco 9\",\"content\":\"content 9\"},{\"id\":ID,\"title\":\"Loco 10\",\"content\":\"content 10\"}],\"pagination\":{\"page\":2,\"page_size\":5,\"total_pages\":3}}", ) diff --git a/examples/demo/tests/requests/snapshots/get_notes_withnext_page@notes_request.snap b/examples/demo/tests/requests/snapshots/get_notes_withnext_page@notes_request.snap index bb52a7230..975174b1b 100644 --- a/examples/demo/tests/requests/snapshots/get_notes_withnext_page@notes_request.snap +++ b/examples/demo/tests/requests/snapshots/get_notes_withnext_page@notes_request.snap @@ -4,5 +4,5 @@ expression: "(notes.status_code(), notes.text())" --- ( 200, - "{\"results\":[{\"title\":\"Loco 11\",\"content\":\"content 11\"}],\"pagination\":{\"page\":2,\"page_size\":10,\"total_pages\":2}}", + "{\"results\":[{\"id\":ID,\"title\":\"Loco 11\",\"content\":\"content 11\"}],\"pagination\":{\"page\":2,\"page_size\":10,\"total_pages\":2}}", ) diff --git a/examples/demo/tests/requests/snapshots/login_with_valid_password@auth_request.snap b/examples/demo/tests/requests/snapshots/login_with_valid_password@auth_request.snap index f06fbaa86..10a558676 100644 --- a/examples/demo/tests/requests/snapshots/login_with_valid_password@auth_request.snap +++ b/examples/demo/tests/requests/snapshots/login_with_valid_password@auth_request.snap @@ -4,5 +4,5 @@ expression: "(response.status_code(), response.text())" --- ( 200, - "{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"loco\",\"is_verified\":true}", + "{\"token\":\"TOKEN\",\"user\":{\"pid\":\"PID\",\"email\":\"test@loco.com\",\"name\":\"loco\",\"last_login\":\"n/a\"}}", ) diff --git a/src/config.rs b/src/config.rs index a8c376b7f..c8d52144e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -501,7 +501,7 @@ impl Config { .as_ref() .and_then(|auth| auth.jwt.as_ref()) .map_or_else( - || Err(Error::Any("sending email error".to_string().into())), + || Err(Error::Any("no JWT config found".to_string().into())), Ok, ) } diff --git a/src/controller/format.rs b/src/controller/format.rs index b76e48166..2f3434590 100644 --- a/src/controller/format.rs +++ b/src/controller/format.rs @@ -28,6 +28,7 @@ use axum_extra::extract::cookie::Cookie; use bytes::{BufMut, BytesMut}; use hyper::{header, StatusCode}; use serde::Serialize; +use serde_json::json; use super::views::ViewRenderer; use crate::{controller::Json, Result}; @@ -104,6 +105,14 @@ pub fn json(t: T) -> Result> { Ok(Json(t)) } +/// Respond with empty json (`{}`) +/// +/// # Errors +/// +/// This function will return an error if serde fails +pub fn empty_json() -> Result> { + Ok(Json(json!({}))) +} /// Returns an HTML response /// /// # Example: diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 215827a1b..68a7d7f8b 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -124,6 +124,15 @@ pub fn unauthorized(msg: &str) -> Result { Err(Error::Unauthorized(msg.to_string())) } +/// Return a bad request with a message +/// +/// # Errors +/// +/// This function will return an error result +pub fn bad_request(msg: &str) -> Result { + Err(Error::BadRequest(msg.to_string())) +} + /// return not found status code /// /// # Errors diff --git a/src/testing.rs b/src/testing.rs index ce84742de..d42e333bf 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -41,8 +41,11 @@ lazy_static! { (r"[0-9A-Za-z]+{40}", "IDENTIFIER"), (r"\w+, \d{1,2} \w+ \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}", "DATE"), (r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})","RANDOM_ID"), - // #6c23875d-3523-4805-8527-f2=\r\n82d3aa7514\ - (r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4})-[0-9a-fA-F]{4}-.*[0-9a-fA-F]{4}", "RANDOM_ID") + + // also handles line break in text-format emails, where they break into a new line and then use '=' as continuation symbol. + // #6c23875d-3523-4805-8527-f2=\r\n82d3aa7514 + // #6c23875d-3523-4805-8527-f282d3aa75=\r\n14 (note postfix after '=' can be short) + (r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4})-[0-9a-fA-F]{4}-.*[0-9a-fA-F]{2}", "RANDOM_ID") ]; }