Skip to content

Commit f183783

Browse files
Save initial signup information for users to aid in spam prevention (#31852)
This will allow instance admins to view signup pattern patterns for public instances. It is modelled after discourse, mastodon, and MediaWiki's approaches. Note: This has privacy implications, but as the above-stated open-source projects take this approach, especially MediaWiki, which I have no doubt looked into this thoroughly, it is likely okay for us, too. However, I would be appreciative of any feedback on how this could be improved. --------- Co-authored-by: Giteabot <[email protected]>
1 parent a323a82 commit f183783

File tree

18 files changed

+61
-23
lines changed

18 files changed

+61
-23
lines changed

cmd/admin_user_create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func runCreateUser(c *cli.Context) error {
158158
IsRestricted: restricted,
159159
}
160160

161-
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
161+
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
162162
return fmt.Errorf("CreateUser: %w", err)
163163
}
164164

custom/conf/app.example.ini

+3
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ INTERNAL_TOKEN =
507507
;; stemming from cached/logged plain-text API tokens.
508508
;; In future releases, this will become the default behavior
509509
;DISABLE_QUERY_AUTH_TOKEN = false
510+
;;
511+
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
512+
;; RECORD_USER_SIGNUP_METADATA = false
510513

511514
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
512515
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

models/user/setting_keys.go

+4
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ const (
1414
UserActivityPubPrivPem = "activitypub.priv_pem"
1515
// UserActivityPubPubPem is user's public key
1616
UserActivityPubPubPem = "activitypub.pub_pem"
17+
// SignupIP is the IP address that the user signed up with
18+
SignupIP = "signup.ip"
19+
// SignupUserAgent is the user agent that the user signed up with
20+
SignupUserAgent = "signup.user_agent"
1721
)

models/user/user.go

+29-5
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ type User struct {
150150
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
151151
}
152152

153+
// Meta defines the meta information of a user, to be stored in the K/V table
154+
type Meta struct {
155+
// Store the initial registration of the user, to aid in spam prevention
156+
// Ensure that one IP isn't creating many accounts (following mediawiki approach)
157+
InitialIP string
158+
InitialUserAgent string
159+
}
160+
153161
func init() {
154162
db.RegisterModel(new(User))
155163
}
@@ -615,17 +623,17 @@ type CreateUserOverwriteOptions struct {
615623
}
616624

617625
// CreateUser creates record of a new user.
618-
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
619-
return createUser(ctx, u, false, overwriteDefault...)
626+
func CreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
627+
return createUser(ctx, u, meta, false, overwriteDefault...)
620628
}
621629

622630
// AdminCreateUser is used by admins to manually create users
623-
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
624-
return createUser(ctx, u, true, overwriteDefault...)
631+
func AdminCreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
632+
return createUser(ctx, u, meta, true, overwriteDefault...)
625633
}
626634

627635
// createUser creates record of a new user.
628-
func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
636+
func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
629637
if err = IsUsableUsername(u.Name); err != nil {
630638
return err
631639
}
@@ -745,6 +753,22 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
745753
return err
746754
}
747755

756+
if setting.RecordUserSignupMetadata {
757+
// insert initial IP and UserAgent
758+
if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil {
759+
return err
760+
}
761+
762+
// trim user agent string to a reasonable length, if necessary
763+
userAgent := strings.TrimSpace(meta.InitialUserAgent)
764+
if len(userAgent) > 255 {
765+
userAgent = userAgent[:255]
766+
}
767+
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
768+
return err
769+
}
770+
}
771+
748772
// insert email address
749773
if err := db.Insert(ctx, &EmailAddress{
750774
UID: u.ID,

models/user/user_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
227227
MustChangePassword: false,
228228
}
229229

230-
err := user_model.CreateUser(db.DefaultContext, user)
230+
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
231231
assert.Error(t, err)
232232
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
233233
}
@@ -241,7 +241,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) {
241241
user.Name = "testuser"
242242
user.LowerName = strings.ToLower(user.Name)
243243
user.ID = 0
244-
err := user_model.CreateUser(db.DefaultContext, user)
244+
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
245245
assert.Error(t, err)
246246
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
247247
}
@@ -258,7 +258,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) {
258258
user.ID = 0
259259
user.Email = "[email protected]"
260260
user.CreatedUnix = creationTimestamp
261-
err := user_model.CreateUser(db.DefaultContext, user)
261+
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
262262
assert.NoError(t, err)
263263

264264
fetched, err := user_model.GetUserByID(context.Background(), user.ID)
@@ -283,7 +283,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) {
283283
user.Email = "[email protected]"
284284
user.CreatedUnix = 0
285285
user.UpdatedUnix = 0
286-
err := user_model.CreateUser(db.DefaultContext, user)
286+
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
287287
assert.NoError(t, err)
288288

289289
timestampEnd := time.Now().Unix()

modules/setting/security.go

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737
DisableQueryAuthToken bool
3838
CSRFCookieName = "_csrf"
3939
CSRFCookieHTTPOnly = true
40+
RecordUserSignupMetadata = false
4041
)
4142

