@@ -1284,7 +1284,7 @@ func ForgotPasswdPost(ctx *context.Context) {
12841284 ctx .HTML (200 , tplForgotPassword )
12851285}
12861286
1287- func commonResetPassword (ctx * context.Context ) * models.User {
1287+ func commonResetPassword (ctx * context.Context ) ( * models.User , * models. TwoFactor ) {
12881288 code := ctx .Query ("code" )
12891289
12901290 ctx .Data ["Title" ] = ctx .Tr ("auth.reset_password" )
@@ -1296,39 +1296,56 @@ func commonResetPassword(ctx *context.Context) *models.User {
12961296
12971297 if len (code ) == 0 {
12981298 ctx .Flash .Error (ctx .Tr ("auth.invalid_code" ))
1299- return nil
1299+ return nil , nil
13001300 }
13011301
13021302 // Fail early, don't frustrate the user
13031303 u := models .VerifyUserActiveCode (code )
13041304 if u == nil {
13051305 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" )
13071318 }
13081319
13091320 // Show the user that they are affecting the account that they intended to
13101321 ctx .Data ["user_email" ] = u .Email
13111322
13121323 if nil != ctx .User && u .ID != ctx .User .ID {
13131324 ctx .Flash .Error (ctx .Tr ("auth.reset_password_wrong_user" , ctx .User .Email , u .Email ))
1314- return nil
1325+ return nil , nil
13151326 }
13161327
1317- return u
1328+ return u , twofa
13181329}
13191330
13201331// ResetPasswd render the account recovery page
13211332func ResetPasswd (ctx * context.Context ) {
13221333 ctx .Data ["IsResetForm" ] = true
13231334
13241335 commonResetPassword (ctx )
1336+ if ctx .Written () {
1337+ return
1338+ }
13251339
13261340 ctx .HTML (200 , tplResetPassword )
13271341}
13281342
13291343// ResetPasswdPost response from account recovery request
13301344func ResetPasswdPost (ctx * context.Context ) {
1331- u := commonResetPassword (ctx )
1345+ u , twofa := commonResetPassword (ctx )
1346+ if ctx .Written () {
1347+ return
1348+ }
13321349
13331350 if u == nil {
13341351 // Flash error has been set
@@ -1350,6 +1367,39 @@ func ResetPasswdPost(ctx *context.Context) {
13501367 return
13511368 }
13521369
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+
13531403 var err error
13541404 if u .Rands , err = models .GetUserSalt (); err != nil {
13551405 ctx .ServerError ("UpdateUser" , err )
@@ -1359,7 +1409,6 @@ func ResetPasswdPost(ctx *context.Context) {
13591409 ctx .ServerError ("UpdateUser" , err )
13601410 return
13611411 }
1362-
13631412 u .HashPassword (passwd )
13641413 u .MustChangePassword = false
13651414 if err := models .UpdateUserCols (u , "must_change_password" , "passwd" , "rands" , "salt" ); err != nil {
@@ -1368,9 +1417,27 @@ func ResetPasswdPost(ctx *context.Context) {
13681417 }
13691418
13701419 log .Trace ("User password reset: %s" , u .Name )
1371-
13721420 ctx .Data ["IsResetFailed" ] = true
13731421 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+
13741441 handleSignInFull (ctx , u , remember , true )
13751442}
13761443
0 commit comments