[GITEA] notifies admins on new user registration
Sends email with information on the new user (time of creation and time of last sign-in) and a link to manage the new user from the admin panel closes: https://codeberg.org/forgejo/forgejo/issues/480 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1371 Co-authored-by: Aravinth Manivannan <realaravinth@batsense.net> Co-committed-by: Aravinth Manivannan <realaravinth@batsense.net> (cherry picked from commitc721aa828b) (cherry picked from commit6487efcb9d) Conflicts: modules/notification/base/notifier.go modules/notification/base/null.go modules/notification/notification.go https://codeberg.org/forgejo/forgejo/pulls/1422 (cherry picked from commit7ea66ee1c5) Conflicts: services/notify/notifier.go services/notify/notify.go services/notify/null.go https://codeberg.org/forgejo/forgejo/pulls/1469
This commit is contained in:
		
							parent
							
								
									589e7d346f
								
							
						
					
					
						commit
						7d2d997011
					
				
					 11 changed files with 218 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -1455,6 +1455,8 @@ LEVEL = Info
 | 
			
		|||
;;
 | 
			
		||||
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
 | 
			
		||||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
 | 
			
		||||
;; Send email notifications to all instance admins on new user sign-ups. Options: enabled, true, false
 | 
			
		||||
;NOTIFY_NEW_SIGN_UPS = false
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ package setting
 | 
			
		|||
var Admin struct {
 | 
			
		||||
	DisableRegularOrgCreation bool
 | 
			
		||||
	DefaultEmailNotification  string
 | 
			
		||||
	NotifyNewSignUps          bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadAdminFrom(rootCfg ConfigProvider) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -439,6 +439,10 @@ activate_email = Verify your email address
 | 
			
		|||
activate_email.title = %s, please verify your email address
 | 
			
		||||
activate_email.text = Please click the following link to verify your email address within <b>%s</b>:
 | 
			
		||||
 | 
			
		||||
admin.new_user.subject = New user %s
 | 
			
		||||
admin.new_user.user_info = User Information
 | 
			
		||||
admin.new_user.text = Please <a href="%s">click here</a> to manage the user from the admin panel.
 | 
			
		||||
 | 
			
		||||
register_notify = Welcome to Gitea
 | 
			
		||||
register_notify.title = %[1]s, welcome to %[2]s
 | 
			
		||||
register_notify.text_1 = this is your registration confirmation email for %s!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/services/externalaccount"
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
	"code.gitea.io/gitea/services/mailer"
 | 
			
		||||
	notify_service "code.gitea.io/gitea/services/notify"
 | 
			
		||||
 | 
			
		||||
	"github.com/markbates/goth"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -586,6 +587,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notify_service.NewUserSignUp(ctx, u)
 | 
			
		||||
	// update external user information
 | 
			
		||||
	if gothUser != nil {
 | 
			
		||||
		if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -609,7 +611,6 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
 | 
			
		|||
		ctx.Data["Email"] = u.Email
 | 
			
		||||
		ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
 | 
			
		||||
		ctx.HTML(http.StatusOK, TplActivate)
 | 
			
		||||
 | 
			
		||||
		if setting.CacheService.Enabled {
 | 
			
		||||
			if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
 | 
			
		||||
				log.Error("Set cache(MailResendLimit) fail: %v", err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										82
									
								
								services/mailer/mail_admin_new_user.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								services/mailer/mail_admin_new_user.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
package mailer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/templates"
 | 
			
		||||
	"code.gitea.io/gitea/modules/translation"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	tplNewUserMail base.TplName = "admin_new_user"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var sa = SendAsyncs
 | 
			
		||||
 | 
			
		||||
// MailNewUser sends notification emails on new user registrations to all admins
 | 
			
		||||
func MailNewUser(ctx context.Context, u *user_model.User) {
 | 
			
		||||
	if !setting.Admin.NotifyNewSignUps {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.MailService == nil {
 | 
			
		||||
		// No mail service configured
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recipients, err := user_model.GetAllUsers(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("user_model.GetAllUsers: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	langMap := make(map[string][]string)
 | 
			
		||||
	for _, r := range recipients {
 | 
			
		||||
		if r.IsAdmin {
 | 
			
		||||
			langMap[r.Language] = append(langMap[r.Language], r.Email)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for lang, tos := range langMap {
 | 
			
		||||
		mailNewUser(ctx, u, lang, tos)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) {
 | 
			
		||||
	locale := translation.NewLocale(lang)
 | 
			
		||||
 | 
			
		||||
	subject := locale.Tr("mail.admin.new_user.subject", u.Name)
 | 
			
		||||
	manageUserURL := setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)
 | 
			
		||||
	body := locale.Tr("mail.admin.new_user.text", manageUserURL)
 | 
			
		||||
	mailMeta := map[string]any{
 | 
			
		||||
		"NewUser":  u,
 | 
			
		||||
		"Subject":  subject,
 | 
			
		||||
		"Body":     body,
 | 
			
		||||
		"Language": locale.Language(),
 | 
			
		||||
		"locale":   locale,
 | 
			
		||||
		"Str2html": templates.Str2html,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var mailBody bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewUserMail), mailMeta); err != nil {
 | 
			
		||||
		log.Error("ExecuteTemplate [%s]: %v", string(tplNewUserMail)+"/body", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msgs := make([]*Message, 0, len(tos))
 | 
			
		||||
	for _, to := range tos {
 | 
			
		||||
		msg := NewMessage(to, subject, mailBody.String())
 | 
			
		||||
		msg.Info = subject
 | 
			
		||||
		msgs = append(msgs, msg)
 | 
			
		||||
	}
 | 
			
		||||
	sa(msgs)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								services/mailer/mail_admin_new_user_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								services/mailer/mail_admin_new_user_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,88 @@
 | 
			
		|||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package mailer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getTestUsers() []*user_model.User {
 | 
			
		||||
	admin := new(user_model.User)
 | 
			
		||||
	admin.Name = "admin"
 | 
			
		||||
	admin.IsAdmin = true
 | 
			
		||||
	admin.Language = "en_US"
 | 
			
		||||
	admin.Email = "admin@forgejo.org"
 | 
			
		||||
 | 
			
		||||
	newUser := new(user_model.User)
 | 
			
		||||
	newUser.Name = "new_user"
 | 
			
		||||
	newUser.Language = "en_US"
 | 
			
		||||
	newUser.IsAdmin = false
 | 
			
		||||
	newUser.Email = "new_user@forgejo.org"
 | 
			
		||||
	newUser.LastLoginUnix = 1693648327
 | 
			
		||||
	newUser.CreatedUnix = 1693648027
 | 
			
		||||
 | 
			
		||||
	user_model.CreateUser(db.DefaultContext, admin)
 | 
			
		||||
	user_model.CreateUser(db.DefaultContext, newUser)
 | 
			
		||||
 | 
			
		||||
	users := make([]*user_model.User, 0)
 | 
			
		||||
	users = append(users, admin)
 | 
			
		||||
	users = append(users, newUser)
 | 
			
		||||
 | 
			
		||||
	return users
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanUpUsers(ctx context.Context, users []*user_model.User) {
 | 
			
		||||
	for _, u := range users {
 | 
			
		||||
		db.DeleteByID(ctx, u.ID, new(user_model.User))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAdminNotificationMail_test(t *testing.T) {
 | 
			
		||||
	mailService := setting.Mailer{
 | 
			
		||||
		From:     "test@forgejo.org",
 | 
			
		||||
		Protocol: "dummy",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setting.MailService = &mailService
 | 
			
		||||
	setting.Domain = "localhost"
 | 
			
		||||
	setting.AppSubURL = "http://localhost"
 | 
			
		||||
 | 
			
		||||
	// test with NOTIFY_NEW_SIGNUPS enabled
 | 
			
		||||
	setting.Admin.NotifyNewSignUps = true
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	NewContext(ctx)
 | 
			
		||||
 | 
			
		||||
	users := getTestUsers()
 | 
			
		||||
	oldSendAsyncs := sa
 | 
			
		||||
	defer func() {
 | 
			
		||||
		sa = oldSendAsyncs
 | 
			
		||||
		cleanUpUsers(ctx, users)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	sa = func(msgs []*Message) {
 | 
			
		||||
		assert.Equal(t, len(msgs), 1, "Test provides only one admin user, so only one email must be sent")
 | 
			
		||||
		assert.Equal(t, msgs[0].To, users[0].Email, "checks if the recipient is the admin of the instance")
 | 
			
		||||
		manageUserURL := "/admin/users/" + strconv.FormatInt(users[1].ID, 10)
 | 
			
		||||
		assert.True(t, strings.ContainsAny(msgs[0].Body, manageUserURL), "checks if the message contains the link to manage the newly created user from the admin panel")
 | 
			
		||||
	}
 | 
			
		||||
	MailNewUser(ctx, users[1])
 | 
			
		||||
 | 
			
		||||
	// test with NOTIFY_NEW_SIGNUPS disabled; emails shouldn't be sent
 | 
			
		||||
	setting.Admin.NotifyNewSignUps = false
 | 
			
		||||
	sa = func(msgs []*Message) {
 | 
			
		||||
		assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since NOTIFY_NEW_SIGNUPS is disabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MailNewUser(ctx, users[1])
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
 | 
			
		|||
		log.Error("SendRepoTransferNotifyMail: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mailNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
 | 
			
		||||
	MailNewUser(ctx, newUser)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,8 @@ type Notifier interface {
 | 
			
		|||
	EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string)
 | 
			
		||||
	DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string)
 | 
			
		||||
 | 
			
		||||
	NewUserSignUp(ctx context.Context, newUser *user_model.User)
 | 
			
		||||
 | 
			
		||||
	NewRelease(ctx context.Context, rel *repo_model.Release)
 | 
			
		||||
	UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)
 | 
			
		||||
	DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -347,6 +347,13 @@ func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, r
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserSignUp notifies deletion of a package to notifiers
 | 
			
		||||
func NewUserSignUp(ctx context.Context, newUser *user_model.User) {
 | 
			
		||||
	for _, notifier := range notifiers {
 | 
			
		||||
		notifier.NewUserSignUp(ctx, newUser)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageCreate notifies creation of a package to notifiers
 | 
			
		||||
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
 | 
			
		||||
	for _, notifier := range notifiers {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -197,6 +197,10 @@ func (*NullNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, r
 | 
			
		|||
func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotifyNewUserSignUp notifies deletion of a package to notifiers
 | 
			
		||||
func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageCreate places a place holder function
 | 
			
		||||
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								templates/mail/admin_new_user.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								templates/mail/admin_new_user.tmpl
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 | 
			
		||||
	<title>{{.Subject}}</title>
 | 
			
		||||
 | 
			
		||||
	<style>
 | 
			
		||||
		blockquote { padding-left: 1em; margin: 1em 0; border-left: 1px solid grey; color: #777}
 | 
			
		||||
		.footer { font-size:small; color:#666;}
 | 
			
		||||
	</style>
 | 
			
		||||
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
	<ul>
 | 
			
		||||
		<h3>{{.locale.Tr "mail.admin.new_user.user_info"}}</h3>
 | 
			
		||||
		<li>{{.locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}</li>
 | 
			
		||||
		<li>{{.locale.Tr "admin.users.last_login"}}: {{DateTime "full" .NewUser.CreatedUnix}}</li>
 | 
			
		||||
	</ul>
 | 
			
		||||
	<p> {{.Body | Str2html}} </p>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue