From 4f86c6cbc5ddd25444645a9a741b0920123aa012 Mon Sep 17 00:00:00 2001 From: npub12wpjffj7q5qjsky5jvk4ldwlxmse5xll3d8gytk4wqd0c5y7jvwspg37n6 <538324a65e0501285894932d5fb5df36e19a1bff8b4e822ed5701afc509e931d@buzz.block.builderlab.xyz> Date: Fri, 31 Jul 2026 15:56:53 -0700 Subject: [PATCH] fix(mobile/ios): align Apple app identities Co-authored-by: Tom Brow Signed-off-by: Tom Brow --- docs/push-gateway-deployment.md | 7 +++ mobile/README.md | 3 +- mobile/ios/Flutter/Debug.xcconfig | 2 +- mobile/ios/Flutter/Release.xcconfig | 2 +- mobile/ios/Runner.xcodeproj/project.pbxproj | 12 ++--- mobile/ios/Runner/Runner.entitlements | 2 +- scripts/mobile-worktree-clean.sh | 12 ++--- scripts/mobile-worktree-overrides.sh | 2 +- scripts/test-mobile-worktree-overrides.sh | 55 +++++++++++++++++++-- 9 files changed, 76 insertions(+), 21 deletions(-) diff --git a/docs/push-gateway-deployment.md b/docs/push-gateway-deployment.md index 9ca28b851..68a5a03a3 100644 --- a/docs/push-gateway-deployment.md +++ b/docs/push-gateway-deployment.md @@ -24,6 +24,13 @@ | `BUZZ_PUSH_GRANT_KEYS` | Capability AEAD keyring, `id:base64-32-bytes[,predecessor...]`; current key first. | | `BUZZ_PUSH_TOKEN_KEYS` | Independent token-custody AEAD keyring in the same format. Never reuse grant keys. | +Each gateway deployment represents exactly one Apple team and bundle-id pair: +`BUZZ_PUSH_APP_ATTEST_APP_ID` and `BUZZ_PUSH_APNS_TOPIC` are scalar startup +settings, and the mounted APNs certificate must match that topic. Dogfood +(`JMTDPW9CG3` / `xyz.block.buzz.dogfood.mobile`) and production +(`EYF346PHUG` / `xyz.block.buzz.mobile`) therefore require separate gateway +deployments with their own matching settings and credentials. + Optional endpoint quota policy variables are `BUZZ_PUSH_ENDPOINT_QUOTA_WINDOW_SECONDS` (default `10`, max `86400`) and `BUZZ_PUSH_ENDPOINT_QUOTA_MAX_DELIVERIES` (default `10`, max `10000`). These are Buzz policy hypotheses, not Apple-published limits; tune under load while retaining a hard ceiling. ## Secret and key rotation rules diff --git a/mobile/README.md b/mobile/README.md index ada0568e2..ba5af8059 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -22,7 +22,8 @@ cd mobile && flutter run ### Worktree-aware debug identity Debug builds produced from a git worktree get a unique app identifier keyed -to the **worktree directory name** (`com.buzz.buzzMobile.` on iOS, +to the **worktree directory name** +(`xyz.block.buzz.dogfood.mobile.` on iOS, `xyz.block.buzz.mobile.` on Android) plus a display-only branch label in the app name (`Buzz (my-branch)`, or a short SHA when the worktree is detached). Because the identifier follows the directory rather than the diff --git a/mobile/ios/Flutter/Debug.xcconfig b/mobile/ios/Flutter/Debug.xcconfig index 3225694e1..1bb634c21 100644 --- a/mobile/ios/Flutter/Debug.xcconfig +++ b/mobile/ios/Flutter/Debug.xcconfig @@ -5,7 +5,7 @@ // without patching tracked files by writing // `mobile/ios/Flutter/AppOverrides.xcconfig` containing // `BUNDLE_IDENTIFIER = your.app.id` (gitignored). -BUNDLE_IDENTIFIER = com.buzz.buzzMobile +BUNDLE_IDENTIFIER = xyz.block.buzz.dogfood.mobile APP_DISPLAY_NAME = Buzz BUZZ_APP_GROUP_IDENTIFIER = group.$(BUNDLE_IDENTIFIER) BUZZ_KEYCHAIN_ACCESS_GROUP = $(BUNDLE_IDENTIFIER) diff --git a/mobile/ios/Flutter/Release.xcconfig b/mobile/ios/Flutter/Release.xcconfig index 2032998ca..36aa98436 100644 --- a/mobile/ios/Flutter/Release.xcconfig +++ b/mobile/ios/Flutter/Release.xcconfig @@ -4,7 +4,7 @@ // Defaults for the iOS Release build. Internal/custom builds (e.g. an // enterprise-signed distribution) override these without patching tracked // files by writing `mobile/ios/Flutter/AppOverrides.xcconfig` (gitignored). -BUNDLE_IDENTIFIER = com.buzz.buzzMobile +BUNDLE_IDENTIFIER = xyz.block.buzz.mobile APP_DISPLAY_NAME = Buzz CODE_SIGN_STYLE = Automatic CODE_SIGN_IDENTITY = iPhone Developer diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index fdd1c0d85..6bf4b21f5 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -631,7 +631,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EYF346PHUG; ENABLE_BITCODE = NO; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; INFOPLIST_FILE = Runner/Info.plist; @@ -820,7 +820,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JMTDPW9CG3; ENABLE_BITCODE = NO; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; INFOPLIST_FILE = Runner/Info.plist; @@ -844,7 +844,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EYF346PHUG; ENABLE_BITCODE = NO; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; INFOPLIST_FILE = Runner/Info.plist; @@ -867,7 +867,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JMTDPW9CG3; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -891,7 +891,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EYF346PHUG; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -915,7 +915,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EYF346PHUG; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index 5725dc08c..8b77cecac 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -4,7 +4,7 @@ aps-environment $(BUZZ_IOS_PUSH_ENVIRONMENT) - com.apple.developer.app-attest.environment + com.apple.developer.devicecheck.appattest-environment $(BUZZ_APP_ATTEST_ENVIRONMENT) com.apple.security.application-groups diff --git a/scripts/mobile-worktree-clean.sh b/scripts/mobile-worktree-clean.sh index a64f52a0d..644737b7e 100755 --- a/scripts/mobile-worktree-clean.sh +++ b/scripts/mobile-worktree-clean.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # Uninstalls stale worktree-suffixed Buzz debug builds from booted iOS -# simulators and connected Android devices/emulators. Production installs -# (com.buzz.buzzMobile / xyz.block.buzz.mobile, no suffix) are never touched: -# only identifiers with a worktree suffix appended after the production id -# are matched. Run `just mobile-clean` (or this script directly); pass -# --dry-run to list what would be removed without uninstalling. +# simulators and connected Android devices/emulators. Unsuffixed app installs +# (`xyz.block.buzz.dogfood.mobile` and `xyz.block.buzz.mobile`) are never +# touched. Only identifiers with a worktree suffix appended after the dogfood +# or production id are matched. Run `just mobile-clean` (or this script +# directly); pass --dry-run to list what would be removed without uninstalling. set -euo pipefail -ios_prefix="com.buzz.buzzMobile." +ios_prefix="xyz.block.buzz.dogfood.mobile." android_prefix="xyz.block.buzz.mobile." dry_run=0 diff --git a/scripts/mobile-worktree-overrides.sh b/scripts/mobile-worktree-overrides.sh index 176180db1..54bd32545 100755 --- a/scripts/mobile-worktree-overrides.sh +++ b/scripts/mobile-worktree-overrides.sh @@ -70,7 +70,7 @@ case "$android_slug" in [0-9]*) android_slug="w_$android_slug" ;; esac -ios_bundle_id="com.buzz.buzzMobile.${ios_slug}" +ios_bundle_id="xyz.block.buzz.dogfood.mobile.${ios_slug}" android_suffix=".${android_slug}" cat > "$ios_overrides" < /dev/null -grep -q '^BUNDLE_IDENTIFIER = com\.buzz\.buzzMobile\.feature-work-1$' "$ios" \ +grep -q '^BUNDLE_IDENTIFIER = xyz\.block\.buzz\.dogfood\.mobile\.feature-work-1$' "$ios" \ && grep -q '^applicationIdSuffix=\.feature_work_1$' "$android" \ && pass "branch switch keeps the install identity stable (per worktree)" \ || fail "install identity must not change on branch switch" @@ -122,10 +122,15 @@ grep -q '^applicationIdSuffix=\.w_2fast$' "$wt2/mobile/android/worktree.properti # ── Tracked build files: overrides are debug-only, release stays production ── debug_xcconfig="$repo_root/mobile/ios/Flutter/Debug.xcconfig" release_xcconfig="$repo_root/mobile/ios/Flutter/Release.xcconfig" +pbxproj="$repo_root/mobile/ios/Runner.xcodeproj/project.pbxproj" +runner_entitlements="$repo_root/mobile/ios/Runner/Runner.entitlements" gradle="$repo_root/mobile/android/app/build.gradle.kts" manifest="$repo_root/mobile/android/app/src/main/AndroidManifest.xml" plist="$repo_root/mobile/ios/Runner/Info.plist" +grep -q '^BUNDLE_IDENTIFIER = xyz\.block\.buzz\.dogfood\.mobile$' "$debug_xcconfig" \ + && pass "Debug.xcconfig defaults to the dogfood bundle identifier" \ + || fail "Debug.xcconfig must default to xyz.block.buzz.dogfood.mobile" grep -q 'WorktreeOverrides.xcconfig' "$debug_xcconfig" \ && pass "Debug.xcconfig includes WorktreeOverrides" \ || fail "Debug.xcconfig must include WorktreeOverrides.xcconfig" @@ -136,15 +141,57 @@ if [[ -n "$worktree_line" && -n "$app_line" && "$worktree_line" -lt "$app_line" else fail "Debug.xcconfig must include AppOverrides.xcconfig after WorktreeOverrides.xcconfig" fi +grep -q '^ios_prefix="xyz.block.buzz.dogfood.mobile\."$' "$clean_script" \ + && pass "cleanup targets the iOS dogfood worktree prefix" \ + || fail "cleanup must share the iOS dogfood prefix used by worktree overrides" + grep -q 'WorktreeOverrides' "$release_xcconfig" \ && fail "Release.xcconfig must not include WorktreeOverrides.xcconfig" \ || pass "Release.xcconfig does not include WorktreeOverrides" -grep -q '^BUNDLE_IDENTIFIER = com\.buzz\.buzzMobile$' "$release_xcconfig" \ +grep -q '^BUNDLE_IDENTIFIER = xyz\.block\.buzz\.mobile$' "$release_xcconfig" \ && pass "Release.xcconfig keeps the production bundle identifier" \ - || fail "Release.xcconfig must keep BUNDLE_IDENTIFIER = com.buzz.buzzMobile" + || fail "Release.xcconfig must keep BUNDLE_IDENTIFIER = xyz.block.buzz.mobile" grep -q '^APP_DISPLAY_NAME = Buzz$' "$release_xcconfig" \ && pass "Release.xcconfig keeps the production display name" \ || fail "Release.xcconfig must keep APP_DISPLAY_NAME = Buzz" +# Split the retired identifiers so the regression test does not match itself. +retired_bundle_id='com.buzz.buzz'"Mobile" +if git -C "$repo_root" grep -q -F "$retired_bundle_id"; then + fail "tracked files must not retain the retired iOS bundle identifier" +else + pass "tracked files do not retain the retired iOS bundle identifier" +fi +grep -q 'com.apple.developer.devicecheck.appattest-environment' "$runner_entitlements" \ + && pass "Runner uses the App Attest entitlement key accepted by Apple" \ + || fail "Runner must use com.apple.developer.devicecheck.appattest-environment" +retired_entitlement_key='com.apple.developer.app-attest.'"environment" +if grep -q "$retired_entitlement_key" "$runner_entitlements"; then + fail "Runner must not retain the invalid App Attest entitlement key" +else + pass "Runner omits the invalid App Attest entitlement key" +fi +team_map=$(awk ' + /\/\* (Debug|Release|Profile) \*\/ = \{/ { + config = $3 + } + /DEVELOPMENT_TEAM =/ { + team = $3 + gsub(/;/, "", team) + print config, team + } +' "$pbxproj" | sort) +expected_team_map=$(printf '%s\n' \ + 'Debug JMTDPW9CG3' \ + 'Debug JMTDPW9CG3' \ + 'Profile EYF346PHUG' \ + 'Profile EYF346PHUG' \ + 'Release EYF346PHUG' \ + 'Release EYF346PHUG') +if [[ "$team_map" == "$expected_team_map" ]]; then + pass "Runner and NotificationService use dogfood for Debug and production for Release/Profile" +else + fail "unexpected iOS development-team map: $team_map" +fi grep -q '$(APP_DISPLAY_NAME)' "$plist" \ && pass "Info.plist display name resolves from build settings" \ || fail "Info.plist CFBundleDisplayName must be \$(APP_DISPLAY_NAME)"