-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subscription oauth v2 #1033
base: main
Are you sure you want to change the base?
Subscription oauth v2 #1033
Conversation
There's basically no PIR stuff in BSK at the moment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding some comments. Still going through the code.
Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift
Outdated
Show resolved
Hide resolved
...lerErrorMessageObserver/ControllerErrorMesssageObserverThroughDistributedNotifications.swift
Outdated
Show resolved
Hide resolved
try load(options: startupOptions) | ||
|
||
if (try? tokenStore.fetchToken()) == nil { | ||
throw TunnelError.startingTunnelWithoutAuthToken | ||
} | ||
try await load(options: startupOptions) | ||
Logger.networkProtection.log("Startup options loaded correctly") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling needs to be improved here. Previously we could only have startingTunnelWithoutAuthToken
, but now we can get a number of different possible errors from tokenProvider.adopt
and tokenProvider.getTokenContainer
.
These errors should ideally implement CustomNSError
and include underlying error information. See PacketTunnelProvider.TunnelError
for reference.
The reason this is important is because it's what'll allow us to debug tunnel start issues coming from these changes, and understand what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood, atm getTokenContainer can fail for a variety of reasons, from storage to API, decoding and decryption errors, we don't have a single set Error so implementing this is quite difficult, should I wrap any possible error into a higher level TunnelError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'd do that.
We could wrap this in a (possibly new) TunnelError
that contains an underlying error (by implementing CustomNSError
) so we know where this is coming from at a high level, and what's the underlying problem.
That said, I'd encourage you to also consider implementing CustomNSError
in the subscription errors we can get here too as that'll give us detailed error information in our pixels if we do. I know this can sound a bit annoying but the more detail we can get, the easier it will be to understand what's failing if something does fail, as we'll get detailed info in the pixels.
This PR has been inactive for more than 7 days and will be automatically closed 7 days from now. |
Note: I've edited the description to add links to the related PRs for easy of navigation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some additional comments, still going through all this.
Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift
Outdated
Show resolved
Hide resolved
Sources/NetworkProtection/KeyManagement/NetworkProtectionKeychainStore.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's almost nothing PIR related in BSK so I have no comments here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First batch of comments
Tests/BrowserServicesKitTests/Resources/privacy-reference-tests
Outdated
Show resolved
Hide resolved
assertionFailure("Failed to retrieve auth token: \(error)") | ||
} | ||
return nil | ||
} | ||
set(newValue) { | ||
do { | ||
guard let newValue else { | ||
try removeAccessToken() | ||
return | ||
} | ||
try store(accessToken: newValue) | ||
} catch { | ||
assertionFailure("Failed set token: \(error)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we also need error handling for the legacy token store. Having pixel for failures to read/write at point where we for example attempt to migrate the token would be an important signal.
do { | ||
let transactionJWS = try await recoverSubscriptionFromDeadToken() | ||
return .success(transactionJWS) | ||
} catch { | ||
return .failure(.purchaseFailed(OAuthClientError.deadToken)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This handling seems to be incorrect:
- in L:117 you attempt to
recoverSubscriptionFromDeadToken()
- inside this function you call
appStoreRestoreFlow.restoreAccountFromPastPurchase()
- this does not makes sense as this call already failed in L:107 (scenario for no subscription to be recovered via past App Store purchases)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restoreAccountFromPastPurchase
fails because the token is dead, the first action of recoverSubscriptionFromDeadToken
is to sign out the user (aka deleting everything, including the dead token) and re-run restoreAccountFromPastPurchase
. The assumption is that the user had a token (dead) so a subscription was present, so the same subscription is recoverable with a new token.
|
||
do { | ||
let subscription = try await subscriptionManager.confirmPurchase(signature: transactionJWS) | ||
if subscription.isActive { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this check. If the subscription after completing the purchase ends up in wrong state, it is a BE issue and there is little we can do about it, cannot recover from it nor we don't special handle it.
I would only keep the refetch and check if entitlements were granted to the account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to handle this but:
- The request has a 3 max retries with a 4s delay between retries
APIRequestV2.RetryPolicy(maxRetries: 3, delay: 4.0)
but the call itself doesn't fail if the subscription is inactive - If the subscription is inactive we log it and return a failure to the script page with the correct error, so I would like to detect it still. I have never seen a failure here, I'm just trying to fail gracefully in case of BE errors.
- I moved the token refresh out of the if so it is done no matter what.
subscription.platform != .apple { | ||
return externalID | ||
@discardableResult | ||
private func recoverSubscriptionFromDeadToken() async throws -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this is being called 3x from different scenarios but none of them seems for me to be likely to encounter dead token as the account was either just restored or created. In my opinion that while dead token is possible option during the purchase flow it should be treated as other "default" errors but is not recoverable here. We should just fail at given step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this is confusing, so I did the following:
- removed the recovery function call from every
AppStorePurchaseFlow
call and returned the dead token error normally - made recoverSubscriptionFromDeadToken() public and generic, so can be used from averyone receiving a dead token error, I'll update the clients accordingly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second part of the review
1349661
to
402a26b
Compare
402a26b
to
cad6354
Compare
This PR has been inactive for more than 7 days and will be automatically closed 7 days from now. |
Task/Issue URL: https://app.asana.com/0/1205842942115003/1207991044706235/f
iOS PR: duckduckgo/iOS#3480
macOS PR: duckduckgo/macos-browser#3580
What kind of version bump will this require?: Major
CC: @miasma13
iOS PR: duckduckgo/iOS#3480
macOS PR: duckduckgo/macos-browser#3580
Description:
This PR introduces the use of OAuth V2 authentication in Privacy Pro Subscription.
The code changes are comprehensive due to the paradigm changes between the old access token lifecycle and the new JWT lifecycle.
The Subscription UI and UX should be unchanged.
Steps to test this PR:
Test all Privacy Pro Subscription features and UX, more details here
Internal references:
Software Engineering Expectations
Technical Design Template