fix(mobile/ios): align Apple app identities

Co-authored-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
npub12wpjffj7q5qjsky5jvk4ldwlxmse5xll3d8gytk4wqd0c5y7jvwspg37n6
2026-07-31 15:56:53 -07:00
co-authored by Tom Brow
parent 737457569a
commit 4f86c6cbc5
9 changed files with 76 additions and 21 deletions
+7
View File
@@ -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
+2 -1
View File
@@ -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.<slug>` on iOS,
to the **worktree directory name**
(`xyz.block.buzz.dogfood.mobile.<slug>` on iOS,
`xyz.block.buzz.mobile.<slug>` 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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+6 -6
View File
@@ -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;
+1 -1
View File
@@ -4,7 +4,7 @@
<dict>
<key>aps-environment</key>
<string>$(BUZZ_IOS_PUSH_ENVIRONMENT)</string>
<key>com.apple.developer.app-attest.environment</key>
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>$(BUZZ_APP_ATTEST_ENVIRONMENT)</string>
<key>com.apple.security.application-groups</key>
<array>
+6 -6
View File
@@ -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
+1 -1
View File
@@ -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" <<XCCONFIG
+51 -4
View File
@@ -63,7 +63,7 @@ out="$("$wt/scripts/mobile-worktree-overrides.sh")"
ios="$wt/mobile/ios/Flutter/WorktreeOverrides.xcconfig"
android="$wt/mobile/android/worktree.properties"
[[ -f "$ios" && -f "$android" ]] || fail "worktree must write both override files"
grep -q '^BUNDLE_IDENTIFIER = com\.buzz\.buzzMobile\.feature-work-1$' "$ios" \
grep -q '^BUNDLE_IDENTIFIER = xyz\.block\.buzz\.dogfood\.mobile\.feature-work-1$' "$ios" \
&& pass "iOS bundle identifier keys to the sanitized worktree directory name" \
|| fail "iOS bundle identifier must key to the worktree dir, got: $(cat "$ios")"
grep -q '^APP_DISPLAY_NAME = Buzz (Fix_Thing-2)$' "$ios" \
@@ -82,7 +82,7 @@ printf '%s' "$out" | grep -q 'Worktree Feature_Work-1' \
# ── Branch switch in the same worktree: identity stable, label follows ───────
git -C "$wt" checkout -q -b "another/branch-name"
"$wt/scripts/mobile-worktree-overrides.sh" > /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 '<key>com.apple.developer.devicecheck.appattest-environment</key>' "$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 '<string>$(APP_DISPLAY_NAME)</string>' "$plist" \
&& pass "Info.plist display name resolves from build settings" \
|| fail "Info.plist CFBundleDisplayName must be \$(APP_DISPLAY_NAME)"