Skip to content

Commit 13b89bf

Browse files
committed
Merge branch 'feature/347-Add-support-for-Device-Management' into beta
2 parents 8415bc6 + 0e29958 commit 13b89bf

File tree

13 files changed

+121
-72
lines changed

13 files changed

+121
-72
lines changed

cli/commands/account.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"time"
3333

3434
"github.com/ivpn/desktop-app/cli/flags"
35+
"github.com/ivpn/desktop-app/cli/helpers"
3536
"github.com/ivpn/desktop-app/daemon/api/types"
3637
"github.com/ivpn/desktop-app/daemon/service/srverrors"
3738
"github.com/ivpn/desktop-app/daemon/vpn"
@@ -92,9 +93,9 @@ func doLogin(accountID string, force bool) error {
9293
accountID = string(data)
9394
}
9495

95-
apiStatus, err := _proto.SessionNew(accountID, force, "")
96+
resp, err := _proto.SessionNew(accountID, force, "")
9697
if err != nil {
97-
if apiStatus == types.The2FARequired {
98+
if resp.APIStatus == types.The2FARequired {
9899
fmt.Println("Account has two-factor authentication enabled.")
99100
fmt.Print("Please enter TOTP token to login: ")
100101
reader := bufio.NewReader(os.Stdin)
@@ -103,14 +104,19 @@ func doLogin(accountID string, force bool) error {
103104
topt = strings.TrimSuffix(topt, "\n")
104105
topt = strings.TrimSuffix(topt, "\r")
105106

106-
apiStatus, err = _proto.SessionNew(accountID, force, topt)
107+
resp, err = _proto.SessionNew(accountID, force, topt)
107108
}
108109

109-
if apiStatus == types.CodeSessionsLimitReached {
110+
if resp.APIStatus == types.CodeSessionsLimitReached {
110111
PrintTips([]TipType{TipForceLogin})
111112

112-
fmt.Println("Visit Device Management to manage your devices: 'https://www.ivpn.net/account/device-management'")
113-
fmt.Println("")
113+
if !helpers.IsLegacyAccount(accountID) && len(resp.Account.DeviceManagementURL) > 0 {
114+
prefixText := "Visit Device Management"
115+
if !resp.Account.DeviceManagement {
116+
prefixText = "Enable Device Management"
117+
}
118+
fmt.Println(fmt.Sprintf("%s to manage your devices: '%s'", prefixText, resp.Account.DeviceManagementURL))
119+
}
114120
}
115121

116122
if err != nil {

cli/helpers/helpers.go

+4
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,7 @@ func TrimSpacesAndRemoveQuotes(s string) string {
8888
}
8989
return s
9090
}
91+
92+
func IsLegacyAccount(accountID string) bool {
93+
return len(accountID) <= 12 && strings.HasPrefix(accountID, "ivpn")
94+
}

cli/protocol/client.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -168,23 +168,22 @@ func (c *Client) GetHelloResponse() types.HelloResp {
168168
}
169169

170170
// SessionNew creates new session
171-
func (c *Client) SessionNew(accountID string, forceLogin bool, the2FA string) (apiStatus int, err error) {
171+
func (c *Client) SessionNew(accountID string, forceLogin bool, the2FA string) (resp types.SessionNewResp, err error) {
172172
if err := c.ensureConnected(); err != nil {
173-
return 0, err
173+
return resp, err
174174
}
175175

176176
req := types.SessionNew{AccountID: accountID, ForceLogin: forceLogin, Confirmation2FA: the2FA}
177-
var resp types.SessionNewResp
178177

179178
if err := c.sendRecv(&req, &resp); err != nil {
180-
return 0, err
179+
return resp, err
181180
}
182181

183182
if len(resp.Session.Session) <= 0 {
184-
return resp.APIStatus, fmt.Errorf("[%d] %s", resp.APIStatus, resp.APIErrorMessage)
183+
return resp, fmt.Errorf("[%d] %s", resp.APIStatus, resp.APIErrorMessage)
185184
}
186185

187-
return resp.APIStatus, nil
186+
return resp, nil
188187
}
189188

190189
// SessionDelete remove session

daemon/api/api.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ func (a *API) DoRequestByAlias(apiAlias string, ipTypeRequired protocolTypes.Req
284284
}
285285
}
286286

287-
return a.requestRaw(ipTypeRequired, alias.host, alias.path, "", "", nil, 0, 0)
287+
responseData, _, err = a.requestRaw(ipTypeRequired, alias.host, alias.path, "", "", nil, 0, 0)
288+
return responseData, err
288289
}
289290

290291
// SessionNew - try to register new session
@@ -310,7 +311,7 @@ func (a *API) SessionNew(accountID string, wgPublicKey string, kemKeys types.Kem
310311
Captcha: captcha,
311312
Confirmation2FA: confirmation2FA}
312313

313-
data, err := a.requestRaw(protocolTypes.IPvAny, "", _sessionNewPath, "POST", "application/json", request, 0, 0)
314+
data, httpResp, err := a.requestRaw(protocolTypes.IPvAny, "", _sessionNewPath, "POST", "application/json", request, 0, 0)
314315
if err != nil {
315316
return nil, nil, nil, rawResponse, err
316317
}
@@ -319,7 +320,7 @@ func (a *API) SessionNew(accountID string, wgPublicKey string, kemKeys types.Kem
319320

320321
// Check is it API error
321322
if err := json.Unmarshal(data, &apiErr); err != nil {
322-
return nil, nil, nil, rawResponse, fmt.Errorf("failed to deserialize API response: %w", err)
323+
return nil, nil, nil, rawResponse, fmt.Errorf("[%d; status=%s] failed to deserialize API response: %w", httpResp.StatusCode, httpResp.Status, err)
323324
}
324325

325326
// success
@@ -353,7 +354,7 @@ func (a *API) SessionStatus(session string) (
353354

354355
request := &types.SessionStatusRequest{Session: session}
355356

356-
data, err := a.requestRaw(protocolTypes.IPvAny, "", _sessionStatusPath, "POST", "application/json", request, 0, 0)
357+
data, _, err := a.requestRaw(protocolTypes.IPvAny, "", _sessionStatusPath, "POST", "application/json", request, 0, 0)
357358
if err != nil {
358359
return nil, nil, err
359360
}
@@ -442,7 +443,7 @@ func (a *API) GeoLookup(timeoutMs int, ipTypeRequired protocolTypes.RequiredIPPr
442443
gl.isRunning = false
443444
close(gl.done)
444445
}()
445-
gl.response, gl.err = a.requestRaw(ipType, "", _geoLookupPath, "GET", "", nil, timeoutMs, 0)
446+
gl.response, _, gl.err = a.requestRaw(ipType, "", _geoLookupPath, "GET", "", nil, timeoutMs, 0)
446447
if err := json.Unmarshal(gl.response, &gl.location); err != nil {
447448
gl.err = fmt.Errorf("failed to deserialize API response: %w", err)
448449
}

daemon/api/api_internal.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -330,26 +330,30 @@ func (a *API) doRequestAPIHost(ipTypeRequired types.RequiredIPProtocol, isCanUse
330330
return nil, fmt.Errorf("unable to access IVPN API server: %w", firstErr)
331331
}
332332

333-
func (a *API) requestRaw(ipTypeRequired types.RequiredIPProtocol, host string, urlPath string, method string, contentType string, requestObject interface{}, timeoutMs int, timeoutDialMs int) (responseData []byte, err error) {
333+
func (a *API) requestRaw(ipTypeRequired types.RequiredIPProtocol, host string, urlPath string, method string, contentType string, requestObject interface{}, timeoutMs int, timeoutDialMs int) (responseData []byte, httpResp *http.Response, err error) {
334334
resp, err := a.doRequest(ipTypeRequired, host, urlPath, method, contentType, requestObject, timeoutMs, timeoutDialMs)
335335
if err != nil {
336-
return nil, fmt.Errorf("API request failed: %w", err)
336+
return nil, nil, fmt.Errorf("API request failed: %w", err)
337+
}
338+
339+
if resp.StatusCode != 200 {
340+
log.Debug(fmt.Sprintf("API response: (HTTP status code %d); status=%s", resp.StatusCode, resp.Status))
337341
}
338342

339343
body, err := io.ReadAll(resp.Body)
340344
if err != nil {
341-
return nil, fmt.Errorf("failed to get API HTTP response body: %w", err)
345+
return nil, nil, fmt.Errorf("failed to read API HTTP response body: %w", err)
342346
}
343347

344-
return body, nil
348+
return body, resp, nil
345349
}
346350

347351
func (a *API) request(host string, urlPath string, method string, contentType string, requestObject interface{}, responseObject interface{}) error {
348352
return a.requestEx(host, urlPath, method, contentType, requestObject, responseObject, 0, 0)
349353
}
350354

351355
func (a *API) requestEx(host string, urlPath string, method string, contentType string, requestObject interface{}, responseObject interface{}, timeoutMs int, timeoutDialMs int) error {
352-
body, err := a.requestRaw(types.IPvAny, host, urlPath, method, contentType, requestObject, timeoutMs, timeoutDialMs)
356+
body, _, err := a.requestRaw(types.IPvAny, host, urlPath, method, contentType, requestObject, timeoutMs, timeoutDialMs)
353357
if err != nil {
354358
return err
355359
}

daemon/api/types/responses.go

+14-12
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,20 @@ type APIErrorResponse struct {
3535

3636
// ServiceStatusAPIResp account info
3737
type ServiceStatusAPIResp struct {
38-
Active bool `json:"is_active"`
39-
ActiveUntil int64 `json:"active_until"`
40-
CurrentPlan string `json:"current_plan"`
41-
PaymentMethod string `json:"payment_method"`
42-
IsRenewable bool `json:"is_renewable"`
43-
WillAutoRebill bool `json:"will_auto_rebill"`
44-
IsFreeTrial bool `json:"is_on_free_trial"`
45-
Capabilities []string `json:"capabilities"`
46-
Upgradable bool `json:"upgradable"`
47-
UpgradeToPlan string `json:"upgrade_to_plan"`
48-
UpgradeToURL string `json:"upgrade_to_url"`
49-
Limit int `json:"limit"` // applicable for 'session limit' error
38+
Active bool `json:"is_active"`
39+
ActiveUntil int64 `json:"active_until"`
40+
CurrentPlan string `json:"current_plan"`
41+
PaymentMethod string `json:"payment_method"`
42+
IsRenewable bool `json:"is_renewable"`
43+
WillAutoRebill bool `json:"will_auto_rebill"`
44+
IsFreeTrial bool `json:"is_on_free_trial"`
45+
Capabilities []string `json:"capabilities"`
46+
Upgradable bool `json:"upgradable"`
47+
UpgradeToPlan string `json:"upgrade_to_plan"`
48+
UpgradeToURL string `json:"upgrade_to_url"`
49+
DeviceManagement bool `json:"device_management"`
50+
DeviceManagementURL string `json:"device_management_url"` // applicable for 'session limit' error
51+
Limit int `json:"limit"` // applicable for 'session limit' error
5052
}
5153

5254
// KemCiphers in use for KEM: to exchange WG PresharedKey

daemon/service/preferences/account.go

+14-12
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,20 @@ const (
3535

3636
// AccountStatus contains information about current account
3737
type AccountStatus struct {
38-
Active bool
39-
ActiveUntil int64
40-
CurrentPlan string
41-
PaymentMethod string
42-
IsRenewable bool
43-
WillAutoRebill bool
44-
IsFreeTrial bool
45-
Capabilities []string
46-
Upgradable bool
47-
UpgradeToPlan string
48-
UpgradeToURL string
49-
Limit int
38+
Active bool
39+
ActiveUntil int64
40+
CurrentPlan string
41+
PaymentMethod string
42+
IsRenewable bool
43+
WillAutoRebill bool
44+
IsFreeTrial bool
45+
Capabilities []string
46+
Upgradable bool
47+
UpgradeToPlan string
48+
UpgradeToURL string
49+
DeviceManagement bool
50+
DeviceManagementURL string
51+
Limit int
5052
}
5153

5254
func (a AccountStatus) IsInitialized() bool {

daemon/service/service.go

+14-12
Original file line numberDiff line numberDiff line change
@@ -1860,18 +1860,20 @@ func (s *Service) RequestSessionStatus() (
18601860

18611861
func (s *Service) createAccountStatus(apiResp api_types.ServiceStatusAPIResp) preferences.AccountStatus {
18621862
return preferences.AccountStatus{
1863-
Active: apiResp.Active,
1864-
ActiveUntil: apiResp.ActiveUntil,
1865-
CurrentPlan: apiResp.CurrentPlan,
1866-
PaymentMethod: apiResp.PaymentMethod,
1867-
IsRenewable: apiResp.IsRenewable,
1868-
WillAutoRebill: apiResp.WillAutoRebill,
1869-
IsFreeTrial: apiResp.IsFreeTrial,
1870-
Capabilities: apiResp.Capabilities,
1871-
Upgradable: apiResp.Upgradable,
1872-
UpgradeToPlan: apiResp.UpgradeToPlan,
1873-
UpgradeToURL: apiResp.UpgradeToURL,
1874-
Limit: apiResp.Limit}
1863+
Active: apiResp.Active,
1864+
ActiveUntil: apiResp.ActiveUntil,
1865+
CurrentPlan: apiResp.CurrentPlan,
1866+
PaymentMethod: apiResp.PaymentMethod,
1867+
IsRenewable: apiResp.IsRenewable,
1868+
WillAutoRebill: apiResp.WillAutoRebill,
1869+
IsFreeTrial: apiResp.IsFreeTrial,
1870+
Capabilities: apiResp.Capabilities,
1871+
Upgradable: apiResp.Upgradable,
1872+
UpgradeToPlan: apiResp.UpgradeToPlan,
1873+
UpgradeToURL: apiResp.UpgradeToURL,
1874+
DeviceManagement: apiResp.DeviceManagement,
1875+
DeviceManagementURL: apiResp.DeviceManagementURL,
1876+
Limit: apiResp.Limit}
18751877
}
18761878

18771879
func (s *Service) startSessionChecker() {

ui/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ libivpn.dylib
3333
References/macOS/_compiled
3434
References/macOS/HelperProjects/uninstaller/bin
3535
References/Windows/bin/
36+
out/*
37+
addons/wifi-info-macos/build/*

ui/src/components/Component-Login.vue

+3
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,12 @@ export default {
253253
accountID: this.accountID,
254254
devicesMaxLimit: resp.Account.Limit,
255255
CurrentPlan: resp.Account.CurrentPlan,
256+
PaymentMethod: resp.Account.PaymentMethod,
256257
Upgradable: resp.Account.Upgradable,
257258
UpgradeToPlan: resp.Account.UpgradeToPlan,
258259
UpgradeToURL: resp.Account.UpgradeToURL,
260+
DeviceManagement: resp.Account.DeviceManagement,
261+
DeviceManagementURL: resp.Account.DeviceManagementURL,
259262
extraArgs: {
260263
confirmation2FA: oldConfirmation2FA,
261264
},

ui/src/ipc/main-listener.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,18 @@ ipcMain.handle("renderer-request-shell-open-external", async (event, uri) => {
423423
}
424424

425425
if (!isAllowedUrl) {
426-
const errMsg = `Opening the link '${uri}' is blocked. Not allowed to open links which are not starting from: ${config.URLsAllowedPrefixes}`;
427-
console.log(errMsg);
428-
throw Error(errMsg);
426+
const errMsgText = `The link cannot be opened`;
427+
const errMsgTextLnk = `${uri}`;
428+
const errMsgDetail = `Links must start with: "${config.URLsAllowedPrefixes}". Opening links that do not meet this criterion is not allowed.`;
429+
console.log(errMsgText + " " + errMsgTextLnk+ " " + errMsgDetail);
430+
431+
dialog.showMessageBoxSync(event.sender.getOwnerBrowserWindow(), {
432+
type: "error",
433+
message: errMsgText,
434+
detail: errMsgTextLnk+ "\n\n" + errMsgDetail,
435+
buttons: ["OK"],
436+
});
437+
return;
429438
}
430439
return shell.openExternal(uri);
431440
});

ui/src/store/module-account.js

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export default {
5050
Upgradable: false,
5151
UpgradeToPlan: "",
5252
UpgradeToURL: "",
53+
DeviceManagement: false,
54+
DeviceManagementURL: "", // applicable for 'session limit' error
5355
Limit: 0, // applicable for 'session limit' error
5456
},
5557
},

ui/src/views/AccountLimit.vue

+24-11
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,11 @@
2929
</button>
3030

3131
<div style="height: 16px"></div>
32-
<button
33-
class="slave"
34-
v-if="isCanForceLogout"
35-
v-on:click="onVisitDeviceManagement"
36-
>
37-
Visit Device Management
38-
</button>
32+
<div v-if="isLegacyAccount == false && this.DeviceManagementURL">
33+
<button class="slave" v-on:click="onVisitDeviceManagement">
34+
{{ devManagementButtonText }}
35+
</button>
36+
</div>
3937

4038
<div style="height: 16px"></div>
4139
<div class="centered">
@@ -71,9 +69,12 @@ export default {
7169
this.accountID = params.accountID;
7270
this.devicesMaxLimit = params.devicesMaxLimit;
7371
this.CurrentPlan = params.CurrentPlan;
72+
this.PaymentMethod = params.PaymentMethod;
7473
this.Upgradable = params.Upgradable;
7574
this.UpgradeToPlan = params.UpgradeToPlan;
7675
this.UpgradeToURL = params.UpgradeToURL;
76+
this.DeviceManagement = params.DeviceManagement;
77+
this.DeviceManagementURL = params.DeviceManagementURL;
7778
} else {
7879
console.error("AccountLimit view: history params are not defined!");
7980
}
@@ -85,10 +86,12 @@ export default {
8586
accountID: null,
8687
devicesMaxLimit: 0,
8788
CurrentPlan: null,
89+
PaymentMethod: null,
8890
Upgradable: null,
8991
UpgradeToPlan: null,
9092
UpgradeToURL: null,
91-
93+
DeviceManagement: false,
94+
DeviceManagementURL: "",
9295
extraArgs: null,
9396
};
9497
},
@@ -100,6 +103,18 @@ export default {
100103
if (this.accountID == null || this.accountID === "") return false;
101104
return true;
102105
},
106+
isLegacyAccount: function () {
107+
return typeof this.accountID === "string" &&
108+
this.accountID.startsWith("ivpn") &&
109+
this.accountID.length <= 12
110+
? true
111+
: false;
112+
},
113+
devManagementButtonText: function () {
114+
return this.DeviceManagement
115+
? "Visit Device Management"
116+
: "Enable Device Management";
117+
},
103118
},
104119
methods: {
105120
onTryAgain: function () {
@@ -125,9 +140,7 @@ export default {
125140
sender.shellOpenExternal(`https://www.ivpn.net/contactus`);
126141
},
127142
onVisitDeviceManagement: function () {
128-
sender.shellOpenExternal(
129-
`https://www.ivpn.net/account/device-management`,
130-
);
143+
sender.shellOpenExternal(this.DeviceManagementURL);
131144
},
132145
},
133146
};

0 commit comments

Comments
 (0)