mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	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 <teabot@gitea.io>
This commit is contained in:
		| @@ -158,7 +158,7 @@ func runCreateUser(c *cli.Context) error { | ||||
| 		IsRestricted: restricted, | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { | ||||
| 	if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		return fmt.Errorf("CreateUser: %w", err) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -507,6 +507,9 @@ INTERNAL_TOKEN = | ||||
| ;; stemming from cached/logged plain-text API tokens. | ||||
| ;; In future releases, this will become the default behavior | ||||
| ;DISABLE_QUERY_AUTH_TOKEN = false | ||||
| ;; | ||||
| ;; On user registration, record the IP address and user agent of the user to help identify potential abuse. | ||||
| ;; RECORD_USER_SIGNUP_METADATA = false | ||||
|  | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|   | ||||
| @@ -14,4 +14,8 @@ const ( | ||||
| 	UserActivityPubPrivPem = "activitypub.priv_pem" | ||||
| 	// UserActivityPubPubPem is user's public key | ||||
| 	UserActivityPubPubPem = "activitypub.pub_pem" | ||||
| 	// SignupIP is the IP address that the user signed up with | ||||
| 	SignupIP = "signup.ip" | ||||
| 	// SignupUserAgent is the user agent that the user signed up with | ||||
| 	SignupUserAgent = "signup.user_agent" | ||||
| ) | ||||
|   | ||||
| @@ -150,6 +150,14 @@ type User struct { | ||||
| 	KeepActivityPrivate bool   `xorm:"NOT NULL DEFAULT false"` | ||||
| } | ||||
|  | ||||
| // Meta defines the meta information of a user, to be stored in the K/V table | ||||
| type Meta struct { | ||||
| 	// Store the initial registration of the user, to aid in spam prevention | ||||
| 	// Ensure that one IP isn't creating many accounts (following mediawiki approach) | ||||
| 	InitialIP        string | ||||
| 	InitialUserAgent string | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	db.RegisterModel(new(User)) | ||||
| } | ||||
| @@ -615,17 +623,17 @@ type CreateUserOverwriteOptions struct { | ||||
| } | ||||
|  | ||||
| // CreateUser creates record of a new user. | ||||
| func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| 	return createUser(ctx, u, false, overwriteDefault...) | ||||
| func CreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| 	return createUser(ctx, u, meta, false, overwriteDefault...) | ||||
| } | ||||
|  | ||||
| // AdminCreateUser is used by admins to manually create users | ||||
| func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| 	return createUser(ctx, u, true, overwriteDefault...) | ||||
| func AdminCreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| 	return createUser(ctx, u, meta, true, overwriteDefault...) | ||||
| } | ||||
|  | ||||
| // createUser creates record of a new user. | ||||
| func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { | ||||
| 	if err = IsUsableUsername(u.Name); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -745,6 +753,22 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if setting.RecordUserSignupMetadata { | ||||
| 		// insert initial IP and UserAgent | ||||
| 		if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// trim user agent string to a reasonable length, if necessary | ||||
| 		userAgent := strings.TrimSpace(meta.InitialUserAgent) | ||||
| 		if len(userAgent) > 255 { | ||||
| 			userAgent = userAgent[:255] | ||||
| 		} | ||||
| 		if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// insert email address | ||||
| 	if err := db.Insert(ctx, &EmailAddress{ | ||||
| 		UID:         u.ID, | ||||
|   | ||||
| @@ -227,7 +227,7 @@ func TestCreateUserInvalidEmail(t *testing.T) { | ||||
| 		MustChangePassword: false, | ||||
| 	} | ||||
|  | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user) | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{}) | ||||
| 	assert.Error(t, err) | ||||
| 	assert.True(t, user_model.IsErrEmailCharIsNotSupported(err)) | ||||
| } | ||||
| @@ -241,7 +241,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) { | ||||
| 	user.Name = "testuser" | ||||
| 	user.LowerName = strings.ToLower(user.Name) | ||||
| 	user.ID = 0 | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user) | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{}) | ||||
| 	assert.Error(t, err) | ||||
| 	assert.True(t, user_model.IsErrEmailAlreadyUsed(err)) | ||||
| } | ||||
| @@ -258,7 +258,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) { | ||||
| 	user.ID = 0 | ||||
| 	user.Email = "unique@example.com" | ||||
| 	user.CreatedUnix = creationTimestamp | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user) | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{}) | ||||
| 	assert.NoError(t, err) | ||||
|  | ||||
| 	fetched, err := user_model.GetUserByID(context.Background(), user.ID) | ||||
| @@ -283,7 +283,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) { | ||||
| 	user.Email = "unique@example.com" | ||||
| 	user.CreatedUnix = 0 | ||||
| 	user.UpdatedUnix = 0 | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user) | ||||
| 	err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{}) | ||||
| 	assert.NoError(t, err) | ||||
|  | ||||
| 	timestampEnd := time.Now().Unix() | ||||
|   | ||||
| @@ -37,6 +37,7 @@ var ( | ||||
| 	DisableQueryAuthToken              bool | ||||
| 	CSRFCookieName                     = "_csrf" | ||||
| 	CSRFCookieHTTPOnly                 = true | ||||
| 	RecordUserSignupMetadata           = false | ||||
| ) | ||||
|  | ||||
| // 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) { | ||||
| 	// TODO: default value should be true in future releases | ||||
| 	DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) | ||||
|  | ||||
| 	RecordUserSignupMetadata = sec.Key("RECORD_USER_SIGNUP_METADATA").MustBool(false) | ||||
|  | ||||
| 	// warn if the setting is set to false explicitly | ||||
| 	if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken { | ||||
| 		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.") | ||||
|   | ||||
| @@ -133,7 +133,7 @@ func CreateUser(ctx *context.APIContext) { | ||||
| 		u.UpdatedUnix = u.CreatedUnix | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil { | ||||
| 	if err := user_model.AdminCreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		if user_model.IsErrUserAlreadyExist(err) || | ||||
| 			user_model.IsErrEmailAlreadyUsed(err) || | ||||
| 			db.IsErrNameReserved(err) || | ||||
|   | ||||
| @@ -554,7 +554,7 @@ func SubmitInstall(ctx *context.Context) { | ||||
| 			IsActive:     optional.Some(true), | ||||
| 		} | ||||
|  | ||||
| 		if err = user_model.CreateUser(ctx, u, overwriteDefault); err != nil { | ||||
| 		if err = user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 			if !user_model.IsErrUserAlreadyExist(err) { | ||||
| 				setting.InstallLock = false | ||||
| 				ctx.Data["Err_AdminName"] = true | ||||
|   | ||||
| @@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) { | ||||
| 		u.MustChangePassword = form.MustChangePassword | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil { | ||||
| 	if err := user_model.AdminCreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		switch { | ||||
| 		case user_model.IsErrUserAlreadyExist(err): | ||||
| 			ctx.Data["Err_UserName"] = true | ||||
|   | ||||
| @@ -541,7 +541,11 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any | ||||
| // createUserInContext creates a user and handles errors within a given context. | ||||
| // Optionally a template can be specified. | ||||
| 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) { | ||||
| 	if err := user_model.CreateUser(ctx, u, overwrites); err != nil { | ||||
| 	meta := &user_model.Meta{ | ||||
| 		InitialIP:        ctx.RemoteAddr(), | ||||
| 		InitialUserAgent: ctx.Req.UserAgent(), | ||||
| 	} | ||||
| 	if err := user_model.CreateUser(ctx, u, meta, overwrites); err != nil { | ||||
| 		if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { | ||||
| 			if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { | ||||
| 				var user *user_model.User | ||||
|   | ||||
| @@ -164,7 +164,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { | ||||
| 		IsActive: optional.Some(true), | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.CreateUser(req.Context(), user, &overwriteDefault); err != nil { | ||||
| 	if err := user_model.CreateUser(req.Context(), user, &user_model.Meta{}, &overwriteDefault); err != nil { | ||||
| 		// FIXME: should I create a system notice? | ||||
| 		log.Error("CreateUser: %v", err) | ||||
| 		return nil | ||||
|   | ||||
| @@ -89,7 +89,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u | ||||
| 			IsActive:     optional.Some(true), | ||||
| 		} | ||||
|  | ||||
| 		err := user_model.CreateUser(ctx, user, overwriteDefault) | ||||
| 		err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault) | ||||
| 		if err != nil { | ||||
| 			return user, err | ||||
| 		} | ||||
|   | ||||
| @@ -129,7 +129,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { | ||||
| 				IsActive:     optional.Some(true), | ||||
| 			} | ||||
|  | ||||
| 			err = user_model.CreateUser(ctx, usr, overwriteDefault) | ||||
| 			err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault) | ||||
| 			if err != nil { | ||||
| 				log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) | ||||
| 			} | ||||
|   | ||||
| @@ -36,7 +36,7 @@ func TestSource(t *testing.T) { | ||||
| 		Email:       "external@example.com", | ||||
| 	} | ||||
|  | ||||
| 	err := user_model.CreateUser(context.Background(), user, &user_model.CreateUserOverwriteOptions{}) | ||||
| 	err := user_model.CreateUser(context.Background(), user, &user_model.Meta{}, &user_model.CreateUserOverwriteOptions{}) | ||||
| 	assert.NoError(t, err) | ||||
|  | ||||
| 	e := &user_model.ExternalLoginUser{ | ||||
|   | ||||
| @@ -63,7 +63,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u | ||||
| 		IsActive: optional.Some(true), | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil { | ||||
| 	if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -79,7 +79,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u | ||||
| 		IsActive: optional.Some(true), | ||||
| 	} | ||||
|  | ||||
| 	if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil { | ||||
| 	if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -176,7 +176,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) ( | ||||
| 		KeepEmailPrivate:             optional.Some(true), | ||||
| 		EmailNotificationsPreference: &emailNotificationPreference, | ||||
| 	} | ||||
| 	if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil { | ||||
| 	if err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -92,7 +92,7 @@ func TestCreateUser(t *testing.T) { | ||||
| 		MustChangePassword: false, | ||||
| 	} | ||||
|  | ||||
| 	assert.NoError(t, user_model.CreateUser(db.DefaultContext, user)) | ||||
| 	assert.NoError(t, user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})) | ||||
|  | ||||
| 	assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) | ||||
| } | ||||
| @@ -177,7 +177,7 @@ func TestCreateUser_Issue5882(t *testing.T) { | ||||
| 	for _, v := range tt { | ||||
| 		setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation | ||||
|  | ||||
| 		assert.NoError(t, user_model.CreateUser(db.DefaultContext, v.user)) | ||||
| 		assert.NoError(t, user_model.CreateUser(db.DefaultContext, v.user, &user_model.Meta{})) | ||||
|  | ||||
| 		u, err := user_model.GetUserByEmail(db.DefaultContext, v.user.Email) | ||||
| 		assert.NoError(t, err) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user