fix(release): validate Firebase project consistency
This commit is contained in:
parent
89851097fd
commit
0b9589f4fa
|
|
@ -38,6 +38,12 @@ edit, branch, or tag was made during this audit.
|
|||
development blockers are the four public Firebase Android values and the FCM
|
||||
service-account credential. No release-readiness claim is made until those
|
||||
credentials are imported and provider/device behavior is exercised.
|
||||
- The existing Google Cloud project `who-need-help-development` was selected in
|
||||
the Firebase console through the user's already authenticated dev-port Chrome
|
||||
session. Firebase requires the account holder to accept its Terms before it
|
||||
can add Firebase services to that existing project. That legal acceptance
|
||||
remains pending explicit user confirmation; no Firebase project, app,
|
||||
service account, credential, or environment value was created or changed.
|
||||
- Real browser Web Push was exercised on the development origin through the
|
||||
user's existing dev-port Chrome profile. The exact origin permission was
|
||||
changed from `Ask (default)` to `Allow`; the application registered a second,
|
||||
|
|
@ -58,7 +64,7 @@ edit, branch, or tag was made during this audit.
|
|||
topology, external PostgreSQL 18.4, healthy application containers, and
|
||||
passing public readiness. The plan correctly refused release because the
|
||||
production checkout still lacks browser VAPID, the Firebase Android client,
|
||||
server FCM delivery, and Android App Links. It reported 46 pending local
|
||||
server FCM delivery, and Android App Links. It reported 57 pending local
|
||||
commits and made no remote change.
|
||||
- A separate read-only isolation check observed the public Git `main` reference
|
||||
still at production commit `921e04b3608007675e22e7e26e0beb3975dbba58`.
|
||||
|
|
@ -82,6 +88,13 @@ edit, branch, or tag was made during this audit.
|
|||
the database, test, public Git, and Devpost are excluded. An isolated offline
|
||||
fixture passed read-only plan, successful apply, and injected-edge-failure
|
||||
recovery, including restoration of the original image selection.
|
||||
- The clean local commit
|
||||
`89851097fd5cbe58ce4dc41c2322810894cad50a` was packaged as a Git bundle under
|
||||
`output/releases/89851097fd5cbe58ce4dc41c2322810894cad50a/`. Its SHA-256
|
||||
checksum, bundle object graph, and `HEAD` identity all passed verification.
|
||||
The isolated production rollback drill was repeated after packaging and again
|
||||
passed plan, apply, and injected edge-failure recovery without contacting or
|
||||
changing the production runtime.
|
||||
- A current development database backup was created at
|
||||
`output/backups/compose-20260723-194923.dump` with SHA-256
|
||||
`4202a152751d588c19069eb46a25753901238729b47e6197945afdca20b65c2e`.
|
||||
|
|
|
|||
|
|
@ -79,6 +79,30 @@ valid_fcm_service_account_json() {
|
|||
' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
fcm_service_account_project_id() {
|
||||
jq -er '
|
||||
select(
|
||||
.type == "service_account" and
|
||||
(.project_id | type == "string" and length > 0) and
|
||||
(.client_email | type == "string" and length > 0) and
|
||||
(.private_key | type == "string" and length > 0)
|
||||
)
|
||||
| .project_id
|
||||
' 2>/dev/null
|
||||
}
|
||||
|
||||
firebase_client_values_valid() {
|
||||
local application_id sender_id prefix
|
||||
|
||||
application_id=$(value WNH_FIREBASE_APPLICATION_ID)
|
||||
sender_id=$(value WNH_FIREBASE_GCM_SENDER_ID)
|
||||
prefix="1:$sender_id:android:"
|
||||
|
||||
[[ "$sender_id" =~ ^[0-9]+$ &&
|
||||
"$application_id" == "$prefix"* &&
|
||||
-n "${application_id#"$prefix"}" ]]
|
||||
}
|
||||
|
||||
failures=0
|
||||
warnings=0
|
||||
|
||||
|
|
@ -174,38 +198,65 @@ if all_empty WNH_FIREBASE_APPLICATION_ID WNH_FIREBASE_API_KEY \
|
|||
missing "Android Firebase client" "four WNH_FIREBASE_* Android client values"
|
||||
elif all_set WNH_FIREBASE_APPLICATION_ID WNH_FIREBASE_API_KEY \
|
||||
WNH_FIREBASE_PROJECT_ID WNH_FIREBASE_GCM_SENDER_ID; then
|
||||
ready "Android Firebase client" "complete client configuration"
|
||||
if firebase_client_values_valid; then
|
||||
ready "Android Firebase client" "complete internally consistent client configuration"
|
||||
else
|
||||
invalid "Android Firebase client" \
|
||||
"application ID must belong to the numeric configured sender/project number"
|
||||
fi
|
||||
else
|
||||
partial "Android Firebase client" "all four WNH_FIREBASE_* values are required together"
|
||||
fi
|
||||
|
||||
fcm_file=$(value FCM_SERVICE_ACCOUNT_FILE)
|
||||
fcm_base64=$(value FCM_SERVICE_ACCOUNT_JSON_BASE64)
|
||||
if [[ -z "$(value FCM_PROJECT_ID)" && -z "$fcm_file" && -z "$fcm_base64" ]]; then
|
||||
fcm_project_id=$(value FCM_PROJECT_ID)
|
||||
firebase_project_id=$(value WNH_FIREBASE_PROJECT_ID)
|
||||
fcm_credential_project_id=
|
||||
if [[ -z "$fcm_project_id" && -z "$fcm_file" && -z "$fcm_base64" ]]; then
|
||||
missing "Android FCM delivery" "FCM project ID and one service-account source"
|
||||
elif [[ -z "$(value FCM_PROJECT_ID)" || (-n "$fcm_file" && -n "$fcm_base64") ||
|
||||
elif [[ -z "$fcm_project_id" || (-n "$fcm_file" && -n "$fcm_base64") ||
|
||||
(-z "$fcm_file" && -z "$fcm_base64") ]]; then
|
||||
partial "Android FCM delivery" "project ID and exactly one credential source are required"
|
||||
elif [[ -n "$fcm_file" ]]; then
|
||||
if [[ "$fcm_file" == /* && -r "$fcm_file" ]] &&
|
||||
valid_fcm_service_account_json <"$fcm_file"; then
|
||||
ready "Android FCM delivery" "complete service-account file is configured"
|
||||
fcm_credential_project_id=$(fcm_service_account_project_id <"$fcm_file") &&
|
||||
[[ "$fcm_credential_project_id" == "$fcm_project_id" ]] &&
|
||||
[[ -z "$firebase_project_id" || "$fcm_project_id" == "$firebase_project_id" ]]; then
|
||||
ready "Android FCM delivery" "service account and Android client use the same project"
|
||||
else
|
||||
invalid "Android FCM delivery" \
|
||||
"FCM_SERVICE_ACCOUNT_FILE must be an absolute readable complete service-account JSON file"
|
||||
"credential source and configured Firebase/FCM project IDs are incomplete or inconsistent"
|
||||
fi
|
||||
elif printf '%s' "$fcm_base64" | base64 --decode 2>/dev/null |
|
||||
valid_fcm_service_account_json; then
|
||||
ready "Android FCM delivery" "complete Base64 service-account document is configured"
|
||||
elif fcm_credential_project_id=$(
|
||||
printf '%s' "$fcm_base64" |
|
||||
base64 --decode 2>/dev/null |
|
||||
fcm_service_account_project_id
|
||||
) &&
|
||||
[[ "$fcm_credential_project_id" == "$fcm_project_id" ]] &&
|
||||
[[ -z "$firebase_project_id" || "$fcm_project_id" == "$firebase_project_id" ]]; then
|
||||
ready "Android FCM delivery" "service account and Android client use the same project"
|
||||
else
|
||||
invalid "Android FCM delivery" \
|
||||
"Base64 credential is not a complete service-account JSON document"
|
||||
"credential source and configured Firebase/FCM project IDs are incomplete or inconsistent"
|
||||
fi
|
||||
|
||||
expected_android_package=
|
||||
case "$deployment_env" in
|
||||
development) expected_android_package=org.whoneedhelp.mobile.development ;;
|
||||
test) expected_android_package=org.whoneedhelp.mobile.staging ;;
|
||||
production) expected_android_package=org.whoneedhelp.mobile ;;
|
||||
esac
|
||||
|
||||
if all_empty ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then
|
||||
missing "Android App Links" "package name and signing certificate fingerprint"
|
||||
elif all_set ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then
|
||||
ready "Android App Links" "package and signing fingerprints are configured"
|
||||
if [[ -n "$expected_android_package" &&
|
||||
"$(value ANDROID_APP_LINKS_PACKAGE_NAME)" == "$expected_android_package" ]]; then
|
||||
ready "Android App Links" "package and signing fingerprints match this environment"
|
||||
else
|
||||
invalid "Android App Links" "package does not match DEPLOYMENT_ENV=$deployment_env"
|
||||
fi
|
||||
else
|
||||
partial "Android App Links" "package and signing fingerprints must be configured together"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ if [ -z "$package_name" ]; then
|
|||
fi
|
||||
|
||||
if ! jq --exit-status --arg package "$package_name" '
|
||||
(.project_info.project_number) as $project_number
|
||||
|
|
||||
[
|
||||
.client[]?
|
||||
| select(.client_info.android_client_info.package_name == $package)
|
||||
|
|
@ -39,6 +41,8 @@ if ! jq --exit-status --arg package "$package_name" '
|
|||
and (.project_info.project_id | type == "string" and length > 0)
|
||||
and (.project_info.project_number | type == "string" and length > 0)
|
||||
and ($clients[0].client_info.mobilesdk_app_id | type == "string" and length > 0)
|
||||
and ($clients[0].client_info.mobilesdk_app_id
|
||||
| startswith("1:" + $project_number + ":android:"))
|
||||
and ($clients[0].api_key[0].current_key | type == "string" and length > 0)
|
||||
' "$client_file" >/dev/null; then
|
||||
echo "Firebase Android configuration does not contain exactly one complete client for package $package_name." >&2
|
||||
|
|
|
|||
|
|
@ -202,6 +202,16 @@ grep -Fx 'WNH_FIREBASE_API_KEY=quality-firebase-api-key' "$credential_env" >/dev
|
|||
grep -Fx 'WNH_FIREBASE_PROJECT_ID=quality-development' "$credential_env" >/dev/null
|
||||
grep -Fx 'WNH_FIREBASE_GCM_SENDER_ID=123456789' "$credential_env" >/dev/null
|
||||
|
||||
firebase_mismatched_client="$scan_dir/google-services-mismatched.json"
|
||||
printf '%s\n' \
|
||||
'{"project_info":{"project_number":"123456789","project_id":"quality-development"},"client":[{"client_info":{"mobilesdk_app_id":"1:987654321:android:quality","android_client_info":{"package_name":"org.whoneedhelp.mobile.staging"}},"api_key":[{"current_key":"quality-firebase-api-key"}]}]}' \
|
||||
>"$firebase_mismatched_client"
|
||||
if ./scripts/import-firebase-android-config.sh \
|
||||
"$credential_env" "$firebase_mismatched_client" >/dev/null 2>&1; then
|
||||
echo "Firebase importer accepted an application ID from another project number." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking Android environment isolation"
|
||||
android_fingerprint=D7:C4:F1:12:4D:F4:68:E5:B3:54:DE:D8:96:E8:01:51:29:41:F1:8A:71:0C:18:B0:E7:98:AA:2B:81:DA:11:DF
|
||||
android_env="$scan_dir/android-development.env"
|
||||
|
|
@ -399,6 +409,60 @@ if ./scripts/check-environment-readiness.sh \
|
|||
echo "Environment readiness accepted an incomplete FCM service account." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mismatched_firebase_env="$scan_dir/production.mismatched-firebase.env"
|
||||
cp "$production_env" "$mismatched_firebase_env"
|
||||
sed -i \
|
||||
's|^WNH_FIREBASE_APPLICATION_ID=.*|WNH_FIREBASE_APPLICATION_ID=1:987654321:android:quality|' \
|
||||
"$mismatched_firebase_env"
|
||||
if ./scripts/validate-production-env.sh \
|
||||
"$mismatched_firebase_env" help.test >/dev/null 2>&1; then
|
||||
echo "Production validation accepted a Firebase application from another sender." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ./scripts/check-environment-readiness.sh \
|
||||
"$mismatched_firebase_env" --require-release >/dev/null 2>&1; then
|
||||
echo "Environment readiness accepted a Firebase application from another sender." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mismatched_fcm_env="$scan_dir/production.mismatched-fcm.env"
|
||||
mismatched_fcm_base64=$(
|
||||
printf '%s' \
|
||||
'{"type":"service_account","project_id":"another-project","client_email":"quality-fcm@another-project.iam.gserviceaccount.com","private_key":"quality-private-key"}' |
|
||||
base64 -w 0
|
||||
)
|
||||
cp "$production_env" "$mismatched_fcm_env"
|
||||
sed -i \
|
||||
"s|^FCM_SERVICE_ACCOUNT_JSON_BASE64=.*|FCM_SERVICE_ACCOUNT_JSON_BASE64=$mismatched_fcm_base64|" \
|
||||
"$mismatched_fcm_env"
|
||||
if ./scripts/validate-production-env.sh \
|
||||
"$mismatched_fcm_env" help.test >/dev/null 2>&1; then
|
||||
echo "Production validation accepted an FCM service account from another project." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ./scripts/check-environment-readiness.sh \
|
||||
"$mismatched_fcm_env" --require-release >/dev/null 2>&1; then
|
||||
echo "Environment readiness accepted an FCM service account from another project." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mismatched_app_links_env="$scan_dir/production.mismatched-app-links.env"
|
||||
cp "$production_env" "$mismatched_app_links_env"
|
||||
sed -i \
|
||||
's|^ANDROID_APP_LINKS_PACKAGE_NAME=.*|ANDROID_APP_LINKS_PACKAGE_NAME=org.whoneedhelp.mobile.staging|' \
|
||||
"$mismatched_app_links_env"
|
||||
if ./scripts/validate-production-env.sh \
|
||||
"$mismatched_app_links_env" help.test >/dev/null 2>&1; then
|
||||
echo "Production validation accepted the staging Android package." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ./scripts/check-environment-readiness.sh \
|
||||
"$mismatched_app_links_env" --require-release >/dev/null 2>&1; then
|
||||
echo "Environment readiness accepted the staging Android package for production." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -Fx 'COMPOSE_PROJECT_NAME=who_need_help_production' "$production_env" >/dev/null
|
||||
grep -E '^APP_IMAGE=who-need-help:production-[0-9a-f]{12}$' "$production_env" >/dev/null
|
||||
grep -Fx 'EDGE_COMPOSE_PROJECT_NAME=who_need_help_edge' "$production_env" >/dev/null
|
||||
|
|
|
|||
|
|
@ -351,6 +351,15 @@ if ((firebase_nonempty != 0 && firebase_nonempty != ${#firebase_values[@]})); th
|
|||
echo "All four WNH_FIREBASE_* Android client values must be configured together." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ((firebase_nonempty == ${#firebase_values[@]})); then
|
||||
firebase_prefix="1:$firebase_sender_id:android:"
|
||||
[[ "$firebase_sender_id" =~ ^[0-9]+$ &&
|
||||
"$firebase_application_id" == "$firebase_prefix"* &&
|
||||
-n "${firebase_application_id#"$firebase_prefix"}" ]] || {
|
||||
echo "WNH_FIREBASE_APPLICATION_ID does not belong to WNH_FIREBASE_GCM_SENDER_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
vapid_values=(
|
||||
"$web_push_vapid_public_key"
|
||||
|
|
@ -386,6 +395,7 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
exit 1
|
||||
}
|
||||
|
||||
fcm_credential_project_id=
|
||||
if [[ -n "$fcm_service_account_file" ]]; then
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "Required command is unavailable for FCM validation: jq" >&2
|
||||
|
|
@ -399,6 +409,7 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
echo "FCM_SERVICE_ACCOUNT_FILE is not a complete service-account JSON document." >&2
|
||||
exit 1
|
||||
}
|
||||
fcm_credential_project_id=$(jq --raw-output '.project_id' "$fcm_service_account_file")
|
||||
else
|
||||
for command in base64 jq; do
|
||||
command -v "$command" >/dev/null 2>&1 || {
|
||||
|
|
@ -406,12 +417,30 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
exit 1
|
||||
}
|
||||
done
|
||||
printf '%s' "$fcm_service_account_json_base64" |
|
||||
base64 --decode 2>/dev/null |
|
||||
fcm_decoded_json=$(
|
||||
printf '%s' "$fcm_service_account_json_base64" |
|
||||
base64 --decode 2>/dev/null
|
||||
) || {
|
||||
echo "FCM_SERVICE_ACCOUNT_JSON_BASE64 is not valid Base64." >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s' "$fcm_decoded_json" |
|
||||
valid_fcm_service_account_json || {
|
||||
echo "FCM_SERVICE_ACCOUNT_JSON_BASE64 is not a complete service-account JSON document." >&2
|
||||
exit 1
|
||||
}
|
||||
fcm_credential_project_id=$(printf '%s' "$fcm_decoded_json" | jq --raw-output '.project_id')
|
||||
fi
|
||||
|
||||
[[ "$fcm_credential_project_id" == "$fcm_project_id" ]] || {
|
||||
echo "FCM service-account project does not match FCM_PROJECT_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
if ((firebase_nonempty == ${#firebase_values[@]})); then
|
||||
[[ "$fcm_project_id" == "$firebase_project_id" ]] || {
|
||||
echo "FCM_PROJECT_ID does not match WNH_FIREBASE_PROJECT_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -424,6 +453,10 @@ if [[ -n "$android_app_links_package_name" || -n "$android_app_links_fingerprint
|
|||
echo "ANDROID_APP_LINKS_PACKAGE_NAME is not a valid Android application ID." >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$android_app_links_package_name" == org.whoneedhelp.mobile ]] || {
|
||||
echo "Production Android App Links must use org.whoneedhelp.mobile." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
IFS=',' read -r -a android_fingerprints <<<"$android_app_links_fingerprints"
|
||||
[[ ${#android_fingerprints[@]} -gt 0 ]] || {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,18 @@ if ((firebase_nonempty != 0 && firebase_nonempty != ${#firebase_values[@]})); th
|
|||
echo "All four test WNH_FIREBASE_* Android client values must be configured together." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ((firebase_nonempty == ${#firebase_values[@]})); then
|
||||
firebase_application_id=${firebase_values[0]}
|
||||
firebase_project_id=${firebase_values[2]}
|
||||
firebase_sender_id=${firebase_values[3]}
|
||||
firebase_prefix="1:$firebase_sender_id:android:"
|
||||
[[ "$firebase_sender_id" =~ ^[0-9]+$ &&
|
||||
"$firebase_application_id" == "$firebase_prefix"* &&
|
||||
-n "${firebase_application_id#"$firebase_prefix"}" ]] || {
|
||||
echo "Test WNH_FIREBASE_APPLICATION_ID does not belong to WNH_FIREBASE_GCM_SENDER_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
vapid_values=(
|
||||
"$(read_value WEB_PUSH_VAPID_PUBLIC_KEY 2>/dev/null || true)"
|
||||
|
|
@ -184,6 +196,7 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
exit 1
|
||||
}
|
||||
|
||||
fcm_credential_project_id=
|
||||
if [[ -n "$fcm_service_account_file" ]]; then
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "Required command is unavailable for FCM validation: jq" >&2
|
||||
|
|
@ -197,6 +210,7 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
echo "Test FCM_SERVICE_ACCOUNT_FILE is not a complete service-account JSON document." >&2
|
||||
exit 1
|
||||
}
|
||||
fcm_credential_project_id=$(jq --raw-output '.project_id' "$fcm_service_account_file")
|
||||
else
|
||||
for command in base64 jq; do
|
||||
command -v "$command" >/dev/null 2>&1 || {
|
||||
|
|
@ -204,12 +218,30 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
|
|||
exit 1
|
||||
}
|
||||
done
|
||||
printf '%s' "$fcm_service_account_json_base64" |
|
||||
base64 --decode 2>/dev/null |
|
||||
fcm_decoded_json=$(
|
||||
printf '%s' "$fcm_service_account_json_base64" |
|
||||
base64 --decode 2>/dev/null
|
||||
) || {
|
||||
echo "Test FCM_SERVICE_ACCOUNT_JSON_BASE64 is not valid Base64." >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s' "$fcm_decoded_json" |
|
||||
valid_fcm_service_account_json || {
|
||||
echo "Test FCM_SERVICE_ACCOUNT_JSON_BASE64 is not a complete service-account JSON document." >&2
|
||||
exit 1
|
||||
}
|
||||
fcm_credential_project_id=$(printf '%s' "$fcm_decoded_json" | jq --raw-output '.project_id')
|
||||
fi
|
||||
|
||||
[[ "$fcm_credential_project_id" == "$fcm_project_id" ]] || {
|
||||
echo "Test FCM service-account project does not match FCM_PROJECT_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
if ((firebase_nonempty == ${#firebase_values[@]})); then
|
||||
[[ "$fcm_project_id" == "$firebase_project_id" ]] || {
|
||||
echo "Test FCM_PROJECT_ID does not match WNH_FIREBASE_PROJECT_ID." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -224,6 +256,10 @@ if [[ -n "$android_package" || -n "$android_fingerprints" ]]; then
|
|||
echo "ANDROID_APP_LINKS_PACKAGE_NAME is not a valid Android application ID." >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$android_package" == org.whoneedhelp.mobile.staging ]] || {
|
||||
echo "Test Android App Links must use org.whoneedhelp.mobile.staging." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
IFS=',' read -r -a android_fingerprint_values <<<"$android_fingerprints"
|
||||
for fingerprint in "${android_fingerprint_values[@]}"; do
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user