Skip to content

Commit 32556db

Browse files
KM-17445: use token auth to change email
1 parent ad1f142 commit 32556db

10 files changed

Lines changed: 49 additions & 73 deletions

File tree

LocalPackages/PIAAccount/Sources/PIAAccount/Models/ResponseModels.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,14 @@ public struct DedicatedIPTokenDetails: Codable, Sendable {
300300
/// - username + password
301301
/// - username + apiToken + expiresAt
302302
public struct VpnSignUpInformation: Codable, Sendable {
303+
/// The newly created user's username
303304
public let username: String
305+
/// The newly created user's password. Might be missing due to some error on account creation.
304306
public let password: String?
305307
/// The API token string
306-
public let apiToken: String?
308+
public let apiToken: String
307309
/// ISO 8601 expiration date string
308-
public let expiresAt: String?
310+
public let expiresAt: String
309311

310312
enum CodingKeys: String, CodingKey {
311313
case username = "username"

LocalPackages/PIAAccount/Sources/PIAAccount/PIAAccountAPI.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,6 @@ public protocol PIAAccountAPI {
161161
@discardableResult
162162
func setEmail(email: String, resetPassword: Bool) async throws -> String?
163163

164-
/// Sets or updates the account email (iOS-specific with credentials)
165-
/// - Parameters:
166-
/// - username: Account username
167-
/// - password: Account password
168-
/// - email: New email address
169-
/// - resetPassword: Whether to trigger password reset
170-
/// - Returns: New password if resetPassword is true, nil otherwise
171-
/// - Throws: PIAAccountError if the request fails
172-
@discardableResult
173-
func setEmail(username: String, password: String, email: String, resetPassword: Bool) async throws -> String?
174-
175164
// MARK: - Dedicated IP
176165

177166
/// Retrieves the list of countries and regions where Dedicated IPs are available.

LocalPackages/PIAAccount/Sources/PIAAccount/PIAAccountClient.swift

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -241,34 +241,6 @@ public actor PIAAccountClient: PIAAccountAPI {
241241
return response.password
242242
}
243243

244-
public func setEmail(username: String, password: String, email: String, resetPassword: Bool) async throws -> String? {
245-
// Create Basic Auth header with username:password (matching Kotlin IOSAccount.kt line 244-247)
246-
let credentials = "\(username):\(password)"
247-
guard let credentialsData = credentials.data(using: .utf8) else {
248-
throw PIAAccountError.encodingFailed(
249-
NSError(domain: "PIAAccount", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to encode credentials"])
250-
)
251-
}
252-
let base64Credentials = credentialsData.base64EncodedString()
253-
let headers = ["Authorization": "Basic \(base64Credentials)"]
254-
255-
let formParams = [
256-
"username": username,
257-
"password": password,
258-
"email": email,
259-
"reset_password": resetPassword ? "true" : "false"
260-
]
261-
262-
let response: SetEmailInformation = try await endpointManager.executeWithFailover(
263-
path: .setEmail,
264-
method: .post,
265-
bodyType: .formEncoded(formParams),
266-
headers: headers
267-
)
268-
269-
return response.password
270-
}
271-
272244
// MARK: - Dedicated IP
273245

274246
public func supportedDedicatedIPCountries() async throws -> DipCountriesResponse {
@@ -414,14 +386,11 @@ public actor PIAAccountClient: PIAAccountAPI {
414386
bodyType: .json(bodyData)
415387
)
416388

417-
// store a token if we received one
418-
if let token = response.apiToken, let expiresAt = response.expiresAt {
419-
let tokenResponse = APITokenResponse(apiToken: token, expiresAt: expiresAt)
420-
// Store API token
421-
try await tokenManager.storeAPIToken(tokenResponse)
422-
// Request VPN token
423-
try await refreshVPNToken()
424-
}
389+
let tokenResponse = APITokenResponse(apiToken: response.apiToken, expiresAt: response.expiresAt)
390+
// Store API token
391+
try await tokenManager.storeAPIToken(tokenResponse)
392+
// Request VPN token
393+
try await refreshVPNToken()
425394

426395
return response
427396
}

LocalPackages/PIALibrary/Sources/PIALibrary/Account/DefaultAccountProvider.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ public final class DefaultAccountProvider: AccountProvider, ConfigurationAccess,
119119
guard let username = accessedDatabase.secure.username() else {
120120
return nil
121121
}
122-
guard let password = accessedDatabase.secure.password(for: username) else {
123-
return nil
124-
}
122+
// TODO: do we need this???
123+
let password = accessedDatabase.secure.password(for: username) ?? ""
125124
return UserAccount(
126125
credentials: Credentials(username: username, password: password),
127126
info: accessedDatabase.plain.accountInfo
@@ -495,7 +494,8 @@ public final class DefaultAccountProvider: AccountProvider, ConfigurationAccess,
495494
accessedDatabase.plain.lastSignupEmail = request.email
496495

497496
do {
498-
let (credentials, needsToken) = try await webServices.signup(with: signup)
497+
let signupResponse = try await webServices.signup(with: signup)
498+
let credentials = signupResponse.buildCredentials()
499499

500500
if let transaction = request.transaction {
501501
accessedStore.finishTransaction(transaction, success: true)
@@ -506,9 +506,6 @@ public final class DefaultAccountProvider: AccountProvider, ConfigurationAccess,
506506
accessedDatabase.secure.setUsername(credentials.username)
507507
accessedDatabase.secure.setPassword(credentials.password, for: credentials.username)
508508

509-
if needsToken {
510-
try await webServices.token(credentials: credentials)
511-
}
512509
let accountInfo = try await webServices.info()
513510
accessedDatabase.plain.accountInfo = accountInfo
514511
accessedDatabase.secure.setPublicUsername(accountInfo.username)

LocalPackages/PIALibrary/Sources/PIALibrary/Account/EphemeralAccountProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ final class EphemeralAccountProvider: AccountProvider, ProvidersAccess, InAppAcc
120120
}
121121

122122
do {
123-
guard let (credentials, _) = try await webServices?.signup(with: signup) else {
123+
guard let signupResponse = try await webServices?.signup(with: signup) else {
124124
DispatchQueue.main.async { callback?(nil, nil) }
125125
return
126126
}
@@ -129,7 +129,7 @@ final class EphemeralAccountProvider: AccountProvider, ProvidersAccess, InAppAcc
129129
accessedStore.finishTransaction(transaction, success: true)
130130
}
131131

132-
let user = UserAccount(credentials: credentials, info: nil)
132+
let user = UserAccount(credentials: signupResponse.buildCredentials(), info: nil)
133133
self.currentUser = user
134134
self.isLoggedIn = true
135135
DispatchQueue.main.async { callback?(user, nil) }

LocalPackages/PIALibrary/Sources/PIALibrary/Mock/MockWebServices.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ final class MockWebServices: WebServices {
7070

7171
func deleteAccount() async throws {}
7272

73-
func signup(with request: Signup) async throws -> (credentials: Credentials, needsToken: Bool) {
73+
func signup(with request: Signup) async throws -> SignupResponse {
7474
guard let result = credentials?() else {
7575
throw ClientError.unsupported
7676
}
77-
return (credentials: result, needsToken: true)
77+
return .credentials(result)
7878
}
7979

8080
func redeem(with request: Redeem, _ callback: ((Credentials?, Error?) -> Void)?) {

LocalPackages/PIALibrary/Sources/PIALibrary/WebServices/PIAWebServices.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ final class PIAWebServices: WebServices, ConfigurationAccess {
176176

177177
private func mapNativeLoginError(_ error: Error) -> ClientError {
178178
let code = (error as? PIAAccountError)?.code ?? (error as? PIAMultipleErrors)?.code
179+
log.debug("\(#function) code: \(code ?? 0) error: \(error)")
179180
switch code ?? 0 {
180181
case 402:
181182
return .expired
@@ -223,13 +224,9 @@ final class PIAWebServices: WebServices, ConfigurationAccess {
223224

224225
func update(credentials: Credentials, resetPassword reset: Bool, email: String) async throws {
225226
do {
226-
if reset {
227-
let newPassword = try await nativeAccountAPI.setEmail(email: email, resetPassword: reset)
228-
if let newPassword = newPassword {
229-
Client.configuration.tempAccountPassword = newPassword
230-
}
231-
} else {
232-
try await nativeAccountAPI.setEmail(username: credentials.username, password: credentials.password, email: email, resetPassword: reset)
227+
let newPassword = try await nativeAccountAPI.setEmail(email: email, resetPassword: reset)
228+
if reset, let newPassword {
229+
Client.configuration.tempAccountPassword = newPassword
233230
}
234231
} catch {
235232
throw mapNativeLoginError(error)
@@ -258,7 +255,7 @@ final class PIAWebServices: WebServices, ConfigurationAccess {
258255
}
259256

260257
#if os(iOS) || os(tvOS)
261-
func signup(with request: Signup) async throws -> (credentials: Credentials, needsToken: Bool) {
258+
func signup(with request: Signup) async throws -> SignupResponse {
262259
var marketingJSON = ""
263260
if let marketing = request.marketing {
264261
marketingJSON = stringify(json: marketing)
@@ -278,10 +275,13 @@ final class PIAWebServices: WebServices, ConfigurationAccess {
278275

279276
do {
280277
let response = try await nativeAccountAPI.signUp(information: info)
281-
let needsToken = (response.apiToken?.isEmpty ?? true) || (response.expiresAt?.isEmpty ?? true)
282-
let credentials = Credentials(username: response.username, password: response.password ?? "")
283-
return (credentials: credentials, needsToken: needsToken)
278+
if let password = response.password {
279+
return .credentials(Credentials(username: response.username, password: password))
280+
} else {
281+
return .username(response.username)
282+
}
284283
} catch {
284+
log.error("Failed to signup: \(error)")
285285
let code = (error as? PIAAccountError)?.code ?? (error as? PIAMultipleErrors)?.code
286286
throw code == 400 ? ClientError.badReceipt : ClientError.invalidParameter
287287
}

LocalPackages/PIALibrary/Sources/PIALibrary/WebServices/Signup.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,20 @@ private let log = PIALogger.logger(for: SignupRequest.self)
6363
}
6464
}
6565
#endif
66+
67+
public enum SignupResponse: Sendable {
68+
case username(String)
69+
case credentials(Credentials)
70+
71+
/// Creates ``Credentials`` from the reponse.
72+
///
73+
/// When on the ``username(_:)`` case, password will be empty string.
74+
public func buildCredentials() -> Credentials {
75+
switch self {
76+
case .username(let username):
77+
return Credentials(username: username, password: "")
78+
case .credentials(let credentials):
79+
return credentials
80+
}
81+
}
82+
}

LocalPackages/PIALibrary/Sources/PIALibrary/WebServices/WebServices.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protocol WebServices: AnyObject {
6262
*/
6363
func deleteAccount() async throws
6464

65-
func signup(with request: Signup) async throws -> (credentials: Credentials, needsToken: Bool)
65+
func signup(with request: Signup) async throws -> SignupResponse
6666

6767
func processPayment(credentials: Credentials, request: Payment) async throws
6868

PIA VPN/UI/ConfirmVPNPlanViewController.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ final class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNav
134134
self?.textEmail.text = ""
135135

136136
let alert = Macros.alert(L10n.Signup.Unreachable.vcTitle, L10n.Welcome.Update.Account.Email.error)
137-
alert.addDefaultAction(L10n.Global.close)
137+
alert.addActionWithTitle(L10n.Global.close) {
138+
self?.perform(segue: StoryboardSegue.Signup.successShowCredentialsSegueIdentifier)
139+
}
138140
self?.present(alert, animated: true, completion: nil)
139141

140142
return

0 commit comments

Comments
 (0)