Skip to content

Commit

Permalink
Merge pull request #1768 from fastn-stack/auth/reset-password
Browse files Browse the repository at this point in the history
Auth/reset password and some auth changes
  • Loading branch information
Heulitig authored Feb 23, 2024
2 parents 70199d7 + bc4b374 commit 4fd0b6d
Show file tree
Hide file tree
Showing 24 changed files with 780 additions and 200 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/run-integration-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ sleep 10

echo "Running integration tests"
cd "${FASTN_ROOT}/integration-tests" || exit 1
fastn --trace test --headless
fastn test --headless
6 changes: 1 addition & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ jobs:
FASTN_DB_URL: postgres://testuser:testpassword@localhost:5432/testdb
DEBUG: true
FASTN_ENABLE_AUTH: true
FASTN_SMTP_USERNAME:
FASTN_SMTP_PASSWORD:
FASTN_SMTP_HOST:
FASTN_SMTP_SENDER_EMAIL:
FASTN_SMTP_SENDER_NAME:
FASTN_ENABLE_EMAIL: false
services:
postgres:
image: postgres:latest
Expand Down
12 changes: 8 additions & 4 deletions fastn-core/src/auth/email_password/confirm_email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ pub(crate) async fn confirm_email(
// TODO: this redirect route should be configurable
tracing::info!("provided code has expired.");
return Ok(fastn_core::http::temporary_redirect(format!(
"{}://{}/-/auth/resend-confirmation-email/",
req_config.request.connection_info.scheme(),
req_config.request.connection_info.host(),
"{scheme}://{host}{resend_confirmation_email_route}",
scheme = req_config.request.connection_info.scheme(),
host = req_config.request.connection_info.host(),
resend_confirmation_email_route = fastn_core::auth::Route::ResendConfirmationEmail
)));
}

Expand Down Expand Up @@ -84,7 +85,10 @@ pub(crate) async fn confirm_email(
.is_ok();

let next_path = if onboarding_enabled {
format!("/-/auth/onboarding/?next={}", next)
format!(
"{onboarding_route}?next={next}",
onboarding_route = fastn_core::auth::Route::Onboarding
)
} else {
next.to_string()
};
Expand Down
50 changes: 22 additions & 28 deletions fastn-core/src/auth/email_password/create_account.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::auth::email_password::{create_account_ftd, create_and_send_confirmation_email};
use crate::auth::email_password::{create_account_ftd, redirect_url_from_next};

#[derive(serde::Deserialize, serde::Serialize, validator::Validate, Debug)]
struct UserPayload {
Expand All @@ -13,6 +13,16 @@ struct UserPayload {
arg = "(&'v_a str, &'v_a str, &'v_a str)"
))]
password: String,
#[validate(must_match(
other = "password",
message = "password and confirm password field do not match"
))]
password2: String,
#[validate(custom(
function = "fastn_core::auth::validator::accept_terms",
message = "you must accept the terms and conditions"
))]
accept_terms: bool,
}

