Commit 9a3ae603 authored by Jose Blaya's avatar Jose Blaya
Browse files

wip

parent 16961e22
This diff is collapsed.
......@@ -623,7 +623,7 @@ You will not be charged during this process.</string>
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgW-nz-hik">
<rect key="frame" x="27" y="29" width="266" height="268.5"/>
<rect key="frame" x="27" y="29" width="266" height="307.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in to your account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2Rk-V0-kLQ">
<rect key="frame" x="47.5" y="0.0" width="171" height="19.5"/>
......@@ -661,7 +661,7 @@ You will not be charged during this process.</string>
</connections>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Emv-5o-nzd">
<rect key="frame" x="0.0" y="248.5" width="266" height="20"/>
<rect key="frame" x="0.0" y="287.5" width="266" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="82T-Gm-hZ2"/>
</constraints>
......@@ -669,15 +669,29 @@ You will not be charged during this process.</string>
<segue destination="vXZ-lx-hvc" kind="show" identifier="RestoreLoginPurchaseSegue" id="aVb-49-Y5l"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4OL-1u-sdN">
<rect key="frame" x="0.0" y="248.5" width="266" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="SLD-gi-EF7"/>
</constraints>
<connections>
<action selector="logInWithReceipt:" destination="gOX-c2-oGJ" eventType="touchUpInside" id="5hZ-Tf-eeJ"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Emv-5o-nzd" firstAttribute="top" secondItem="4OL-1u-sdN" secondAttribute="bottom" constant="19" id="D3k-hO-D8H"/>
<constraint firstAttribute="trailing" secondItem="4OL-1u-sdN" secondAttribute="trailing" id="GmP-XN-FQH"/>
<constraint firstItem="Wtm-HW-6FS" firstAttribute="trailing" secondItem="Gcb-kt-Yu7" secondAttribute="trailing" id="H7o-gu-nyL"/>
<constraint firstItem="2Rk-V0-kLQ" firstAttribute="centerX" secondItem="XgW-nz-hik" secondAttribute="centerX" id="IxB-Hs-m36"/>
<constraint firstItem="jEh-9o-rVi" firstAttribute="trailing" secondItem="Gcb-kt-Yu7" secondAttribute="trailing" id="KNu-Za-GOt"/>
<constraint firstItem="Wtm-HW-6FS" firstAttribute="leading" secondItem="Gcb-kt-Yu7" secondAttribute="leading" id="KoY-pt-6W0"/>
<constraint firstItem="4OL-1u-sdN" firstAttribute="top" secondItem="jEh-9o-rVi" secondAttribute="bottom" constant="19" id="LBP-uO-0f4"/>
<constraint firstItem="Wtm-HW-6FS" firstAttribute="top" secondItem="Gcb-kt-Yu7" secondAttribute="bottom" constant="15" id="Mtw-Tl-kU5"/>
<constraint firstItem="jEh-9o-rVi" firstAttribute="top" secondItem="Wtm-HW-6FS" secondAttribute="bottom" constant="15" id="N4Q-cw-0Vj"/>
<constraint firstItem="Emv-5o-nzd" firstAttribute="leading" secondItem="XgW-nz-hik" secondAttribute="leading" id="NZQ-d1-PES"/>
<constraint firstItem="4OL-1u-sdN" firstAttribute="leading" secondItem="XgW-nz-hik" secondAttribute="leading" id="Ts2-s1-uha"/>
<constraint firstAttribute="bottom" secondItem="Emv-5o-nzd" secondAttribute="bottom" id="UPk-Wm-uMP"/>
<constraint firstAttribute="bottom" secondItem="Emv-5o-nzd" secondAttribute="bottom" id="VqD-zG-Kh8"/>
<constraint firstItem="Wtm-HW-6FS" firstAttribute="height" secondItem="Gcb-kt-Yu7" secondAttribute="height" id="ZnI-xV-5hO"/>
<constraint firstAttribute="trailing" secondItem="jEh-9o-rVi" secondAttribute="trailing" id="b42-kd-WBq"/>
......@@ -686,7 +700,6 @@ You will not be charged during this process.</string>
<constraint firstItem="2Rk-V0-kLQ" firstAttribute="top" secondItem="XgW-nz-hik" secondAttribute="top" id="gX3-qV-EcT"/>
<constraint firstItem="Gcb-kt-Yu7" firstAttribute="top" secondItem="2Rk-V0-kLQ" secondAttribute="bottom" constant="30" id="gew-Yr-Sqc"/>
<constraint firstItem="jEh-9o-rVi" firstAttribute="leading" secondItem="Gcb-kt-Yu7" secondAttribute="leading" id="mBe-Fy-A5M"/>
<constraint firstItem="Emv-5o-nzd" firstAttribute="top" secondItem="jEh-9o-rVi" secondAttribute="bottom" constant="19" id="xkr-9V-VBV"/>
</constraints>
</view>
</subviews>
......@@ -777,6 +790,7 @@ You will not be charged during this process.</string>
<outlet property="buttonLogin" destination="jEh-9o-rVi" id="eaK-D0-FJE"/>
<outlet property="couldNotGetPlanButton" destination="Emv-5o-nzd" id="vhr-De-KQ4"/>
<outlet property="labelTitle" destination="2Rk-V0-kLQ" id="qdr-SQ-OwV"/>
<outlet property="loginWithReceipt" destination="4OL-1u-sdN" id="gC0-4X-aYT"/>
<outlet property="scrollView" destination="RgS-fn-CJH" id="gQR-aC-e9f"/>
<outlet property="textPassword" destination="Wtm-HW-6FS" id="bvM-nY-mSr"/>
<outlet property="textUsername" destination="Gcb-kt-Yu7" id="5rD-If-yIW"/>
......@@ -1057,11 +1071,11 @@ You will not be charged during this process.</string>
<objects>
<viewController storyboardIdentifier="PIAWelcomeViewController" automaticallyAdjustsScrollViewInsets="NO" id="vXZ-lx-hvc" customClass="PIAWelcomeViewController" customModule="PIALibrary" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NEF-LS-PZ6">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
<connections>
<segue destination="lku-HF-3OI" kind="embed" id="UXm-xH-vgv"/>
</connections>
......@@ -1451,7 +1465,7 @@ You will not be charged during this process.</string>
<image name="shield-icon" width="29" height="30"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="750-Xo-1Ft"/>
<segue reference="aVb-49-Y5l"/>
<segue reference="apR-1h-IYV"/>
<segue reference="WeP-PD-hnE"/>
</inferredMetricsTieBreakers>
......
......@@ -15,6 +15,7 @@
"login.error.validation" = "You must enter a username and password.";
"login.error.unauthorized" = "Your username or password is incorrect.";
"login.error.throttled" = "Too many failed login attempts with this username. Please try again later.";
"login.receipt.button" = "Login using purchase receipt";
"purchase.title" = "Select a VPN plan";
"purchase.subtitle" = "7-day money back guarantee";
......
......@@ -65,6 +65,18 @@ public protocol AccountProvider: class {
*/
func login(with request: LoginRequest, _ callback: LibraryCallback<UserAccount>?)
/**
Logs into system using the purchase receipt. The `isLoggedIn` variable becomes `true` on success.
- Precondition: `isLoggedIn` is `false`.
- Postcondition:
- Sets `currentUser` on success.
- Posts `Notification.Name.PIAAccountDidLogin` on success.
- Parameter request: The login receipt request.
- Parameter callback: Returns an `UserAccount`.
*/
func login(with receiptRequest: LoginReceiptRequest, _ callback: LibraryCallback<UserAccount>?)
/**
Refreshes information associated with the account currently logged in.
......
//
// LoginReceiptRequest.swift
// Pods
//
// Created by Jose Antonio Blaya Garcia on 17/03/2020.
// Copyright © 2020 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access iOS Client.
//
// The Private Internet Access iOS Client is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// The Private Internet Access iOS Client is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with the Private
// Internet Access iOS Client. If not, see <https://www.gnu.org/licenses/>.
//
import Foundation
/// A login receipt request.
///
/// - Seealso: `AccountProvider.login(...)`
public struct LoginReceiptRequest {
/// The `Data` purchase receipt for the device.
public let receipt: Data
/// :nodoc:
public init(receipt: Data) {
self.receipt = receipt
}
}
......@@ -32,6 +32,8 @@ protocol WebServices: class {
func token(credentials: Credentials, _ callback: LibraryCallback<String>?)
func token(receipt: Data, _ callback: LibraryCallback<String>?)
func info(token: String, _ callback: LibraryCallback<AccountInfo>?)
func update(credentials: Credentials, email: String, _ callback: SuccessLibraryCallback?)
......
......@@ -26,6 +26,7 @@ import SwiftyBeaver
private let log = SwiftyBeaver.self
class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAccess, WebServicesAccess, InAppAccess, WebServicesConsumer {
private let customWebServices: WebServices?
init(webServices: WebServices? = nil) {
......@@ -153,6 +154,42 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
for: first)
}
}
func login(with receiptRequest: LoginReceiptRequest, _ callback: ((UserAccount?, Error?) -> Void)?) {
guard !isLoggedIn else {
preconditionFailure()
}
webServices.token(receipt: receiptRequest.receipt) { (token, error) in
guard let token = token else {
callback?(nil, error)
return
}
self.webServices.info(token: token) { (accountInfo, error) in
guard let accountInfo = accountInfo else {
callback?(nil, error)
return
}
self.updateDatabaseWith(token,
andUsername: "PIA")
//Save after confirm the login was successful.
self.accessedDatabase.plain.accountInfo = accountInfo
let user = UserAccount(credentials: Credentials(username: "", password: ""), info: accountInfo)
Macros.postNotification(.PIAAccountDidLogin, [
.user: user
])
callback?(user, nil)
}
}
}
func login(with request: LoginRequest, _ callback: ((UserAccount?, Error?) -> Void)?) {
guard !isLoggedIn else {
......
......@@ -59,6 +59,37 @@ class PIAWebServices: WebServices, ConfigurationAccess {
})
}
/***
Generates a new auth token for the specific user
*/
func token(receipt: Data, _ callback: ((String?, Error?) -> Void)?) {
let endpoint = ClientEndpoint.token
let status = [200, 401, 429]
let errors: [Int: ClientError] = [
401: .unauthorized,
429: .throttled
]
let parameters = ["store": "apple_app_store",
"receipt": receipt.base64EncodedString()]
req(nil, .post, endpoint, useAuthToken: true, parameters, status, JSONRequestExecutor() { (json, status, error) in
if let knownError = self.knownError(endpoint, status, errors) {
callback?(nil, knownError)
return
}
guard let json = json else {
callback?(nil, error)
return
}
guard let token = GlossToken(json: json)?.parsed else {
callback?(nil, ClientError.malformedResponseData)
return
}
callback?(token, nil)
})
}
func info(token: String, _ callback: ((AccountInfo?, Error?) -> Void)?) {
let endpoint = ClientEndpoint.account
let status = [200, 401, 429]
......
......@@ -25,6 +25,7 @@ import Foundation
/// Simulates account-related operations
public class MockAccountProvider: AccountProvider, WebServicesConsumer {
/// Mocks the outcome of a sign-up operation.
///
/// - Seealso: `AccountProvider.signup(...)`
......@@ -198,6 +199,15 @@ public class MockAccountProvider: AccountProvider, WebServicesConsumer {
delegate.login(with: request, callback)
}
public func login(with receiptRequest: LoginReceiptRequest, _ callback: LibraryCallback<UserAccount>?) {
guard !mockIsUnauthorized else {
callback?(nil, ClientError.unauthorized)
return
}
delegate.login(with: receiptRequest, callback)
}
/// :nodoc:
public func refreshAccountInfo(force: Bool, _ callback: ((AccountInfo?, Error?) -> Void)?) {
guard !mockIsUnauthorized else {
......
......@@ -43,6 +43,12 @@ class MockWebServices: WebServices {
callback?(result, error)
}
func token(receipt: Data, _ callback: ((String?, Error?) -> Void)?) {
let result = "AUTH_TOKEN"
let error: ClientError? = (result == nil) ? .unsupported : nil
callback?(result, error)
}
func info(token: String, _ callback: ((AccountInfo?, Error?) -> Void)?) {
let result = accountInfo?()
let error: ClientError? = (result == nil) ? .unsupported : nil
......
......@@ -62,7 +62,7 @@ internal enum L10n {
internal static let anonymous = L10n.tr("Signup", "purchase.trials.anonymous")
/// Support 10 devices at once
internal static let devices = L10n.tr("Signup", "purchase.trials.devices")
/// Start your 7-days free trial
/// Start your 7-day free trial
internal static let intro = L10n.tr("Signup", "purchase.trials.intro")
/// Connect to any region easily
internal static let region = L10n.tr("Signup", "purchase.trials.region")
......@@ -287,6 +287,10 @@ internal enum L10n {
/// Password
internal static let placeholder = L10n.tr("Welcome", "login.password.placeholder")
}
internal enum Receipt {
/// Login using purchase receipt
internal static let button = L10n.tr("Welcome", "login.receipt.button")
}
internal enum Restore {
/// Didn't receive account details?
internal static let button = L10n.tr("Welcome", "login.restore.button")
......
......@@ -26,6 +26,7 @@ import SwiftyBeaver
private let log = SwiftyBeaver.self
class LoginViewController: AutolayoutViewController, WelcomeChild {
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var labelTitle: UILabel!
......@@ -38,6 +39,8 @@ class LoginViewController: AutolayoutViewController, WelcomeChild {
@IBOutlet private weak var couldNotGetPlanButton: UIButton!
@IBOutlet private weak var loginWithReceipt: UIButton!
var preset: Preset?
private weak var delegate: PIAWelcomeViewControllerDelegate?
......@@ -97,6 +100,65 @@ class LoginViewController: AutolayoutViewController, WelcomeChild {
}
// MARK: Actions
@IBAction private func logInWithReceipt(_ sender: Any?) {
guard !isLogging else {
return
}
guard let receipt = Client.store.paymentReceipt else {
return
}
let request = LoginReceiptRequest(receipt: receipt)
log.debug("Logging in...")
enableInteractions(false)
self.showLoadingAnimation()
preset?.accountProvider.login(with: request) { (user, error) in
self.enableInteractions(true)
self.hideLoadingAnimation()
guard let user = user else {
var errorMessage: String?
if let error = error {
if let clientError = error as? ClientError {
switch clientError {
case .unauthorized:
errorMessage = L10n.Welcome.Login.Error.unauthorized
case .throttled:
errorMessage = L10n.Welcome.Login.Error.throttled
default:
break
}
}
if (errorMessage == nil) {
errorMessage = error.localizedDescription
}
log.error("Failed to log in (error: \(error))")
} else {
log.error("Failed to log in")
}
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: errorMessage ?? L10n.Welcome.Login.Error.title)
return
}
log.debug("Login succeeded!")
self.completionDelegate?.welcomeDidLogin(withUser: user, topViewController: self)
}
}
@IBAction private func logIn(_ sender: Any?) {
guard !isLogging else {
......@@ -194,6 +256,7 @@ class LoginViewController: AutolayoutViewController, WelcomeChild {
Theme.current.applyTitle(labelTitle, appearance: .dark)
Theme.current.applyInput(textUsername)
Theme.current.applyInput(textPassword)
Theme.current.applyButtonLabelMediumStyle(loginWithReceipt)
Theme.current.applyButtonLabelMediumStyle(couldNotGetPlanButton)
}
......@@ -205,6 +268,8 @@ class LoginViewController: AutolayoutViewController, WelcomeChild {
buttonLogin.accessibilityIdentifier = "uitests.login.submit"
couldNotGetPlanButton.setTitle(L10n.Welcome.Login.Restore.button,
for: [])
loginWithReceipt.setTitle(L10n.Welcome.Login.Receipt.button,
for: [])
}
}
......
......@@ -322,7 +322,29 @@ class EphemeralAccountProvider: AccountProvider, ProvidersAccess, InAppAccess {
}
}
func login(with receiptRequest: LoginReceiptRequest, _ callback: ((UserAccount?, Error?) -> Void)?) {
webServices?.token(receipt: receiptRequest.receipt) { (token, error) in
guard let token = token else {
callback?(nil, error)
return
}
self.webServices?.info(token: token) { (info, error) in
guard let info = info else {
callback?(nil, error)
return
}
let user = UserAccount(credentials: Credentials(username: "", password: ""), info: info)
self.currentUser = user
self.isLoggedIn = true
callback?(user, nil)
}
}
}
func refreshAccountInfo(force: Bool, _ callback: ((AccountInfo?, Error?) -> Void)?) {
fatalError("Not implemented")
}
......
......@@ -76,13 +76,13 @@ SPEC CHECKSUMS:
Gloss: 13ab6b4b0ff4cb2448466edc957479b1bccea8ba
lottie-ios: 06e0b54aab85ba128e332687d7f4ac4861a7a7ae
OpenSSL-Apple: 2c9efbc94e0a1ada434cc197b02bd70052a82281
PIAWireguard: 2386a73dd5c658264504b7e1e84cb6bf11795ee4
PIAWireguard: c97665b0ae3d49989a31d2600cd7109364f807d4
PopupDialog: f4bb461bf70ff422be0b56656566424ee9273080
QuickLayout: a730730b646b231fd4ef7cffaeb1e81fe0e1ca92
ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
SwiftEntryKit: 83d312243af7397e38a222b17b7a744b9a7d2145
SwiftyBeaver: 4cc0080d2e23f980652e28978db11a5c9da39165
TunnelKit: 1733ed87934deb57edc7aa5f4b25185bf302a7d4
TunnelKit: d13d005c0c7febd18a03a87e1ab7d4414fef3ffd
PODFILE CHECKSUM: 62df8fef13bf55237e8f3aefb550c71393e9c62c
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment