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 v2 #3480

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

Conversation

federicocappelli
Copy link
Member

@federicocappelli federicocappelli commented Oct 25, 2024

Task/Issue URL: https://app.asana.com/0/1205842942115003/1207991044706235/f
CC: @miasma13

macOS PR: duckduckgo/macos-browser#3580
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:

Software Engineering Expectations
Technical Design Template

# Conflicts:
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
# Conflicts:
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/AppDependencyProvider.swift
#	DuckDuckGo/NetworkProtectionRootView.swift
#	DuckDuckGo/NetworkProtectionTunnelController.swift
#	DuckDuckGo/Subscription/DefaultSubscriptionManager+AccountManagerKeychainAccessDelegate.swift
#	DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift
#	DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift
#	DuckDuckGo/Subscription/Views/SubscriptionContainerViewFactory.swift
#	PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo/AppDependencyProvider.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
Copy link

github-actions bot commented Oct 25, 2024

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

Generated by 🚫 dangerJS against 5f574f3

# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/Feedback/VPNMetadataCollector.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
…li/subscription_oauth_api_v2

# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@federicocappelli federicocappelli requested review from THISISDINOSAUR and samsymons and removed request for THISISDINOSAUR December 13, 2024 10:51
Copy link

This PR has been inactive for more than 7 days and will be automatically closed 7 days from now.

Copy link

This PR has been inactive for more than 7 days and will be automatically closed 7 days from now.

# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/AppDelegate.swift
#	DuckDuckGo/SettingsViewModel.swift
#	DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/AppDependencyProvider.swift
#	DuckDuckGo/SettingsState.swift
#	DuckDuckGo/SettingsSubscriptionView.swift
#	DuckDuckGoTests/Subscription/SubscriptionPagesUseSubscriptionFeatureTests.swift
@diegoreymendez
Copy link
Contributor

Note: I've edited the description to add links to the related PRs for easy of navigation.

@diegoreymendez
Copy link
Contributor

I'm seeing trouble in iOS to get the subscription enabled. After recovering my subscription I see this message.

Screenshot 2025-01-10 at 13 27 57 jpeg

@federicocappelli
Copy link
Member Author

@diegoreymendez same issue than macOS, fixing now

@diegoreymendez
Copy link
Contributor

diegoreymendez commented Jan 13, 2025

I messaged you directly, but I'll still add a message here for history. Just tested the latest test build on iOS and the subscription expired after about 1 hour of usage (the subscription isn't expired as I'm using my own long-living account).

PixelParameters.source: "browser"
]

DailyPixel.fireDailyAndCount(pixel: .privacyProKeychainAccessError,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as for macOS, in new code we retained the 'privacyProKeychainAccessError' pixel but there are no places where it is fired. Additionally we lack feedback from v2 keychain store regarding access errors.

@@ -283,7 +283,7 @@ struct NetworkProtectionStatusView: View {

@ViewBuilder
private func about() -> some View {
let viewModel = UnifiedFeedbackFormViewModel(subscriptionManager: statusModel.subscriptionManager,
let viewModel = UnifiedFeedbackFormViewModel(subscriptionManager: AppDependencyProvider.shared.subscriptionManager,
Copy link
Contributor

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 wrongly resolved merge conflict, we can obtain the subsc. manager from the model. Left line seems to be correct.

guard case .success(false) = await accountManager.hasEntitlement(forProductName: .networkProtection) else { return }
let subscriptionManager = AppDependencyProvider.shared.subscriptionManager

guard let tokenContainer = try? await subscriptionManager.getTokenContainer(policy: .local),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should't we use 'guard !subscriptionManager.isFeatureActive(.networkProtection)' here?

private let settings: VPNSettings
private let defaults: UserDefaults

init(statusObserver: ConnectionStatusObserver,
serverInfoObserver: ConnectionServerInfoObserver,
accountManager: AccountManager = AppDependencyProvider.shared.subscriptionManager.accountManager,
serverInfoObserver: ConnectionServerInfoObserver,// ConnectionServerInfoObserverThroughSession(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Leftover?


struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility {
private let userDefaults: UserDefaults
private let accountManager: AccountManager
private let oAuthClient: any OAuthClient
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we use 'any SubscriptionManager' here?

do {
try await self.subscriptionManager.getTokenContainer(policy: .localForceRefresh)
Logger.subscription.log("Token background refresh task completed successfully in \(Date().timeIntervalSince(refreshStartDate)) seconds")
task.setTaskCompleted(success: true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we schedule another task after completing one?

Comment on lines 527 to 530
Logger.networkProtection.log("Subscription Entitlements check...")
let isNetworkProtectionEnabled = await subscriptionManager.isFeatureActive(.networkProtection)
Logger.networkProtection.log("NetworkProtectionEnabled if: \( isNetworkProtectionEnabled ? "Enabled" : "Disabled", privacy: .public)")
return .success(isNetworkProtectionEnabled)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if valid, but earlier the closure returned .failure when fetching entitlement failed. Is this not possible for the new implementation of entitlements?

let purchaseTransactionJWS: String

switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id,
emailAccessToken: emailAccessToken) {
switch await appStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will look closer at it in BSK but I see you dropped the emailAccessToken: emailAccessToken) param from the purchase call.
This was the access token from Email Protection feature. When we were creating a new account during the purchase and we passed this token as param the user account on BE was linked with user's Duck address from Email Protection. Not sure if this is still supported in v2.

Comment on lines +323 to +328
_ = try await subscriptionManager.exchange(tokenV1: authToken)
Logger.subscription.log("v1 token exchanged for v2")

onSetSubscription?()

} else {
Logger.subscription.error("Failed to obtain subscription options")
} catch {
Logger.subscription.error("Failed to exchange v1 token for v2")
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe worthy adding a pixel for (also on macOS)?

Comment on lines +355 to +375
// guard let accessToken = accountManager.accessToken else {
// Logger.subscription.error("Missing access token")
// return nil
// }
//
// switch await accountManager.fetchAccountDetails(with: accessToken) {
// case .success(let accountDetails):
// switch await subscriptionManager.subscriptionEndpointService.getSubscription(accessToken: accessToken) {
// case .success:
// accountManager.storeAccount(token: accessToken,
// email: accountDetails.email,
// externalID: accountDetails.externalID)
// onBackToSettings?()
// case .failure(let error):
// Logger.subscription.error("Error retrieving subscription details: \(error.localizedDescription)")
// }
// case .failure(let error):
// Logger.subscription.error("Could not get account Details: \(error.localizedDescription)")
// setTransactionError(.generalError)
// }
// return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Probably commented out code can be removed.
  2. What about handling the error scenarios like in original code e.g. L:373?

# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift
# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	DuckDuckGo/AppLifecycle/AppStates/Inactive.swift
#	DuckDuckGo/OldAppDelegate.swift
# Conflicts:
#	DuckDuckGo/DuckPlayer/DuckPlayer.swift
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.

3 participants