diff --git a/management/server/account.go b/management/server/account.go index 89d3dd659..cf1767a31 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -56,6 +56,7 @@ type AccountManager interface { GetPeerByKey(peerKey string) (*Peer, error) GetPeers(accountID, userID string) ([]*Peer, error) MarkPeerConnected(peerKey string, connected bool) error + MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error DeletePeer(accountID, peerID, userID string) (*Peer, error) GetPeerByIP(accountId string, peerIP string) (*Peer, error) UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index cfd5413c2..7d4d60207 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -137,7 +137,11 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi return status.Error(codes.Internal, "internal server error") } expired, left := peer.LoginExpired(account.Settings) - if peer.UserID != "" && expired { + if peer.UserID != "" && (expired || peer.Status.LoginExpired) { + err = s.accountManager.MarkPeerLoginExpired(peerKey.String(), true) + if err != nil { + log.Warnf("failed marking peer login expired %s %v", peerKey, err) + } return status.Errorf(codes.PermissionDenied, "peer login has expired %v ago. Please log in once more", left) } @@ -377,9 +381,13 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p return nil, status.Error(codes.Internal, "internal server error") } expired, left := peer.LoginExpired(account.Settings) - if peer.UserID != "" && expired { + if peer.UserID != "" && (expired || peer.Status.LoginExpired) { // it might be that peer expired but user has logged in already, check token then if loginReq.GetJwtToken() == "" { + err = s.accountManager.MarkPeerLoginExpired(peerKey.String(), true) + if err != nil { + log.Warnf("failed marking peer login expired %s %v", peerKey, err) + } return nil, status.Errorf(codes.PermissionDenied, "peer login has expired %v ago. Please log in once more", left) } diff --git a/management/server/http/peers.go b/management/server/http/peers.go index a4048b875..64752a680 100644 --- a/management/server/http/peers.go +++ b/management/server/http/peers.go @@ -152,8 +152,6 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string fqdn = peer.DNSLabel } - expired, _ := peer.LoginExpired(account.Settings) - return &api.Peer{ Id: peer.ID, Name: peer.Name, @@ -170,6 +168,6 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string DnsLabel: fqdn, LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, - LoginExpired: expired, + LoginExpired: peer.Status.LoginExpired, } } diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 00b295869..30cf9ec30 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -23,6 +23,7 @@ type MockAccountManager struct { GetPeerByKeyFunc func(peerKey string) (*server.Peer, error) GetPeersFunc func(accountID, userID string) ([]*server.Peer, error) MarkPeerConnectedFunc func(peerKey string, connected bool) error + MarkPeerLoginExpiredFunc func(peerPubKey string, loginExpired bool) error DeletePeerFunc func(accountID, peerKey, userID string) (*server.Peer, error) GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error) GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error) @@ -161,6 +162,14 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented") } +// MarkPeerLoginExpired mock implementation of MarkPeerLoginExpired from server.AccountManager interface +func (am *MockAccountManager) MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error { + if am.MarkPeerLoginExpiredFunc != nil { + return am.MarkPeerLoginExpiredFunc(peerPubKey, loginExpired) + } + return status.Errorf(codes.Unimplemented, "method MarkPeerLoginExpired is not implemented") +} + // GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) { if am.GetPeerByIPFunc != nil { diff --git a/management/server/peer.go b/management/server/peer.go index 92d17e885..28204d1a1 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -32,6 +32,8 @@ type PeerStatus struct { LastSeen time.Time // Connected indicates whether peer is connected to the management service or not Connected bool + // LoginExpired + LoginExpired bool } // Peer represents a machine connected to the network. @@ -115,8 +117,9 @@ func (p *Peer) EventMeta(dnsDomain string) map[string]any { // Copy PeerStatus func (p *PeerStatus) Copy() *PeerStatus { return &PeerStatus{ - LastSeen: p.LastSeen, - Connected: p.Connected, + LastSeen: p.LastSeen, + Connected: p.Connected, + LoginExpired: p.LoginExpired, } } @@ -173,6 +176,40 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er return peers, nil } +// MarkPeerLoginExpired when peer login has expired +func (am *DefaultAccountManager) MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error { + account, err := am.Store.GetAccountByPeerPubKey(peerPubKey) + if err != nil { + return err + } + + unlock := am.Store.AcquireAccountLock(account.Id) + defer unlock() + + // ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account) + account, err = am.Store.GetAccount(account.Id) + if err != nil { + return err + } + + peer, err := account.FindPeerByPubKey(peerPubKey) + if err != nil { + return err + } + + newStatus := peer.Status.Copy() + newStatus.LastSeen = time.Now() + newStatus.LoginExpired = loginExpired + peer.Status = newStatus + account.UpdatePeer(peer) + + err = am.Store.SavePeerStatus(account.Id, peer.ID, *newStatus) + if err != nil { + return err + } + return nil +} + // MarkPeerConnected marks peer as connected (true) or disconnected (false) func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected bool) error { @@ -198,6 +235,10 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected newStatus := peer.Status.Copy() newStatus.LastSeen = time.Now() newStatus.Connected = connected + // whenever peer got connected that means that it logged in successfully + if newStatus.Connected { + newStatus.LoginExpired = false + } peer.Status = newStatus account.UpdatePeer(peer) @@ -545,6 +586,10 @@ func (am *DefaultAccountManager) UpdatePeerLastLogin(peerID string) error { } peer.LastLogin = time.Now() + newStatus := peer.Status.Copy() + newStatus.LoginExpired = false + peer.Status = newStatus + account.UpdatePeer(peer) err = am.Store.SaveAccount(account)