Skip to content

Commit

Permalink
Add support for Device Management
Browse files Browse the repository at this point in the history
- Display assigned device name in the Account screen after login
- Show ‘Visit Device Management’ action when presenting force log out prompt

#347
  • Loading branch information
stenya committed Jan 25, 2024
1 parent eff50e4 commit b4a22d8
Show file tree
Hide file tree
Showing 20 changed files with 115 additions and 137 deletions.
7 changes: 7 additions & 0 deletions cli/commands/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ func doLogin(accountID string, force bool) error {

if apiStatus == types.CodeSessionsLimitReached {
PrintTips([]TipType{TipForceLogin})

fmt.Println("Visit Device Management to manage your devices: 'https://www.ivpn.net/account/device-management'")
fmt.Println("")
}

if err != nil {
Expand Down Expand Up @@ -244,6 +247,10 @@ func checkStatus() error {

fmt.Fprintln(w, fmt.Sprintf("Account ID:\t%v", helloResp.Session.AccountID))

if len(helloResp.Session.DeviceName) > 0 {
fmt.Fprintln(w, fmt.Sprintf("Device name:\t%v", helloResp.Session.DeviceName))
}

if acc.IsFreeTrial {
fmt.Fprintln(w, fmt.Sprintf("Plan:\tFree Trial"))
} else {
Expand Down
6 changes: 3 additions & 3 deletions cli/protocol/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,13 @@ func (c *Client) SessionDelete(needToDisableFirewall, resetAppSettingsToDefaults
}

// SessionStatus get session status
func (c *Client) SessionStatus() (ret types.AccountStatusResp, err error) {
func (c *Client) SessionStatus() (ret types.SessionStatusResp, err error) {
if err := c.ensureConnected(); err != nil {
return ret, err
}

req := types.AccountStatus{}
var resp types.AccountStatusResp
req := types.SessionStatus{}
var resp types.SessionStatusResp

if err := c.sendRecv(&req, &resp); err != nil {
return ret, err
Expand Down
4 changes: 2 additions & 2 deletions daemon/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func (a *API) SessionNew(accountID string, wgPublicKey string, kemKeys types.Kem

// SessionStatus - get session status
func (a *API) SessionStatus(session string) (
*types.ServiceStatusAPIResp,
*types.SessionStatusResponse,
*types.APIErrorResponse,
error) {

Expand All @@ -368,7 +368,7 @@ func (a *API) SessionStatus(session string) (
if err := json.Unmarshal(data, &resp); err != nil {
return nil, nil, fmt.Errorf("failed to deserialize API response: %w", err)
}
return &resp.ServiceStatus, &apiErr, nil
return &resp, &apiErr, nil
}

return nil, &apiErr, types.CreateAPIError(apiErr.Status, apiErr.Message)
Expand Down
2 changes: 2 additions & 0 deletions daemon/api/types/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type SessionNewResponse struct {
CaptchaImage string `json:"captcha_image"`

ServiceStatus ServiceStatusAPIResp `json:"service_status"`
DeviceName string `json:"device_name,omitempty"`

WireGuard struct {
Status int `json:"status"`
Expand All @@ -92,6 +93,7 @@ type SessionsWireGuardResponse struct {
type SessionStatusResponse struct {
APIErrorResponse
ServiceStatus ServiceStatusAPIResp `json:"service_status"`
DeviceName string `json:"device_name,omitempty"`
}

// GeoLookupResponse geolocation info
Expand Down
13 changes: 7 additions & 6 deletions daemon/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ type Service interface {
apiCode int,
apiErrorMsg string,
sessionToken string,
accountInfo preferences.AccountStatus,
sessionData preferences.SessionMutableData,
err error)

WireGuardGenerateKeys(updateIfNecessary bool) error
Expand Down Expand Up @@ -1008,20 +1008,21 @@ func (p *Protocol) processRequest(conn net.Conn, message string) {
// notify all clients about changed session status
p.notifyClients(p.createHelloResponse())

case "AccountStatus":
var resp types.AccountStatusResp
apiCode, apiErrMsg, sessionToken, accountInfo, err := p._service.RequestSessionStatus()
case "SessionStatus":
var resp types.SessionStatusResp
apiCode, apiErrMsg, sessionToken, sessionData, err := p._service.RequestSessionStatus()
if err != nil && apiCode == 0 {
// if apiCode == 0 - it is not API error. Sending error response
p.sendErrorResponse(conn, reqCmd, err)
break
}
// Sending session info
resp = types.AccountStatusResp{
resp = types.SessionStatusResp{
APIStatus: apiCode,
APIErrorMessage: apiErrMsg,
SessionToken: sessionToken,
Account: accountInfo}
Account: sessionData.Account,
DeviceName: sessionData.DeviceName}

// send response
p.sendResponse(conn, &resp, reqCmd.Idx)
Expand Down
10 changes: 6 additions & 4 deletions daemon/protocol/protocol_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ func (p *Protocol) OnServiceSessionChanged() {
p.notifyClients(helloResp)
}

// OnAccountStatus - handler of account status info. Notifying clients.
func (p *Protocol) OnAccountStatus(sessionToken string, accountInfo preferences.AccountStatus) {
// OnSessionStatus - handler of session/account status info. Notifying clients.
func (p *Protocol) OnSessionStatus(sessionToken string, sessionData preferences.SessionMutableData) {
if len(sessionToken) == 0 {
return
}

p.notifyClients(&types.AccountStatusResp{
p.notifyClients(&types.SessionStatusResp{
SessionToken: sessionToken,
Account: accountInfo})
Account: sessionData.Account,
DeviceName: sessionData.DeviceName,
})
}

// OnKillSwitchStateChanged - Firewall change handler
Expand Down
3 changes: 1 addition & 2 deletions daemon/protocol/types/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,7 @@ type SessionDelete struct {
IsCanDeleteSessionLocally bool
}

// AccountStatus get account status
type AccountStatus struct {
type SessionStatus struct {
RequestBase
}

Expand Down
6 changes: 4 additions & 2 deletions daemon/protocol/types/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ type HelloResp struct {
type SessionResp struct {
AccountID string
Session string
DeviceName string
WgPublicKey string
WgLocalIP string
WgKeyGenerated int64 // Unix time
Expand All @@ -179,6 +180,7 @@ func CreateSessionResp(s preferences.SessionStatus) SessionResp {
return SessionResp{
AccountID: s.AccountID,
Session: s.Session,
DeviceName: s.DeviceName,
WgPublicKey: s.WGPublicKey,
WgLocalIP: s.WGLocalIP,
WgKeyGenerated: s.WGKeyGenerated.Unix(),
Expand All @@ -196,13 +198,13 @@ type SessionNewResp struct {
RawResponse string
}

// AccountStatusResp - information about account status (or error info)
type AccountStatusResp struct {
type SessionStatusResp struct {
CommandBase
APIStatus int
APIErrorMessage string
SessionToken string
Account preferences.AccountStatus
DeviceName string
}

// KillSwitchStatusResp returns kill-switch status
Expand Down
2 changes: 1 addition & 1 deletion daemon/service/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type IWgKeysManager interface {
// IServiceEventsReceiver is the receiver for service events (normally, it is protocol object)
type IServiceEventsReceiver interface {
OnServiceSessionChanged()
OnAccountStatus(sessionToken string, account preferences.AccountStatus)
OnSessionStatus(sessionToken string, sessionData preferences.SessionMutableData)
OnKillSwitchStateChanged()
OnWiFiChanged(wifiNotifier.WifiInfo)
OnPingStatus(retMap map[string]int)
Expand Down
17 changes: 13 additions & 4 deletions daemon/service/preferences/preferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ type Preferences struct {
WiFiControl WiFiParams
}

type SessionMutableData struct {
Account AccountStatus
DeviceName string
}

func Create() *Preferences {
// init default values
return &Preferences{
Expand All @@ -139,6 +144,7 @@ func (p *Preferences) IsInverseSplitTunneling() bool {
func (p *Preferences) SetSession(accountInfo AccountStatus,
accountID string,
session string,
deviceName string,
vpnUser string,
vpnPass string,
wgPublicKey string,
Expand All @@ -152,15 +158,16 @@ func (p *Preferences) SetSession(accountInfo AccountStatus,
p.Account = accountInfo
}

p.setSession(accountID, session, vpnUser, vpnPass, wgPublicKey, wgPrivateKey, wgLocalIP, wgPreSharedKey)
p.setSession(accountID, session, deviceName, vpnUser, vpnPass, wgPublicKey, wgPrivateKey, wgLocalIP, wgPreSharedKey)
p.SavePreferences()
}

func (p *Preferences) UpdateAccountInfo(acc AccountStatus) {
func (p *Preferences) UpdateSessionData(sData SessionMutableData) {
if len(p.Session.AccountID) == 0 || len(p.Session.Session) == 0 {
acc = AccountStatus{}
sData = SessionMutableData{}
}
p.Account = acc
p.Account = sData.Account
p.Session.DeviceName = sData.DeviceName
p.SavePreferences()
}

Expand Down Expand Up @@ -293,6 +300,7 @@ func (p *Preferences) LoadPreferences() error {

func (p *Preferences) setSession(accountID string,
session string,
deviceName string,
vpnUser string,
vpnPass string,
wgPublicKey string,
Expand All @@ -303,6 +311,7 @@ func (p *Preferences) setSession(accountID string,
p.Session = SessionStatus{
AccountID: strings.TrimSpace(accountID),
Session: strings.TrimSpace(session),
DeviceName: strings.TrimSpace(deviceName),
OpenVPNUser: strings.TrimSpace(vpnUser),
OpenVPNPass: strings.TrimSpace(vpnPass),
WGKeysRegenInerval: p.Session.WGKeysRegenInerval} // keep 'WGKeysRegenInerval' from previous Session object
Expand Down
1 change: 1 addition & 0 deletions daemon/service/preferences/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
type SessionStatus struct {
AccountID string
Session string `json:",omitempty"`
DeviceName string `json:",omitempty"`
OpenVPNUser string `json:",omitempty"`
OpenVPNPass string `json:",omitempty"`
WGPublicKey string
Expand Down
57 changes: 25 additions & 32 deletions daemon/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1521,11 +1521,12 @@ func (s *Service) SplitTunnelling_AddedPidInfo(pid int, exec string, cmdToExecut
// SESSIONS
//////////////////////////////////////////////////////////

func (s *Service) setCredentials(accountInfo preferences.AccountStatus, accountID, session, vpnUser, vpnPass, wgPublicKey, wgPrivateKey, wgLocalIP string, wgKeyGenerated int64, wgPreSharedKey string) error {
func (s *Service) setCredentials(accountInfo preferences.AccountStatus, accountID, session, deviceName, vpnUser, vpnPass, wgPublicKey, wgPrivateKey, wgLocalIP string, wgKeyGenerated int64, wgPreSharedKey string) error {
// save session info
s._preferences.SetSession(accountInfo,
accountID,
session,
deviceName,
vpnUser,
vpnPass,
wgPublicKey,
Expand Down Expand Up @@ -1682,6 +1683,7 @@ func (s *Service) SessionNew(accountID string, forceLogin bool, captchaID string
s.setCredentials(accountInfo,
accountID,
successResp.Token,
successResp.DeviceName,
successResp.VpnUsername,
successResp.VpnPassword,
publicKey,
Expand Down Expand Up @@ -1762,7 +1764,7 @@ func (s *Service) logOut(sessionNeedToDeleteOnBackend bool, isCanDeleteSessionLo
}
}

s._preferences.SetSession(preferences.AccountStatus{}, "", "", "", "", "", "", "", "")
s._preferences.SetSession(preferences.AccountStatus{}, "", "", "", "", "", "", "", "", "")
log.Info("Logged out locally")

// notify clients about session update
Expand All @@ -1778,30 +1780,30 @@ func (s *Service) OnSessionNotFound() {
s.logOut(needToDeleteOnBackend, canLogoutOnlyLocally)
}

func (s *Service) OnAccountStatus(sessionToken string, accountInfo preferences.AccountStatus) {
func (s *Service) OnSessionStatus(sessionToken string, sessionData preferences.SessionMutableData) {
// save last known info about account status
s._preferences.UpdateAccountInfo(accountInfo)
s._preferences.UpdateSessionData(sessionData)
// notify about account status
s._evtReceiver.OnAccountStatus(sessionToken, accountInfo)
s._evtReceiver.OnSessionStatus(sessionToken, sessionData)
}

// RequestSessionStatus receives session status
func (s *Service) RequestSessionStatus() (
apiCode int,
apiErrorMsg string,
sessionToken string,
accountInfo preferences.AccountStatus,
sessionStatus preferences.SessionMutableData,
err error) {

session := s.Preferences().Session
if !session.IsLoggedIn() {
return apiCode, "", "", accountInfo, srverrors.ErrorNotLoggedIn{}
return apiCode, "", "", sessionStatus, srverrors.ErrorNotLoggedIn{}
}

// if no connectivity - skip request (and activate _isWaitingToUpdateAccInfoChan)
if err := s.IsConnectivityBlocked(); err != nil {
s._isNeedToUpdateSessionInfo = true
return apiCode, "", "", accountInfo, fmt.Errorf("session status request skipped (%w)", err)
return apiCode, "", "", sessionStatus, fmt.Errorf("session status request skipped (%w)", err)
}
// defer: ensure s._isWaitingToUpdateAccInfoChan is empty
defer func() {
Expand All @@ -1817,52 +1819,43 @@ func (s *Service) RequestSessionStatus() (
// It could happen that logout\login was performed during the session check
// Ignoring result if there is already a new session
log.Info("Ignoring requested session status result. Local session already changed.")
return apiCode, "", "", accountInfo, srverrors.ErrorNotLoggedIn{}
return apiCode, "", "", sessionStatus, srverrors.ErrorNotLoggedIn{}
}

if stat != nil {
sessionStatus.Account = s.createAccountStatus(stat.ServiceStatus)
sessionStatus.DeviceName = stat.DeviceName
}

apiCode = 0
if apiErr != nil {
apiCode = apiErr.Status

// Session not found - can happens when user forced to logout from another device
if apiCode == api_types.SessionNotFound {
s.OnSessionNotFound()
}

// save last account info AND notify clients that account not active
if apiCode == api_types.AccountNotActive {
accountInfo = preferences.AccountStatus{}
if stat != nil {
accountInfo = s.createAccountStatus(*stat)
}
accountInfo.Active = false
// notify about account status
s.OnAccountStatus(session.Session, accountInfo)
return apiCode, apiErr.Message, session.Session, accountInfo, err
sessionStatus.Account.Active = false
s.OnSessionStatus(session.Session, sessionStatus)
return apiCode, apiErr.Message, session.Session, sessionStatus, err
}
}

if err != nil {
// in case of other API error
if apiErr != nil {
return apiCode, apiErr.Message, "", accountInfo, err
return apiCode, apiErr.Message, "", sessionStatus, err
}

// not API error
return apiCode, "", "", accountInfo, err
return apiCode, "", "", sessionStatus, err
}

if stat == nil {
return apiCode, "", "", accountInfo, fmt.Errorf("unexpected error when creating requesting session status")
return apiCode, "", "", sessionStatus, fmt.Errorf("unexpected error when creating requesting session status")
}

// get account status info
accountInfo = s.createAccountStatus(*stat)
// ave last account info AND notify about account status
s.OnAccountStatus(session.Session, accountInfo)

// success
return apiCode, "", session.Session, accountInfo, nil
// save last account info AND notify about account status
s.OnSessionStatus(session.Session, sessionStatus)
return apiCode, "", session.Session, sessionStatus, nil
}

func (s *Service) createAccountStatus(apiResp api_types.ServiceStatusAPIResp) preferences.AccountStatus {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ if (gotTheLock) {
applyMinimizedState();
break;

case "account/accountStatus":
case "account/sessionStatus":
// When IVPN apps detect a plan downgrade (from Pro to Standard), an active VPN connection that uses Pro features (MultiHop or Port forwarding)
// should be disconnected or reconnected with Standard plan features.
// Before the active VPN connection is disconnected by the app,
Expand Down
Loading

0 comments on commit b4a22d8

Please sign in to comment.