From d24cb5d94e0620eb95af5b1f561c441471e1a0df Mon Sep 17 00:00:00 2001 From: Philippe PITTOLI Date: Sat, 6 Jul 2024 18:04:12 +0200 Subject: [PATCH] Each email address change should be validated. --- src/authd.cr | 3 +++ src/authd/user.cr | 4 ++++ src/process.cr | 24 ++++++++++++++++++++++++ src/requests/moduser.cr | 24 ++++++++++++++++++++++-- src/requests/password.cr | 17 +---------------- src/requests/register.cr | 31 ++++++++++--------------------- src/requests/users.cr | 12 ++++++++---- src/responses/errors.cr | 6 ++++++ 8 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 src/process.cr diff --git a/src/authd.cr b/src/authd.cr index b7c544e..c15c070 100644 --- a/src/authd.cr +++ b/src/authd.cr @@ -39,5 +39,8 @@ require "./authd/exceptions" # Requests and responses. require "./network" +# Run child processes. +require "./process" + # Functions to request the authd server. require "./authd/client.cr" diff --git a/src/authd/user.cr b/src/authd/user.cr index 7bafbf0..552f53b 100644 --- a/src/authd/user.cr +++ b/src/authd/user.cr @@ -26,6 +26,10 @@ class AuthD::User property activation_key : String? = nil property email : String? + # Not yet validated email address: useful to keep a the previous validated email address + # until the new address is validated. + property pending_email : String? = nil + def initialize(@email = nil) end diff --git a/src/process.cr b/src/process.cr new file mode 100644 index 0000000..36655fe --- /dev/null +++ b/src/process.cr @@ -0,0 +1,24 @@ +def run_process(cmd : String, params : Array(String), env : Hash(String, String)) + unless Process.run(cmd, params, env, + true # clear environment + # input: Process::Redirect::Inherit, + # output: Process::Redirect::Inherit, + # error: Process::Redirect::Inherit + ).success? + raise "cannot run #{cmd} #{params.join(" ")}" + end +end + +# Send a token to recovery the user's password. +def send_recovery_token(authd : AuthD::Service, login : String, email : String, token : String) + run_process(authd.configuration.mailer_exe, + [ "send", authd.configuration.recovery_template, email ], + { "HOME" => "/", "LOGIN" => login, "TOKEN" => token }) +end + +# Send a token to validate the user's email address. +def send_activation_token(authd : AuthD::Service, login : String, email : String, token : String) + run_process(authd.configuration.mailer_exe, + [ "send", authd.configuration.activation_template, email ], + { "HOME" => "/", "LOGIN" => login, "TOKEN" => token }) +end diff --git a/src/requests/moduser.cr b/src/requests/moduser.cr index 92962b0..1827ceb 100644 --- a/src/requests/moduser.cr +++ b/src/requests/moduser.cr @@ -33,8 +33,28 @@ class AuthD::Request cloned_user.password_hash = authd.hash_password s end - @email.try do |email| - cloned_user.contact.email = email + # In case of a new email address: + # 1. the new address is stored as "pending_email" + # 2. the new address has to be validated before being used as primary email address + if email = @email + # Verify the email address isn't already in the database. + if authd.users_per_email.get? Base64.encode(email).chomp + return Response::ErrorEmailAddressAlreadyUsed.new + end + cloned_user.contact.pending_email = email + cloned_user.contact.new_activation_key + + begin + u_login = cloned_user.login + u_email = cloned_user.contact.pending_email.not_nil! + u_activation_token = cloned_user.contact.activation_key.not_nil! + + # Once the user is created and stored, we try to contact him. + send_activation_token authd, u_login, u_email, u_activation_token + rescue e + Baguette::Log.error "mailer: #{e}" + return Response::ErrorCannotContactUser.new + end end begin diff --git a/src/requests/password.cr b/src/requests/password.cr index dea0c30..cf5b7aa 100644 --- a/src/requests/password.cr +++ b/src/requests/password.cr @@ -35,26 +35,11 @@ class AuthD::Request user.password_renew_key.not_nil! end - mailer_exe = authd.configuration.mailer_exe - template_name = authd.configuration.recovery_template - u_login = user.login u_email = user.contact.email.not_nil! u_token = user.password_renew_key.not_nil! - # Once the user is created and stored, we try to contact him. - unless Process.run(mailer_exe, - # PARAMETERS - [ "send", template_name, u_email ], - # ENV - { "HOME" => "/", "LOGIN" => u_login, "TOKEN" => u_token }, - true # clear environment - # input: Process::Redirect::Inherit, - # output: Process::Redirect::Inherit, - # error: Process::Redirect::Inherit - ).success? - raise "cannot contact user #{u_login} address #{u_email}" - end + send_recovery_token authd, u_login, u_email, u_token Response::PasswordRecoverySent.new end diff --git a/src/requests/register.cr b/src/requests/register.cr index 4cbd90b..ef6e8e2 100644 --- a/src/requests/register.cr +++ b/src/requests/register.cr @@ -25,13 +25,16 @@ class AuthD::Request return Response::ErrorMailRequired.new end - if ! @email.nil? + if m = @email # Test on the email address format. grok = Grok.new [ "%{EMAILADDRESS:email}" ] - result = grok.parse @email.not_nil! + result = grok.parse m email = result["email"]? return Response::ErrorInvalidEmailFormat.new if email.nil? + + # Verify the email address isn't already in the database. + return Response::ErrorEmailAddressAlreadyUsed.new if authd.users_per_email.get? Base64.encode(m).chomp end # In this case we should not accept its registration. @@ -42,7 +45,7 @@ class AuthD::Request password = authd.hash_password @password user = User.new uid, @login, password - user.contact.email = @email unless @email.nil? + user.contact.pending_email = @email unless @email.nil? user.contact.new_activation_key @profile.try do |profile| @@ -52,26 +55,12 @@ class AuthD::Request user.date_registration = Time.local begin - mailer_exe = authd.configuration.mailer_exe - template_name = authd.configuration.activation_template - - u_login = user.login - u_email = user.contact.email.not_nil! - u_activation_key = user.contact.activation_key.not_nil! + u_login = user.login + u_email = user.contact.pending_email.not_nil! + u_activation_token = user.contact.activation_key.not_nil! # Once the user is created and stored, we try to contact him. - unless Process.run(mailer_exe, - # PARAMETERS - [ "send", template_name, u_email ], - # ENV - { "HOME" => "/", "LOGIN" => u_login, "TOKEN" => u_activation_key }, - true # clear environment - # input: Process::Redirect::Inherit, - # output: Process::Redirect::Inherit, - # error: Process::Redirect::Inherit - ).success? - raise "cannot contact user #{u_login} address #{u_email}" - end + send_activation_token authd, u_login, u_email, u_activation_token rescue e Baguette::Log.error "mailer: #{e}" return Response::ErrorCannotContactUser.new diff --git a/src/requests/users.cr b/src/requests/users.cr index 83bc00e..a5e5e29 100644 --- a/src/requests/users.cr +++ b/src/requests/users.cr @@ -17,13 +17,17 @@ class AuthD::Request end # Remove the user contact activation key: the email is validated. - if user.contact.activation_key == @activation_key - user.contact.activation_key = nil - else + if user.contact.activation_key != @activation_key return Response::ErrorInvalidActivationKey.new end - authd.users_per_uid.update user.uid.to_s, user + cloned_user = user.clone + + cloned_user.contact.activation_key = nil + cloned_user.contact.email = cloned_user.contact.pending_email + cloned_user.contact.pending_email = nil + + authd.users_per_uid.update cloned_user Response::UserValidated.new user.to_public end diff --git a/src/responses/errors.cr b/src/responses/errors.cr index 81d34a2..ecb5886 100644 --- a/src/responses/errors.cr +++ b/src/responses/errors.cr @@ -108,4 +108,10 @@ class AuthD::Response end end AuthD.responses << ErrorEmailAddressNotValidated + + IPC::JSON.message ErrorEmailAddressAlreadyUsed, 37 do + def initialize() + end + end + AuthD.responses << ErrorEmailAddressAlreadyUsed end