Passed
Push — master ( 6a78e3...31c3ef )
by Jean-Christophe
19:36
created

resetPasswordErrorMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 0
c 1
b 0
f 0
dl 0
loc 1
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Ubiquity\controllers\auth\traits;
4
5
use Ajax\semantic\html\collections\form\HtmlForm;
6
use Ubiquity\controllers\auth\AuthFiles;
7
use Ubiquity\controllers\auth\AuthTokens;
8
use Ubiquity\utils\base\UDateTime;
9
use Ubiquity\utils\flash\FlashMessage;
10
use Ubiquity\utils\http\URequest;
11
12
/**
13
 * 
14
 * Ubiquity\controllers\auth\traits$AuthAccountRecoveryTrait
15
 * This class is part of Ubiquity
16
 * @author jc
17
 * @version 1.0.0
18
 * 
19
 */
20
trait AuthAccountRecoveryTrait {
21
22
	protected static string $TOKENS_RECOVERY_ACCOUNT='account.recovery';
23
24
	abstract protected function fMessage(FlashMessage $fMessage, $id = null):string;
25
26
	abstract protected function _getFiles(): AuthFiles;
27
28
	abstract protected function getBaseUrl():string;
29
30
	abstract protected function authLoadView($viewName, $vars = [ ]):void;
31
32
	abstract protected function useAjax():bool;
33
34
	abstract public function _getBodySelector():string;
35
36
	abstract public function _addFrmAjaxBehavior($id):HtmlForm;
37
38
	abstract public function _getPasswordInputName():string;
39
40
	abstract protected function passwordConfLabel():string;
41
42
	abstract protected function passwordLabel():string;
43
44
	abstract public function info($force = null);
45
46
	abstract protected function validateEmail(string $mail):bool;
47
48
	/**
49
	 * @return bool
50
	 */
51 1
	protected function hasAccountRecovery():bool{
52 1
		return false;
53
	}
54
55
	/**
56
	 * To override
57
	 * Displayed when an account recovery operation is initiated.
58
	 * @param FlashMessage $fMessage
59
	 */
60
	protected function recoveryInitMessage(FlashMessage $fMessage){
61
62
	}
63
64
	/**
65
	 * To override
66
	 * Displayed when email is sent for a recovery account operation.
67
	 * @param FlashMessage $fMessage
68
	 */
69
	protected function recoveryEmailSendMessage(FlashMessage $fMessage){
70
71
	}
72
73
	/**
74
	 * To override
75
	 * Displayed when email is not associated with an existing account.
76
	 * @param FlashMessage $fMessage
77
	 */
78
	protected function recoveryEmailErrorMessage(FlashMessage $fMessage){
79
80
	}
81
82
	/**
83
	 * To override
84
	 * Displayed when a new password is set with recovery account.
85
	 * @param FlashMessage $fMessage
86
	 */
87
	protected function resetPasswordSuccessMessage(FlashMessage $fMessage){
88
89
	}
90
91
	/**
92
	 * To override
93
	 * Displayed when an error occurs when a new password is set with recovery account.
94
	 * @param FlashMessage $fMessage
95
	 */
96
	protected function resetPasswordErrorMessage(FlashMessage $fMessage){
97
98
	}
99
100
	/**
101
	 * To override
102
	 * Displayed when the account recovery link is valid.
103
	 * @param FlashMessage $fMessage
104
	 */
105
	protected function emailAccountRecoverySuccess(FlashMessage $fMessage){
106
107
	}
108
109
	/**
110
	 * To override
111
	 * Displayed when the account recovery link is not valid.
112
	 * @param FlashMessage $fMessage
113
	 */
114
	protected function emailAccountRecoveryError(FlashMessage $fMessage){
115
116
	}
117
118
	/**
119
	 * Returns the recovery account link caption.
120
	 * Default : Forgot your password?
121
	 * @return string
122
	 */
123
	protected function recoveryAccountCaption():string{
124
		return 'Forgot your password?';
125
	}
126
127
	/**
128
	 * Returns the default validity duration for an email account recovery.
129
	 * @return \DateInterval
130
	 */
131
	protected function accountRecoveryDuration():\DateInterval{
132
		return new \DateInterval('PT30M');
133
	}
134
135
	/**
136
	 * To override
137
	 * Returns the AuthTokens instance used for tokens generation for a recovery account.
138
	 * @return AuthTokens
139
	 */
140
	protected function getAuthTokensAccountRecovery():AuthTokens{
141
		return new AuthTokens(self::$TOKENS_RECOVERY_ACCOUNT,10,$this->accountRecoveryDuration()->s,true);
142
	}
143
144
	/**
145
	 * To override
146
	 * Checks if a valid account matches this email.
147
	 * @param string $email
148
	 * @return bool
149
	 */
150
	protected function isValidEmailForRecovery(string $email):bool {
151
		return true;
152
	}
153
154
	/**
155
	 * Sends an email for account recovery (password reset).
156
	 * @param string $email
157
	 * @param string $validationURL
158
	 * @param string $expire
159
	 * @return boolean
160
	 */
161
	protected function _sendEmailAccountRecovery(string $email,string $validationURL,string $expire):bool{
162
		return false;
163
	}
164
165
	/**
166
	 * To override
167
	 * Changes the active password associated with the account corresponding to this email.
168
	 * @param string $email
169
	 * @param string $newPasswordHash
170
	 * @return bool
171
	 */
172
	protected function passwordResetAction(string $email,string $newPasswordHash):bool{
173
		return false;
174
	}
175
176
	protected function getAccountRecoveryLink():string{
177
		$href=$this->getBaseUrl().'/recoveryInit';
178
		$target=$this->_getBodySelector();
179
		$caption=$this->recoveryAccountCaption();
180
		return "<a href='$href' data-target='$target'>$caption</a>";
181
	}
182
183
	public function recoveryInit(){
184
		$fMessage = new FlashMessage( 'Enter the email associated with your account to receive a password reset link.', 'Account recovery', 'info', 'user' );
185
		$this->recoveryInitMessage ( $fMessage );
186
		$message = $this->fMessage ( $fMessage );
187
		if($this->useAjax()){
188
			$frm=$this->jquery->semantic()->htmlForm('frm-account-recovery');
189
			$frm->addExtraFieldRules('email',['empty','email']);
190
			$frm->setValidationParams(['inline'=>true,'on'=>'blur']);
191
		}
192
		$this->authLoadView ( $this->_getFiles ()->getViewInitRecovery(), [ '_message' => $message,'submitURL' => $this->getBaseUrl ().'/recoveryInfo','bodySelector' => $this->_getBodySelector()] );
193
	}
194
195
	/**
196
	 * @post
197
	 */
198
	#[\Ubiquity\attributes\items\router\Post]
199
	public function recoveryInfo(){
200
		if(URequest::isPost()){
201
			if($this->isValidEmailForRecovery($email=URequest::filterPost('email',FILTER_VALIDATE_EMAIL))) {
202
				$this->prepareEmailAccountRecovery($email);
203
				$fMessage = new FlashMessage (sprintf('A password reset email has been sent to <b>%s</b>.<br>You can only use this link temporarily, from the same machine, on this browser.',$email), 'Account recovery', 'success', 'email');
204
				$this->recoveryEmailSendMessage($fMessage);
205
			}else{
206
				$fMessage = new FlashMessage (sprintf('No account is associated with the email address <b>%s</b>.<br><a href="%s" data-target="%s">Try again.</a>.',$email,$this->getBaseUrl().'/recoveryInit',$this->_getBodySelector()), 'Account recovery', 'error', 'user');
207
				$this->recoveryEmailErrorMessage($fMessage);
208
			}
209
			echo $this->fMessage ( $fMessage );
210
		}
211
	}
212
213
	public function recovery(string $key,string $hashMail) {
214
		$tokens = $this->getAuthTokensAccountRecovery();
215
		if ($tokens->exists($key)) {
216
			if (!$tokens->expired($key)) {
217
				$data = $tokens->fetch($key);
218
				if(\is_array($data)) {
219
					$email = $data['email'];
220
					if (\md5($email) === $hashMail && $this->validateEmail($email)) {
221
						$fMessage = new FlashMessage ("Enter a new password associated to the account <b>$email</b>.", 'Account recovery', 'success', 'user');
222
						$this->emailAccountRecoverySuccess($fMessage);
223
						$message=$this->fMessage($fMessage);
224
						if($this->useAjax()) {
225
							$frm = $this->_addFrmAjaxBehavior('frm-account-recovery');
226
							$passwordInputName = $this->_getPasswordInputName();
227
							$frm->addExtraFieldRules($passwordInputName . '-conf', ['empty', "match[$passwordInputName]"]);
228
						}
229
						$this->authLoadView ( $this->_getFiles ()->getViewRecovery(), [ 'key'=>$key,'email'=>$email,'_message' => $message,'submitURL' => $this->getBaseUrl ().'/recoverySubmit','bodySelector' => $this->_getBodySelector(),'passwordInputName' => $this->_getPasswordInputName (),'passwordLabel' => $this->passwordLabel (),'passwordConfLabel'=>$this->passwordConfLabel()] );
230
						return ;
231
					}
232
				}
233
				$msg = 'This recovery link was not generated on this device!';
234
			} else {
235
				$msg = 'This recovery link is no longer active!';
236
			}
237
		}
238
		$fMessage = new FlashMessage ($msg ?? 'This account recovery link is not valid!', 'Account recovery', 'error', 'user');
239
		$this->emailAccountRecoveryError($fMessage);
240
		echo $this->fMessage($fMessage);
241
	}
242
243
	protected function generateEmailAccountRecoveryUrl($email):array {
244
		$duration=$this->accountRecoveryDuration();
245
		$tokens=$this->getAuthTokensAccountRecovery();
246
		$d=new \DateTime();
247
		$dExpire=$d->add($duration);
248
		$key=$tokens->store(['email'=>$email]);
249
		return ['url'=>$key.'/'.\md5($email),'expire'=>$dExpire];
250
	}
251
252
	protected function prepareEmailAccountRecovery(string $email){
253
		$data=$this->generateEmailAccountRecoveryUrl($email);
254
		$validationURL=$this->getBaseUrl().'/recovery/'.$data['url'];
255
		$this->_sendEmailAccountRecovery($email, $validationURL,UDateTime::elapsed($data['expire']));
256
	}
257
258
	/**
259
	 * @post
260
	 */
261
	#[\Ubiquity\attributes\items\router\Post]
262
	public function recoverySubmit(){
263
		if(URequest::isPost() && URequest::has('key')){
264
			$isValid=false;
265
			$msg='This account recovery link is invalid!';
266
			$tokens = $this->getAuthTokensAccountRecovery();
267
			$key=URequest::post('key');
268
			if ($tokens->exists($key)) {
269
				if(!$tokens->expired($key)){
270
					$data=$tokens->fetch($key);
271
					$email=$data['email'];
272
					if($email===URequest::post('email')){
273
						if($this->passwordResetAction($email,URequest::password_hash('password'))){
274
							$fMessage = new FlashMessage ("Your password has been updated correctly for the account associated with <b>$email</b>.", 'Account recovery', 'success', 'user');
275
							$this->resetPasswordSuccessMessage($fMessage);
276
							echo $this->info(true);
277
							$isValid=true;
278
						}else{
279
							$msg='An error occurs when updating your password!';
280
						}
281
					}
282
				}else{
283
					$msg='This account recovery link is expired!';
284
				}
285
				$tokens->remove($key);
286
			}
287
			if(!$isValid){
288
				$fMessage = new FlashMessage ($msg, 'Account recovery', 'error', 'user');
289
				$this->resetPasswordErrorMessage($fMessage);
290
			}
291
			echo $this->fMessage($fMessage);
292
		}
293
	}
294
295
}
296
297