2022-01-02 21:12:35 +08:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2022-01-02 21:12:35 +08:00
package auth
import (
"errors"
"net/http"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
)
var (
tplTwofa base . TplName = "user/auth/twofa"
tplTwofaScratch base . TplName = "user/auth/twofa_scratch"
)
// TwoFactor shows the user a two-factor authentication page.
func TwoFactor ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "twofa" )
2023-10-14 02:56:41 +02:00
if CheckAutoLogin ( ctx ) {
2022-01-02 21:12:35 +08:00
return
}
// Ensure user is in a 2FA session.
if ctx . Session . Get ( "twofaUid" ) == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
return
}
ctx . HTML ( http . StatusOK , tplTwofa )
}
// TwoFactorPost validates a user's two-factor authentication token.
func TwoFactorPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . TwoFactorAuthForm )
ctx . Data [ "Title" ] = ctx . Tr ( "twofa" )
// Ensure user is in a 2FA session.
idSess := ctx . Session . Get ( "twofaUid" )
if idSess == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
return
}
id := idSess . ( int64 )
2023-09-15 08:13:19 +02:00
twofa , err := auth . GetTwoFactorByUID ( ctx , id )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
// Validate the passcode with the stored TOTP secret.
ok , err := twofa . ValidateTOTP ( form . Passcode )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if ok && twofa . LastUsedPasscode != form . Passcode {
remember := ctx . Session . Get ( "twofaRemember" ) . ( bool )
2022-12-03 10:48:26 +08:00
u , err := user_model . GetUserByID ( ctx , id )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if ctx . Session . Get ( "linkAccount" ) != nil {
2023-09-25 15:17:37 +02:00
err = externalaccount . LinkAccountFromStore ( ctx , ctx . Session , u )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
}
twofa . LastUsedPasscode = form . Passcode
2023-09-15 08:13:19 +02:00
if err = auth . UpdateTwoFactor ( ctx , twofa ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "UserSignIn" , err )
return
}
handleSignIn ( ctx , u , remember )
return
}
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_passcode_incorrect" ) , tplTwofa , forms . TwoFactorAuthForm { } )
}
// TwoFactorScratch shows the scratch code form for two-factor authentication.
func TwoFactorScratch ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "twofa_scratch" )
2023-10-14 02:56:41 +02:00
if CheckAutoLogin ( ctx ) {
2022-01-02 21:12:35 +08:00
return
}
// Ensure user is in a 2FA session.
if ctx . Session . Get ( "twofaUid" ) == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
return
}
ctx . HTML ( http . StatusOK , tplTwofaScratch )
}
// TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
func TwoFactorScratchPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . TwoFactorScratchAuthForm )
ctx . Data [ "Title" ] = ctx . Tr ( "twofa_scratch" )
// Ensure user is in a 2FA session.
idSess := ctx . Session . Get ( "twofaUid" )
if idSess == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
return
}
id := idSess . ( int64 )
2023-09-15 08:13:19 +02:00
twofa , err := auth . GetTwoFactorByUID ( ctx , id )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
// Validate the passcode with the stored TOTP secret.
if twofa . VerifyScratchToken ( form . Token ) {
// Invalidate the scratch token.
_ , err = twofa . GenerateScratchToken ( )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2023-09-15 08:13:19 +02:00
if err = auth . UpdateTwoFactor ( ctx , twofa ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "UserSignIn" , err )
return
}
remember := ctx . Session . Get ( "twofaRemember" ) . ( bool )
2022-12-03 10:48:26 +08:00
u , err := user_model . GetUserByID ( ctx , id )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
handleSignInFull ( ctx , u , remember , false )
if ctx . Written ( ) {
return
}
ctx . Flash . Info ( ctx . Tr ( "auth.twofa_scratch_used" ) )
ctx . Redirect ( setting . AppSubURL + "/user/settings/security" )
return
}
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_scratch_token_incorrect" ) , tplTwofaScratch , forms . TwoFactorScratchAuthForm { } )
}