1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 10:18:38 +00:00

Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187)

Fix #880

Design:

1. A global setting `security.TWO_FACTOR_AUTH`.
* To support org-level config, we need to introduce a better "owner
setting" system first (in the future)
2. A user without 2FA can login and may explore, but can NOT read or
write to any repositories via API/web.
3. Keep things as simple as possible.
* This option only aggressively suggest users to enable their 2FA at the
moment, it does NOT guarantee that users must have 2FA before all other
operations, it should be good enough for real world use cases.
* Some details and tests could be improved in the future since this
change only adds a check and seems won't affect too much.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
wxiaoguang
2025-04-29 06:31:59 +08:00
committed by GitHub
parent 4ed07244b9
commit 0148d03f21
47 changed files with 324 additions and 223 deletions

View File

@@ -28,8 +28,6 @@ import (
"code.gitea.io/gitea/services/auth/source/sspi"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"xorm.io/xorm/convert"
)
const (
@@ -149,7 +147,6 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
RestrictedFilter: form.RestrictedFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
@@ -163,7 +160,6 @@ func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
SkipVerify: form.SkipVerify,
HeloHostname: form.HeloHostname,
DisableHelo: form.DisableHelo,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
@@ -198,7 +194,6 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
Scopes: scopes,
RequiredClaimName: form.Oauth2RequiredClaimName,
RequiredClaimValue: form.Oauth2RequiredClaimValue,
SkipLocalTwoFA: form.SkipLocalTwoFA,
GroupClaimName: form.Oauth2GroupClaimName,
RestrictedGroup: form.Oauth2RestrictedGroup,
AdminGroup: form.Oauth2AdminGroup,
@@ -252,7 +247,7 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["SSPIDefaultLanguage"] = ""
hasTLS := false
var config convert.Conversion
var config auth.Config
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
@@ -262,9 +257,8 @@ func NewAuthSourcePost(ctx *context.Context) {
hasTLS = true
case auth.PAM:
config = &pam_service.Source{
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
SkipLocalTwoFA: form.SkipLocalTwoFA,
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
}
case auth.OAuth2:
config = parseOAuth2Config(form)
@@ -302,11 +296,12 @@ func NewAuthSourcePost(ctx *context.Context) {
}
if err := auth.CreateSource(ctx, &auth.Source{
Type: auth.Type(form.Type),
Name: form.Name,
IsActive: form.IsActive,
IsSyncEnabled: form.IsSyncEnabled,
Cfg: config,
Type: auth.Type(form.Type),
Name: form.Name,
IsActive: form.IsActive,
IsSyncEnabled: form.IsSyncEnabled,
TwoFactorPolicy: form.TwoFactorPolicy,
Cfg: config,
}); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true
@@ -384,7 +379,7 @@ func EditAuthSourcePost(ctx *context.Context) {
return
}
var config convert.Conversion
var config auth.Config
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
@@ -421,6 +416,7 @@ func EditAuthSourcePost(ctx *context.Context) {
source.IsActive = form.IsActive
source.IsSyncEnabled = form.IsSyncEnabled
source.Cfg = config
source.TwoFactorPolicy = form.TwoFactorPolicy
if err := auth.UpdateSource(ctx, source); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true