Skip to content
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 api v2 #3580

Open
wants to merge 77 commits into
base: main
Choose a base branch
from

Conversation

federicocappelli
Copy link
Member

@federicocappelli federicocappelli commented Nov 21, 2024

Task/Issue URL: https://app.asana.com/0/1205842942115003/1207991044706236/f
Tech Design URL: https://app.asana.com/0/1205842942115003/1207991044706232/f
CC: @miasma13

iOS PR: duckduckgo/iOS#3480
BSK PR: duckduckgo/BrowserServicesKit#1033

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:

Pull Request Review Checklist
Software Engineering Expectations
Technical Design Template
Pull Request Documentation

# Conflicts:
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@federicocappelli federicocappelli changed the title Fcappelli/subscription oauth api v2 Subscription oauth api v2 Nov 22, 2024
Copy link
Contributor

github-actions bot commented Nov 26, 2024

Warnings
⚠️ PR has more than 500 lines of code changing. Consider splitting into smaller PRs if possible.

Generated by 🚫 dangerJS against f3a5d24

# Conflicts:
#	DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/Application/AppDelegate.swift
#	DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift
#	DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift
#	DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift
#	DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift
#	LocalPackages/SubscriptionUI/Sources/SubscriptionUI/DebugMenu/SubscriptionDebugMenu.swift
#	LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift
#	UnitTests/Freemium/DBP/Experiment/FreemiumDBPPixelExperimentManagingTests.swift
#	UnitTests/Freemium/DBP/FreemiumDBPFeatureTests.swift
#	UnitTests/Menus/MoreOptionsMenuTests.swift
#	UnitTests/Subscription/SubscriptionAppStoreRestorerTests.swift
#	UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureForStripeTests.swift
#	UnitTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift
#	UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift
@federicocappelli
Copy link
Member Author

I'm still seeing the same issue I reported Friday where the VPN stops on its own and shows me a subscription expired error.

Steps to reproduce:

  • Connect the VPN
  • Leave the computer unattended for ~1 hour.
  • Wake computer from sleep - VPN is disconnected and shows a subscription expired error.

I'm still testing to narrow down the precise steps but it seems to failing reliably on my end.

Which environment and purchase platform are you using? App store subscriptions are renewing every 5 minutes until a max of (can't remember) times, so finding the VPN disconnected seems correct, are the settings showing an expired subscription?

@diegoreymendez
Copy link
Contributor

I'm using my pre-existing account from stripe + staging.

@federicocappelli
Copy link
Member Author

I'm using my pre-existing account from stripe + staging.

Then it's not right, I'm investigating

@diegoreymendez
Copy link
Contributor

diegoreymendez commented Jan 13, 2025

Did some additional testing - it's not always disconnecting, but when it doesn't disconnect I often see this. The subscription was added correctly and the VPN was enabled with a working subscription (my own pre-existing subscription in staging).

Screenshot 2025-01-13 at 1 57 00 PM

Comment on lines 970 to 981
let features = await subscriptionManager.currentSubscriptionFeatures(forceRefresh: false)
let vpnFeature = features.first { $0.entitlement == .networkProtection }
let dbpFeature = features.first { $0.entitlement == .dataBrokerProtection }
let itrFeature = features.first { $0.entitlement == .identityTheftRestoration }
let itrgFeature = features.first { $0.entitlement == .identityTheftRestorationGlobal }

Task { @MainActor in
self.networkProtectionItem.isEnabled = vpnFeature?.enabled ?? false
self.dataBrokerProtectionItem.isEnabled = dbpFeature?.enabled ?? false
let hasIdentityTheftRestoration = itrFeature?.enabled ?? false
let hasIdentityTheftRestorationGlobal = itrgFeature?.enabled ?? false
self.identityTheftRestorationItem.isEnabled = hasIdentityTheftRestoration || hasIdentityTheftRestorationGlobal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can migrate the whole block to the new simpler entitlement check with subscriptionManager.isFeatureActive(...)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subscriptionManager.isFeatureActive(...) is a bit too heavy for checking all entitlements because it is fetching (local) auth token and subscription every time, this is the same but just faster. We have two places where we use this approach, the code duplication is not super pretty but it's difficult to parametrise.

DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift Outdated Show resolved Hide resolved
DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift Outdated Show resolved Hide resolved
@@ -397,13 +385,14 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
#if NETP_SYSTEM_EXTENSION
"\(Bundle.main.bundleIdentifier!).authToken"
#else
NetworkProtectionKeychainTokenStore.Defaults.tokenStoreService
"com.duckduckgo.networkprotection.authToken"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a constant here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I transformed tokenServiceName in a let so that's now a constant, there was no need to be a var calculated at runtime.
I didn't move the string somewhere else (like in the previous implementation) because is only used there.

@@ -436,16 +417,60 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
case .staging:
subscriptionEnvironment.serviceEnvironment = .staging
}
subscriptionEnvironment.purchasePlatform = .stripe // we don't care about the purchasePlatform
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've no idea why we don't care about the purchase platform. Could we document what this means, maybe providing a link with reference or a clearer explanation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added: The SysExt doesn't care about the purchase platform because the only operations executed here are about the Auth token. No purchase or platforms-related operations are performed.

serviceName: Self.tokenServiceName,
keychainType: Bundle.keychainType)
let authClient = DefaultOAuthClient(tokensStorage: tokenStorage,
legacyTokenStorage: nil, // Note: The VPN SysExt will stop at the first transition from auth v1 to v2, is up to the user to re-enable it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this to me in a bit more detail? It's not clear to me what the "transition" is... is it the first time they manually start the VPN?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've confirmed with Thomas that it is actually possible to exchange a token v1 multiple times so this comment is not relevant anymore, I've updated the code so the SysExt can now perform a full migration from auth v1 to v2 and never stop

Copy link
Contributor

@diegoreymendez diegoreymendez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looking but added several comments.

Copy link
Contributor

@THISISDINOSAUR THISISDINOSAUR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing and PIR seems to work now. I've just left one minor comment on the code

@@ -52,6 +53,7 @@ struct DataBrokerProtectionAppEvents {
// In this case, let's disable the agent and delete any left-over data because there's nothing for it to do
if let profileQueriesCount = try? DataBrokerProtectionManager.shared.dataManager.profileQueriesCount(),
profileQueriesCount > 0 {
Logger.dataBrokerProtection.log("Found \(profileQueriesCount) profile queries in DB. Disabling agent.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is wrong (confused by the comment above no longer being accurate)
If there are profile queries, we don't disable it, we want the agent to be running. We restart it rather than disable it (to help with the flakeyness of login items)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, modified with "Restarting agent"

@samsymons samsymons removed their request for review January 15, 2025 05:36
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/Common/Extensions/WKWebViewExtension.swift
#	LocalPackages/NewTabPage/Sources/NewTabPage/Configuration/NewTabPageConfigurationClient.swift
#	LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesClient.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants