28
28
from ..mails import send_password_initialization_mail
29
29
from ..mails import send_password_reset_mail
30
30
from .forms import FirstLoginForm
31
+ from .forms import ForgottenPasswordCodeForm
31
32
from .forms import ForgottenPasswordForm
32
33
from .forms import LoginForm
33
34
from .forms import PasswordForm
@@ -195,13 +196,15 @@ def forgotten():
195
196
if not request .form :
196
197
return render_template ("core/forgotten-password.html" , form = form )
197
198
199
+ item_name = "link" if current_app .features .has_trusted_hosts else "code"
200
+
198
201
if not form .validate ():
199
- flash (_ ("Could not send the password reset link ." ), "error" )
202
+ flash (_ (f "Could not send the password reset { item_name } ." ), "error" )
200
203
return render_template ("core/forgotten-password.html" , form = form )
201
204
202
205
user = get_user_from_login (form .login .data )
203
206
success_message = _ (
204
- "A password reset link has been sent at your email address. "
207
+ f "A password reset { item_name } has been sent at your email address. "
205
208
"You should receive it within a few minutes."
206
209
)
207
210
if current_app .config ["CANAILLE" ]["HIDE_INVALID_LOGINS" ] and (
@@ -237,32 +240,62 @@ def forgotten():
237
240
"error" ,
238
241
)
239
242
240
- return render_template ("core/forgotten-password.html" , form = form )
243
+ if current_app .features .has_trusted_hosts :
244
+ return render_template ("core/forgotten-password.html" , form = form )
245
+ else :
246
+ return redirect (url_for (".forgotten_code" , user = user ))
241
247
242
248
243
- @bp .route ("/reset/<user:user>/<hash>" , methods = ["GET" , "POST" ])
244
- def reset (user , hash ):
245
- if not current_app .config ["CANAILLE" ]["ENABLE_PASSWORD_RECOVERY" ]:
249
+ @bp .route ("/reset-code/<user:user>" , methods = ["GET" , "POST" ])
250
+ @smtp_needed ()
251
+ def forgotten_code (user ):
252
+ if (
253
+ not current_app .config ["CANAILLE" ]["ENABLE_PASSWORD_RECOVERY" ]
254
+ or current_app .features .has_trusted_hosts
255
+ ):
246
256
abort (404 )
247
257
248
- form = PasswordResetForm (request .form )
249
- hashes = {
250
- build_hash (
251
- user .identifier ,
252
- email ,
253
- user .password if user .has_password () else "" ,
258
+ if not user .can_edit_self :
259
+ flash (
260
+ _ (
261
+ "The user '%(user)s' does not have permissions to update their password. " ,
262
+ user = user .formatted_name ,
263
+ ),
264
+ "error" ,
254
265
)
255
- for email in user .emails
256
- }
257
- if not user or hash not in hashes :
266
+ return redirect (url_for (".forgotten" ))
267
+
268
+ form = ForgottenPasswordCodeForm (request .form )
269
+ if not request .form :
270
+ return render_template ("core/forgotten-password-code.html" , form = form )
271
+
272
+ if not form .validate () or not user .is_otp_valid (form .code .data , "EMAIL_OTP" ):
273
+ flash (_ ("Invalid code." ), "error" )
274
+ return render_template ("core/forgotten-password-code.html" , form = form )
275
+
276
+ return redirect (url_for (".reset" , user = user , token = form .code .data ))
277
+
278
+
279
+ @bp .route ("/reset/<user:user>/<token>" , methods = ["GET" , "POST" ])
280
+ def reset (user , token ):
281
+ if not current_app .config ["CANAILLE" ]["ENABLE_PASSWORD_RECOVERY" ]:
282
+ abort (404 )
283
+ form = PasswordResetForm (request .form )
284
+
285
+ if current_app .features .has_trusted_hosts :
286
+ token = build_hash (token )
287
+ if not user or not user .is_otp_valid (token , "EMAIL_OTP" ):
288
+ item_name = "link" if current_app .features .has_trusted_hosts else "code"
258
289
flash (
259
- _ ("The password reset link that brought you here was invalid." ),
290
+ _ (f "The password reset { item_name } that brought you here was invalid." ),
260
291
"error" ,
261
292
)
262
293
return redirect (url_for ("core.account.index" ))
263
294
264
295
if request .form and form .validate ():
265
296
Backend .instance .set_user_password (user , form .password .data )
297
+ user .clear_otp ()
298
+ Backend .instance .save (user )
266
299
login_user (user )
267
300
268
301
flash (_ ("Your password has been updated successfully" ), "success" )
@@ -273,7 +306,9 @@ def reset(user, hash):
273
306
)
274
307
)
275
308
276
- return render_template ("core/reset-password.html" , form = form , user = user , hash = hash )
309
+ return render_template (
310
+ "core/reset-password.html" , form = form , user = user , token = token
311
+ )
277
312
278
313
279
314
@bp .route ("/setup-mfa" )
0 commit comments