4243
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
@@ -164,6 +165,8 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
164165
// TODO: default value should be true in future releases
165166
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
166167

168+
RecordUserSignupMetadata = sec.Key("RECORD_USER_SIGNUP_METADATA").MustBool(false)
169+
167170
// warn if the setting is set to false explicitly
168171
if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken {
169172
log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.")

routers/api/v1/admin/user.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func CreateUser(ctx *context.APIContext) {
133133
u.UpdatedUnix = u.CreatedUnix
134134
}
135135

136-
if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
136+
if err := user_model.AdminCreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
137137
if user_model.IsErrUserAlreadyExist(err) ||
138138
user_model.IsErrEmailAlreadyUsed(err) ||
139139
db.IsErrNameReserved(err) ||

routers/install/install.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ func SubmitInstall(ctx *context.Context) {
554554
IsActive: optional.Some(true),
555555
}
556556

557-
if err = user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
557+
if err = user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
558558
if !user_model.IsErrUserAlreadyExist(err) {
559559
setting.InstallLock = false
560560
ctx.Data["Err_AdminName"] = true

routers/web/admin/users.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) {
177177
u.MustChangePassword = form.MustChangePassword
178178
}
179179

180-
if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
180+
if err := user_model.AdminCreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
181181
switch {
182182
case user_model.IsErrUserAlreadyExist(err):
183183
ctx.Data["Err_UserName"] = true

routers/web/auth/auth.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,11 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any
541541
// createUserInContext creates a user and handles errors within a given context.
542542
// Optionally a template can be specified.
543543
func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
544-
if err := user_model.CreateUser(ctx, u, overwrites); err != nil {
544+
meta := &user_model.Meta{
545+
InitialIP: ctx.RemoteAddr(),
546+
InitialUserAgent: ctx.Req.UserAgent(),
547+
}
548+
if err := user_model.CreateUser(ctx, u, meta, overwrites); err != nil {
545549
if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
546550
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
547551
var user *user_model.User

services/auth/reverseproxy.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User {
164164
IsActive: optional.Some(true),
165165
}
166166

167-
if err := user_model.CreateUser(req.Context(), user, &overwriteDefault); err != nil {
167+
if err := user_model.CreateUser(req.Context(), user, &user_model.Meta{}, &overwriteDefault); err != nil {
168168
// FIXME: should I create a system notice?
169169
log.Error("CreateUser: %v", err)
170170
return nil

services/auth/source/ldap/source_authenticate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
8989
IsActive: optional.Some(true),
9090
}
9191

92-
err := user_model.CreateUser(ctx, user, overwriteDefault)
92+
err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault)
9393
if err != nil {
9494
return user, err
9595
}

services/auth/source/ldap/source_sync.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
129129
IsActive: optional.Some(true),
130130
}
131131

132-
err = user_model.CreateUser(ctx, usr, overwriteDefault)
132+
err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault)
133133
if err != nil {
134134
log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err)
135135
}

services/auth/source/oauth2/source_sync_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestSource(t *testing.T) {
3636
3737
}
3838

39-
err := user_model.CreateUser(context.Background(), user, &user_model.CreateUserOverwriteOptions{})
39+
err := user_model.CreateUser(context.Background(), user, &user_model.Meta{}, &user_model.CreateUserOverwriteOptions{})
4040
assert.NoError(t, err)
4141

4242
e := &user_model.ExternalLoginUser{

services/auth/source/pam/source_authenticate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
6363
IsActive: optional.Some(true),
6464
}
6565

66-
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
66+
if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil {
6767
return user, err
6868
}
6969

services/auth/source/smtp/source_authenticate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
7979
IsActive: optional.Some(true),
8080
}
8181

82-
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
82+
if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil {
8383
return user, err
8484
}
8585

services/auth/sspi.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (
176176
KeepEmailPrivate: optional.Some(true),
177177
EmailNotificationsPreference: &emailNotificationPreference,
178178
}
179-
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
179+
if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil {
180180
return nil, err
181181
}
182182

services/user/user_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestCreateUser(t *testing.T) {
9292
MustChangePassword: false,
9393
}
9494

95-
assert.NoError(t, user_model.CreateUser(db.DefaultContext, user))
95+
assert.NoError(t, user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{}))
9696

9797
assert.NoError(t, DeleteUser(db.DefaultContext, user, false))
9898
}
@@ -177,7 +177,7 @@ func TestCreateUser_Issue5882(t *testing.T) {
177177
for _, v := range tt {
178178
setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation
179179

180-
assert.NoError(t, user_model.CreateUser(db.DefaultContext, v.user))
180+
assert.NoError(t, user_model.CreateUser(db.DefaultContext, v.user, &user_model.Meta{}))
181181

182182
u, err := user_model.GetUserByEmail(db.DefaultContext, v.user.Email)
183183
assert.NoError(t, err)

0 commit comments

Comments
 (0)