-
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
AuthV2 / Adding v2 classes #1194
Conversation
return attributes | ||
} | ||
} | ||
|
||
public enum KeychainType { |
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.
moved to BSK/Common
@@ -21,7 +21,6 @@ import Common | |||
import os.log | |||
|
|||
public protocol SubscriptionCookieManaging { | |||
init(subscriptionManager: SubscriptionManager, currentCookieStore: @MainActor @escaping () -> HTTPCookieStore?, eventMapping: EventMapping<SubscriptionCookieManagerEvent>) |
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 init doesn't belong to the protocol, removing this the entire manager can be easily mocked
Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionUpdateManagerTests.swift
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.
I have reviewed the code, please find some comments and let me follow with some smoke testing of the apps tomorrow.
/// Not parsed from | ||
public var features: [SubscriptionEntitlement]? | ||
|
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.
What is the purpose for this var if it is not a part of parsed model?
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 SubscriptionEntitlement are created with a combination of (A) user entitlements and (B) subscription entitlements, both A and B are fetched after the subscription is fetched and saved here, and then the entire subscription is saved in the cache. So this is a way of saving the features without overcomplicating with separate models and handling logic.
cacheSerialQueue.sync { | ||
subscriptionCache.reset() | ||
} | ||
// NotificationCenter.default.post(name: .subscriptionDidChange, object: self, userInfo: nil) |
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.
Are we able to determine if this notification is necessary or not? If we are not sure at this point let's have a TODO to verify it later.
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.
Agree, todo added
case .failure(let error): | ||
Logger.subscriptionAppStorePurchaseFlow.error("purchaseSubscription error: \(String(reflecting: error), privacy: .public)") | ||
|
||
await subscriptionManager.signOut(notifyUI: true) |
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 old implementation had an inverse and skipped the notification here. IIRC this was connected with a trigger to reload/close Privacy Pro related tabs and on mac this would hit the very tab you were making the purchase 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.
I'm sure I tested this specific case but I can't remember now, so I'm reverting this to false and add a todo for later
Logger.subscriptionAppStoreRestoreFlow.error("Subscription expired") | ||
// Removing all traces of the subscription and the account | ||
await subscriptionManager.signOut(notifyUI: false) | ||
return .failure(.subscriptionExpired) |
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.
Handling for situation when restore from past purchase gives us expired subscription is not the same as in the old API. Please refer to
BrowserServicesKit/Sources/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift
Line 121 in 20b2408
let details = RestoredAccountDetails(authToken: authToken, accessToken: accessToken, externalID: externalID, email: email) |
/// A `SubscriptionFeature` is **availableForUser** if the logged in user has the required entitlements. | ||
public struct SubscriptionFeatureV2: Equatable, CustomDebugStringConvertible { | ||
public var entitlement: SubscriptionEntitlement | ||
public var availableForUser: Bool |
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.
availableForUser
-> isAvailableForUser
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.
fixed
import Networking | ||
import Common | ||
|
||
public final class SubscriptionTokenKeychainStorageV2: AuthTokenStoring { |
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.
Maybe a better name for SubscriptionTokenKeychainStorageV2
would be AuthTokenKeychainStorage
/ AuthTokenContainerKeychainStorage
/ AccountTokenContainerKeychainStorage
, WDYT?
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 name comes from auth v1 SubscriptionTokenKeychainStorage
, I would like to keep the same name for now for highlighting that they are the same class.
multiple accounts/tokens at the same time | ||
*/ | ||
enum SubscriptionKeychainField: String, CaseIterable { | ||
case tokens = "subscription.v2.tokens" |
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.
tokens
-> tokenContainer
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.
changed
|
||
if referenceCachedTokenContainer == nil { // new login | ||
Logger.subscription.debug("New login detected") | ||
NotificationCenter.default.post(name: .accountDidSignIn, object: self, userInfo: nil) |
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 if this is a good approach, I understand where it came from with translating v1 behaviour to v2 but conceptually the old implementation for the accountDidSignIn/Out
events was triggered by storing/clearing authentication data. Here we have it implemented as a side-effect of the getTokenContainer
getter.
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.
That point in the code is the only place where we know if a login has changed, and it is a function that is used 100% of the time when a token is retrieved.
Removing this notification from here would force us to implement a check manually every time we get a token, I wouldn't know how to do that.
I wouldn't categorise a notification as a side effect; it's simply notifying that something has changed from the only function responsible for retrieving the tokens.
If you find a safer and nicer place where to check, I'm open to suggestions; I haven't found one.
let subscription = try await subscriptionEndpointService.getSubscription(accessToken: "", // Token is unused | ||
cachePolicy: .returnCacheDataDontLoad) | ||
switch subscription.platform { |
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 suspect it is highly unlikely this logic to succeed, if the token is dead it means that it is long time since the app interacted with BE, hence the subscription cache will be stale (currently it is set to 20 minutes).
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. The dead token handling needs to be reviewed completely as you pointed out in your first PR review, and I created a task dedicated to that https://app.asana.com/0/72649045549333/1209252725803000
} | ||
|
||
/// If the client succeeds in making a refresh request but does not get the response, then the second refresh request will fail with `invalidTokenRequest` and the stored token will become unusable and un-refreshable. | ||
private func throwAppropriateDeadTokenError() async throws -> TokenContainer { |
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.
What is the real purpose of this function?
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 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.
👍 LGTM
Task/Issue URL: https://app.asana.com/0/1205842942115003/1209170372758737/f
iOS PR: duckduckgo/iOS#3882
macOS PR: duckduckgo/macos-browser#3789
What kind of version bump will this require?: Patch
Description:
Subscription
renamedPrivacyProSubscription
APIService
renamedSubscriptionAPIService
GetProductsItem
) that code is shared between V1 and V2 and has been moved in the V2 version of the classInternal references:
Software Engineering Expectations
Technical Design Template