Sign in with Apple (backend + iOS) + repricing to $10/20/30/50/100 - #188
Open
mattsodomsky wants to merge 3 commits into
Open
Sign in with Apple (backend + iOS) + repricing to $10/20/30/50/100#188mattsodomsky wants to merge 3 commits into
mattsodomsky wants to merge 3 commits into
Conversation
App Store guideline 4.8 requires a privacy-preserving login alongside Google. This adds Sign in with Apple WITHOUT touching the encryption model: it's a second, independent provider, never linked to Google. - identity_key seam: google_id generalizes to identity_key (the raw provider subject). For Google it's the Google sub (unchanged — same PBKDF2 key, same SHA256 primary key, zero data migration); for Apple it's "apple:<sub>", its own key space. google_id stays as a backward-compatible alias so existing plumbing is untouched. - NativeToken carries identity_key and still reads legacy google_id tokens (they live up to a year in keychains). - AppleIdentity verifies Apple's identity-token JWS against Apple's public JWKS (signature, iss, aud=bundle id, exp, sha256(nonce) replay defense) and returns the sub. No secret needed — Apple's keys are public. Fully unit-tested with a throwaway signing key + stubbed JWKS. - POST /native/apple_auth mints a bearer token for the Apple identity. No consent gate: the token is already app-bound, so the confused-deputy risk the Google web flow guards against doesn't exist here. 256 -> existing suite still green (the seam is transparent); +11 new specs for Apple verification and the endpoint, incl. proof an Apple identity never collides with a Google resonance sharing the raw sub. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New offered tiers on both web and iOS: $10/$20/$30/$50/$100 (keeps tier_10 and tier_100; adds tier_20/30/50; retires $1/$1000 from offers). Split 'offered' from 'recognized as active' for both Stripe and Apple/ Google: - STRIPE_PRICE_IDS / *_PRODUCT_IDS = the 5 offered tiers (checkout) - *_RECOGNIZED_* = offered + grandfathered $1/$1000 (entitlement) A subscription's price lives at the storefront, not our DB — so dropping the old price IDs from recognition would keep billing an existing $1 or $1000 subscriber while cutting off their access. Recognizing the legacy ids grandfathers them; we simply stop *offering* those tiers. Regression proves a retired-tier subscriber stays active. Web settings buttons now render dynamically from STRIPE_PRICE_IDS (label derived from the tier name), so the view can never drift from the config again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the client half to this PR (was going to be a follow-on). - Sign in with Apple on the landing screen, using Apple's own SignInWithAppleButton. The request sets request.nonce = sha256(nonce); on completion the app posts the identity token + raw nonce to /native/apple_auth (built earlier in this PR), stores the bearer token, and refreshes. Cancellation is silent. AppleSignIn holds the nonce helpers; the flow lives in AppModel.completeAppleSignIn. - com.apple.developer.applesignin entitlement + CODE_SIGN_ENTITLEMENTS wired in project.yml. (The capability must also be enabled on the App ID in the Apple Developer portal — see the PR checklist.) - Product ids swapped to the new tiers tier_10/20/30/50/100; Yours.storekit regenerated so simulator testing has the 5 products. Builds clean; landing screen verified in the simulator showing both sign-in buttons at the right size/style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backend half of the App-Store launch push. Two changes, both server-side and fully tested; the iOS UI half (the Apple button + new StoreKit product list) is a follow-on PR. This is safe to review and merge on its own — it doesn't change any client behavior until the iOS PR ships.
268 specs, 0 failures. Rubocop clean. Boots clean.
1. Sign in with Apple — parallel identity provider
Guideline 4.8 needs a privacy-preserving login alongside Google. This adds Sign in with Apple without touching the encryption model — it's a second, independent provider, never linked to Google.
identity_keyseam:google_idgeneralizes toidentity_key(the raw provider subject). Google → the Googlesub(unchanged: same PBKDF2 key, same SHA256 primary key, zero data migration). Apple →apple:<sub>, its own key space.google_idstays as a backward-compatible alias, so all existing plumbing is untouched.AppleIdentityverifies Apple's identity-token JWS against Apple's public JWKS (signature,iss,aud= bundle id,exp, andsha256(nonce)replay defense). No secret needed — Apple's keys are public.POST /native/apple_authmints a bearer token for the Apple identity. No consent gate (the token is app-bound, so the confused-deputy risk the Google web flow guards against doesn't exist).substring.2. Repricing → $10 / $20 / $30 / $50 / $100 (web + iOS)
Keeps
tier_10/tier_100, addstier_20/30/50, retires$1/$1000from offers — with grandfathering. A subscription's price lives at the storefront, not our DB, so dropping the old price IDs from recognition would keep billing an existing$1/$1000subscriber while cutting their access. Fix: split offered (checkout) from recognized-as-active (offered + grandfathered legacy). Existing old-tier subscribers keep access; we just stop offering those tiers. Web buttons now render dynamically from the config so they can't drift.✅ Your part (external setup — none of it blocks reviewing/merging this code)
None of these are done yet; the code reads them from env / talks to the storefronts once you've set them up. Do them before the iOS follow-on PR ships to TestFlight.
A. Apple Developer / Xcode
fyi.yours.app(Apple Developer → Identifiers → your App ID → Capabilities). Also add it in Xcode's Signing & Capabilities when the iOS PR lands.APPLE_SIGN_IN_CLIENT_IDSif you ever add a Services ID;auddefaults to the bundle id.)B. App Store Connect — subscription products
fyi.yours.subscription.tier_20— $20fyi.yours.subscription.tier_30— $30fyi.yours.subscription.tier_50— $50tier_10andtier_100as-is. Leavetier_1/tier_1000alone too (don't delete — grandfathering; just no longer offered).C. Stripe (web) — prices + env
STRIPE_PRICE_ID_20,STRIPE_PRICE_ID_30,STRIPE_PRICE_ID_50. KeepSTRIPE_PRICE_ID_10andSTRIPE_PRICE_ID_100.STRIPE_PRICE_ID_1/STRIPE_PRICE_ID_1000set on Fly so they stay recognized. If you're certain there are none, you can leave them unset.D. After the iOS follow-on PR
APPLE_PRODUCT_IDS(or rely on the defaults) at the 5 new product ids; updateYours.storekitfor local testing.What I'll do next (the iOS follow-on PR)
ASAuthorizationController) to the landing screen, POST the token to/native/apple_auth.Yours.storekitto the 5 new tiers.🤖 Generated with Claude Code