fix: an email change drops the address proof
- /update clears email_verified_at in the same write when the email changes (case-only changes keep it) - the admin Users form clears it in FormBeforeUpdate; storedEmail helper shared with guardCredentials - README: the proof follows the address, never mass-assigned, no backfill
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/sm-user-plugin/classes"
|
||||
@@ -211,7 +212,8 @@ func (c usersAdminController) guardPrivilegedMember(ctx context.Context, userID
|
||||
// FormBeforeUpdate starts with the takeover guard: a changed email or a
|
||||
// submitted password on a member of a privileged group needs
|
||||
// golem15.users.manage_privileged_groups. Everything else an update does
|
||||
// comes after it.
|
||||
// comes after it, including clearing the proof of the address
|
||||
// (email_verified_at) when the email changes.
|
||||
func (c usersAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
|
||||
user, ok := model.(*models.User)
|
||||
if !ok || user == nil {
|
||||
@@ -223,6 +225,16 @@ func (c usersAdminController) FormBeforeUpdate(ctx context.Context, model any) e
|
||||
if err := c.requireFreeEmail(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
// The proof of the address follows the address (D-27): a changed email
|
||||
// is unproven until its owner proves it; a change of letter case only
|
||||
// keeps it. cabana's Save writes the cleared column.
|
||||
stored, err := c.storedEmail(ctx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.EqualFold(stored, user.Email) {
|
||||
user.EmailVerifiedAt = nil
|
||||
}
|
||||
changed, err := c.storePassword(ctx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -462,26 +474,36 @@ func (usersAdminController) AdminSetPermissionValues(_ context.Context, field st
|
||||
return nil
|
||||
}
|
||||
|
||||
// guardCredentials compares the email the save would write with the stored
|
||||
// one and looks for a submitted password, then asks guardPrivilegedMember
|
||||
// for the fields that would change.
|
||||
func (c usersAdminController) guardCredentials(ctx context.Context, user *models.User) error {
|
||||
// storedEmail reads the stored email of the user being updated, before the
|
||||
// form's values are saved.
|
||||
func (c usersAdminController) storedEmail(ctx context.Context, user *models.User) (string, error) {
|
||||
db := requestDB(ctx, c.db)
|
||||
if db == nil {
|
||||
return errors.New("users admin: gorm handle is missing")
|
||||
return "", errors.New("users admin: gorm handle is missing")
|
||||
}
|
||||
var stored []string
|
||||
err := db.Session(&gorm.Session{NewDB: true}).WithContext(ctx).Unscoped().
|
||||
Model(&models.User{}).Where("id = ?", user.ID).Pluck("email", &stored).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if len(stored) != 1 {
|
||||
return errors.New("users admin: the user being updated was not found")
|
||||
return "", errors.New("users admin: the user being updated was not found")
|
||||
}
|
||||
return stored[0], nil
|
||||
}
|
||||
|
||||
// guardCredentials compares the email the save would write with the stored
|
||||
// one and looks for a submitted password, then asks guardPrivilegedMember
|
||||
// for the fields that would change.
|
||||
func (c usersAdminController) guardCredentials(ctx context.Context, user *models.User) error {
|
||||
stored, err := c.storedEmail(ctx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const message = usersLang + "privileged_member_forbidden"
|
||||
details := map[string]any{}
|
||||
if stored[0] != user.Email {
|
||||
if stored != user.Email {
|
||||
details["email"] = []string{message}
|
||||
}
|
||||
if values, ok := cabana.VirtualFieldsFromContext(ctx); ok {
|
||||
|
||||
Reference in New Issue
Block a user