@@ -1282,7 +1282,7 @@ func ForgotPasswdPost(ctx *context.Context) {
12821282 ctx .HTML (200 , tplForgotPassword )
12831283}
12841284
1285- func commonResetPassword (ctx * context.Context ) * models.User {
1285+ func commonResetPassword (ctx * context.Context ) ( * models.User , * models. TwoFactor ) {
12861286 code := ctx .Query ("code" )
12871287
12881288 ctx .Data ["Title" ] = ctx .Tr ("auth.reset_password" )
@@ -1294,39 +1294,56 @@ func commonResetPassword(ctx *context.Context) *models.User {
12941294
12951295 if len (code ) == 0 {
12961296 ctx .Flash .Error (ctx .Tr ("auth.invalid_code" ))
1297- return nil
1297+ return nil , nil
12981298 }
12991299
13001300 // Fail early, don't frustrate the user
13011301 u := models .VerifyUserActiveCode (code )
13021302 if u == nil {
13031303 ctx .Flash .Error (ctx .Tr ("auth.invalid_code" ))
1304- return nil
1304+ return nil , nil
1305+ }
1306+
1307+ twofa , err := models .GetTwoFactorByUID (u .ID )
1308+ if err != nil {
1309+ if ! models .IsErrTwoFactorNotEnrolled (err ) {
1310+ ctx .Error (http .StatusInternalServerError , "CommonResetPassword" , err .Error ())
1311+ return nil , nil
1312+ }
1313+ } else {
1314+ ctx .Data ["has_two_factor" ] = true
1315+ ctx .Data ["scratch_code" ] = ctx .QueryBool ("scratch_code" )
13051316 }
13061317
13071318 // Show the user that they are affecting the account that they intended to
13081319 ctx .Data ["user_email" ] = u .Email
13091320
13101321 if nil != ctx .User && u .ID != ctx .User .ID {
13111322 ctx .Flash .Error (ctx .Tr ("auth.reset_password_wrong_user" , ctx .User .Email , u .Email ))
1312- return nil
1323+ return nil , nil
13131324 }
13141325
1315- return u
1326+ return u , twofa
13161327}
13171328
13181329// ResetPasswd render the account recovery page
13191330func ResetPasswd (ctx * context.Context ) {
13201331 ctx .Data ["IsResetForm" ] = true
13211332
13221333 commonResetPassword (ctx )
1334+ if ctx .Written () {
1335+ return
1336+ }
13231337
13241338 ctx .HTML (200 , tplResetPassword )
13251339}
13261340
13271341// ResetPasswdPost response from account recovery request
13281342func ResetPasswdPost (ctx * context.Context ) {
1329- u := commonResetPassword (ctx )
1343+ u , twofa := commonResetPassword (ctx )
1344+ if ctx .Written () {
1345+ return
1346+ }
13301347
13311348 if u == nil {
13321349 // Flash error has been set
@@ -1348,6 +1365,39 @@ func ResetPasswdPost(ctx *context.Context) {
13481365 return
13491366 }
13501367
1368+ // Handle two-factor
1369+ regenerateScratchToken := false
1370+ if twofa != nil {
1371+ if ctx .QueryBool ("scratch_code" ) {
1372+ if ! twofa .VerifyScratchToken (ctx .Query ("token" )) {
1373+ ctx .Data ["IsResetForm" ] = true
1374+ ctx .Data ["Err_Token" ] = true
1375+ ctx .RenderWithErr (ctx .Tr ("auth.twofa_scratch_token_incorrect" ), tplResetPassword , nil )
1376+ return
1377+ }
1378+ regenerateScratchToken = true
1379+ } else {
1380+ passcode := ctx .Query ("passcode" )
1381+ ok , err := twofa .ValidateTOTP (passcode )
1382+ if err != nil {
1383+ ctx .Error (http .StatusInternalServerError , "ValidateTOTP" , err .Error ())
1384+ return
1385+ }
1386+ if ! ok || twofa .LastUsedPasscode == passcode {
1387+ ctx .Data ["IsResetForm" ] = true
1388+ ctx .Data ["Err_Passcode" ] = true
1389+ ctx .RenderWithErr (ctx .Tr ("auth.twofa_passcode_incorrect" ), tplResetPassword , nil )
1390+ return
1391+ }
1392+
1393+ twofa .LastUsedPasscode = passcode
1394+ if err = models .UpdateTwoFactor (twofa ); err != nil {
1395+ ctx .ServerError ("ResetPasswdPost: UpdateTwoFactor" , err )
1396+ return
1397+ }
1398+ }
1399+ }
1400+
13511401 var err error
13521402 if u .Rands , err = models .GetUserSalt (); err != nil {
13531403 ctx .ServerError ("UpdateUser" , err )
@@ -1357,7 +1407,6 @@ func ResetPasswdPost(ctx *context.Context) {
13571407 ctx .ServerError ("UpdateUser" , err )
13581408 return
13591409 }
1360-
13611410 u .HashPassword (passwd )
13621411 u .MustChangePassword = false
13631412 if err := models .UpdateUserCols (u , "must_change_password" , "passwd" , "rands" , "salt" ); err != nil {
@@ -1366,9 +1415,27 @@ func ResetPasswdPost(ctx *context.Context) {
13661415 }
13671416
13681417 log .Trace ("User password reset: %s" , u .Name )
1369-
13701418 ctx .Data ["IsResetFailed" ] = true
13711419 remember := len (ctx .Query ("remember" )) != 0
1420+
1421+ if regenerateScratchToken {
1422+ // Invalidate the scratch token.
1423+ _ , err = twofa .GenerateScratchToken ()
1424+ if err != nil {
1425+ ctx .ServerError ("UserSignIn" , err )
1426+ return
1427+ }
1428+ if err = models .UpdateTwoFactor (twofa ); err != nil {
1429+ ctx .ServerError ("UserSignIn" , err )
1430+ return
1431+ }
1432+
1433+ handleSignInFull (ctx , u , remember , false )
1434+ ctx .Flash .Info (ctx .Tr ("auth.twofa_scratch_used" ))
1435+ ctx .Redirect (setting .AppSubURL + "/user/settings/security" )
1436+ return
1437+ }
1438+
13721439 handleSignInFull (ctx , u , remember , true )
13731440}
13741441
0 commit comments