pub(crate) async fn create_account(
Expand All @@ -31,7 +41,7 @@ pub(crate) async fn create_account(

let main = fastn_core::Document {
package_name: req_config.config.package.name.clone(),
id: "/-/auth/create-account/".to_string(),
id: fastn_core::auth::Route::CreateAccount.to_string(),
content: create_account_ftd().to_string(),
parent_path: fastn_ds::Path::new("/"),
};
Expand Down Expand Up @@ -156,31 +166,15 @@ pub(crate) async fn create_account(

let user = save_user_email_transaction.expect("expected transaction to yield Some");

tracing::info!("fastn_user email inserted");

let (conf_link, session_id) = create_and_send_confirmation_email(
user.email.0.to_string(),
&mut conn,
req_config,
next.clone(),
)
.await?;

// email is not enabled, we should log conf link assuming dev mode
if !req_config
.config
.ds
.env_bool("FASTN_ENABLE_EMAIL", true)
.await?
{
println!("CONFIRMATION LINK: {}", conf_link);
}
tracing::info!("fastn_user email inserted, id: {}", user.id);

let next = format!(
"{resend_conf_route}?email={email}&next={next}",
resend_conf_route = fastn_core::auth::Route::ResendConfirmationEmail,
email = user.email.0
);

fastn_core::auth::set_session_cookie_and_redirect_to_next(
&req_config.request,
&req_config.config.ds,
session_id,
next,
)
.await
Ok(fastn_core::http::temporary_redirect(
redirect_url_from_next(&req_config.request, next),
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) async fn create_and_send_confirmation_email(
let key = generate_key(64);
let now = chrono::Utc::now();

let (email_id, user_id): (i64, i64) = fastn_core::schema::fastn_user_email::table
let query_result: Result<(i64, i64), _> = fastn_core::schema::fastn_user_email::table
.select((
fastn_core::schema::fastn_user_email::id,
fastn_core::schema::fastn_user_email::user_id,
Expand All @@ -22,7 +22,14 @@ pub(crate) async fn create_and_send_confirmation_email(
.eq(fastn_core::utils::citext(email.as_str())),
)
.first(conn)
.await?;
.await;

if let Err(e) = query_result {
tracing::error!("failed to get email_id and user_id from db: {:?}", e);
return Err(fastn_core::error::Error::generic("Bad request"));
}

let (email_id, user_id) = query_result.unwrap();

// create a non active fastn_auth_session entry for auto login
let session_id: i64 = diesel::insert_into(fastn_core::schema::fastn_auth_session::table)
Expand Down Expand Up @@ -50,21 +57,6 @@ pub(crate) async fn create_and_send_confirmation_email(

let confirmation_link = confirmation_link(&req_config.request, stored_key, next);

let mailer = fastn_core::mail::Mailer::from_env(&req_config.config.ds).await;

if mailer.is_err() {
return Err(fastn_core::Error::generic(
"Failed to create mailer from env. Creating mailer requires the following environment variables: \
\tFASTN_SMTP_USERNAME \
\tFASTN_SMTP_PASSWORD \
\tFASTN_SMTP_HOST \
\tFASTN_SMTP_SENDER_EMAIL \
\tFASTN_SMTP_SENDER_NAME",
));
}

let mailer = mailer.unwrap();

let name: String = fastn_core::schema::fastn_user::table
.select(fastn_core::schema::fastn_user::name)
.filter(fastn_core::schema::fastn_user::id.eq(user_id))
Expand Down Expand Up @@ -115,21 +107,22 @@ pub(crate) async fn create_and_send_confirmation_email(

tracing::info!("confirmation link: {}", &confirmation_link);

mailer
.send_raw(
req_config
.config
.ds
.env_bool("FASTN_ENABLE_EMAIL", true)
.await?,
format!("{} <{}>", name, email)
.parse::<lettre::message::Mailbox>()
.unwrap(),
"Verify your email",
confirmation_mail_body(html, &confirmation_link),
)
.await
.map_err(|e| fastn_core::Error::generic(format!("failed to send email: {e}")))?;
fastn_core::mail::Mailer::send_raw(
req_config
.config
.ds
.env_bool("FASTN_ENABLE_EMAIL", true)
.await
.unwrap_or(true),
&req_config.config.ds,
format!("{} <{}>", name, email)
.parse::<lettre::message::Mailbox>()
.unwrap(),
"Verify your email",
confirmation_mail_body(html, &confirmation_link),
)
.await
.map_err(|e| fastn_core::Error::generic(format!("failed to send email: {e}")))?;

Ok((confirmation_link, session_id))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub(crate) async fn email_confirmation_sent(
) -> fastn_core::Result<fastn_core::http::Response> {
let main = fastn_core::Document {
package_name: req_config.config.package.name.clone(),
id: "/-/auth/confirmation-email-sent/".to_string(),
id: fastn_core::auth::Route::EmailConfirmationSent.to_string(),
content: email_confirmation_sent_ftd().to_string(),
parent_path: fastn_ds::Path::new("/"),
};
Expand Down
2 changes: 1 addition & 1 deletion fastn-core/src/auth/email_password/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) async fn login(

let main = fastn_core::Document {
package_name: req_config.config.package.name.clone(),
id: "/-/auth/login/".to_string(),
id: fastn_core::auth::Route::Login.to_string(),
content: fastn_core::auth::email_password::login_ftd().to_string(),
parent_path: fastn_ds::Path::new("/"),
};
Expand Down
30 changes: 28 additions & 2 deletions fastn-core/src/auth/email_password/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ mod create_and_send_confirmation_email;
mod email_confirmation_sent;
mod login;
mod onboarding;
mod resend_email;
mod resend_confirmation_email;
mod set_password;
mod urls;

pub(crate) use {
Expand All @@ -14,7 +15,8 @@ pub(crate) use {
email_confirmation_sent::email_confirmation_sent,
login::login,
onboarding::onboarding,
resend_email::resend_email,
resend_confirmation_email::resend_confirmation_email,
set_password::*,
urls::{confirmation_link, redirect_url_from_next},
};

Expand Down Expand Up @@ -56,6 +58,30 @@ fn email_confirmation_sent_ftd() -> &'static str {
"#
}

fn forgot_password_request_success_ftd() -> &'static str {
r#"
-- auth.forgot-password-request-email-sent-page:
"#
}

fn set_password_form_ftd() -> &'static str {
r#"
-- auth.set-password-form-page:
"#
}

fn set_password_success_ftd() -> &'static str {
r#"
-- auth.set-password-success-page:
"#
}

fn forgot_password_form_ftd() -> &'static str {
r#"
-- auth.forgot-password-form-page:
"#
}

fn create_account_ftd() -> &'static str {
r#"
-- auth.create-account-page:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use crate::auth::email_password::{create_and_send_confirmation_email, redirect_url_from_next};
use crate::auth::email_password::create_and_send_confirmation_email;

pub(crate) async fn resend_email(
pub(crate) async fn resend_confirmation_email(
req: &fastn_core::http::Request,
req_config: &mut fastn_core::RequestConfig,
db_pool: &fastn_core::db::PgPool,
next: String,
) -> fastn_core::Result<fastn_core::http::Response> {
// TODO: should be able to use username for this too
// TODO: use req.body and make it POST
// verify email with regex or validator crate
// on GET this handler should render auth.resend-email-page
let email = req.query().get("email");

if email.is_none() {
Expand All @@ -23,19 +20,35 @@ pub(crate) async fn resend_email(
}
};

if !validator::validate_email(&email) {
return Ok(fastn_core::http::api_error("Bad Request")?);
}

let mut conn = db_pool
.get()
.await
.map_err(|e| fastn_core::Error::DatabaseError {
message: format!("Failed to get connection to db. {:?}", e),
})?;

create_and_send_confirmation_email(email, &mut conn, req_config, next.clone()).await?;
let (conf_link, session_id) =
create_and_send_confirmation_email(email, &mut conn, req_config, next.clone()).await?;

// email is not enabled, we should log conf link assuming dev mode
if !req_config
.config
.ds
.env_bool("FASTN_ENABLE_EMAIL", true)
.await?
{
println!("CONFIRMATION LINK: {}", conf_link);
}

// TODO: there's no GET /-/auth/login/ yet
// the client will have to create one for now
// this path should be configuratble too
Ok(fastn_core::http::temporary_redirect(
redirect_url_from_next(req, next),
))
fastn_core::auth::set_session_cookie_and_redirect_to_next(
&req_config.request,
&req_config.config.ds,
session_id,
next,
)
.await
}
Loading

0 comments on commit 4fd0b6d

Please sign in to comment.