0
0
Fork 0
mirror of https://github.com/go-gitea/gitea synced 2025-04-12 14:24:15 +02:00

Save initial signup information for users to aid in spam prevention ()

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:
techknowlogick 2024-09-09 17:05:16 -04:00 committed by GitHub
parent a323a82ec4
commit f183783baa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 61 additions and 23 deletions

View file

@ -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)
}

View file

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -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"
)

View file

@ -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,

View file

@ -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()

View file

@ -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.")

View file

@ -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) ||

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)
}

View file

@ -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{

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)