Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve auth #472

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions examples/demo/src/controllers/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use loco_rs::prelude::*;
use loco_rs::{controller::bad_request, prelude::*};
use serde::{Deserialize, Serialize};

use crate::{
Expand All @@ -7,7 +7,7 @@ use crate::{
_entities::users,
users::{LoginParams, RegisterParams},
},
views::auth::LoginResponse,
views::auth::UserSession,
};
#[derive(Debug, Deserialize, Serialize)]
pub struct VerifyParams {
Expand All @@ -30,7 +30,7 @@ pub struct ResetParams {
async fn register(
State(ctx): State<AppContext>,
Json(params): Json<RegisterParams>,
) -> Result<Json<()>> {
) -> impl IntoResponse {
let res = users::Model::create_with_password(&ctx.db, &params).await;

let user = match res {
Expand All @@ -41,7 +41,7 @@ async fn register(
user_email = &params.email,
"could not register user",
);
return format::json(());
return bad_request("could not register user");
}
};

Expand All @@ -52,15 +52,20 @@ 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
/// the system.
async fn verify(
State(ctx): State<AppContext>,
Json(params): Json<VerifyParams>,
) -> Result<Json<()>> {
) -> impl IntoResponse {
let user = users::Model::find_by_verification_token(&ctx.db, &params.token).await?;

if user.email_verified_at.is_some() {
Expand All @@ -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
Expand All @@ -81,11 +86,11 @@ async fn verify(
async fn forgot(
State(ctx): State<AppContext>,
Json(params): Json<ForgotParams>,
) -> Result<Json<()>> {
) -> impl IntoResponse {
let Ok(user) = users::Model::find_by_email(&ctx.db, &params.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
Expand All @@ -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<AppContext>, Json(params): Json<ResetParams>) -> Result<Json<()>> {
async fn reset(
State(ctx): State<AppContext>,
Json(params): Json<ResetParams>,
) -> impl IntoResponse {
let Ok(user) = users::Model::find_by_reset_token(&ctx.db, &params.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, &params.password)
.await?;

format::json(())
format::empty_json()
}

/// Creates a user login and returns a token
async fn login(
State(ctx): State<AppContext>,
Json(params): Json<LoginParams>,
) -> Result<Json<LoginResponse>> {
) -> impl IntoResponse {
let user = users::Model::find_by_email(&ctx.db, &params.email).await?;

let valid = user.verify_password(&params.password);
Expand All @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion examples/demo/src/controllers/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TeraView>) -> Result<impl IntoResponse> {
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<HelloView>) -> Result<impl IntoResponse> {
// 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
Expand Down
8 changes: 6 additions & 2 deletions examples/demo/src/controllers/mysession.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
use axum_session::{Session, SessionNullPool};
use loco_rs::prelude::*;

pub async fn get_session(session: Session<SessionNullPool>) -> Result<()> {
println!("{:#?}", session);
/// Get a session
///
/// # Errors
///
/// This function will return an error if result fails
pub async fn get_session(_session: Session<SessionNullPool>) -> Result<()> {
format::empty()
}

Expand Down
6 changes: 3 additions & 3 deletions examples/demo/src/controllers/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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(&[
Expand Down
4 changes: 2 additions & 2 deletions examples/demo/src/mailers/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
},
Expand All @@ -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()
},
Expand Down
4 changes: 2 additions & 2 deletions examples/demo/src/mailers/auth/forgot/html.t
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
;<html>
<html>

<body>
Hey {{name}},
Forgot your password? No worries! You can reset it by clicking the link below:
<a href="http://{{domain}}/reset#{{resetToken}}">Reset Your Password</a>
<a href="{{host}}/reset/{{resetToken}}">Reset Your Password</a>
If you didn't request a password reset, please ignore this email.
Best regards,<br>The Loco Team</br>
</body>
Expand Down
2 changes: 1 addition & 1 deletion examples/demo/src/mailers/auth/forgot/text.t
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Reset your password with this link:

http://localhost/reset#{{resetToken}}
{{host}}/reset/{{resetToken}}
2 changes: 1 addition & 1 deletion examples/demo/src/mailers/auth/welcome/html.t
Original file line number Diff line number Diff line change
Expand Up @@ -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:
<a href="http://{{domain}}/verify#{{verifyToken}}">
<a href="{{host}}/auth/verify/{{verifyToken}}">
Verify Your Account
</a>
<p>Best regards,<br>The Loco Team</p>
Expand Down
2 changes: 1 addition & 1 deletion examples/demo/src/mailers/auth/welcome/text.t
Original file line number Diff line number Diff line change
@@ -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}}
2 changes: 1 addition & 1 deletion examples/demo/src/models/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Authenticable for super::_entities::users::Model {
}

async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult<Self> {
super::_entities::users::Model::find_by_pid(db, claims_key).await
Self::find_by_pid(db, claims_key).await
}
}

Expand Down
20 changes: 14 additions & 6 deletions examples/demo/src/views/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
}
}
}
9 changes: 7 additions & 2 deletions examples/demo/src/views/dashboard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use loco_rs::prelude::*;
use serde_json::json;

pub fn home(v: impl ViewRenderer) -> Result<impl IntoResponse> {
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<impl IntoResponse> {
format::render().view(v, "home/hello.html", json!({}))
}
2 changes: 2 additions & 0 deletions examples/demo/src/views/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::models::_entities::notes;

#[derive(Debug, Deserialize, Serialize)]
pub struct ListResponse {
id: i32,
title: Option<String>,
content: Option<String>,
}
Expand All @@ -23,6 +24,7 @@ impl ListResponse {
#[must_use]
pub fn new(note: &notes::Model) -> Self {
Self {
id: note.id,
title: note.title.clone(),
content: note.content.clone(),
}
Expand Down
25 changes: 0 additions & 25 deletions examples/demo/tests/requests/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,31 +101,6 @@ async fn can_get_note() {
.await;
}

#[tokio::test]
#[serial]
async fn can_get_note_gzip() {
configure_insta!();

testing::request::<App, _, _>(|request, ctx| async move {
testing::seed::<App>(&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() {
Expand Down
6 changes: 3 additions & 3 deletions examples/demo/tests/requests/prepare_data.rs
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\"}}",
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ expression: ctx.mailer.unwrap().deliveries()
Deliveries {
count: 1,
messages: [
"From: System <system@example.com>\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;<html>\r\n\r\n<body>\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 <a href=3D\"http://http://localhost:3000/verify#RANDOM_ID\">\r\n Verify Your Account\r\n </a>\r\n <p>Best regards,<br>The Loco Team</p>\r\n</body>\r\n\r\n</html>\r\n\r\n--IDENTIFIER--\r\n",
"From: System <system@example.com>\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;<html>\r\n\r\n<body>\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 <a href=3D\"http://localhost/auth/verify/RANDOM_IDNTIFIER--\r\n",
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ expression: ctx.mailer.unwrap().deliveries()
Deliveries {
count: 2,
messages: [
"From: System <system@example.com>\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;<html>\r\n\r\n<body>\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 <a href=3D\"http://http://localhost:3000/verify#RANDOM_ID\">\r\n Verify Your Account\r\n </a>\r\n <p>Best regards,<br>The Loco Team</p>\r\n</body>\r\n\r\n</html>\r\n\r\n--IDENTIFIER--\r\n",
"From: System <system@example.com>\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;<html>\r\n\r\n<body>\r\n Hey loco,\r\n Forgot your password? No worries! You can reset it by clicking the link b=\r\nelow:\r\n <a href=3D\"http://http://localhost:3000/reset#RANDOM_ID\">Reset Your Password</a>\r\n If you didn't request a password reset, please ignore this email.\r\n Best regards,<br>The Loco Team</br>\r\n</body>\r\n\r\n</html>\r\n\r\n--IDENTIFIER--\r\n",
"From: System <system@example.com>\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;<html>\r\n\r\n<body>\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 <a href=3D\"http://localhost/auth/verify/RANDOM_IDNTIFIER--\r\n",
"From: System <system@example.com>\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<html>\r\n\r\n<body>\r\n Hey loco,\r\n Forgot your password? No worries! You can reset it by clicking the link b=\r\nelow:\r\n <a href=3D\"http://localhost/reset/RANDOM_ID\">R=\r\neset Your Password</a>\r\n If you didn't request a password reset, please ignore this email.\r\n Best regards,<br>The Loco Team</br>\r\n</body>\r\n\r\n</html>\r\n\r\n--IDENTIFIER--\r\n",
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ expression: "(reset_response.status_code(), reset_response.text())"
---
(
200,
"null",
"{}",
)
Loading
Loading