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

[PoC]: disallow sharing anonymous app store account #3778

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

aboedo
Copy link
Member

@aboedo aboedo commented Mar 27, 2024

This adds a new dangerousSetting that allows the SDK to detect when the user has logged out of their App Store account on an anonymous ID, and reset the user cache in order to revoke access.

This is very much a draft, but I tested this locally by:

  • enabling the setting
  • making a purchase as anonymous user 1
  • deleting transactions through StoreKit Test to simulate an app store logout
  • foregrounding the SDK
  • access revoked

Notes:

  • This is a dangerousSetting, and will likely not be a behavior most developers would want to have enabled. But it might be useful for some developers, as a way to prevent account sharing as an exploit.
  • This is only compatible with StoreKit 2, as we need a single system to check purchasing history, and StoreKit 1 is fairly unreliable for it given receipt refresh & throttle issues.
  • This could be similarly implemented for Android
  • Tests are broken, I need to add the systemInfo param in a couple of places
  • No new tests added yet, but this should be coverable by BackendIntegrationTests using StoreKit Test.

@aboedo aboedo added the feat A new feature label Mar 27, 2024
@aboedo aboedo self-assigned this Mar 27, 2024
Comment on lines +155 to +156
if self.currentUserIsAnonymous
&& !systemInfo.dangerousSettings.disallowSharingAppStoreAccountsForAnonymousIDs {
Copy link
Member Author

Choose a reason for hiding this comment

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

logOut on anonymous user would normally fail, this enables it for the dangerous setting

Copy link
Contributor

Choose a reason for hiding this comment

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

i assume the state is correct but "not disallow sharing" sounds like it should be "allowing sharing". Am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you asking about the flag name or the behavior?

I mean, my brain was fried when writing and this might not be a good flag name, but conceptually this says "on logout, if the user is anonymous and you want to allow for sharing between anonymous ids (so, not disallow), error out", which basically maps to existing default behavior.

If the user is anonymous and you don't want to alias anonymous IDs, then we allow logOut to proceed.

I think this is correct?

Comment on lines +161 to +163
let newAppUserID = Self.generateRandomID()
self.resetCacheAndSave(newUserID: newAppUserID)
Logger.info(Strings.identity.log_out_success(newAppUserID: newAppUserID))
Copy link
Member Author

Choose a reason for hiding this comment

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

only change here is to make it easier to see in logs what the new user is

Comment on lines +86 to +90
case .app_store_logout_detected_anonymous_id_not_sharing:
return "The current user doesn't have transactions in their App Store Account, " +
"but their corresponding CustomerInfo does. \n " +
"The user will be reset under assumption that they've logged out of their App Store Account " +
"\n becuase of the disallowSharingAppStoreAccountsForAnonymousIDs dangerousSetting"
Copy link
Member Author

Choose a reason for hiding this comment

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

my brain is fried, this copy can probably be cleaned up for readability

*/
private extension Purchases {

func resetUserIfPurchaseHistoryIsEmptyIfNeeded() {
Copy link
Member Author

Choose a reason for hiding this comment

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

here's where the magic lives:

  • check if the current customer info has any purchases. If they don't, then no-op, so that we're not creating new anonymous users all the time.
  • If the customer info has any purchases, but the app store account doesn't have, then assume that this is a log out. This is only valid for anonymous users, since identified users could have purchases from a different store.

Comment on lines +52 to +54
.with(dangerousSettings: .init(autoSyncPurchases: true,
internalSettings: DangerousSettings.Internal(usesStoreKit2JWS: useStoreKit2),
disallowSharingAppStoreAccountsForAnonymousIDs: true))
Copy link
Member Author

Choose a reason for hiding this comment

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

example usage, but this can be removed before merging.

Copy link
Member Author

Choose a reason for hiding this comment

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

note that it needs to be used alongside SK2 + SK2 JWTs

Copy link
Member Author

Choose a reason for hiding this comment

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

@MarkVillacampa there isn't a way to force SK2 JWT before iOS v5, right? If so I'll have to rebase this PR on that

Copy link
Member

Choose a reason for hiding this comment

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

Not an easy way to force SK2 JWT before v5, no. But do we have to check that flag to use SK2 for this very specific dangerous setting? I'd assume if somebody enables it they know what they're doing :D

Copy link
Member Author

@aboedo aboedo Apr 1, 2024

Choose a reason for hiding this comment

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

But I mean, you can't use the flag in any case because it's internal, so they wouldn't be able to access it unless they do @testable (what this sample app is doing) or otherwise fork the SDK, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat A new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants