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()); 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 = mas_router::AccountVerifyEmail::new(user_email.data);
let next = if let Some(action) = query.post_auth_action { let next = if let Some(action) = query.post_auth_action {
next.and_then(action) next.and_then(action)

View File

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

View File

@ -14,23 +14,30 @@
#![allow(clippy::trait_duplication_in_bounds)] #![allow(clippy::trait_duplication_in_bounds)]
use std::str::FromStr;
use argon2::Argon2; use argon2::Argon2;
use axum::{ use axum::{
extract::{Extension, Form, Query}, extract::{Extension, Form, Query},
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };
use axum_extra::extract::PrivateCookieJar; use axum_extra::extract::PrivateCookieJar;
use lettre::{message::Mailbox, Address};
use mas_axum_utils::{ use mas_axum_utils::{
csrf::{CsrfExt, CsrfToken, ProtectedForm}, csrf::{CsrfExt, CsrfToken, ProtectedForm},
FancyError, SessionInfoExt, FancyError, SessionInfoExt,
}; };
use mas_config::Encrypter; use mas_config::Encrypter;
use mas_email::Mailer;
use mas_router::Route; use mas_router::Route;
use mas_storage::user::{register_user, start_session, username_exists}; use mas_storage::user::{
use mas_templates::{ add_user_email, add_user_email_verification_code, register_user, start_session, username_exists,
FieldError, FormError, RegisterContext, RegisterFormField, TemplateContext, Templates,
ToFormState,
}; };
use mas_templates::{
EmailVerificationContext, FieldError, FormError, RegisterContext, RegisterFormField,
TemplateContext, Templates, ToFormState,
};
use rand::{distributions::Uniform, thread_rng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{PgConnection, PgPool}; use sqlx::{PgConnection, PgPool};
@ -39,6 +46,7 @@ use super::shared::OptionalPostAuthAction;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub(crate) struct RegisterForm { pub(crate) struct RegisterForm {
username: String, username: String,
email: String,
password: String, password: String,
password_confirm: String, password_confirm: String,
} }
@ -78,6 +86,7 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
Extension(mailer): Extension<Mailer>,
Extension(templates): Extension<Templates>, Extension(templates): Extension<Templates>,
Extension(pool): Extension<PgPool>, Extension(pool): Extension<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
@ -100,6 +109,12 @@ pub(crate) async fn post(
state.add_error_on_field(RegisterFormField::Username, FieldError::Exists); 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() { if form.password.is_empty() {
state.add_error_on_field(RegisterFormField::Password, FieldError::Required); state.add_error_on_field(RegisterFormField::Password, FieldError::Required);
} }
@ -133,13 +148,32 @@ pub(crate) async fn post(
let pfh = Argon2::default(); let pfh = Argon2::default();
let user = register_user(&mut txn, pfh, &form.username, &form.password).await?; 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?; let session = start_session(&mut txn, user).await?;
txn.commit().await?; txn.commit().await?;
let cookie_jar = cookie_jar.set_session(&session); let cookie_jar = cookie_jar.set_session(&session);
let reply = query.go_next(); Ok((cookie_jar, next.go()).into_response())
Ok((cookie_jar, reply).into_response())
} }
async fn render( 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] #[must_use]
pub fn and_then(mut self, action: PostAuthAction) -> Self { pub fn and_then(mut self, action: PostAuthAction) -> Self {
self.post_auth_action = Some(action); self.post_auth_action = Some(action);

View File

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

View File

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

View File

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

View File

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