1
0
mirror of https://github.com/go-gitea/gitea synced 2024-12-24 03:35:55 +01:00

Move twofactor to models/login (#17143)

This commit is contained in:
Lunny Xiao 2021-09-25 21:00:12 +08:00 committed by GitHub
parent 6fb7fb6cfc
commit 91e21d4fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 131 additions and 121 deletions

@ -1876,25 +1876,6 @@ func (err ErrTeamNotExist) Error() string {
return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name) return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
} }
//
// Two-factor authentication
//
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}
// ____ ___ .__ .___ // ____ ___ .__ .___
// | | \______ | | _________ __| _/ // | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ | // | | /\____ \| | / _ \__ \ / __ |
@ -1959,28 +1940,6 @@ func (err ErrExternalLoginUserNotExist) Error() string {
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID) return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
} }
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// .___ ________ .___ .__ // .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______ // | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/ // | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/

@ -17,5 +17,6 @@ func TestMain(m *testing.M) {
"oauth2_application.yml", "oauth2_application.yml",
"oauth2_authorization_code.yml", "oauth2_authorization_code.yml",
"oauth2_grant.yml", "oauth2_grant.yml",
"u2f_registration.yml",
) )
} }

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package login
import ( import (
"crypto/md5" "crypto/md5"
@ -21,6 +21,25 @@ import (
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
//
// Two-factor authentication
//
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}
// TwoFactor represents a two-factor authentication token. // TwoFactor represents a two-factor authentication token.
type TwoFactor struct { type TwoFactor struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -44,11 +63,12 @@ func (t *TwoFactor) GenerateScratchToken() (string, error) {
return "", err return "", err
} }
t.ScratchSalt, _ = util.RandomString(10) t.ScratchSalt, _ = util.RandomString(10)
t.ScratchHash = hashToken(token, t.ScratchSalt) t.ScratchHash = HashToken(token, t.ScratchSalt)
return token, nil return token, nil
} }
func hashToken(token, salt string) string { // HashToken return the hashable salt
func HashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New) tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash) return fmt.Sprintf("%x", tempHash)
} }
@ -58,7 +78,7 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 { if len(token) == 0 {
return false return false
} }
tempHash := hashToken(token, t.ScratchSalt) tempHash := HashToken(token, t.ScratchSalt)
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1 return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
} }

@ -2,9 +2,11 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package login
import ( import (
"fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -12,6 +14,28 @@ import (
"github.com/tstranex/u2f" "github.com/tstranex/u2f"
) )
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// U2FRegistration represents the registration data and counter of a security key // U2FRegistration represents the registration data and counter of a security key
type U2FRegistration struct { type U2FRegistration struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -91,13 +115,13 @@ func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid) return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid)
} }
func createRegistration(e db.Engine, user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) { func createRegistration(e db.Engine, userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
raw, err := reg.MarshalBinary() raw, err := reg.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
} }
r := &U2FRegistration{ r := &U2FRegistration{
UserID: user.ID, UserID: userID,
Name: name, Name: name,
Counter: 0, Counter: 0,
Raw: raw, Raw: raw,
@ -110,8 +134,8 @@ func createRegistration(e db.Engine, user *User, name string, reg *u2f.Registrat
} }
// CreateRegistration will create a new U2FRegistration from the given Registration // CreateRegistration will create a new U2FRegistration from the given Registration
func CreateRegistration(user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) { func CreateRegistration(userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
return createRegistration(db.GetEngine(db.DefaultContext), user, name, reg) return createRegistration(db.GetEngine(db.DefaultContext), userID, name, reg)
} }
// DeleteRegistration will delete U2FRegistration // DeleteRegistration will delete U2FRegistration

@ -2,12 +2,13 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package login
import ( import (
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tstranex/u2f" "github.com/tstranex/u2f"
) )
@ -55,14 +56,13 @@ func TestU2FRegistration_UpdateLargeCounter(t *testing.T) {
func TestCreateRegistration(t *testing.T) { func TestCreateRegistration(t *testing.T) {
assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, db.PrepareTestDatabase())
user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
res, err := CreateRegistration(user, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")}) res, err := CreateRegistration(1, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "U2F Created Key", res.Name) assert.Equal(t, "U2F Created Key", res.Name)
assert.Equal(t, []byte("Test"), res.Raw) assert.Equal(t, []byte("Test"), res.Raw)
db.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: user.ID}) db.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: 1})
} }
func TestDeleteRegistration(t *testing.T) { func TestDeleteRegistration(t *testing.T) {

@ -6,6 +6,7 @@ package models
import ( import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -44,8 +45,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey} return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {

@ -8,6 +8,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
@ -129,8 +130,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey} return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
@ -165,8 +166,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey} return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
@ -218,8 +219,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey} return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {

@ -11,6 +11,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -67,7 +68,7 @@ func NewAccessToken(t *AccessToken) error {
} }
t.TokenSalt = salt t.TokenSalt = salt
t.Token = base.EncodeSha1(gouuid.New().String()) t.Token = base.EncodeSha1(gouuid.New().String())
t.TokenHash = hashToken(t.Token, t.TokenSalt) t.TokenHash = login.HashToken(t.Token, t.TokenSalt)
t.TokenLastEight = t.Token[len(t.Token)-8:] t.TokenLastEight = t.Token[len(t.Token)-8:]
_, err = db.GetEngine(db.DefaultContext).Insert(t) _, err = db.GetEngine(db.DefaultContext).Insert(t)
return err return err
@ -129,7 +130,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) {
} }
for _, t := range tokens { for _, t := range tokens {
tempHash := hashToken(token, t.TokenSalt) tempHash := login.HashToken(token, t.TokenSalt)
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 { if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
if successfulAccessTokenCache != nil { if successfulAccessTokenCache != nil {
successfulAccessTokenCache.Add(token, t.ID) successfulAccessTokenCache.Add(token, t.ID)

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -79,13 +80,13 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
return results return results
} }
func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*TwoFactor, error) { func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*login.TwoFactor, error) {
if len(users) == 0 { if len(users) == 0 {
return nil, nil return nil, nil
} }
userIDs := users.getUserIDs() userIDs := users.getUserIDs()
tokenMaps := make(map[int64]*TwoFactor, len(userIDs)) tokenMaps := make(map[int64]*login.TwoFactor, len(userIDs))
err := e. err := e.
In("uid", userIDs). In("uid", userIDs).
Find(&tokenMaps) Find(&tokenMaps)

@ -14,6 +14,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -219,9 +220,9 @@ func (ctx *APIContext) CheckForOTP() {
} }
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
twofa, err := models.GetTwoFactorByUID(ctx.Context.User.ID) twofa, err := login.GetTwoFactorByUID(ctx.Context.User.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user return // No 2FA enrollment for this user
} }
ctx.Context.Error(http.StatusInternalServerError) ctx.Context.Error(http.StatusInternalServerError)

@ -8,7 +8,7 @@ package context
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
@ -154,9 +154,9 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
return // Skip 2FA return // Skip 2FA
} }
twofa, err := models.GetTwoFactorByUID(ctx.User.ID) twofa, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user return // No 2FA enrollment for this user
} }
ctx.InternalServerError(err) ctx.InternalServerError(err)

@ -195,9 +195,9 @@ func prepareUserInfo(ctx *context.Context) *models.User {
ctx.Data["Sources"] = sources ctx.Data["Sources"] = sources
ctx.Data["TwoFactorEnabled"] = true ctx.Data["TwoFactorEnabled"] = true
_, err = models.GetTwoFactorByUID(u.ID) _, err = login.GetTwoFactorByUID(u.ID)
if err != nil { if err != nil {
if !models.IsErrTwoFactorNotEnrolled(err) { if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err) ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return nil return nil
} }
@ -295,13 +295,13 @@ func EditUserPost(ctx *context.Context) {
} }
if form.Reset2FA { if form.Reset2FA {
tf, err := models.GetTwoFactorByUID(u.ID) tf, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("GetTwoFactorByUID", err) ctx.ServerError("GetTwoFactorByUID", err)
return return
} }
if err = models.DeleteTwoFactorByID(tf.ID, u.ID); err != nil { if err = login.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
ctx.ServerError("DeleteTwoFactorByID", err) ctx.ServerError("DeleteTwoFactorByID", err)
return return
} }

@ -21,6 +21,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -174,12 +175,12 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
} }
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true { if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true {
_, err = models.GetTwoFactorByUID(ctx.User.ID) _, err = login.GetTwoFactorByUID(ctx.User.ID)
if err == nil { if err == nil {
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented // TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page") ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
return return
} else if !models.IsErrTwoFactorNotEnrolled(err) { } else if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err) ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return return
} }

@ -213,9 +213,9 @@ func SignInPost(ctx *context.Context) {
// If this user is enrolled in 2FA, we can't sign the user in just yet. // If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page. // Instead, redirect them to the 2FA authentication page.
_, err = models.GetTwoFactorByUID(u.ID) _, err = login.GetTwoFactorByUID(u.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
handleSignIn(ctx, u, form.Remember) handleSignIn(ctx, u, form.Remember)
} else { } else {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
@ -237,7 +237,7 @@ func SignInPost(ctx *context.Context) {
return return
} }
regs, err := models.GetU2FRegistrationsByUID(u.ID) regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 { if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f") ctx.Redirect(setting.AppSubURL + "/user/u2f")
return return
@ -277,7 +277,7 @@ func TwoFactorPost(ctx *context.Context) {
} }
id := idSess.(int64) id := idSess.(int64)
twofa, err := models.GetTwoFactorByUID(id) twofa, err := login.GetTwoFactorByUID(id)
if err != nil { if err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
@ -313,7 +313,7 @@ func TwoFactorPost(ctx *context.Context) {
} }
twofa.LastUsedPasscode = form.Passcode twofa.LastUsedPasscode = form.Passcode
if err = models.UpdateTwoFactor(twofa); err != nil { if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }
@ -356,7 +356,7 @@ func TwoFactorScratchPost(ctx *context.Context) {
} }
id := idSess.(int64) id := idSess.(int64)
twofa, err := models.GetTwoFactorByUID(id) twofa, err := login.GetTwoFactorByUID(id)
if err != nil { if err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
@ -370,7 +370,7 @@ func TwoFactorScratchPost(ctx *context.Context) {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }
if err = models.UpdateTwoFactor(twofa); err != nil { if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }
@ -418,7 +418,7 @@ func U2FChallenge(ctx *context.Context) {
return return
} }
id := idSess.(int64) id := idSess.(int64)
regs, err := models.GetU2FRegistrationsByUID(id) regs, err := login.GetU2FRegistrationsByUID(id)
if err != nil { if err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
@ -454,7 +454,7 @@ func U2FSign(ctx *context.Context) {
} }
challenge := challSess.(*u2f.Challenge) challenge := challSess.(*u2f.Challenge)
id := idSess.(int64) id := idSess.(int64)
regs, err := models.GetU2FRegistrationsByUID(id) regs, err := login.GetU2FRegistrationsByUID(id)
if err != nil { if err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
@ -717,8 +717,8 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *models.Us
needs2FA := false needs2FA := false
if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
_, err := models.GetTwoFactorByUID(u.ID) _, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }
@ -775,7 +775,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *models.Us
} }
// If U2F is enrolled -> Redirect to U2F instead // If U2F is enrolled -> Redirect to U2F instead
regs, err := models.GetU2FRegistrationsByUID(u.ID) regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 { if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f") ctx.Redirect(setting.AppSubURL + "/user/u2f")
return return
@ -935,9 +935,9 @@ func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remem
// If this user is enrolled in 2FA, we can't sign the user in just yet. // If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page. // Instead, redirect them to the 2FA authentication page.
// We deliberately ignore the skip local 2fa setting here because we are linking to a previous user here // We deliberately ignore the skip local 2fa setting here because we are linking to a previous user here
_, err := models.GetTwoFactorByUID(u.ID) _, err := login.GetTwoFactorByUID(u.ID)
if err != nil { if err != nil {
if !models.IsErrTwoFactorNotEnrolled(err) { if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserLinkAccount", err) ctx.ServerError("UserLinkAccount", err)
return return
} }
@ -967,7 +967,7 @@ func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remem
} }
// If U2F is enrolled -> Redirect to U2F instead // If U2F is enrolled -> Redirect to U2F instead
regs, err := models.GetU2FRegistrationsByUID(u.ID) regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 { if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f") ctx.Redirect(setting.AppSubURL + "/user/u2f")
return return
@ -1561,7 +1561,7 @@ func ForgotPasswdPost(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplForgotPassword) ctx.HTML(http.StatusOK, tplForgotPassword)
} }
func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) { func commonResetPassword(ctx *context.Context) (*models.User, *login.TwoFactor) {
code := ctx.FormString("code") code := ctx.FormString("code")
ctx.Data["Title"] = ctx.Tr("auth.reset_password") ctx.Data["Title"] = ctx.Tr("auth.reset_password")
@ -1583,9 +1583,9 @@ func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor)
return nil, nil return nil, nil
} }
twofa, err := models.GetTwoFactorByUID(u.ID) twofa, err := login.GetTwoFactorByUID(u.ID)
if err != nil { if err != nil {
if !models.IsErrTwoFactorNotEnrolled(err) { if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
return nil, nil return nil, nil
} }
@ -1680,7 +1680,7 @@ func ResetPasswdPost(ctx *context.Context) {
} }
twofa.LastUsedPasscode = passcode twofa.LastUsedPasscode = passcode
if err = models.UpdateTwoFactor(twofa); err != nil { if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err) ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
return return
} }
@ -1712,7 +1712,7 @@ func ResetPasswdPost(ctx *context.Context) {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }
if err = models.UpdateTwoFactor(twofa); err != nil { if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return
} }

@ -56,9 +56,9 @@ func DeleteAccountLink(ctx *context.Context) {
func loadSecurityData(ctx *context.Context) { func loadSecurityData(ctx *context.Context) {
enrolled := true enrolled := true
_, err := models.GetTwoFactorByUID(ctx.User.ID) _, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
enrolled = false enrolled = false
} else { } else {
ctx.ServerError("SettingsTwoFactor", err) ctx.ServerError("SettingsTwoFactor", err)
@ -67,7 +67,7 @@ func loadSecurityData(ctx *context.Context) {
} }
ctx.Data["TwofaEnrolled"] = enrolled ctx.Data["TwofaEnrolled"] = enrolled
if enrolled { if enrolled {
ctx.Data["U2FRegistrations"], err = models.GetU2FRegistrationsByUID(ctx.User.ID) ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID)
if err != nil { if err != nil {
ctx.ServerError("GetU2FRegistrationsByUID", err) ctx.ServerError("GetU2FRegistrationsByUID", err)
return return

@ -13,7 +13,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -29,9 +29,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true ctx.Data["PageIsSettingsSecurity"] = true
t, err := models.GetTwoFactorByUID(ctx.User.ID) t, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} }
@ -45,7 +45,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
return return
} }
if err = models.UpdateTwoFactor(t); err != nil { if err = login.UpdateTwoFactor(t); err != nil {
ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err) ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
return return
} }
@ -59,9 +59,9 @@ func DisableTwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true ctx.Data["PageIsSettingsSecurity"] = true
t, err := models.GetTwoFactorByUID(ctx.User.ID) t, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil { if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} }
@ -69,8 +69,8 @@ func DisableTwoFactor(ctx *context.Context) {
return return
} }
if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil { if err = login.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
if models.IsErrTwoFactorNotEnrolled(err) { if login.IsErrTwoFactorNotEnrolled(err) {
// There is a potential DB race here - we must have been disabled by another request in the intervening period // There is a potential DB race here - we must have been disabled by another request in the intervening period
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
@ -146,7 +146,7 @@ func EnrollTwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true ctx.Data["PageIsSettingsSecurity"] = true
t, err := models.GetTwoFactorByUID(ctx.User.ID) t, err := login.GetTwoFactorByUID(ctx.User.ID)
if t != nil { if t != nil {
// already enrolled - we should redirect back! // already enrolled - we should redirect back!
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User) log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
@ -154,7 +154,7 @@ func EnrollTwoFactor(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return return
} }
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err) ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
return return
} }
@ -172,14 +172,14 @@ func EnrollTwoFactorPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true ctx.Data["PageIsSettingsSecurity"] = true
t, err := models.GetTwoFactorByUID(ctx.User.ID) t, err := login.GetTwoFactorByUID(ctx.User.ID)
if t != nil { if t != nil {
// already enrolled // already enrolled
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return return
} }
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err) ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
return return
} }
@ -209,7 +209,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
return return
} }
t = &models.TwoFactor{ t = &login.TwoFactor{
UID: ctx.User.ID, UID: ctx.User.ID,
} }
err = t.SetSecret(secret) err = t.SetSecret(secret)
@ -238,7 +238,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
log.Error("Unable to save changes to the session: %v", err) log.Error("Unable to save changes to the session: %v", err)
} }
if err = models.NewTwoFactor(t); err != nil { if err = login.NewTwoFactor(t); err != nil {
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us. // FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
// If there is a unique constraint fail we should just tolerate the error // If there is a unique constraint fail we should just tolerate the error
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err) ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)

@ -8,7 +8,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -34,7 +34,7 @@ func U2FRegister(ctx *context.Context) {
ctx.ServerError("Unable to set session key for u2fChallenge", err) ctx.ServerError("Unable to set session key for u2fChallenge", err)
return return
} }
regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID) regs, err := login.GetU2FRegistrationsByUID(ctx.User.ID)
if err != nil { if err != nil {
ctx.ServerError("GetU2FRegistrationsByUID", err) ctx.ServerError("GetU2FRegistrationsByUID", err)
return return
@ -78,7 +78,7 @@ func U2FRegisterPost(ctx *context.Context) {
ctx.ServerError("u2f.Register", err) ctx.ServerError("u2f.Register", err)
return return
} }
if _, err = models.CreateRegistration(ctx.User, name, reg); err != nil { if _, err = login.CreateRegistration(ctx.User.ID, name, reg); err != nil {
ctx.ServerError("u2f.Register", err) ctx.ServerError("u2f.Register", err)
return return
} }
@ -88,9 +88,9 @@ func U2FRegisterPost(ctx *context.Context) {
// U2FDelete deletes an security key by id // U2FDelete deletes an security key by id
func U2FDelete(ctx *context.Context) { func U2FDelete(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.U2FDeleteForm) form := web.GetForm(ctx).(*forms.U2FDeleteForm)
reg, err := models.GetU2FRegistrationByID(form.ID) reg, err := login.GetU2FRegistrationByID(form.ID)
if err != nil { if err != nil {
if models.IsErrU2FRegistrationNotExist(err) { if login.IsErrU2FRegistrationNotExist(err) {
ctx.Status(200) ctx.Status(200)
return return
} }
@ -101,7 +101,7 @@ func U2FDelete(ctx *context.Context) {
ctx.Status(401) ctx.Status(401)
return return
} }
if err := models.DeleteRegistration(reg); err != nil { if err := login.DeleteRegistration(reg); err != nil {
ctx.ServerError("DeleteRegistration", err) ctx.ServerError("DeleteRegistration", err)
return return
} }