mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-24 19:12:24 +00:00
[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 (cherry picked from commit7d2d997011) (cherry picked from commit435a54f140) (cherry picked from commit8ec7b3e448) [GITEA] notifies admins on new user registration (squash) performance bottleneck Refs: https://codeberg.org/forgejo/forgejo/issues/1479 (cherry picked from commit97ac9147ff) (cherry picked from commit19f295c16b) (cherry picked from commit3367dcb2cf) [GITEA] notifies admins on new user registration (squash) cosmetic changes Co-authored-by: delvh <dev.lh@web.de> (cherry picked from commit9f1670e040) (cherry picked from commitde5bb2a224) (cherry picked from commit8f8e52f31a) (cherry picked from commite0d5130312) (cherry picked from commitf1288d6d9b) (cherry picked from commitf664f41658) (cherry picked from commite44e6c7e47) (cherry picked from commitc0d958cc4c) (cherry picked from commita88baa5e48) [GITEA] notifies admins on new user registration (squash) ctx.Locale (cherry picked from commit2f6329f693)
This commit is contained in:
parent
df257aee22
commit
9acd6ff137
14 changed files with 234 additions and 3 deletions
|
|
@ -1462,6 +1462,8 @@ LEVEL = Info
|
||||||
;;
|
;;
|
||||||
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
||||||
|
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
|
||||||
|
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,7 @@ And the following unique queues:
|
||||||
|
|
||||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
|
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
|
||||||
|
- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act.
|
||||||
|
|
||||||
## Security (`security`)
|
## Security (`security`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) {
|
||||||
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
|
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllAdmins returns a slice of all adminusers found in DB.
|
||||||
|
func GetAllAdmins(ctx context.Context) ([]*User, error) {
|
||||||
|
users := make([]*User, 0)
|
||||||
|
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users)
|
||||||
|
}
|
||||||
|
|
||||||
// IsLocal returns true if user login type is LoginPlain.
|
// IsLocal returns true if user login type is LoginPlain.
|
||||||
func (u *User) IsLocal() bool {
|
func (u *User) IsLocal() bool {
|
||||||
return u.LoginType <= auth.Plain
|
return u.LoginType <= auth.Plain
|
||||||
|
|
|
||||||
|
|
@ -544,3 +544,13 @@ func Test_ValidateUser(t *testing.T) {
|
||||||
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
|
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAllAdmins(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
admins, err := user_model.GetAllAdmins(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, admins, 1)
|
||||||
|
assert.Equal(t, int64(1), admins[0].ID)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ package setting
|
||||||
|
|
||||||
// Admin settings
|
// Admin settings
|
||||||
var Admin struct {
|
var Admin struct {
|
||||||
DisableRegularOrgCreation bool
|
DisableRegularOrgCreation bool
|
||||||
DefaultEmailNotification string
|
DefaultEmailNotification string
|
||||||
|
SendNotificationEmailOnNewUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
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.title = %s, please verify your email address
|
||||||
activate_email.text = Please click the following link to verify your email address within <b>%s</b>:
|
activate_email.text = Please click the following link to verify your email address within <b>%s</b>:
|
||||||
|
|
||||||
|
admin.new_user.subject = New user %s just signed up
|
||||||
|
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 = Welcome to Gitea
|
||||||
register_notify.title = %[1]s, welcome to %[2]s
|
register_notify.title = %[1]s, welcome to %[2]s
|
||||||
register_notify.text_1 = this is your registration confirmation email for %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/externalaccount"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/markbates/goth"
|
"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
|
// update external user information
|
||||||
if gothUser != nil {
|
if gothUser != nil {
|
||||||
if err := externalaccount.UpdateExternalUser(u, *gothUser); err != 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["Email"] = u.Email
|
||||||
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
||||||
ctx.HTML(http.StatusOK, TplActivate)
|
ctx.HTML(http.StatusOK, TplActivate)
|
||||||
|
|
||||||
if setting.CacheService.Enabled {
|
if setting.CacheService.Enabled {
|
||||||
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
||||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||||
|
|
|
||||||
80
services/mailer/mail_admin_new_user.go
Normal file
80
services/mailer/mail_admin_new_user.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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 = "notify/admin_new_user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sa = SendAsync
|
||||||
|
|
||||||
|
// MailNewUser sends notification emails on new user registrations to all admins
|
||||||
|
func MailNewUser(ctx context.Context, u *user_model.User) {
|
||||||
|
if !setting.Admin.SendNotificationEmailOnNewUser {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.MailService == nil {
|
||||||
|
// No mail service configured
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
recipients, err := user_model.GetAllAdmins(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("user_model.GetAllAdmins: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
langMap := make(map[string][]string)
|
||||||
|
for _, r := range recipients {
|
||||||
|
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@example.com"
|
||||||
|
|
||||||
|
newUser := new(user_model.User)
|
||||||
|
newUser.Name = "new_user"
|
||||||
|
newUser.Language = "en_US"
|
||||||
|
newUser.IsAdmin = false
|
||||||
|
newUser.Email = "new_user@example.com"
|
||||||
|
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@example.com",
|
||||||
|
Protocol: "dummy",
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.MailService = &mailService
|
||||||
|
setting.Domain = "localhost"
|
||||||
|
setting.AppSubURL = "http://localhost"
|
||||||
|
|
||||||
|
// test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER enabled
|
||||||
|
setting.Admin.SendNotificationEmailOnNewUser = true
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
NewContext(ctx)
|
||||||
|
|
||||||
|
users := getTestUsers()
|
||||||
|
oldSendAsync := sa
|
||||||
|
defer func() {
|
||||||
|
sa = oldSendAsync
|
||||||
|
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 SEND_NOTIFICATION_EMAIL_ON_NEW_USER disabled; emails shouldn't be sent
|
||||||
|
setting.Admin.SendNotificationEmailOnNewUser = false
|
||||||
|
sa = func(msgs ...*Message) {
|
||||||
|
assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since SEND_NOTIFICATION_EMAIL_ON_NEW_USER is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
MailNewUser(ctx, users[1])
|
||||||
|
}
|
||||||
|
|
@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
|
||||||
log.Error("SendRepoTransferNotifyMail: %v", err)
|
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)
|
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)
|
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)
|
NewRelease(ctx context.Context, rel *repo_model.Release)
|
||||||
UpdateRelease(ctx context.Context, doer *user_model.User, 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)
|
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 about a newly signed up user 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
|
// PackageCreate notifies creation of a package to notifiers
|
||||||
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||||
for _, notifier := range notifiers {
|
for _, notifier := range notifiers {
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,9 @@ 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) {
|
func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
|
||||||
|
}
|
||||||
|
|
||||||
// PackageCreate places a place holder function
|
// PackageCreate places a place holder function
|
||||||
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
templates/mail/notify/admin_new_user.tmpl
Normal file
22
templates/mail/notify/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>{{ctx.Locale.Tr "mail.admin.new_user.user_info"}}</h3>
|
||||||
|
<li>{{ctx.Locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}</li>
|
||||||
|
<li>{{ctx.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