mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-12 04:39:05 +02:00
More WIP
This commit is contained in:
@@ -36,7 +36,7 @@ type LdapService struct {
|
||||
userService *UserService
|
||||
groupService *UserGroupService
|
||||
fileStorage storage.FileStorage
|
||||
clientFactory func() (ldapClient, error)
|
||||
clientFactory func(ctx context.Context) (ldapClient, error)
|
||||
}
|
||||
|
||||
type savePicture struct {
|
||||
@@ -84,15 +84,18 @@ func NewLdapService(db *gorm.DB, httpClient *http.Client, appConfigService *appc
|
||||
return service
|
||||
}
|
||||
|
||||
func (s *LdapService) createClient() (ldapClient, error) {
|
||||
dbConfig := s.appConfigService.GetDbConfig()
|
||||
func (s *LdapService) createClient(ctx context.Context) (ldapClient, error) {
|
||||
dbConfig, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
if !dbConfig.LdapEnabled.IsTrue() {
|
||||
return nil, fmt.Errorf("LDAP is not enabled")
|
||||
}
|
||||
|
||||
// Setup LDAP connection
|
||||
client, err := ldap.DialURL(dbConfig.LdapUrl.Value, ldap.DialWithTLSConfig(&tls.Config{
|
||||
client, err := ldap.DialURL(dbConfig.LdapUrl.String(), ldap.DialWithTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: dbConfig.LdapSkipCertVerify.IsTrue(), //nolint:gosec
|
||||
}))
|
||||
if err != nil {
|
||||
@@ -100,7 +103,7 @@ func (s *LdapService) createClient() (ldapClient, error) {
|
||||
}
|
||||
|
||||
// Bind as service account
|
||||
err = client.Bind(dbConfig.LdapBindDn.Value, dbConfig.LdapBindPassword.Value)
|
||||
err = client.Bind(dbConfig.LdapBindDn.String(), dbConfig.LdapBindPassword.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind to LDAP: %w", err)
|
||||
}
|
||||
@@ -196,13 +199,13 @@ func (s *LdapService) fetchDesiredState(ctx context.Context, client ldapClient)
|
||||
|
||||
func (s *LdapService) applyAdminGroupMembership(desiredUsers []ldapDesiredUser, desiredGroups []ldapDesiredGroup) {
|
||||
dbConfig := s.appConfigService.GetDbConfig()
|
||||
if dbConfig.LdapAdminGroupName.Value == "" {
|
||||
if dbConfig.LdapAdminGroupName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
adminUsernames := make(map[string]struct{})
|
||||
for _, group := range desiredGroups {
|
||||
if group.input.Name != dbConfig.LdapAdminGroupName.Value {
|
||||
if group.input.Name != dbConfig.LdapAdminGroupName {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -259,7 +262,7 @@ func (s *LdapService) fetchGroupsFromLDAP(ctx context.Context, client ldapClient
|
||||
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
||||
memberUsernames := make([]string, 0, len(groupMembers))
|
||||
for _, member := range groupMembers {
|
||||
username := s.resolveGroupMemberUsername(ctx, client, member, usernamesByDN)
|
||||
username := s.resolveGroupMemberUsername(ctx, client, member, usernamesByDN, dbConfig.LdapAttributeUserUsername.Value)
|
||||
if username == "" {
|
||||
continue
|
||||
}
|
||||
@@ -291,27 +294,30 @@ func (s *LdapService) fetchGroupsFromLDAP(ctx context.Context, client ldapClient
|
||||
}
|
||||
|
||||
func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient) (desiredUsers []ldapDesiredUser, ldapUserIDs map[string]struct{}, usernamesByDN map[string]string, err error) {
|
||||
dbConfig := s.appConfigService.GetDbConfig()
|
||||
dbConfig, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
// Query LDAP for all users we want to manage
|
||||
searchAttrs := []string{
|
||||
"sn",
|
||||
"cn",
|
||||
dbConfig.LdapAttributeUserUniqueIdentifier.Value,
|
||||
dbConfig.LdapAttributeUserUsername.Value,
|
||||
dbConfig.LdapAttributeUserEmail.Value,
|
||||
dbConfig.LdapAttributeUserFirstName.Value,
|
||||
dbConfig.LdapAttributeUserLastName.Value,
|
||||
dbConfig.LdapAttributeUserProfilePicture.Value,
|
||||
dbConfig.LdapAttributeUserDisplayName.Value,
|
||||
dbConfig.LdapAttributeUserUniqueIdentifier.String(),
|
||||
dbConfig.LdapAttributeUserUsername.String(),
|
||||
dbConfig.LdapAttributeUserEmail.String(),
|
||||
dbConfig.LdapAttributeUserFirstName.String(),
|
||||
dbConfig.LdapAttributeUserLastName.String(),
|
||||
dbConfig.LdapAttributeUserProfilePicture.String(),
|
||||
dbConfig.LdapAttributeUserDisplayName.String(),
|
||||
}
|
||||
|
||||
// Filters must start and finish with ()!
|
||||
searchReq := ldap.NewSearchRequest(
|
||||
dbConfig.LdapBase.Value,
|
||||
dbConfig.LdapBase.String(),
|
||||
ldap.ScopeWholeSubtree,
|
||||
0, 0, 0, false,
|
||||
dbConfig.LdapUserSearchFilter.Value,
|
||||
dbConfig.LdapUserSearchFilter.String(),
|
||||
searchAttrs,
|
||||
[]ldap.Control{},
|
||||
)
|
||||
@@ -327,28 +333,28 @@ func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient)
|
||||
desiredUsers = make([]ldapDesiredUser, 0, len(result.Entries))
|
||||
|
||||
for _, value := range result.Entries {
|
||||
username := norm.NFC.String(value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value))
|
||||
username := norm.NFC.String(value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.String()))
|
||||
if normalizedDN := normalizeLDAPDN(value.DN); normalizedDN != "" && username != "" {
|
||||
usernamesByDN[normalizedDN] = username
|
||||
}
|
||||
|
||||
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
||||
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.String()))
|
||||
|
||||
// Skip users without a valid LDAP ID
|
||||
if ldapID == "" {
|
||||
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
||||
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.String()))
|
||||
continue
|
||||
}
|
||||
|
||||
ldapUserIDs[ldapID] = struct{}{}
|
||||
|
||||
newUser := dto.UserCreateDto{
|
||||
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value),
|
||||
Email: utils.PtrOrNil(value.GetAttributeValue(dbConfig.LdapAttributeUserEmail.Value)),
|
||||
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.String()),
|
||||
Email: utils.PtrOrNil(value.GetAttributeValue(dbConfig.LdapAttributeUserEmail.String())),
|
||||
EmailVerified: true,
|
||||
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.Value),
|
||||
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.Value),
|
||||
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.Value),
|
||||
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.String()),
|
||||
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.String()),
|
||||
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.String()),
|
||||
// Admin status is computed after groups are loaded so it can use the
|
||||
// configured group member attribute instead of a hard-coded memberOf.
|
||||
IsAdmin: false,
|
||||
@@ -370,16 +376,14 @@ func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient)
|
||||
desiredUsers = append(desiredUsers, ldapDesiredUser{
|
||||
ldapID: ldapID,
|
||||
input: newUser,
|
||||
picture: value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.Value),
|
||||
picture: value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.String()),
|
||||
})
|
||||
}
|
||||
|
||||
return desiredUsers, ldapUserIDs, usernamesByDN, nil
|
||||
}
|
||||
|
||||
func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client ldapClient, member string, usernamesByDN map[string]string) string {
|
||||
dbConfig := s.appConfigService.GetDbConfig()
|
||||
|
||||
func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client ldapClient, member string, usernamesByDN map[string]string, usernameAttr string) string {
|
||||
// First try the DN cache we built while loading users
|
||||
username, exists := usernamesByDN[normalizeLDAPDN(member)]
|
||||
if exists && username != "" {
|
||||
@@ -387,14 +391,15 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
||||
}
|
||||
|
||||
// Then try to extract the username directly from the DN
|
||||
username = getDNProperty(dbConfig.LdapAttributeUserUsername.Value, member)
|
||||
username = getDNProperty(usernameAttr, member)
|
||||
if username != "" {
|
||||
return norm.NFC.String(username)
|
||||
}
|
||||
|
||||
// posixGroup (and similar) stores bare usernames in memberUid, not DNs. Treat any value
|
||||
// that is not a valid DN as the username directly — see https://github.com/pocket-id/pocket-id/issues/1408
|
||||
if _, err := ldap.ParseDN(member); err != nil {
|
||||
_, err := ldap.ParseDN(member)
|
||||
if err != nil {
|
||||
return norm.NFC.String(member)
|
||||
}
|
||||
|
||||
@@ -404,7 +409,7 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
||||
ldap.ScopeBaseObject,
|
||||
0, 0, 0, false,
|
||||
"(objectClass=*)",
|
||||
[]string{dbConfig.LdapAttributeUserUsername.Value},
|
||||
[]string{usernameAttr},
|
||||
[]ldap.Control{},
|
||||
)
|
||||
|
||||
@@ -414,7 +419,7 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
||||
return ""
|
||||
}
|
||||
|
||||
username = userResult.Entries[0].GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value)
|
||||
username = userResult.Entries[0].GetAttributeValue(usernameAttr)
|
||||
if username == "" {
|
||||
slog.WarnContext(ctx, "Could not extract username from group member DN", slog.String("member", member))
|
||||
return ""
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
||||
@@ -64,13 +65,18 @@ func (s *UserGroupService) getInternal(ctx context.Context, id string, tx *gorm.
|
||||
}
|
||||
|
||||
func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
tx := s.db.Begin()
|
||||
defer func() {
|
||||
tx.Rollback()
|
||||
}()
|
||||
|
||||
var group model.UserGroup
|
||||
err := tx.
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Where("id = ?", id).
|
||||
First(&group).
|
||||
@@ -80,7 +86,7 @@ func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
||||
}
|
||||
|
||||
// Disallow deleting the group if it is an LDAP group and LDAP is enabled
|
||||
if group.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
||||
if group.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||
return &common.LdapUserGroupUpdateError{}
|
||||
}
|
||||
|
||||
@@ -123,10 +129,9 @@ func (s *UserGroupService) createInternal(ctx context.Context, input dto.UserGro
|
||||
Preload("Users").
|
||||
Create(&group).
|
||||
Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return model.UserGroup{}, &common.AlreadyInUseError{Property: "name"}
|
||||
}
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return model.UserGroup{}, &common.AlreadyInUseError{Property: "name"}
|
||||
} else if err != nil {
|
||||
return model.UserGroup{}, err
|
||||
}
|
||||
|
||||
@@ -162,8 +167,13 @@ func (s *UserGroupService) updateInternal(ctx context.Context, id string, input
|
||||
return model.UserGroup{}, err
|
||||
}
|
||||
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return model.UserGroup{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
// Disallow updating the group if it is an LDAP group and LDAP is enabled
|
||||
if !isLdapSync && group.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
||||
if !isLdapSync && group.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||
return model.UserGroup{}, &common.LdapUserGroupUpdateError{}
|
||||
}
|
||||
|
||||
@@ -217,7 +227,7 @@ func (s *UserGroupService) updateUsersInternal(ctx context.Context, id string, u
|
||||
// Fetch the users based on the userIds
|
||||
var users []model.User
|
||||
if len(userIds) > 0 {
|
||||
err := tx.
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Where("id IN (?)", userIds).
|
||||
Find(&users).
|
||||
|
||||
@@ -13,17 +13,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||
profilepicture "github.com/pocket-id/pocket-id/backend/internal/utils/image"
|
||||
)
|
||||
|
||||
@@ -205,9 +205,13 @@ func (s *UserService) DeleteUser(ctx context.Context, userID string, allowLdapDe
|
||||
}
|
||||
|
||||
func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userID string, allowLdapDelete bool) error {
|
||||
var user model.User
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
err := tx.
|
||||
var user model.User
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Where("id = ?", userID).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
@@ -218,7 +222,7 @@ func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userI
|
||||
}
|
||||
|
||||
// Disallow deleting the user if it is an LDAP user, LDAP is enabled, and the user is not disabled
|
||||
if !allowLdapDelete && !user.Disabled && user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
||||
if !allowLdapDelete && !user.Disabled && user.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||
return &common.LdapUserUpdateError{}
|
||||
}
|
||||
|
||||
@@ -254,7 +258,12 @@ func (s *UserService) CreateUser(ctx context.Context, input dto.UserCreateDto) (
|
||||
}
|
||||
|
||||
func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
||||
if s.appConfigService.GetDbConfig().RequireUserEmail.IsTrue() && input.Email == nil {
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return model.User{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
if cfg.RequireUserEmail.IsTrue() && input.Email == nil {
|
||||
return model.User{}, &common.UserEmailNotSetError{}
|
||||
}
|
||||
|
||||
@@ -286,7 +295,7 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
||||
user.LdapID = &input.LdapID
|
||||
}
|
||||
|
||||
err := tx.WithContext(ctx).Create(&user).Error
|
||||
err = tx.WithContext(ctx).Create(&user).Error
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
// Do not follow this path if we're using LDAP, as we don't want to roll-back the transaction here
|
||||
if !isLdapSync {
|
||||
@@ -305,12 +314,14 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
||||
// Apply default groups and claims for new non-LDAP users
|
||||
if !isLdapSync {
|
||||
if len(input.UserGroupIds) == 0 {
|
||||
if err := s.applyDefaultGroups(ctx, &user, tx); err != nil {
|
||||
err = s.applyDefaultGroups(ctx, &user, tx)
|
||||
if err != nil {
|
||||
return model.User{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.applyDefaultCustomClaims(ctx, &user, tx); err != nil {
|
||||
err = s.applyDefaultCustomClaims(ctx, &user, tx)
|
||||
if err != nil {
|
||||
return model.User{}, err
|
||||
}
|
||||
}
|
||||
@@ -323,12 +334,15 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
||||
}
|
||||
|
||||
func (s *UserService) applyDefaultGroups(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
||||
config := s.appConfigService.GetDbConfig()
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
var groupIDs []string
|
||||
v := config.SignupDefaultUserGroupIDs.Value
|
||||
v := cfg.SignupDefaultUserGroupIDs
|
||||
if v != "" && v != "[]" {
|
||||
err := json.Unmarshal([]byte(v), &groupIDs)
|
||||
err = json.Unmarshal([]byte(v), &groupIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid SignupDefaultUserGroupIDs JSON: %w", err)
|
||||
}
|
||||
@@ -355,12 +369,15 @@ func (s *UserService) applyDefaultGroups(ctx context.Context, user *model.User,
|
||||
}
|
||||
|
||||
func (s *UserService) applyDefaultCustomClaims(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
||||
config := s.appConfigService.GetDbConfig()
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
var claims []dto.CustomClaimCreateDto
|
||||
v := config.SignupDefaultCustomClaims.Value
|
||||
v := cfg.SignupDefaultCustomClaims
|
||||
if v != "" && v != "[]" {
|
||||
err := json.Unmarshal([]byte(v), &claims)
|
||||
err = json.Unmarshal([]byte(v), &claims)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid SignupDefaultCustomClaims JSON: %w", err)
|
||||
}
|
||||
@@ -395,12 +412,17 @@ func (s *UserService) UpdateUser(ctx context.Context, userID string, updatedUser
|
||||
}
|
||||
|
||||
func (s *UserService) updateUserInternal(ctx context.Context, userID string, updatedUser dto.UserCreateDto, updateOwnUser bool, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
||||
if s.appConfigService.GetDbConfig().RequireUserEmail.IsTrue() && updatedUser.Email == nil {
|
||||
cfg, err := appconfig.FromCtx(ctx)
|
||||
if err != nil {
|
||||
return model.User{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||
}
|
||||
|
||||
if cfg.RequireUserEmail.IsTrue() && updatedUser.Email == nil {
|
||||
return model.User{}, &common.UserEmailNotSetError{}
|
||||
}
|
||||
|
||||
var user model.User
|
||||
err := tx.
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Where("id = ?", userID).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
@@ -411,8 +433,8 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
||||
}
|
||||
|
||||
// Check if this is an LDAP user and LDAP is enabled
|
||||
isLdapUser := user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue()
|
||||
allowOwnAccountEdit := s.appConfigService.GetDbConfig().AllowOwnAccountEdit.IsTrue()
|
||||
isLdapUser := user.LdapID != nil && cfg.LdapEnabled.IsTrue()
|
||||
allowOwnAccountEdit := cfg.AllowOwnAccountEdit.IsTrue()
|
||||
|
||||
if !isLdapSync && (isLdapUser || (!allowOwnAccountEdit && updateOwnUser)) {
|
||||
// Restricted update: Only locale can be changed when:
|
||||
@@ -430,7 +452,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
||||
|
||||
if (user.Email == nil && updatedUser.Email != nil) || (user.Email != nil && updatedUser.Email != nil && *user.Email != *updatedUser.Email) {
|
||||
// Email has changed, reset email verification status
|
||||
user.EmailVerified = s.appConfigService.GetDbConfig().EmailsVerified.IsTrue()
|
||||
user.EmailVerified = cfg.EmailsVerified.IsTrue()
|
||||
}
|
||||
|
||||
user.Email = updatedUser.Email
|
||||
|
||||
Reference in New Issue
Block a user