diff --git a/models/login/main_test.go b/models/login/main_test.go index ef4b5907bfd4c..141952a5941de 100644 --- a/models/login/main_test.go +++ b/models/login/main_test.go @@ -17,5 +17,6 @@ func TestMain(m *testing.M) { "oauth2_application.yml", "oauth2_authorization_code.yml", "oauth2_grant.yml", + "u2f_registration.yml", ) } diff --git a/models/login/twofactor.go b/models/login/twofactor.go index b85f20ff24e80..1c4d2734fca02 100644 --- a/models/login/twofactor.go +++ b/models/login/twofactor.go @@ -63,16 +63,12 @@ func (t *TwoFactor) GenerateScratchToken() (string, error) { return "", err } t.ScratchSalt, _ = util.RandomString(10) - t.ScratchHash = hashToken(token, t.ScratchSalt) + t.ScratchHash = HashToken(token, t.ScratchSalt) return token, nil } // HashToken return the hashable salt func HashToken(token, salt string) string { - return hashToken(token, salt) -} - -func hashToken(token, salt string) string { tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New) return fmt.Sprintf("%x", tempHash) } @@ -82,7 +78,7 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool { if len(token) == 0 { return false } - tempHash := hashToken(token, t.ScratchSalt) + tempHash := HashToken(token, t.ScratchSalt) return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1 } diff --git a/modules/context/api.go b/modules/context/api.go index e80e63cd96233..e5216d911f8a6 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -14,6 +14,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -219,9 +220,9 @@ func (ctx *APIContext) CheckForOTP() { } 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 models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { return // No 2FA enrollment for this user } ctx.Context.Error(http.StatusInternalServerError) diff --git a/modules/context/auth.go b/modules/context/auth.go index 0a62b2741e4a7..7faa93d78b594 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -8,7 +8,7 @@ package context import ( "net/http" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "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) { return // Skip 2FA } - twofa, err := models.GetTwoFactorByUID(ctx.User.ID) + twofa, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { return // No 2FA enrollment for this user } ctx.InternalServerError(err) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 2556cae3a87a2..ea666ab4d4dbb 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -195,9 +195,9 @@ func prepareUserInfo(ctx *context.Context) *models.User { ctx.Data["Sources"] = sources ctx.Data["TwoFactorEnabled"] = true - _, err = models.GetTwoFactorByUID(u.ID) + _, err = login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) return nil } @@ -295,13 +295,13 @@ func EditUserPost(ctx *context.Context) { } if form.Reset2FA { - tf, err := models.GetTwoFactorByUID(u.ID) - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + tf, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("GetTwoFactorByUID", err) 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) return } diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index fbd1e19a8219b..162338a9597c0 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -21,6 +21,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -174,12 +175,12 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { } if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true { - _, err = models.GetTwoFactorByUID(ctx.User.ID) + _, err = login.GetTwoFactorByUID(ctx.User.ID) 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 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 - } else if !models.IsErrTwoFactorNotEnrolled(err) { + } else if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) return } diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go index 733ace81b02a5..12328e46a1657 100644 --- a/routers/web/user/auth.go +++ b/routers/web/user/auth.go @@ -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. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err = login.GetTwoFactorByUID(u.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { handleSignIn(ctx, u, form.Remember) } else { ctx.ServerError("UserSignIn", err) @@ -237,7 +237,7 @@ func SignInPost(ctx *context.Context) { return } - regs, err := models.GetU2FRegistrationsByUID(u.ID) + regs, err := login.GetU2FRegistrationsByUID(u.ID) if err == nil && len(regs) > 0 { ctx.Redirect(setting.AppSubURL + "/user/u2f") return @@ -277,7 +277,7 @@ func TwoFactorPost(ctx *context.Context) { } id := idSess.(int64) - twofa, err := models.GetTwoFactorByUID(id) + twofa, err := login.GetTwoFactorByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -313,7 +313,7 @@ func TwoFactorPost(ctx *context.Context) { } twofa.LastUsedPasscode = form.Passcode - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } @@ -356,7 +356,7 @@ func TwoFactorScratchPost(ctx *context.Context) { } id := idSess.(int64) - twofa, err := models.GetTwoFactorByUID(id) + twofa, err := login.GetTwoFactorByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -370,7 +370,7 @@ func TwoFactorScratchPost(ctx *context.Context) { ctx.ServerError("UserSignIn", err) return } - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } @@ -418,7 +418,7 @@ func U2FChallenge(ctx *context.Context) { return } id := idSess.(int64) - regs, err := models.GetU2FRegistrationsByUID(id) + regs, err := login.GetU2FRegistrationsByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -454,7 +454,7 @@ func U2FSign(ctx *context.Context) { } challenge := challSess.(*u2f.Challenge) id := idSess.(int64) - regs, err := models.GetU2FRegistrationsByUID(id) + regs, err := login.GetU2FRegistrationsByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -717,8 +717,8 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *models.Us needs2FA := false if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { - _, err := models.GetTwoFactorByUID(u.ID) - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + _, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserSignIn", err) return } @@ -775,7 +775,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *models.Us } // 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 { ctx.Redirect(setting.AppSubURL + "/user/u2f") 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. // 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 - _, err := models.GetTwoFactorByUID(u.ID) + _, err := login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserLinkAccount", err) 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 - regs, err := models.GetU2FRegistrationsByUID(u.ID) + regs, err := login.GetU2FRegistrationsByUID(u.ID) if err == nil && len(regs) > 0 { ctx.Redirect(setting.AppSubURL + "/user/u2f") return @@ -1561,7 +1561,7 @@ func ForgotPasswdPost(ctx *context.Context) { 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") ctx.Data["Title"] = ctx.Tr("auth.reset_password") @@ -1583,9 +1583,9 @@ func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) return nil, nil } - twofa, err := models.GetTwoFactorByUID(u.ID) + twofa, err := login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) return nil, nil } @@ -1680,7 +1680,7 @@ func ResetPasswdPost(ctx *context.Context) { } twofa.LastUsedPasscode = passcode - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err) return } @@ -1712,7 +1712,7 @@ func ResetPasswdPost(ctx *context.Context) { ctx.ServerError("UserSignIn", err) return } - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } diff --git a/routers/web/user/setting/security.go b/routers/web/user/setting/security.go index d4abe84d9601c..53f672282d1a2 100644 --- a/routers/web/user/setting/security.go +++ b/routers/web/user/setting/security.go @@ -56,9 +56,9 @@ func DeleteAccountLink(ctx *context.Context) { func loadSecurityData(ctx *context.Context) { enrolled := true - _, err := models.GetTwoFactorByUID(ctx.User.ID) + _, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { enrolled = false } else { ctx.ServerError("SettingsTwoFactor", err) @@ -67,7 +67,7 @@ func loadSecurityData(ctx *context.Context) { } ctx.Data["TwofaEnrolled"] = enrolled if enrolled { - ctx.Data["U2FRegistrations"], err = models.GetU2FRegistrationsByUID(ctx.User.ID) + ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID) if err != nil { ctx.ServerError("GetU2FRegistrationsByUID", err) return diff --git a/routers/web/user/setting/security_twofa.go b/routers/web/user/setting/security_twofa.go index 7b08a05939b30..5b1cbab17fe74 100644 --- a/routers/web/user/setting/security_twofa.go +++ b/routers/web/user/setting/security_twofa.go @@ -13,7 +13,7 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -29,9 +29,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } @@ -45,7 +45,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - if err = models.UpdateTwoFactor(t); err != nil { + if err = login.UpdateTwoFactor(t); err != nil { ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err) return } @@ -59,9 +59,9 @@ func DisableTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } @@ -69,8 +69,8 @@ func DisableTwoFactor(ctx *context.Context) { return } - if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if err = login.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil { + if login.IsErrTwoFactorNotEnrolled(err) { // 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.Redirect(setting.AppSubURL + "/user/settings/security") @@ -146,7 +146,7 @@ func EnrollTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if t != nil { // already enrolled - we should redirect back! 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") return } - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err) return } @@ -172,14 +172,14 @@ func EnrollTwoFactorPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if t != nil { // already enrolled ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") 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) return } @@ -209,7 +209,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - t = &models.TwoFactor{ + t = &login.TwoFactor{ UID: ctx.User.ID, } err = t.SetSecret(secret) @@ -238,7 +238,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { 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. // If there is a unique constraint fail we should just tolerate the error ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err) diff --git a/routers/web/user/setting/security_u2f.go b/routers/web/user/setting/security_u2f.go index f9e35549fbfa3..d1d6d1e8cad89 100644 --- a/routers/web/user/setting/security_u2f.go +++ b/routers/web/user/setting/security_u2f.go @@ -8,7 +8,7 @@ import ( "errors" "net/http" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "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) return } - regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID) + regs, err := login.GetU2FRegistrationsByUID(ctx.User.ID) if err != nil { ctx.ServerError("GetU2FRegistrationsByUID", err) return @@ -78,7 +78,7 @@ func U2FRegisterPost(ctx *context.Context) { ctx.ServerError("u2f.Register", err) 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) return } @@ -88,9 +88,9 @@ func U2FRegisterPost(ctx *context.Context) { // U2FDelete deletes an security key by id func U2FDelete(ctx *context.Context) { form := web.GetForm(ctx).(*forms.U2FDeleteForm) - reg, err := models.GetU2FRegistrationByID(form.ID) + reg, err := login.GetU2FRegistrationByID(form.ID) if err != nil { - if models.IsErrU2FRegistrationNotExist(err) { + if login.IsErrU2FRegistrationNotExist(err) { ctx.Status(200) return } @@ -101,7 +101,7 @@ func U2FDelete(ctx *context.Context) { ctx.Status(401) return } - if err := models.DeleteRegistration(reg); err != nil { + if err := login.DeleteRegistration(reg); err != nil { ctx.ServerError("DeleteRegistration", err) return }