mirror of https://github.com/go-gitea/gitea.git
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:
parent
a323a82ec4
commit
f183783baa
|
@ -158,7 +158,7 @@ func runCreateUser(c *cli.Context) error {
|
||||||
IsRestricted: restricted,
|
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)
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -507,6 +507,9 @@ INTERNAL_TOKEN =
|
||||||
;; stemming from cached/logged plain-text API tokens.
|
;; stemming from cached/logged plain-text API tokens.
|
||||||
;; In future releases, this will become the default behavior
|
;; In future releases, this will become the default behavior
|
||||||
;DISABLE_QUERY_AUTH_TOKEN = false
|
;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"
|
UserActivityPubPrivPem = "activitypub.priv_pem"
|
||||||
// UserActivityPubPubPem is user's public key
|
// UserActivityPubPubPem is user's public key
|
||||||
UserActivityPubPubPem = "activitypub.pub_pem"
|
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"`
|
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() {
|
func init() {
|
||||||
db.RegisterModel(new(User))
|
db.RegisterModel(new(User))
|
||||||
}
|
}
|
||||||
|
@ -615,17 +623,17 @@ type CreateUserOverwriteOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates record of a new user.
|
// CreateUser creates record of a new user.
|
||||||
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
func CreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||||
return createUser(ctx, u, false, overwriteDefault...)
|
return createUser(ctx, u, meta, false, overwriteDefault...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminCreateUser is used by admins to manually create users
|
// AdminCreateUser is used by admins to manually create users
|
||||||
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
func AdminCreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||||
return createUser(ctx, u, true, overwriteDefault...)
|
return createUser(ctx, u, meta, true, overwriteDefault...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUser creates record of a new user.
|
// 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 {
|
if err = IsUsableUsername(u.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -745,6 +753,22 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
|
||||||
return err
|
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
|
// insert email address
|
||||||
if err := db.Insert(ctx, &EmailAddress{
|
if err := db.Insert(ctx, &EmailAddress{
|
||||||
UID: u.ID,
|
UID: u.ID,
|
||||||
|
|
|
@ -227,7 +227,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
|
||||||
MustChangePassword: false,
|
MustChangePassword: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := user_model.CreateUser(db.DefaultContext, user)
|
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
|
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
||||||
user.Name = "testuser"
|
user.Name = "testuser"
|
||||||
user.LowerName = strings.ToLower(user.Name)
|
user.LowerName = strings.ToLower(user.Name)
|
||||||
user.ID = 0
|
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.Error(t, err)
|
||||||
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
|
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) {
|
||||||
user.ID = 0
|
user.ID = 0
|
||||||
user.Email = "unique@example.com"
|
user.Email = "unique@example.com"
|
||||||
user.CreatedUnix = creationTimestamp
|
user.CreatedUnix = creationTimestamp
|
||||||
err := user_model.CreateUser(db.DefaultContext, user)
|
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
fetched, err := user_model.GetUserByID(context.Background(), user.ID)
|
fetched, err := user_model.GetUserByID(context.Background(), user.ID)
|
||||||
|
@ -283,7 +283,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) {
|
||||||
user.Email = "unique@example.com"
|
user.Email = "unique@example.com"
|
||||||
user.CreatedUnix = 0
|
user.CreatedUnix = 0
|
||||||
user.UpdatedUnix = 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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
timestampEnd := time.Now().Unix()
|
timestampEnd := time.Now().Unix()
|
||||||
|
|
|
@ -37,6 +37,7 @@ var (
|
||||||
DisableQueryAuthToken bool
|
DisableQueryAuthToken bool
|
||||||
CSRFCookieName = "_csrf"
|
CSRFCookieName = "_csrf"
|
||||||
CSRFCookieHTTPOnly = true
|
CSRFCookieHTTPOnly = true
|
||||||
|
RecordUserSignupMetadata = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
|
// 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
|
// TODO: default value should be true in future releases
|
||||||
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
|
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
|
// warn if the setting is set to false explicitly
|
||||||
if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken {
|
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.")
|
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
|
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) ||
|
if user_model.IsErrUserAlreadyExist(err) ||
|
||||||
user_model.IsErrEmailAlreadyUsed(err) ||
|
user_model.IsErrEmailAlreadyUsed(err) ||
|
||||||
db.IsErrNameReserved(err) ||
|
db.IsErrNameReserved(err) ||
|
||||||
|
|
|
@ -554,7 +554,7 @@ func SubmitInstall(ctx *context.Context) {
|
||||||
IsActive: optional.Some(true),
|
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) {
|
if !user_model.IsErrUserAlreadyExist(err) {
|
||||||
setting.InstallLock = false
|
setting.InstallLock = false
|
||||||
ctx.Data["Err_AdminName"] = true
|
ctx.Data["Err_AdminName"] = true
|
||||||
|
|
|
@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) {
|
||||||
u.MustChangePassword = form.MustChangePassword
|
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 {
|
switch {
|
||||||
case user_model.IsErrUserAlreadyExist(err):
|
case user_model.IsErrUserAlreadyExist(err):
|
||||||
ctx.Data["Err_UserName"] = true
|
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.
|
// createUserInContext creates a user and handles errors within a given context.
|
||||||
// Optionally a template can be specified.
|
// 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) {
|
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 allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
|
||||||
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
|
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
|
||||||
var user *user_model.User
|
var user *user_model.User
|
||||||
|
|
|
@ -164,7 +164,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User {
|
||||||
IsActive: optional.Some(true),
|
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?
|
// FIXME: should I create a system notice?
|
||||||
log.Error("CreateUser: %v", err)
|
log.Error("CreateUser: %v", err)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := user_model.CreateUser(ctx, user, overwriteDefault)
|
err := user_model.CreateUser(ctx, user, &user_model.Meta{}, overwriteDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = user_model.CreateUser(ctx, usr, overwriteDefault)
|
err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err)
|
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",
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
e := &user_model.ExternalLoginUser{
|
e := &user_model.ExternalLoginUser{
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||||
IsActive: optional.Some(true),
|
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
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||||
IsActive: optional.Some(true),
|
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
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (
|
||||||
KeepEmailPrivate: optional.Some(true),
|
KeepEmailPrivate: optional.Some(true),
|
||||||
EmailNotificationsPreference: &emailNotificationPreference,
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ func TestCreateUser(t *testing.T) {
|
||||||
MustChangePassword: false,
|
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))
|
assert.NoError(t, DeleteUser(db.DefaultContext, user, false))
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ func TestCreateUser_Issue5882(t *testing.T) {
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation
|
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)
|
u, err := user_model.GetUserByEmail(db.DefaultContext, v.user.Email)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
Loading…
Reference in New Issue