mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-30 10:39:08 +02:00
Merge remote-tracking branch 'origin/main' into refactor/permissions-manager
This commit is contained in:
+180
-63
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -15,7 +16,9 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/idp/dex"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/affectedpeers"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
@@ -374,8 +377,8 @@ func (am *DefaultAccountManager) CreatePAT(ctx context.Context, accountID string
|
||||
return nil, status.Errorf(status.InvalidArgument, "token name can't be empty")
|
||||
}
|
||||
|
||||
if expiresIn < 1 || expiresIn > 365 {
|
||||
return nil, status.Errorf(status.InvalidArgument, "expiration has to be between 1 and 365")
|
||||
if expiresIn < account.PATMinExpireDays || expiresIn > account.PATMaxExpireDays {
|
||||
return nil, status.Errorf(status.InvalidArgument, "expiration has to be between %d and %d", account.PATMinExpireDays, account.PATMaxExpireDays)
|
||||
}
|
||||
|
||||
initiatorUser, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, initiatorUserID)
|
||||
@@ -526,7 +529,8 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var updateAccountPeers bool
|
||||
var snaps []*affectedpeers.Snapshot
|
||||
var changes []affectedpeers.Change
|
||||
var peersToExpire []*nbpeer.Peer
|
||||
var addUserEvents []func()
|
||||
var usersToSave = make([]*types.User, 0, len(updates))
|
||||
@@ -548,6 +552,9 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
return nil, err
|
||||
}
|
||||
initiatorUser = result
|
||||
if role, ok := nbcontext.RoleFromContext(ctx); ok {
|
||||
initiatorUser.Role = types.UserRole(role)
|
||||
}
|
||||
}
|
||||
|
||||
var globalErr error
|
||||
@@ -557,20 +564,25 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
}
|
||||
|
||||
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
_, updatedUser, userPeersToExpire, userEvents, err := am.processUserUpdate(
|
||||
change, updatedUser, userPeersToExpire, userEvents, err := am.processUserUpdate(
|
||||
ctx, transaction, groupsMap, accountID, initiatorUserID, initiatorUser, update, addIfNotExists, settings,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process update for user %s: %w", update.Id, err)
|
||||
}
|
||||
|
||||
updateAccountPeers = true
|
||||
|
||||
err = transaction.SaveUser(ctx, updatedUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save updated user %s: %w", update.Id, err)
|
||||
}
|
||||
|
||||
snap, err := affectedpeers.Load(ctx, transaction, accountID, change)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
snaps = append(snaps, snap)
|
||||
changes = append(changes, change)
|
||||
usersToSave = append(usersToSave, updatedUser)
|
||||
addUserEvents = append(addUserEvents, userEvents...)
|
||||
peersToExpire = append(peersToExpire, userPeersToExpire...)
|
||||
@@ -607,15 +619,15 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
}
|
||||
|
||||
if len(peersToExpire) > 0 {
|
||||
if err := am.expireAndUpdatePeers(ctx, accountID, peersToExpire); err != nil {
|
||||
if err := am.expireAndUpdatePeers(ctx, accountID, peersToExpire, peerExpirationUserBlocked); err != nil {
|
||||
log.WithContext(ctx).Errorf("failed update expired peers: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
} else if updateAccountPeers {
|
||||
} else if len(usersToSave) > 0 {
|
||||
if err = am.Store.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return nil, fmt.Errorf("failed to increment network serial: %w", err)
|
||||
}
|
||||
am.UpdateAccountPeers(ctx, accountID)
|
||||
go am.dispatchAffected(ctx, accountID, snaps, changes)
|
||||
}
|
||||
|
||||
return updatedUsersInfo, globalErr
|
||||
@@ -687,32 +699,21 @@ func (am *DefaultAccountManager) prepareUserUpdateEvents(ctx context.Context, ac
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transaction store.Store, groupsMap map[string]*types.Group,
|
||||
accountID, initiatorUserId string, initiatorUser, update *types.User, addIfNotExists bool, settings *types.Settings) (bool, *types.User, []*nbpeer.Peer, []func(), error) {
|
||||
accountID, initiatorUserId string, initiatorUser, update *types.User, addIfNotExists bool, settings *types.Settings) (affectedpeers.Change, *types.User, []*nbpeer.Peer, []func(), error) {
|
||||
|
||||
var change affectedpeers.Change
|
||||
|
||||
if update == nil {
|
||||
return false, nil, nil, nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
||||
}
|
||||
|
||||
if initiatorUserId != activity.SystemInitiator {
|
||||
freshInitiator, err := transaction.GetUserByUserID(ctx, store.LockingStrengthUpdate, initiatorUserId)
|
||||
if err != nil {
|
||||
return false, nil, nil, nil, fmt.Errorf("failed to re-read initiator user in transaction: %w", err)
|
||||
}
|
||||
|
||||
// Ensure the initiator still has admin privileges
|
||||
if initiatorUser.HasAdminPower() && !freshInitiator.HasAdminPower() {
|
||||
return false, nil, nil, nil, status.Errorf(status.PermissionDenied, "initiator role was changed during request processing")
|
||||
}
|
||||
initiatorUser = freshInitiator
|
||||
return change, nil, nil, nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
||||
}
|
||||
|
||||
oldUser, isNewUser, err := getUserOrCreateIfNotExists(ctx, transaction, accountID, update, addIfNotExists)
|
||||
if err != nil {
|
||||
return false, nil, nil, nil, err
|
||||
return change, nil, nil, nil, err
|
||||
}
|
||||
|
||||
if err := validateUserUpdate(groupsMap, initiatorUser, oldUser, update); err != nil {
|
||||
return false, nil, nil, nil, err
|
||||
return change, nil, nil, nil, err
|
||||
}
|
||||
|
||||
// only auto groups, revoked status, and integration reference can be updated for now
|
||||
@@ -733,13 +734,13 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
||||
var transferredOwnerRole bool
|
||||
result, err := handleOwnerRoleTransfer(ctx, transaction, initiatorUser, update)
|
||||
if err != nil {
|
||||
return false, nil, nil, nil, err
|
||||
return change, nil, nil, nil, err
|
||||
}
|
||||
transferredOwnerRole = result
|
||||
|
||||
userPeers, err := transaction.GetUserPeers(ctx, store.LockingStrengthNone, updatedUser.AccountID, update.Id)
|
||||
if err != nil {
|
||||
return false, nil, nil, nil, err
|
||||
return change, nil, nil, nil, err
|
||||
}
|
||||
|
||||
var peersToExpire []*nbpeer.Peer
|
||||
@@ -748,6 +749,32 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
||||
peersToExpire = userPeers
|
||||
}
|
||||
|
||||
// A user reaches a peer's network map only through the SSH rules: as part of a
|
||||
// group -> user mapping, and as part of the account's allowed-user set. Creating,
|
||||
// blocking or unblocking a user adds it to or removes it from both, so every group
|
||||
// it maps into changes — including the All group that holds every active user.
|
||||
// Otherwise only the auto-groups it joined or left do.
|
||||
if isNewUser || oldUser.IsBlocked() != updatedUser.IsBlocked() {
|
||||
change.AllowedUsersChanged = true
|
||||
change.UserGroupIDs = slices.Concat(oldUser.AutoGroups, updatedUser.AutoGroups, allGroupIDs(groupsMap))
|
||||
} else {
|
||||
change.UserGroupIDs = slices.Concat(
|
||||
util.Difference(oldUser.AutoGroups, updatedUser.AutoGroups),
|
||||
util.Difference(updatedUser.AutoGroups, oldUser.AutoGroups),
|
||||
)
|
||||
}
|
||||
|
||||
// The user's peers are the changed entity in every scenario the update can
|
||||
// produce — group membership, IPv6 assignment, SSH mappings — so they refresh
|
||||
// together with every peer they can connect to, like on a regular peer update.
|
||||
// An update that changes neither the auto-groups nor the active-user set has no
|
||||
// peer-visible effect and refreshes nobody.
|
||||
if len(change.UserGroupIDs) > 0 || change.AllowedUsersChanged {
|
||||
for _, peer := range userPeers {
|
||||
change.ChangedPeerIDs = append(change.ChangedPeerIDs, peer.ID)
|
||||
}
|
||||
}
|
||||
|
||||
var removedGroups, addedGroups []string
|
||||
if update.AutoGroups != nil && settings.GroupsPropagationEnabled {
|
||||
removedGroups = util.Difference(oldUser.AutoGroups, update.AutoGroups)
|
||||
@@ -755,21 +782,38 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
||||
for _, peer := range userPeers {
|
||||
for _, groupID := range removedGroups {
|
||||
if err := transaction.RemovePeerFromGroup(ctx, peer.ID, groupID); err != nil {
|
||||
return false, nil, nil, nil, fmt.Errorf("failed to remove peer %s from group %s: %w", peer.ID, groupID, err)
|
||||
return change, nil, nil, nil, fmt.Errorf("failed to remove peer %s from group %s: %w", peer.ID, groupID, err)
|
||||
}
|
||||
}
|
||||
for _, groupID := range addedGroups {
|
||||
if err := transaction.AddPeerToGroup(ctx, accountID, peer.ID, groupID); err != nil {
|
||||
return false, nil, nil, nil, fmt.Errorf("failed to add peer %s to group %s: %w", peer.ID, groupID, err)
|
||||
return change, nil, nil, nil, fmt.Errorf("failed to add peer %s to group %s: %w", peer.ID, groupID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allGroupChanges := slices.Concat(removedGroups, addedGroups)
|
||||
change.LinkGroups = allGroupChanges
|
||||
|
||||
if err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, allGroupChanges); err != nil {
|
||||
return change, nil, nil, nil, fmt.Errorf("reconcile IPv6 for group changes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
updateAccountPeers := len(userPeers) > 0
|
||||
userEventsToAdd := am.prepareUserUpdateEvents(ctx, updatedUser.AccountID, initiatorUserId, oldUser, updatedUser, transferredOwnerRole, isNewUser, removedGroups, addedGroups, transaction)
|
||||
|
||||
return updateAccountPeers, updatedUser, peersToExpire, userEventsToAdd, nil
|
||||
return change, updatedUser, peersToExpire, userEventsToAdd, nil
|
||||
}
|
||||
|
||||
// allGroupIDs returns the ID of the account's All group, which every active user maps
|
||||
// into, as a slice so callers can concatenate it.
|
||||
func allGroupIDs(groupsMap map[string]*types.Group) []string {
|
||||
for _, group := range groupsMap {
|
||||
if group.IsGroupAll() {
|
||||
return []string{group.ID}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUserOrCreateIfNotExists retrieves the existing user or creates a new one if it doesn't exist.
|
||||
@@ -839,19 +883,23 @@ func validateUserUpdate(groupsMap map[string]*types.Group, initiatorUser, oldUse
|
||||
return nil
|
||||
}
|
||||
|
||||
if !initiatorUser.HasAdminPower() {
|
||||
return status.Errorf(status.PermissionDenied, "only admins and owners can update users")
|
||||
}
|
||||
|
||||
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
||||
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
||||
}
|
||||
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && update.Role != initiatorUser.Role {
|
||||
return status.Errorf(status.PermissionDenied, "admins can't change their role")
|
||||
}
|
||||
if initiatorUser.Role == types.UserRoleAdmin && oldUser.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||
if initiatorUser.Role != types.UserRoleOwner && oldUser.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||
return status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
|
||||
}
|
||||
if initiatorUser.Role == types.UserRoleAdmin && oldUser.Role == types.UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
|
||||
if oldUser.Role == types.UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
|
||||
return status.Errorf(status.PermissionDenied, "unable to block owner user")
|
||||
}
|
||||
if initiatorUser.Role == types.UserRoleAdmin && update.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||
if initiatorUser.Role != types.UserRoleOwner && update.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||
return status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
|
||||
}
|
||||
if oldUser.IsServiceUser && update.Role == types.UserRoleOwner {
|
||||
@@ -927,12 +975,9 @@ func (am *DefaultAccountManager) GetUsersFromAccount(ctx context.Context, accoun
|
||||
user = result
|
||||
}
|
||||
|
||||
// Permission checks are now handled by the HTTP middleware via WithPermission wrapper
|
||||
// This internal method is called from authenticated/authorized handlers
|
||||
accountUsers := []*types.User{}
|
||||
|
||||
// Determine if user has full access based on their role
|
||||
hasFullAccess := user.HasAdminPower() || user.IsServiceUser
|
||||
hasFullAccess := initiatorUserID == activity.SystemInitiator || user.HasAdminPower() || user.IsServiceUser
|
||||
|
||||
switch {
|
||||
case hasFullAccess:
|
||||
@@ -997,8 +1042,8 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.WithContext(ctx).Debugf("Got %d users from ExternalCache for account %s", len(usersFromIntegration), accountID)
|
||||
log.WithContext(ctx).Debugf("Got %d users from InternalCache for account %s", len(queriedUsers), accountID)
|
||||
log.WithContext(ctx).Tracef("Got %d users from ExternalCache for account %s", len(usersFromIntegration), accountID)
|
||||
log.WithContext(ctx).Tracef("Got %d users from InternalCache for account %s", len(queriedUsers), accountID)
|
||||
queriedUsers = append(queriedUsers, usersFromIntegration...)
|
||||
}
|
||||
|
||||
@@ -1056,7 +1101,7 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
|
||||
}
|
||||
|
||||
// expireAndUpdatePeers expires all peers of the given user and updates them in the account
|
||||
func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accountID string, peers []*nbpeer.Peer) error {
|
||||
func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accountID string, peers []*nbpeer.Peer, reason peerExpirationReason) error {
|
||||
log.WithContext(ctx).Debugf("Expiring %d peers for account %s", len(peers), accountID)
|
||||
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||
if err != nil {
|
||||
@@ -1065,28 +1110,37 @@ func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accou
|
||||
dnsDomain := am.networkMapController.GetDNSDomain(settings)
|
||||
|
||||
var peerIDs []string
|
||||
for _, peer := range peers {
|
||||
defer func() {
|
||||
if len(peerIDs) == 0 {
|
||||
return
|
||||
}
|
||||
// this will trigger peer disconnect from the management service
|
||||
log.Debugf("Expiring %d peers for account %s", len(peerIDs), accountID)
|
||||
am.networkMapController.DisconnectPeers(ctx, accountID, peerIDs)
|
||||
}()
|
||||
for _, candidate := range peers {
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, nbcontext.PeerIDKey, peer.Key)
|
||||
peerCtx := context.WithValue(ctx, nbcontext.PeerIDKey, candidate.Key)
|
||||
|
||||
if peer.UserID == "" {
|
||||
if candidate.UserID == "" {
|
||||
// we do not want to expire peers that are added via setup key
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.Status.LoginExpired {
|
||||
peer, err := am.expirePeerIfStillDue(peerCtx, accountID, candidate.ID, settings, reason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
peerIDs = append(peerIDs, peer.ID)
|
||||
peer.MarkLoginExpired(true)
|
||||
|
||||
if err := am.Store.SavePeerStatus(ctx, accountID, peer.ID, *peer.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
meta := peer.EventMeta(dnsDomain)
|
||||
meta["reason"] = string(reason)
|
||||
am.StoreEvent(
|
||||
ctx,
|
||||
peerCtx,
|
||||
peer.UserID, peer.ID, accountID,
|
||||
activity.PeerLoginExpired, peer.EventMeta(dnsDomain),
|
||||
activity.PeerLoginExpired, meta,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1096,19 +1150,58 @@ func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accou
|
||||
}
|
||||
}
|
||||
|
||||
err = am.networkMapController.OnPeersUpdated(ctx, accountID, peerIDs)
|
||||
affectedPeerIDs := am.resolveAffectedPeersForPeerChanges(ctx, am.Store, accountID, peerIDs)
|
||||
err = am.networkMapController.OnPeersUpdated(ctx, accountID, peerIDs, affectedPeerIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("notify network map controller of peer update: %w", err)
|
||||
}
|
||||
|
||||
if len(peerIDs) != 0 {
|
||||
// this will trigger peer disconnect from the management service
|
||||
log.Debugf("Expiring %d peers for account %s", len(peerIDs), accountID)
|
||||
am.networkMapController.DisconnectPeers(ctx, accountID, peerIDs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// expirePeerIfStillDue flags the peer as login-expired and returns its fresh copy, or nil
|
||||
// when it no longer qualifies. The candidate list is read without a lock, so a login that
|
||||
// landed in between would otherwise be overwritten with a stale expired status.
|
||||
func (am *DefaultAccountManager) expirePeerIfStillDue(ctx context.Context, accountID, peerID string, settings *types.Settings, reason peerExpirationReason) (*nbpeer.Peer, error) {
|
||||
var expired *nbpeer.Peer
|
||||
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
peer, err := transaction.GetPeerByID(ctx, store.LockingStrengthUpdate, accountID, peerID)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if peer.Status.LoginExpired || !peerExpirationDue(peer, settings, reason) {
|
||||
return nil
|
||||
}
|
||||
peer.MarkLoginExpired(true)
|
||||
if err := transaction.SavePeerStatus(ctx, accountID, peer.ID, *peer.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
expired = peer
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return expired, nil
|
||||
}
|
||||
|
||||
// peerExpirationDue re-evaluates a time-based expiry against the peer's current state.
|
||||
// Administrative reasons expire the peer unconditionally.
|
||||
func peerExpirationDue(peer *nbpeer.Peer, settings *types.Settings, reason peerExpirationReason) bool {
|
||||
switch reason {
|
||||
case peerExpirationSessionExpired:
|
||||
expired, _ := peer.LoginExpired(settings.PeerLoginExpiration)
|
||||
return settings.PeerLoginExpirationEnabled && expired
|
||||
case peerExpirationInactivity:
|
||||
expired, _ := peer.SessionExpired(settings.PeerInactivityExpiration)
|
||||
return settings.PeerInactivityExpirationEnabled && expired
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) deleteUserFromIDP(ctx context.Context, targetUserID, accountID string) error {
|
||||
if am.userDeleteFromIDPEnabled {
|
||||
log.WithContext(ctx).Debugf("user %s deleted from IdP", targetUserID)
|
||||
@@ -1204,6 +1297,8 @@ func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountI
|
||||
var userPeers []*nbpeer.Peer
|
||||
var targetUser *types.User
|
||||
var settings *types.Settings
|
||||
var snap *affectedpeers.Snapshot
|
||||
var change affectedpeers.Change
|
||||
var err error
|
||||
|
||||
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
@@ -1212,6 +1307,10 @@ func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountI
|
||||
return fmt.Errorf("failed to get user to delete: %w", err)
|
||||
}
|
||||
|
||||
if targetUser.Role == types.UserRoleOwner && targetUser.Id != initiatorUserID {
|
||||
return status.NewOwnerDeletePermissionError()
|
||||
}
|
||||
|
||||
settings, err = transaction.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get account settings: %w", err)
|
||||
@@ -1224,6 +1323,18 @@ func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountI
|
||||
|
||||
if len(userPeers) > 0 {
|
||||
updateAccountPeers = true
|
||||
|
||||
var peerIDs []string
|
||||
for _, peer := range userPeers {
|
||||
peerIDs = append(peerIDs, peer.ID)
|
||||
}
|
||||
// Load before delete so the snapshot still has the peers' group
|
||||
// memberships; the resolver derives them from the peer IDs during the walk.
|
||||
change = affectedpeers.Change{ChangedPeerIDs: peerIDs}
|
||||
if snap, err = affectedpeers.Load(ctx, transaction, accountID, change); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addPeerRemovedEvents, err = deletePeers(ctx, am, transaction, accountID, targetUserInfo.ID, userPeers, settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete user peers: %w", err)
|
||||
@@ -1247,7 +1358,8 @@ func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountI
|
||||
log.WithContext(ctx).Errorf("failed to delete peer %s from integrated validator: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
if err := am.networkMapController.OnPeersDeleted(ctx, accountID, peerIDs); err != nil {
|
||||
affectedPeerIDs := snap.Expand(ctx, accountID, change)
|
||||
if err := am.networkMapController.OnPeersDeleted(ctx, accountID, peerIDs, affectedPeerIDs); err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to delete peers %s from network map: %v", peerIDs, err)
|
||||
}
|
||||
|
||||
@@ -1711,12 +1823,17 @@ func (am *DefaultAccountManager) DeleteUserInvite(ctx context.Context, accountID
|
||||
|
||||
const minPasswordLength = 8
|
||||
|
||||
// validatePassword checks password strength requirements:
|
||||
// validatePassword checks password strength requirements.
|
||||
func validatePassword(password string) error {
|
||||
return ValidatePassword(password)
|
||||
}
|
||||
|
||||
// ValidatePassword checks password strength requirements:
|
||||
// - Minimum 8 characters
|
||||
// - At least 1 digit
|
||||
// - At least 1 uppercase letter
|
||||
// - At least 1 special character
|
||||
func validatePassword(password string) error {
|
||||
func ValidatePassword(password string) error {
|
||||
if len(password) < minPasswordLength {
|
||||
return errors.New("password must be at least 8 characters long")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user