Allow blocking some email domains from registering an account (#14667)
Gitea allows to whitelist email domains so that only email addresses from certain domains are allowed to register an account, but does not currently allows to do the opposite: blacklisting email domains so that addresses from certain domains are *forbidden* to register an account. The idea has been briefly mentioned in the discussion about issue #6350, but never implemented. This PR does that. The rationale is that, in my experience of running a Gitea instance, *a single email domain* is responsible for *most* of the spam accounts, and for *all* of the spam accounts that manage to get past the email confirmation step. So on top of the other spam mitigation measures already available (email confirmation, CAPTCHA, etc.), having the option to block a particularly annoying domain would be helpful. close #13628release/v1.15
parent
d475d53c41
commit
fc4a8c2980
|
@ -617,6 +617,8 @@ REGISTER_MANUAL_CONFIRM = false
|
||||||
; List of domain names that are allowed to be used to register on a Gitea instance
|
; List of domain names that are allowed to be used to register on a Gitea instance
|
||||||
; gitea.io,example.com
|
; gitea.io,example.com
|
||||||
EMAIL_DOMAIN_WHITELIST =
|
EMAIL_DOMAIN_WHITELIST =
|
||||||
|
; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance
|
||||||
|
EMAIL_DOMAIN_BLOCKLIST =
|
||||||
; Disallow registration, only allow admins to create accounts.
|
; Disallow registration, only allow admins to create accounts.
|
||||||
DISABLE_REGISTRATION = false
|
DISABLE_REGISTRATION = false
|
||||||
; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
|
; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
|
||||||
|
|
|
@ -466,6 +466,7 @@ relation to port exhaustion.
|
||||||
- `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time.
|
- `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time.
|
||||||
- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
|
- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
|
||||||
on this instance.
|
on this instance.
|
||||||
|
- `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, list of domain names that cannot be used to register on this instance
|
||||||
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
|
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
|
||||||
- `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones
|
- `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones
|
||||||
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
|
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
|
||||||
|
|
|
@ -120,13 +120,14 @@ For more information, refer to Gitea's [API docs]({{< relref "doc/developers/api
|
||||||
|
|
||||||
There are multiple things you can combine to prevent spammers.
|
There are multiple things you can combine to prevent spammers.
|
||||||
|
|
||||||
1. By only whitelisting certain domains with OpenID (see below)
|
1. By whitelisting or blocklisting certain email domains
|
||||||
2. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY`
|
2. By only whitelisting certain domains with OpenID (see below)
|
||||||
3. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI]({{< relref "doc/usage/command-line.en-us.md" >}}), [API]({{< relref "doc/developers/api-usage.en-us.md" >}}), or Gitea's Admin UI
|
3. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY`
|
||||||
|
4. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI]({{< relref "doc/usage/command-line.en-us.md" >}}), [API]({{< relref "doc/developers/api-usage.en-us.md" >}}), or Gitea's Admin UI
|
||||||
|
|
||||||
### Only allow certain email domains
|
### Only allow/block certain email domains
|
||||||
|
|
||||||
You can configure `EMAIL_DOMAIN_WHITELIST` in your app.ini under `[service]`
|
You can configure `EMAIL_DOMAIN_WHITELIST` or `EMAIL_DOMAIN_BLOCKLIST` in your app.ini under `[service]`
|
||||||
|
|
||||||
### Only allow/block certain OpenID providers
|
### Only allow/block certain OpenID providers
|
||||||
|
|
||||||
|
|
|
@ -95,23 +95,21 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmailDomainWhitelisted validates that the email address
|
// IsEmailDomainListed checks whether the domain of an email address
|
||||||
// provided by the user matches what has been configured .
|
// matches a list of domains
|
||||||
// If the domain whitelist from the config is empty, it marks the
|
func IsEmailDomainListed(list []string, email string) bool {
|
||||||
// email as whitelisted
|
if len(list) == 0 {
|
||||||
func (f RegisterForm) IsEmailDomainWhitelisted() bool {
|
return false
|
||||||
if len(setting.Service.EmailDomainWhitelist) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := strings.LastIndex(f.Email, "@")
|
n := strings.LastIndex(email, "@")
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
domain := strings.ToLower(f.Email[n+1:])
|
domain := strings.ToLower(email[n+1:])
|
||||||
|
|
||||||
for _, v := range setting.Service.EmailDomainWhitelist {
|
for _, v := range list {
|
||||||
if strings.ToLower(v) == domain {
|
if strings.ToLower(v) == domain {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -120,6 +118,19 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEmailDomainAllowed validates that the email address
|
||||||
|
// provided by the user matches what has been configured .
|
||||||
|
// The email is marked as allowed if it matches any of the
|
||||||
|
// domains in the whitelist or if it doesn't match any of
|
||||||
|
// domains in the blocklist, if any such list is not empty.
|
||||||
|
func (f RegisterForm) IsEmailDomainAllowed() bool {
|
||||||
|
if len(setting.Service.EmailDomainWhitelist) == 0 {
|
||||||
|
return !IsEmailDomainListed(setting.Service.EmailDomainBlocklist, f.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsEmailDomainListed(setting.Service.EmailDomainWhitelist, f.Email)
|
||||||
|
}
|
||||||
|
|
||||||
// MustChangePasswordForm form for updating your password after account creation
|
// MustChangePasswordForm form for updating your password after account creation
|
||||||
// by an admin
|
// by an admin
|
||||||
type MustChangePasswordForm struct {
|
type MustChangePasswordForm struct {
|
||||||
|
|
|
@ -12,17 +12,17 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainWhiteList_Empty(t *testing.T) {
|
func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
|
||||||
_ = setting.Service
|
_ = setting.Service
|
||||||
|
|
||||||
setting.Service.EmailDomainWhitelist = []string{}
|
setting.Service.EmailDomainWhitelist = []string{}
|
||||||
|
|
||||||
form := RegisterForm{}
|
form := RegisterForm{}
|
||||||
|
|
||||||
assert.True(t, form.IsEmailDomainWhitelisted())
|
assert.True(t, form.IsEmailDomainAllowed())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
|
func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
|
||||||
_ = setting.Service
|
_ = setting.Service
|
||||||
|
|
||||||
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
|
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
|
||||||
|
@ -37,11 +37,11 @@ func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
form := RegisterForm{Email: v.email}
|
form := RegisterForm{Email: v.email}
|
||||||
|
|
||||||
assert.False(t, form.IsEmailDomainWhitelisted())
|
assert.False(t, form.IsEmailDomainAllowed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
|
func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
|
||||||
_ = setting.Service
|
_ = setting.Service
|
||||||
|
|
||||||
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
|
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
|
||||||
|
@ -59,6 +59,28 @@ func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
form := RegisterForm{Email: v.email}
|
form := RegisterForm{Email: v.email}
|
||||||
|
|
||||||
assert.Equal(t, v.valid, form.IsEmailDomainWhitelisted())
|
assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
|
||||||
|
_ = setting.Service
|
||||||
|
|
||||||
|
setting.Service.EmailDomainWhitelist = []string{}
|
||||||
|
setting.Service.EmailDomainBlocklist = []string{"gitea.io"}
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
email string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{"security@gitea.io", false},
|
||||||
|
{"security@gitea.example", true},
|
||||||
|
{"hdudhdd", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range tt {
|
||||||
|
form := RegisterForm{Email: v.email}
|
||||||
|
|
||||||
|
assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ var Service struct {
|
||||||
RegisterEmailConfirm bool
|
RegisterEmailConfirm bool
|
||||||
RegisterManualConfirm bool
|
RegisterManualConfirm bool
|
||||||
EmailDomainWhitelist []string
|
EmailDomainWhitelist []string
|
||||||
|
EmailDomainBlocklist []string
|
||||||
DisableRegistration bool
|
DisableRegistration bool
|
||||||
AllowOnlyExternalRegistration bool
|
AllowOnlyExternalRegistration bool
|
||||||
ShowRegistrationButton bool
|
ShowRegistrationButton bool
|
||||||
|
@ -72,6 +73,7 @@ func newService() {
|
||||||
Service.RegisterManualConfirm = false
|
Service.RegisterManualConfirm = false
|
||||||
}
|
}
|
||||||
Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
|
Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
|
||||||
|
Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",")
|
||||||
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
|
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
|
||||||
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
||||||
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
||||||
|
|
|
@ -1129,7 +1129,7 @@ func SignUpPost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.IsEmailDomainWhitelisted() {
|
if !form.IsEmailDomainAllowed() {
|
||||||
ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form)
|
ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue