1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Add an email field in the registration form

This commit is contained in:
Quentin Gliech
2022-06-02 15:40:02 +02:00
parent f88ff5517d
commit e0c4b39482
8 changed files with 57 additions and 10 deletions

View File

@ -86,7 +86,7 @@ pub(crate) async fn post(
return Ok((cookie_jar, login.go()).into_response());
};
let user_email = add_user_email(&mut txn, &session.user, form.email).await?;
let user_email = add_user_email(&mut txn, &session.user, &form.email).await?;
let next = mas_router::AccountVerifyEmail::new(user_email.data);
let next = if let Some(action) = query.post_auth_action {
next.and_then(action)

View File

@ -141,7 +141,7 @@ pub(crate) async fn post(
match form {
ManagementForm::Add { email } => {
let user_email = add_user_email(&mut txn, &session.user, email).await?;
let user_email = add_user_email(&mut txn, &session.user, &email).await?;
let next = mas_router::AccountVerifyEmail::new(user_email.data);
start_email_verification(&mailer, &mut txn, &session.user, user_email).await?;
txn.commit().await?;

View File

@ -14,23 +14,30 @@
#![allow(clippy::trait_duplication_in_bounds)]
use std::str::FromStr;
use argon2::Argon2;
use axum::{
extract::{Extension, Form, Query},
response::{Html, IntoResponse, Response},
};
use axum_extra::extract::PrivateCookieJar;
use lettre::{message::Mailbox, Address};
use mas_axum_utils::{
csrf::{CsrfExt, CsrfToken, ProtectedForm},
FancyError, SessionInfoExt,
};
use mas_config::Encrypter;
use mas_email::Mailer;
use mas_router::Route;
use mas_storage::user::{register_user, start_session, username_exists};
use mas_templates::{
FieldError, FormError, RegisterContext, RegisterFormField, TemplateContext, Templates,
ToFormState,
use mas_storage::user::{
add_user_email, add_user_email_verification_code, register_user, start_session, username_exists,
};
use mas_templates::{
EmailVerificationContext, FieldError, FormError, RegisterContext, RegisterFormField,
TemplateContext, Templates, ToFormState,
};
use rand::{distributions::Uniform, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use sqlx::{PgConnection, PgPool};
@ -39,6 +46,7 @@ use super::shared::OptionalPostAuthAction;
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct RegisterForm {
username: String,
email: String,
password: String,
password_confirm: String,
}
@ -78,6 +86,7 @@ pub(crate) async fn get(
}
pub(crate) async fn post(
Extension(mailer): Extension<Mailer>,
Extension(templates): Extension<Templates>,
Extension(pool): Extension<PgPool>,
Query(query): Query<OptionalPostAuthAction>,
@ -100,6 +109,12 @@ pub(crate) async fn post(
state.add_error_on_field(RegisterFormField::Username, FieldError::Exists);
}
if form.email.is_empty() {
state.add_error_on_field(RegisterFormField::Email, FieldError::Required);
} else if Address::from_str(&form.email).is_err() {
state.add_error_on_field(RegisterFormField::Email, FieldError::Invalid);
}
if form.password.is_empty() {
state.add_error_on_field(RegisterFormField::Password, FieldError::Required);
}
@ -133,13 +148,32 @@ pub(crate) async fn post(
let pfh = Argon2::default();
let user = register_user(&mut txn, pfh, &form.username, &form.password).await?;
let user_email = add_user_email(&mut txn, &user, &form.email).await?;
// First, generate a code
let range = Uniform::<u32>::from(0..1_000_000);
let code = thread_rng().sample(range).to_string();
let address: Address = user_email.email.parse()?;
let verification = add_user_email_verification_code(&mut txn, user_email, code).await?;
// And send the verification email
let mailbox = Mailbox::new(Some(user.username.clone()), address);
let context = EmailVerificationContext::new(user.clone().into(), verification.clone().into());
mailer.send_verification_email(mailbox, &context).await?;
let next =
mas_router::AccountVerifyEmail::new(verification.data).and_maybe(query.post_auth_action);
let session = start_session(&mut txn, user).await?;
txn.commit().await?;
let cookie_jar = cookie_jar.set_session(&session);
let reply = query.go_next();
Ok((cookie_jar, reply).into_response())
Ok((cookie_jar, next.go()).into_response())
}
async fn render(

View File

@ -331,6 +331,12 @@ impl AccountVerifyEmail {
}
}
#[must_use]
pub fn and_maybe(mut self, action: Option<PostAuthAction>) -> Self {
self.post_auth_action = action;
self
}
#[must_use]
pub fn and_then(mut self, action: PostAuthAction) -> Self {
self.post_auth_action = Some(action);

View File

@ -572,7 +572,7 @@ pub async fn get_user_email(
pub async fn add_user_email(
executor: impl PgExecutor<'_>,
user: &User<PostgresqlBackend>,
email: String,
email: &str,
) -> anyhow::Result<UserEmail<PostgresqlBackend>> {
let res = sqlx::query_as!(
UserEmailLookup,

View File

@ -319,6 +319,9 @@ pub enum RegisterFormField {
/// The username field
Username,
/// The email field
Email,
/// The password field
Password,
@ -329,7 +332,7 @@ pub enum RegisterFormField {
impl FormField for RegisterFormField {
fn keep(&self) -> bool {
match self {
Self::Username => true,
Self::Username | Self::Email => true,
Self::Password | Self::PasswordConfirm => false,
}
}

View File

@ -33,6 +33,9 @@ pub enum FieldError {
/// An unspecified error on the field
Unspecified,
/// Invalid value for this field
Invalid,
/// That value already exists
Exists,
}

View File

@ -33,6 +33,7 @@ limitations under the License.
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
{{ field::input(label="Username", name="username", form_state=form, autocomplete="username") }}
{{ field::input(label="Email", name="email", type="email", form_state=form, autocomplete="email") }}
{{ field::input(label="Password", name="password", type="password", form_state=form, autocomplete="new-password") }}
{{ field::input(label="Confirm Password", name="password_confirm", type="password", form_state=form, autocomplete="new-password") }}