Skip to content

Sign in with Apple (backend + iOS) + repricing to $10/20/30/50/100 - #188

Open
mattsodomsky wants to merge 3 commits into
mainfrom
claude/apple-signin-pricing
Open

Sign in with Apple (backend + iOS) + repricing to $10/20/30/50/100#188
mattsodomsky wants to merge 3 commits into
mainfrom
claude/apple-signin-pricing

Conversation

@mattsodomsky

@mattsodomsky mattsodomsky commented Jul 9, 2026

Copy link
Copy Markdown
Member

Update — the iOS UI is now in this PR too. It's no longer backend-only. The landing screen has the Sign in with Apple button (Apple's SignInWithAppleButton, nonce → /native/apple_auth), the entitlement is wired, and the StoreKit product list is the new 5 tiers. Builds clean; landing screen verified in the simulator (both buttons, correct size/style). The on-device Apple round-trip still needs a real device + the capability enabled (checklist item A).

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_key seam: google_id generalizes to identity_key (the raw provider subject). Google → the Google sub (unchanged: same PBKDF2 key, same SHA256 primary key, zero data migration). Apple → apple:<sub>, its own key space. google_id stays as a backward-compatible alias, so all existing plumbing is untouched.
  • AppleIdentity verifies Apple's identity-token JWS against Apple's public JWKS (signature, iss, aud = bundle id, exp, and sha256(nonce) replay defense). No secret needed — Apple's keys are public.
  • POST /native/apple_auth mints 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).
  • A regression proves an Apple identity never collides with a Google resonance that happens to share the raw sub string.

2. Repricing → $10 / $20 / $30 / $50 / $100 (web + iOS)

Keeps tier_10/tier_100, adds tier_20/30/50, retires $1/$1000 from 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/$1000 subscriber 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

  • Enable the "Sign in with Apple" capability on the App ID fyi.yours.app (Apple Developer → Identifiers → your App ID → Capabilities). Also add it in Xcode's Signing & Capabilities when the iOS PR lands.
  • No secret to add for verifying the token — Apple's keys are public. (Only set APPLE_SIGN_IN_CLIENT_IDS if you ever add a Services ID; aud defaults to the bundle id.)

B. App Store Connect — subscription products

  • Create three new auto-renewable subscription products, in the existing subscription group, priced monthly:
    • fyi.yours.subscription.tier_20 — $20
    • fyi.yours.subscription.tier_30 — $30
    • fyi.yours.subscription.tier_50 — $50
  • Leave tier_10 and tier_100 as-is. Leave tier_1/tier_1000 alone too (don't delete — grandfathering; just no longer offered).
  • Fill each product's localization/review info so they're "Ready to Submit" — the first IAP change must ship attached to the app version.

C. Stripe (web) — prices + env

  • Create three new recurring monthly prices: $20, $30, $50. ($10 and $100 already exist.)
  • Set these env vars on Fly (production) — new: STRIPE_PRICE_ID_20, STRIPE_PRICE_ID_30, STRIPE_PRICE_ID_50. Keep STRIPE_PRICE_ID_10 and STRIPE_PRICE_ID_100.
  • Grandfathering: if you have existing $1/$1000 Stripe subscribers, keep STRIPE_PRICE_ID_1 / STRIPE_PRICE_ID_1000 set 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

  • Point APPLE_PRODUCT_IDS (or rely on the defaults) at the 5 new product ids; update Yours.storekit for local testing.
  • Build → TestFlight smoke (real Apple sign-in on device + a sandbox purchase of a new tier) → submit the app version with the new IAPs, manual release after approval.

What I'll do next (the iOS follow-on PR)

  • Add the Sign in with Apple button (ASAuthorizationController) to the landing screen, POST the token to /native/apple_auth.
  • Swap the iOS product id list + Yours.storekit to the 5 new tiers.
  • Wire, build to a device, and hand you a TestFlight/submission checklist.

🤖 Generated with Claude Code

mattsodomsky and others added 2 commits July 9, 2026 12:57
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>
@mattsodomsky mattsodomsky changed the title Backend: Sign in with Apple (parallel provider) + repricing to $10/20/30/50/100 Sign in with Apple (backend + iOS) + repricing to $10/20/30/50/100 Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant