This commit is contained in:
wxiaoguang 2025-04-12 12:30:40 +08:00
parent 5015992db5
commit 7ffd745ce0
8 changed files with 44 additions and 2 deletions

View File

@ -518,6 +518,9 @@ INTERNAL_TOKEN =
;;
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
;; RECORD_USER_SIGNUP_METADATA = false
;;
;; Force users to enroll into Two-Factor Authentication. Users without 2FA have no access to any repositories.
;ENFORCE_TWO_FACTOR_AUTH = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

7
modules/session/key.go Normal file
View File

@ -0,0 +1,7 @@
package session
const (
KeyUID = "uid"
KeyUname = "uname"
KeyTwofaSatisfied = "twofaSatisfied"
)

View File

@ -39,6 +39,7 @@ var (
CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true
RecordUserSignupMetadata = false
EnforceTwoFactorAuth = false
)
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
@ -141,6 +142,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
EnforceTwoFactorAuth = sec.Key("ENFORCE_TWO_FACTOR_AUTH").MustBool(false)
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
if InstallLock && InternalToken == "" {

View File

@ -449,6 +449,7 @@ use_scratch_code = Use a scratch code
twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code.
twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in.
twofa_scratch_token_incorrect = Your scratch code is incorrect.
twofa_required = You must setup Two-Factor Authentication to get access to repositories
login_userpass = Sign In
login_openid = OpenID
oauth_signup_tab = Register New Account

View File

@ -86,10 +86,13 @@ func autoSignIn(ctx *context.Context) (bool, error) {
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
twofa, _ := auth.GetTwoFactorByUID(ctx, u.ID)
if err := updateSession(ctx, nil, map[string]any{
// Set session IDs
"uid": u.ID,
"uname": u.Name,
session.KeyTwofaSatisfied: twofa != nil,
}); err != nil {
return false, fmt.Errorf("unable to updateSession: %w", err)
}

View File

@ -6,6 +6,7 @@ package security
import (
"bytes"
"code.gitea.io/gitea/modules/session"
"encoding/base64"
"html/template"
"image/png"
@ -163,12 +164,21 @@ func EnrollTwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
ctx.Data["ShowTwoFactorRequiredMessage"] = false
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if t != nil {
// already enrolled - we should redirect back!
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.Doer)
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
if ctx.Session.Get(session.KeyTwofaSatisfied) == nil {
// in case a 2FA user is using an old session (the session doesn't know 2FA authed),
// he will be navigated to this page, we should update the session status
_ = ctx.Session.Set(session.KeyTwofaSatisfied, true)
_ = ctx.Session.Release()
}
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
@ -194,6 +204,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
ctx.Data["ShowTwoFactorRequiredMessage"] = false
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if t != nil {
@ -246,6 +257,12 @@ func EnrollTwoFactorPost(ctx *context.Context) {
return
}
newTwoFactorErr := auth.NewTwoFactor(ctx, t)
if newTwoFactorErr == nil {
if err := ctx.Session.Set(session.KeyTwofaSatisfied, true); err != nil {
log.Error("Unable to set %s to session: Error: %v", session.KeyTwofaSatisfied, err)
}
}
// Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used
// If we can detect the unique constraint failure below we can move this to after the NewTwoFactor
if err := ctx.Session.Delete("twofaSecret"); err != nil {
@ -261,10 +278,10 @@ func EnrollTwoFactorPost(ctx *context.Context) {
log.Error("Unable to save changes to the session: %v", err)
}
if err = auth.NewTwoFactor(ctx, t); err != nil {
if newTwoFactorErr != 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)
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", newTwoFactorErr)
return
}

View File

@ -196,6 +196,10 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Data["SystemConfig"] = setting.Config()
ctx.Data["ShowTwoFactorRequiredMessage"] = setting.EnforceTwoFactorAuth &&
ctx.Session.Get("uid") != nil &&
ctx.Session.Get(session.KeyTwofaSatisfied) != true
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["DisableStars"] = setting.Repository.DisableStars

View File

@ -18,3 +18,8 @@
<p>{{.Flash.WarningMsg | SanitizeHTML}}</p>
</div>
{{- end -}}
{{- if .ShowTwoFactorRequiredMessage -}}
<div class="ui negative message flash-message flash-error">
<p><a href="{{AppSubUrl}}/user/settings/security/two_factor/enroll">{{ctx.Locale.Tr "auth.twofa_required"}} &raquo;</a></p>
</div>
{{- end -}}