chore: send email on role update (#8489)

* chore: send email on role update

* fix: minor changes

* fix: update template

* fix: minor changes

* fix: return updated user
This commit is contained in:
Nityananda Gohain 2025-07-10 20:47:04 +05:30 committed by GitHub
parent 497315579f
commit 552d44d208
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 41 deletions

View File

@ -289,43 +289,6 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
return return
} }
existingUser, err := h.module.GetUserByID(ctx, claims.OrgID, id)
if err != nil {
render.Error(w, err)
return
}
// only displayName, role can be updated
if user.DisplayName == "" {
user.DisplayName = existingUser.DisplayName
}
if user.Role == "" {
user.Role = existingUser.Role
}
if user.Role != existingUser.Role && claims.Role != types.RoleAdmin {
render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles"))
return
}
// Make sure that the request is not demoting the last admin user.
// also an admin user can only change role of their own or other user
if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() {
adminUsers, err := h.module.GetUsersByRoleInOrg(ctx, claims.OrgID, types.RoleAdmin)
if err != nil {
render.Error(w, err)
return
}
if len(adminUsers) == 1 {
render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin"))
return
}
}
user.UpdatedAt = time.Now()
updatedUser, err := h.module.UpdateUser(ctx, claims.OrgID, id, &user, claims.UserID) updatedUser, err := h.module.UpdateUser(ctx, claims.OrgID, id, &user, claims.UserID)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, err)

View File

@ -176,18 +176,69 @@ func (m *Module) ListUsers(ctx context.Context, orgID string) ([]*types.Gettable
} }
func (m *Module) UpdateUser(ctx context.Context, orgID string, id string, user *types.User, updatedBy string) (*types.User, error) { func (m *Module) UpdateUser(ctx context.Context, orgID string, id string, user *types.User, updatedBy string) (*types.User, error) {
user, err := m.store.UpdateUser(ctx, orgID, id, user)
existingUser, err := m.GetUserByID(ctx, orgID, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
traits := types.NewTraitsFromUser(user) requestor, err := m.GetUserByID(ctx, orgID, updatedBy)
if err != nil {
return nil, err
}
// only displayName, role can be updated
if user.DisplayName == "" {
user.DisplayName = existingUser.DisplayName
}
if user.Role == "" {
user.Role = existingUser.Role
}
if user.Role != existingUser.Role && requestor.Role != types.RoleAdmin.String() {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
}
// Make sure that the request is not demoting the last admin user.
// also an admin user can only change role of their own or other user
if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() {
adminUsers, err := m.GetUsersByRoleInOrg(ctx, orgID, types.RoleAdmin)
if err != nil {
return nil, err
}
if len(adminUsers) == 1 {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin")
}
}
user.UpdatedAt = time.Now()
updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user)
if err != nil {
return nil, err
}
traits := types.NewTraitsFromUser(updatedUser)
m.analytics.IdentifyUser(ctx, user.OrgID, user.ID.String(), traits) m.analytics.IdentifyUser(ctx, user.OrgID, user.ID.String(), traits)
traits["updated_by"] = updatedBy traits["updated_by"] = updatedBy
m.analytics.TrackUser(ctx, user.OrgID, user.ID.String(), "User Updated", traits) m.analytics.TrackUser(ctx, user.OrgID, user.ID.String(), "User Updated", traits)
return user, nil // if the role is updated then send an email
if existingUser.Role != updatedUser.Role {
if err := m.emailing.SendHTML(ctx, existingUser.Email, "Your Role is updated in SigNoz", emailtypes.TemplateNameUpdateRole, map[string]any{
"CustomerName": existingUser.DisplayName,
"UpdatedByEmail": requestor.Email,
"OldRole": existingUser.Role,
"NewRole": updatedUser.Role,
}); err != nil {
m.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err)
}
}
return updatedUser, nil
} }
func (m *Module) DeleteUser(ctx context.Context, orgID string, id string, deletedBy string) error { func (m *Module) DeleteUser(ctx context.Context, orgID string, id string, deletedBy string) error {

View File

@ -12,11 +12,12 @@ import (
var ( var (
// Templates is a list of all the templates that are supported by the emailing service. // Templates is a list of all the templates that are supported by the emailing service.
// This list should be updated whenever a new template is added. // This list should be updated whenever a new template is added.
Templates = []TemplateName{TemplateNameInvitationEmail} Templates = []TemplateName{TemplateNameInvitationEmail, TemplateNameUpdateRole}
) )
var ( var (
TemplateNameInvitationEmail = TemplateName{valuer.NewString("invitation_email")} TemplateNameInvitationEmail = TemplateName{valuer.NewString("invitation_email")}
TemplateNameUpdateRole = TemplateName{valuer.NewString("update_role")}
) )
type TemplateName struct{ valuer.String } type TemplateName struct{ valuer.String }
@ -25,6 +26,8 @@ func NewTemplateName(name string) (TemplateName, error) {
switch name { switch name {
case TemplateNameInvitationEmail.StringValue(): case TemplateNameInvitationEmail.StringValue():
return TemplateNameInvitationEmail, nil return TemplateNameInvitationEmail, nil
case TemplateNameUpdateRole.StringValue():
return TemplateNameUpdateRole, nil
default: default:
return TemplateName{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid template name: %s", name) return TemplateName{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid template name: %s", name)
} }

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<body>
<p>Hi {{.CustomerName}},</p>
<p>We wanted to inform you that your role in the <strong>SigNoz</strong> project has been updated by <strong>{{.UpdatedByEmail}}</strong>.</p>
<p>
<strong>Previous Role:</strong> {{.OldRole}}<br>
<strong>New Role:</strong> {{.NewRole}}
</p>
<p>
Please note that you will need to <strong>log out and log back in</strong> for the changes to take effect.
</p>
<p>
If you were not expecting this change or have any questions, please reach out to your project administrator or contact us at <a href="mailto:support@signoz.io">support@signoz.io</a>.
</p>
<p>Thanks,<br/>The SigNoz Team</p>
</body>
</html>