Skip to content

Commit

Permalink
Merge pull request #73 from katmastt/main
Browse files Browse the repository at this point in the history
email verification implementation
  • Loading branch information
eleni-adamidi authored Jan 16, 2024
2 parents 5bea502 + 4f97fa9 commit cd4ad0f
Show file tree
Hide file tree
Showing 9 changed files with 475 additions and 5 deletions.
33 changes: 33 additions & 0 deletions components/EmailVerifiedFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace app\components;

use app\models\EmailVerificationRequest;
use webvimark\modules\UserManagement\models\User;
use Yii;
use yii\base\ActionFilter;

class EmailVerifiedFilter extends ActionFilter {

public function beforeAction($action) {
// 1. Get current user
$currentUser = User::getCurrentUser();

// 2. Check whether the user is authenticated. If the current user is a guest, then move on
if (!$currentUser) return parent::beforeAction($action);
// 3. Check if the user has already a pending email verification request
$email_request=EmailVerificationRequest::find()->where(['user_id'=>$currentUser->id, 'status'=>0])->andwhere(['>', 'expiry', date('c')])->orderBy('created_at DESC')->one();
// if the user has no valid pending email verification request && his email is not set or confirmed
// redirect him to enter his email
if (empty($email_request) && (trim($currentUser->email)=="" || !$currentUser->email_confirmed)) {
return $this->owner->redirect(['personal/email-verification'])->send();
// if the user has a valid (not expired) pending request
// inform him that a verification email has been sent to his address
} elseif (!empty($email_request)) {
return $this->owner->redirect(['personal/email-verification-sent','email'=>$email_request->email, 'resend'=>0])->send();
}
return parent::beforeAction($action);
}
}

?>
7 changes: 6 additions & 1 deletion config/params-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@
inputs, which is also a safe default. This however, gets inefficient quickly with the increase of the user-base.
Thus, consider defining the minimum username length used throughout the deployment.
*/
'minUsernameLength' => 1
'minUsernameLength' => 1,
'email_verification' => [
'validity_period' => '1 day', // Value should be a string literal that can be added on a date('c') object
'email_verification_url' => 'url to the email verification view'
]

];
192 changes: 189 additions & 3 deletions controllers/PersonalController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
use yii\filters\VerbFilter;
use app\models\Smtp;
use app\models\EmailEventsUser;
use app\models\EmailVerificationRequest;
use app\models\User;
use webvimark\modules\UserManagement\models\User as Userw;
use yii\web\UrlManager;
use yii\helpers\Url;


/*
Expand Down Expand Up @@ -78,6 +82,7 @@ public function actionEmailNotifications()

$user=Userw::getCurrentUser();
$user_id=$user->id;
$user_old_email = $user->email;
$user_notifications=EmailEventsUser::find()->where(['user_id'=>$user_id])->one();
if(empty($user_notifications))
{
Expand All @@ -96,15 +101,196 @@ public function actionEmailNotifications()
}
if($user->load(Yii::$app->request->post()) && $user_notifications->load(Yii::$app->request->post()))
{

if($user_old_email!=$user->email){
$user->email_confirmed=0;
}
$user->update();
$user_notifications->update();
Yii::$app->session->setFlash('success', "Your changes have been successfully submitted");
if($user->email && $user_old_email!=$user->email){
$email_verification_request = new EmailVerificationRequest();
$email_verification_request->email = $user->email;
$email_verification_request->user_id = $user->id;
EmailVerificationRequest::revokeOldAndSaveNew($email_verification_request);
EmailVerificationRequest::sendVerificationEmail($email_verification_request);
Yii::$app->session->setFlash('success', "Your changes have been successfully submitted. We've sent you a verification email.");
return $this->redirect(['user-options']);

}


Yii::$app->session->setFlash('success', "Your changes have been successfully submitted.");
return $this->redirect(['user-options']);
}

return $this->render('user_email_notifications', ['user'=>$user, 'user_notifications'=>$user_notifications,
'smtp_config'=>$smtp_config]);
}

}
public function actionEmailVerification() {
$currentUser=Userw::getCurrentUser();
$form_params = [
'options' => [
'id'=> "email_verification_form"
],
];

/* During GET:
* - If a user that has a verified email ends up here, then pass the user to the view so that the user's
* current email is filled in the first text field. If a user with a not verified mail ends up here, then
* also give this user as *it is expected that non-verified emails won't be set in the email field of the
* user model*.
*/
$email_verification = new EmailVerificationRequest();
if ($currentUser->email){
$email_verification->email = $currentUser->email;
}
if ($email_verification->load(Yii::$app->request->post())) {
if ($email_verification->validate($attributeNames=['email'])) {
$userWithGivenMail = User::find()->where(['email'=>$email_verification['email'], 'email_confirmed'=>1])->one();
Yii::debug('Searching for user that has reserved the given email: '.var_export($userWithGivenMail, true));
//Yii::debug('Host info: '.UrlManager::$hostInfo);
Yii::debug(Url::to(['post/index'], true));
if ($userWithGivenMail and $userWithGivenMail->username === $currentUser->username) {
// Say that this mail has already been assigned to the current user
Yii::debug("User $currentUser->username has already verified $email_verification->email as their email");
// Yii::$app->session->setFlash('danger', "The provided email, <b>$email_verification->email</b>, is already assigned to your account");]
$email_verification->addError('email', "Email $email_verification->email, is already assigned to your account");
return $this->render('email_verification', ['email_verification'=>$email_verification, 'form_params'=>$form_params]);
}
else if ($userWithGivenMail) {
Yii::debug("Given email $email_verification->email is not available");
Yii::$app->session->setFlash('danger', "Email address <b>$email_verification->email</b> is not available.");
return $this->render('email_verification', ['email_verification'=>$email_verification, 'form_params'=>$form_params]);
}
$email_verification->user_id=$currentUser->id;
if ($email_verification->validate()) {
EmailVerificationRequest::revokeOldAndSaveNew($email_verification);
EmailVerificationRequest::sendVerificationEmail($email_verification);
return $this->redirect(array('email-verification-sent', 'email'=>$email_verification->email, 'resend'=>0));


}
Yii::debug($email_verification->errors);
}
else {
Yii::debug('Errors found');
Yii::debug(var_export($email_verification->errors, true));
}

}
/*
* - If the user already has made a mail verification request that hasn't expired yet, notify him so that he
* may visit his mail provider.
* During POST:
* - Get the mail submitted, from both fields. Perform the following checks
* > Assert that the mail address can be validated by Yii's email validator, including DNS lookup and IDN
* > Assert that the given mail does not already exist as a verified mail in the database. If it does,
* then:
* * Check whether it is assigned to the current user. In this case return with a related error
* message. If not, return with an error message informing the user that this email is not
* available.
* - Reaching this point, the checks above have been passed. After this point, the email verification flow is
* followed:
* 1. Generate a secure token for the mail verification URL. Also infer the rest of the information needed
* to create an EmailVerificationRequest record (expiry, user_id) and create the record.
* 2. Using the created record, send the verification mail to the given mail address, containing the
* qualified URL for verifying the email.
* 3. Return by informing the user that a mail has been sent and they need to visit their mail provider
*/

return $this->render('email_verification', ['email_verification'=>$email_verification, 'form_params'=>$form_params]);
}

public function actionEmailVerificationSent ($email, $resend=0){

// resend=0 -> inform the user that a verification email has been sent to his email
// resend=1 -> the user clicked on the resend email button. Revoke the old token, create a new one and send him a verification email
if ($resend==1){
$currentUser = Userw::getCurrentUser();
$user_id = $currentUser->id;
$email_verification = new EmailVerificationRequest();
$email_verification->email = $email;
$email_verification->user_id = $user_id;
if (!empty($email_verification)) {
EmailVerificationRequest::revokeOldAndSaveNew($email_verification);
EmailVerificationRequest::sendVerificationEmail($email_verification);

// redirect to the dashboard to avoid to resend email (if resend=1) on refresh
$project = Yii::$app->createControllerByID('project');
return $project->redirect(['project/index']);
}

}
return $this->render('email_verification_sent', ['email'=>$email]);
//return $this->redirect(['email-verification-sent', 'email'=>$email, 'redirect'=>0]);
}

public function actionEmailVerified () {

$token=$_GET['token'];
$currentUser = Userw::getCurrentUser();
$email_verification_request=EmailVerificationRequest::find()->where(['user_id'=>$currentUser->id, 'status'=>0])->andwhere(['>', 'expiry', date('c')])->one();
// if there is a pending request (not expired) and the token matches the token hash for this request
// validate the email address

if (!empty($email_verification_request) && password_verify($token, $email_verification_request->verification_token)){

$token_expiry = $email_verification_request->expiry;
$token_status = $email_verification_request->status;
$user_id = $email_verification_request->user_id;
$user=User::find()->where(['id'=>$user_id])->one();
EmailVerificationRequest::EmailVerified($email_verification_request);
Yii::$app->session->setFlash('success', "Your email has been verified.");
$project = Yii::$app->createControllerByID('project');
return $project->redirect(['project/index']);

} else {
// if there is no pending request or the token doent match the hash:
// find all the user's requests
$email_verification_requests=EmailVerificationRequest::find()->where(['user_id'=>$currentUser->id])->all();
foreach ($email_verification_requests as $email_request){
$token_expiry = $email_request->expiry;
$token_status = $email_request->status;
$user_id = $email_request->user_id;
$user=User::find()->where(['id'=>$user_id])->one();
if (password_verify($token, $email_request->verification_token)){
$project = Yii::$app->createControllerByID('project');
// if the request is already verified and there is no pending request
// email is verified
// inform user that his email is verified
if ($token_status == 1 && empty($email_verification_request)){
Yii::$app->session->setFlash('success', "Your email is already verified.");
return $project->redirect(['project/index']);
// if the request is already verified, but there is also a pending email verification request
// user clicked on an outdated token
// inform the user to view the latest email and click on the link there
} elseif ($token_status == 1 && !empty($email_verification_request)){
Yii::$app->session->setFlash('danger', "Please view the latest verification email.");
return $this->redirect(['project/index']);
}
// if the request is revoked or expired and the email is not confirmed
// either the token expired or the user sent a new request
// inform him that the token is no longer valid
// redirect him either to re-enter his email (no pending request) or to email_verification_sent view
if (($token_status == 2 || $token_expiry<=date('c')) && $user->email_confirmed == 0){
Yii::$app->session->setFlash('danger', "This token is no longer valid.");
return $this->redirect(['project/index']);
// if the request is revoked or expired, but the email is not confirmed
// the user clicked on an old token
// inform him that his email is confirmed
} elseif (($token_status == 2 || $token_expiry<=date('c')) && $user->email_confirmed == 1) {
Yii::$app->session->setFlash('success', "Your email is already verified.");
return $project->redirect(['project/index']);
}
}
}
// in any other case, inform the user that the token is not valid
// redirct the user to the appropriate view
Yii::$app->session->setFlash('danger', "Invalid token");
$project = Yii::$app->createControllerByID('project');
return $project->redirect(['project/index']);
}


}
}
4 changes: 4 additions & 0 deletions controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace app\controllers;

use app\components\EmailVerifiedFilter;
use app\models\ColdStorageAutoaccept;
use app\models\ColdStorageLimits;
use app\models\ColdStorageRequest;
Expand Down Expand Up @@ -69,6 +70,9 @@ public function behaviors()
'logout' => ['post'],
],
],
'EmailVerifiedFilter' => [
'class' => EmailVerifiedFilter::className()
]
];
}

Expand Down
Loading

0 comments on commit cd4ad0f

Please sign in to comment.