mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-15 01:49:08 +02:00
Activity accounting counts a user as active from their last login or from a peer of theirs being seen. Neither timestamp moved when someone reached a service through the reverse proxy, so a person who only ever uses proxy-protected services and never opens the dashboard has no login on record at all and is skipped outright. The two proxy entry points mean different things, so they write different things. GenerateSessionToken is only reached after an ID token was verified, so a completed SSO sign-in records a login on the user. ValidateTunnelPeer authorises by tunnel IP with no IdP involved, so it records that the peer was seen instead; the owner counts through that. Both write on the granted path only, in UTC, and log and drop failures — no authorisation decision reads them back. The peer write is throttled to once an hour against the peer already in hand, so a busy peer does not rewrite its row behind every request. Both store methods update one column and leave the session-ownership fields to the sync stream that owns them. Peers that accounting excludes, embedded proxy peers and browser clients, are skipped rather than written for nothing.
96 lines
3.0 KiB
Go
96 lines
3.0 KiB
Go
package users
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/netbirdio/netbird/management/server/store"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
)
|
|
|
|
type Manager interface {
|
|
GetUser(ctx context.Context, userID string) (*types.User, error)
|
|
GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error)
|
|
// RefreshLastLogin records an interactive login on the user without
|
|
// rewriting the rest of the row, keeping a newer stored timestamp.
|
|
RefreshLastLogin(ctx context.Context, accountID, userID string, loginAt time.Time) error
|
|
}
|
|
|
|
type managerImpl struct {
|
|
store store.Store
|
|
}
|
|
|
|
type managerMock struct {
|
|
}
|
|
|
|
func NewManager(store store.Store) Manager {
|
|
return &managerImpl{
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (m *managerImpl) RefreshLastLogin(ctx context.Context, accountID, userID string, loginAt time.Time) error {
|
|
return m.store.RefreshUserLastLogin(ctx, accountID, userID, loginAt)
|
|
}
|
|
|
|
func (m *managerImpl) GetUser(ctx context.Context, userID string) (*types.User, error) {
|
|
return m.store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
|
|
}
|
|
|
|
// GetUserWithGroups returns the user and the *types.Group records for the user's AutoGroups, in the same order as
|
|
// AutoGroups. Group ids that don't resolve to a stored group are skipped from the returned slice (the parallel id list is
|
|
// derivable from the returned User). Wraps two store calls today; can be optimised to a single JOIN later if needed.
|
|
// Any store error returns (nil, nil, err) so callers never receive a valid user alongside a non-nil error.
|
|
func (m *managerImpl) GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error) {
|
|
user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(user.AutoGroups) == 0 {
|
|
return user, nil, nil
|
|
}
|
|
groupsMap, err := m.store.GetGroupsByIDs(ctx, store.LockingStrengthNone, user.AccountID, user.AutoGroups)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
groups := make([]*types.Group, 0, len(user.AutoGroups))
|
|
for _, id := range user.AutoGroups {
|
|
if g, ok := groupsMap[id]; ok && g != nil {
|
|
groups = append(groups, g)
|
|
}
|
|
}
|
|
return user, groups, nil
|
|
}
|
|
|
|
func NewManagerMock() Manager {
|
|
return &managerMock{}
|
|
}
|
|
|
|
func (m *managerMock) GetUser(ctx context.Context, userID string) (*types.User, error) {
|
|
switch userID {
|
|
case "adminUser":
|
|
return &types.User{Id: userID, Role: types.UserRoleAdmin}, nil
|
|
case "regularUser":
|
|
return &types.User{Id: userID, Role: types.UserRoleUser}, nil
|
|
case "ownerUser":
|
|
return &types.User{Id: userID, Role: types.UserRoleOwner}, nil
|
|
case "billingUser":
|
|
return &types.User{Id: userID, Role: types.UserRoleBillingAdmin}, nil
|
|
default:
|
|
return nil, errors.New("user not found")
|
|
}
|
|
}
|
|
|
|
func (m *managerMock) RefreshLastLogin(_ context.Context, _, _ string, _ time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *managerMock) GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error) {
|
|
user, err := m.GetUser(ctx, userID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return user, nil, nil
|
|
}
|