PicoMLX / PicoAIProxy

Reverse proxy for OpenAI and Anthropic written in server-side Swift
MIT License
103 stars 6 forks source link

Question: Why not use transactionId as the user identifier? #2

Open atacan opened 4 months ago

atacan commented 4 months ago

First of all, thank you for the great work, and sorry to hear the financial loss that you had.

I was wondering why transaction ID was not used as a user identifier instead you rely on app account token that is set by the app during the purchase. Isn't originalTransactionId also unique by an Apple account that made the purchase?

I thought wherever I save it, the user can delete the app account token. UserDefaults, Keychain, Application support etc. can easily be manipulated by the user on macOS.

ronaldmannak commented 4 months ago

That's a great question. The app account token is not stored on the client, but on Apple App Store servers. These are the steps: 1) Client creates a unique UUID 2) Token is passed to Apple's App Store servers using product.purchase() 3) The proxy fetches the the app account token from the App Store Servers

I could be wrong, but I think there are edge cases where the originalTransactionId isn't available, for instance when not the original transaction receipt is available. I'm also not sure if originalTransactionId works in case of family subscriptions. In both cases, app account token does work. The app account token is used for the rate limiter. In case no app account token is set, you can disable the rate limiter using the environment variables by setting enableRateLimiter to 0. Note that the app account token is a different token than the JWT session token, which is stored on the client side. The session token is just a convenience token so the proxy doesn't have to make a slow call to the Apple App Store Server for every call.

atacan commented 4 months ago

Thank you.

The transaction id that the client puts in the fetchToken request's body is this TranSaction id https://developer.apple.com/documentation/storekit/transaction/3749696-id, right?

I see that it's a UInt64. I haven't had any real transaction ID, could you tell me whether the following concern makes sense?

If this id is a naively incremented integer like 1,2,3... for each transaction, then the attacker can easily try a few integers and find one that would correspond to an active subscription's transaction ID. They can call the /appstore endpoint whenever they need to get a new token.

ronaldmannak commented 3 months ago

@atacan: I've updated the in-app purchase validation and Pico Proxy now verifies the JWS representation of StoreKit 2's Transaction. The previous step where Pico Proxy calls the App Store Server is no longer necessary. See for more details: https://medium.com/p/98626641d3ea

The AppStoreAccount token is still used to identify the original purchaser so that there is one rate limit for the whole family (in case the developer enabled family sharing).