@@ -1284,7 +1284,7 @@ func ForgotPasswdPost(ctx *context.Context) {
1284
1284
ctx .HTML (200 , tplForgotPassword )
1285
1285
}
1286
1286
1287
- func commonResetPassword (ctx * context.Context ) * models.User {
1287
+ func commonResetPassword (ctx * context.Context ) ( * models.User , * models. TwoFactor ) {
1288
1288
code := ctx .Query ("code" )
1289
1289
1290
1290
ctx .Data ["Title" ] = ctx .Tr ("auth.reset_password" )
@@ -1296,39 +1296,56 @@ func commonResetPassword(ctx *context.Context) *models.User {
1296
1296
1297
1297
if len (code ) == 0 {
1298
1298
ctx .Flash .Error (ctx .Tr ("auth.invalid_code" ))
1299
- return nil
1299
+ return nil , nil
1300
1300
}
1301
1301
1302
1302
// Fail early, don't frustrate the user
1303
1303
u := models .VerifyUserActiveCode (code )
1304
1304
if u == nil {
1305
1305
ctx .Flash .Error (ctx .Tr ("auth.invalid_code" ))
1306
- return nil
1306
+ return nil , nil
1307
+ }
1308
+
1309
+ twofa , err := models .GetTwoFactorByUID (u .ID )
1310
+ if err != nil {
1311
+ if ! models .IsErrTwoFactorNotEnrolled (err ) {
1312
+ ctx .Error (http .StatusInternalServerError , "CommonResetPassword" , err .Error ())
1313
+ return nil , nil
1314
+ }
1315
+ } else {
1316
+ ctx .Data ["has_two_factor" ] = true
1317
+ ctx .Data ["scratch_code" ] = ctx .QueryBool ("scratch_code" )
1307
1318
}
1308
1319
1309
1320
// Show the user that they are affecting the account that they intended to
1310
1321
ctx .Data ["user_email" ] = u .Email
1311
1322
1312
1323
if nil != ctx .User && u .ID != ctx .User .ID {
1313
1324
ctx .Flash .Error (ctx .Tr ("auth.reset_password_wrong_user" , ctx .User .Email , u .Email ))
1314
- return nil
1325
+ return nil , nil
1315
1326
}
1316
1327
1317
- return u
1328
+ return u , twofa
1318
1329
}
1319
1330
1320
1331
// ResetPasswd render the account recovery page
1321
1332
func ResetPasswd (ctx * context.Context ) {
1322
1333
ctx .Data ["IsResetForm" ] = true
1323
1334
1324
1335
commonResetPassword (ctx )
1336
+ if ctx .Written () {
1337
+ return
1338
+ }
1325
1339
1326
1340
ctx .HTML (200 , tplResetPassword )
1327
1341
}
1328
1342
1329
1343
// ResetPasswdPost response from account recovery request
1330
1344
func ResetPasswdPost (ctx * context.Context ) {
1331
- u := commonResetPassword (ctx )
1345
+ u , twofa := commonResetPassword (ctx )
1346
+ if ctx .Written () {
1347
+ return
1348
+ }
1332
1349
1333
1350
if u == nil {
1334
1351
// Flash error has been set
@@ -1350,6 +1367,39 @@ func ResetPasswdPost(ctx *context.Context) {
1350
1367
return
1351
1368
}
1352
1369
1370
+ // Handle two-factor
1371
+ regenerateScratchToken := false
1372
+ if twofa != nil {
1373
+ if ctx .QueryBool ("scratch_code" ) {
1374
+ if ! twofa .VerifyScratchToken (ctx .Query ("token" )) {
1375
+ ctx .Data ["IsResetForm" ] = true
1376
+ ctx .Data ["Err_Token" ] = true
1377
+ ctx .RenderWithErr (ctx .Tr ("auth.twofa_scratch_token_incorrect" ), tplResetPassword , nil )
1378
+ return
1379
+ }
1380
+ regenerateScratchToken = true
1381
+ } else {
1382
+ passcode := ctx .Query ("passcode" )
1383
+ ok , err := twofa .ValidateTOTP (passcode )
1384
+ if err != nil {
1385
+ ctx .Error (http .StatusInternalServerError , "ValidateTOTP" , err .Error ())
1386
+ return
1387
+ }
1388
+ if ! ok || twofa .LastUsedPasscode == passcode {
1389
+ ctx .Data ["IsResetForm" ] = true
1390
+ ctx .Data ["Err_Passcode" ] = true
1391
+ ctx .RenderWithErr (ctx .Tr ("auth.twofa_passcode_incorrect" ), tplResetPassword , nil )
1392
+ return
1393
+ }
1394
+
1395
+ twofa .LastUsedPasscode = passcode
1396
+ if err = models .UpdateTwoFactor (twofa ); err != nil {
1397
+ ctx .ServerError ("ResetPasswdPost: UpdateTwoFactor" , err )
1398
+ return
1399
+ }
1400
+ }
1401
+ }
1402
+
1353
1403
var err error
1354
1404
if u .Rands , err = models .GetUserSalt (); err != nil {
1355
1405
ctx .ServerError ("UpdateUser" , err )
@@ -1359,7 +1409,6 @@ func ResetPasswdPost(ctx *context.Context) {
1359
1409
ctx .ServerError ("UpdateUser" , err )
1360
1410
return
1361
1411
}
1362
-
1363
1412
u .HashPassword (passwd )
1364
1413
u .MustChangePassword = false
1365
1414
if err := models .UpdateUserCols (u , "must_change_password" , "passwd" , "rands" , "salt" ); err != nil {
@@ -1368,9 +1417,27 @@ func ResetPasswdPost(ctx *context.Context) {
1368
1417
}
1369
1418
1370
1419
log .Trace ("User password reset: %s" , u .Name )
1371
-
1372
1420
ctx .Data ["IsResetFailed" ] = true
1373
1421
remember := len (ctx .Query ("remember" )) != 0
1422
+
1423
+ if regenerateScratchToken {
1424
+ // Invalidate the scratch token.
1425
+ _ , err = twofa .GenerateScratchToken ()
1426
+ if err != nil {
1427
+ ctx .ServerError ("UserSignIn" , err )
1428
+ return
1429
+ }
1430
+ if err = models .UpdateTwoFactor (twofa ); err != nil {
1431
+ ctx .ServerError ("UserSignIn" , err )
1432
+ return
1433
+ }
1434
+
1435
+ handleSignInFull (ctx , u , remember , false )
1436
+ ctx .Flash .Info (ctx .Tr ("auth.twofa_scratch_used" ))
1437
+ ctx .Redirect (setting .AppSubURL + "/user/settings/security" )
1438
+ return
1439
+ }
1440
+
1374
1441
handleSignInFull (ctx , u , remember , true )
1375
1442
}
1376
1443
0 commit comments