Skip to content

Commit

Permalink
Merge pull request #419 from ivpn/feature/device-management
Browse files Browse the repository at this point in the history
Add support for Device Management
  • Loading branch information
jurajhilje authored Feb 8, 2024
2 parents 1d4dd87 + f56a6ca commit f8a80f2
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 103 deletions.
2 changes: 1 addition & 1 deletion IVPNClient/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ extension AppDelegate: UIApplicationDelegate {
}
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
let endpoint = url.lastPathComponent
handleURLEndpoint(endpoint)
return true
Expand Down
4 changes: 2 additions & 2 deletions IVPNClient/Enums/AddressType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ enum AddressType {
case other

static func validateIpAddress(_ address: String) -> AddressType {
if let _ = IPv4Address(address) {
if IPv4Address(address) != nil {
return .IPv4
} else if let _ = IPv6Address(address) {
} else if IPv6Address(address) != nil {
return .IPv6
} else {
return .other
Expand Down
2 changes: 2 additions & 0 deletions IVPNClient/Enums/ApiResults/ErrorResultSessionNew.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct SessionLimitData: Decodable {
let upgradeToPlan: String
let upgradeToUrl: String
let paymentMethod: String
let deviceManagement: Bool
let deviceManagementUrl: String
}

struct ErrorResultSessionNew: Decodable {
Expand Down
1 change: 1 addition & 0 deletions IVPNClient/Enums/ApiResults/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct Session: Decodable {
let token: String?
let vpnUsername: String?
let vpnPassword: String?
let deviceName: String?
let serviceStatus: ServiceStatus
let wireguard: WireGuardResult?
}
1 change: 1 addition & 0 deletions IVPNClient/Enums/ApiResults/SessionStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Foundation

struct SessionStatus: Decodable {
let status: Int
let deviceName: String?
let serviceStatus: ServiceStatus

var serviceActive: Bool {
Expand Down
12 changes: 12 additions & 0 deletions IVPNClient/Managers/KeyChain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class KeyChain {
private static let sessionTokenKey = "session_token"
private static let vpnUsernameKey = "vpn_username"
private static let vpnPasswordKey = "vpn_password"
private static let deviceNameKey = "deviceName"

static let bundle: Keychain = {
return Keychain(service: "net.ivpn.clients.ios", accessGroup: "WQXXM75BYN.net.ivpn.IVPN-Client").accessibility(.whenPasscodeSetThisDeviceOnly)
Expand Down Expand Up @@ -149,12 +150,22 @@ class KeyChain {
sessionToken = session.token
vpnUsername = session.vpnUsername
vpnPassword = session.vpnPassword
deviceName = session.deviceName

if let wireguardResult = session.wireguard, let ipAddress = wireguardResult.ipAddress {
KeyChain.wgIpAddress = ipAddress
}
}

class var deviceName: String? {
get {
return KeyChain.bundle[deviceNameKey]
}
set {
KeyChain.bundle[deviceNameKey] = newValue
}
}

static func clearAll() {
username = nil
tempUsername = nil
Expand All @@ -166,6 +177,7 @@ class KeyChain {
sessionToken = nil
vpnUsername = nil
vpnPassword = nil
deviceName = nil
}

}
11 changes: 8 additions & 3 deletions IVPNClient/Managers/NavigationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ class NavigationManager {
return viewController
}

static func getLoginViewController() -> UIViewController {
static func getLoginViewController(showLogoutAlert: Bool = false) -> UIViewController {
let storyBoard = UIStoryboard(name: "Signup", bundle: nil)
let viewController = storyBoard.instantiateViewController(withIdentifier: "loginView")
let navController = storyBoard.instantiateViewController(withIdentifier: "loginView") as? UINavigationController
navController?.modalPresentationStyle = .formSheet

return viewController
if let viewController = navController?.topViewController as? LoginViewController {
viewController.showLogoutAlert = showLogoutAlert
}

return navController!
}

static func getChangePlanViewController() -> UIViewController {
Expand Down
6 changes: 4 additions & 2 deletions IVPNClient/Managers/SessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Foundation
func createSessionStart()
func createSessionSuccess()
func createSessionFailure(error: Any?)
func createSessionTooManySessions(error: Any?)
func createSessionTooManySessions(error: Any?, isNewStyleAccount: Bool)
func createSessionAuthenticationError()
func createSessionServiceNotActive()
func createSessionAccountNotActivated(error: Any?)
Expand Down Expand Up @@ -67,6 +67,7 @@ class SessionManager {

var kem = KEM()
let params = sessionNewParams(force: force, username: username, confirmation: confirmation, captcha: captcha, captchaId: captchaId, kem: kem)
let isNewStyleAccount = ServiceStatus.isNewStyleAccount(username: username ?? "")
let request = ApiRequestDI(method: .post, endpoint: Config.apiSessionNew, params: params)

ApiService.shared.requestCustomError(request) { (result: ResultCustomError<Session, ErrorResultSessionNew>) in
Expand Down Expand Up @@ -99,7 +100,7 @@ class SessionManager {
return
case 602:
log(.info, message: "Create session error: createSessionTooManySessions")
self.delegate?.createSessionTooManySessions(error: error)
self.delegate?.createSessionTooManySessions(error: error, isNewStyleAccount: isNewStyleAccount)
return
case 11005:
log(.info, message: "Create session error: createSessionAccountNotActivated")
Expand Down Expand Up @@ -141,6 +142,7 @@ class SessionManager {
switch result {
case .success(let model):
Application.shared.serviceStatus = model.serviceStatus
KeyChain.deviceName = model.deviceName
NotificationCenter.default.post(name: Notification.Name.EvaluatePlanUpdate, object: nil)

if model.serviceActive {
Expand Down
12 changes: 11 additions & 1 deletion IVPNClient/Models/ServiceStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ServiceStatus: Codable {
let upgradeToUrl: String?
let paymentMethod: String?
let capabilities: [String]?
let deviceManagement: Bool

// MARK: - Initialize -

Expand All @@ -48,6 +49,7 @@ struct ServiceStatus: Codable {
upgradeToUrl = service?.upgradeToUrl ?? nil
paymentMethod = service?.paymentMethod ?? nil
capabilities = service?.capabilities ?? nil
deviceManagement = service?.deviceManagement ?? false
}

// MARK: - Methods -
Expand Down Expand Up @@ -89,7 +91,15 @@ struct ServiceStatus: Codable {
}

func isNewStyleAccount() -> Bool {
return paymentMethod == "prepaid"
guard let username = username else {
return true
}

return username.hasPrefix("i-")
}

static func isNewStyleAccount(username: String) -> Bool {
return username.hasPrefix("i-")
}

func daysUntilSubscriptionExpiration() -> Int {
Expand Down
2 changes: 1 addition & 1 deletion IVPNClient/Models/V2Ray/V2RayConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ struct V2RayConfig: Codable {
let connection: [String]
let pragma: String

enum CodingKeys : String, CodingKey {
enum CodingKeys: String, CodingKey {

Check warning on line 115 in IVPNClient/Models/V2Ray/V2RayConfig.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Nesting Violation: Types should be nested at most 1 level deep (nesting)

Check warning on line 115 in IVPNClient/Models/V2Ray/V2RayConfig.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Nesting Violation: Types should be nested at most 1 level deep (nesting)

Check warning on line 115 in IVPNClient/Models/V2Ray/V2RayConfig.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Nesting Violation: Types should be nested at most 1 level deep (nesting)
case host = "Host"

Check warning on line 116 in IVPNClient/Models/V2Ray/V2RayConfig.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Nesting Violation: Statements should be nested at most 5 levels deep (nesting)
case userAgent = "User-Agent"

Check warning on line 117 in IVPNClient/Models/V2Ray/V2RayConfig.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Nesting Violation: Statements should be nested at most 5 levels deep (nesting)
case acceptEncoding = "Accept-Encoding"
Expand Down
4 changes: 2 additions & 2 deletions IVPNClient/Models/V2Ray/V2RayCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class V2RayCore {

func start() -> Error? {
let _ = close()
var error: Error? = nil
var error: Error?

guard let config = makeConfig() else {
return NSError(domain: "", code: 99, userInfo: [NSLocalizedDescriptionKey: "V2Ray configuration cannot be loaded"])
Expand All @@ -52,7 +52,7 @@ class V2RayCore {
}

func close() -> Error? {
var error: Error? = nil
var error: Error?

if let instance = instance {
var stopError: NSError?
Expand Down
17 changes: 13 additions & 4 deletions IVPNClient/Scenes/AccountScreen/AccountViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ class AccountViewController: UITableViewController {
}

@IBAction func deleteAccount(_ sender: UIButton) {
if let url = URL(string: "https://www.ivpn.net/account/settings") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
openWebPageInBrowser("https://www.ivpn.net/account/settings")
}

@IBAction func addMoreTime(_ sender: Any) {
Expand Down Expand Up @@ -92,12 +90,14 @@ class AccountViewController: UITableViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
accountView.initQRCode(viewModel: viewModel)
sessionManager.getSessionStatus()
}

// MARK: - Observers -

func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(subscriptionActivated), name: Notification.Name.SubscriptionActivated, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(subscriptionActivated), name: Notification.Name.ServiceAuthorized, object: nil)
}

// MARK: - Private methods -
Expand Down Expand Up @@ -180,7 +180,7 @@ extension AccountViewController {
logOut(deleteSession: false, deleteSettings: deleteSettings)
navigationController?.dismiss(animated: true)
} else {
showActionAlert(title: "Error with removing session", message: "Unable to contact server to log out. Please check Internet connectivity. Do you want to force log out? This device will continue to count towards your device limit.", action: "Force log out", cancelHandler: { _ in
showActionAlert(title: "Unable to contact server to log out", message: "Please check Internet connectivity. Do you want to force log out? This device will continue to count towards your device limit.", action: "Force log out", cancelHandler: { _ in
NotificationCenter.default.post(name: Notification.Name.UpdateGeoLocation, object: nil)
}, actionHandler: { [self] _ in
forceLogOut = true
Expand All @@ -196,6 +196,15 @@ extension AccountViewController {
}
}

override func sessionStatusSuccess() {
subscriptionActivated()
}

override func sessionStatusNotFound() {
logOut(deleteSession: false)
present(NavigationManager.getLoginViewController(showLogoutAlert: true), animated: true)
}

}

// MARK: - JGProgressHUDDelegate -
Expand Down
10 changes: 10 additions & 0 deletions IVPNClient/Scenes/AccountScreen/View/AccountView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class AccountView: UITableView {
@IBOutlet weak var activeUntilLabel: UILabel!
@IBOutlet weak var logOutActionButton: UIButton!
@IBOutlet weak var activeUntilCell: UITableViewCell!
@IBOutlet weak var deviceNameTitle: UILabel!
@IBOutlet weak var deviceName: UILabel!
@IBOutlet weak var header: UIView!

// MARK: - Properties -

Expand All @@ -48,6 +51,13 @@ class AccountView: UITableView {
subscriptionLabel.text = viewModel.subscriptionText
activeUntilLabel.text = viewModel.activeUntilText
activeUntilCell.isHidden = Application.shared.serviceStatus.isLegacyAccount()
deviceName.text = viewModel.deviceName
deviceNameTitle.isHidden = !viewModel.showDeviceName
deviceName.isHidden = !viewModel.showDeviceName
let headerHeight = viewModel.showDeviceName ? 270 : 210
header.frame = CGRect(x: 0, y: 0, width: Int(header.frame.width), height: headerHeight)
reloadData()
layoutIfNeeded()
}

func initQRCode(viewModel: AccountViewModel) {
Expand Down
Loading

0 comments on commit f8a80f2

Please sign in to comment.