crdt-sync: script the one human step when the Web client secret rotates

seed_session failed with invalid_client on 2026-09-11: the secret in
~/.config/crdt-sync/oauth_client_secret had worked on 2026-08-12 with the
same client id, so it was rotated in the console since. There is no API
for it (no gcloud command, none in the Firebase CLI), so the irreducible
step is copying it from the console page. refresh_oauth_secret.sh opens
that page, reads the paste without echo, rejects anything that is not a
GOCSPX- secret before writing, and runs the seed at once so a wrong paste
fails here rather than on the next timer tick.

xdg-open is detached from stdin: the spawned browser inherited the
terminal and swallowed the paste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pBgaXSJKyY3DarYpDatDF
This commit is contained in:
Krzysztof Rudnicki 2026-09-11 14:10:17 +02:00
parent 5446f4cdd0
commit e56aec431c

View File

@ -0,0 +1,76 @@
#!/bin/bash
# ============================================================================
# Replace the Web OAuth client secret and reseed one app's Firebase session.
#
# When seed_session fails with `invalid_client: The provided client secret is
# invalid`, the secret in ~/.config/crdt-sync/oauth_client_secret no longer
# matches the Web client in the Google Cloud console (it was rotated there).
# There is no API for that secret -- gcloud has no command and the Firebase
# CLI has none -- so the one human step is copying it from the console page.
# This opens that exact page, reads the pasted secret without echoing it,
# writes the file, and immediately runs the seed so a wrong paste fails here
# and not on the next systemd tick.
#
# Usage: tool/refresh_oauth_secret.sh --app <name> [--app <name> ...]
# ============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
readonly SCRIPT_DIR REPO_DIR
readonly CONFIG_DIR="$HOME/.config/crdt-sync"
readonly SECRET_FILE="$CONFIG_DIR/oauth_client_secret"
# The project's Web client, the same id every companion app ships as its
# google_sign_in serverClientId.
readonly CLIENT_ID="845446124781-prdoherj0v64vc6egvvcp3l0693khaur.apps.googleusercontent.com"
readonly CONSOLE_URL="https://console.cloud.google.com/apis/credentials?project=kuhy-syncs"
APPS=()
usage() {
echo "Usage: $(basename "$0") --app <name> [--app <name> ...]"
exit 0
}
while [[ $# -gt 0 ]]; do
case $1 in
--app) APPS+=("$2"); shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
[[ ${#APPS[@]} -gt 0 ]] || { echo "Error: at least one --app is required" >&2; exit 1; }
main() {
echo "1. Opening the credentials page. Under 'OAuth 2.0 Client IDs' open the"
echo " Web client (id starts with ${CLIENT_ID%%-*}-prdoh...) and copy its"
echo " Client secret. If it says the secret was rotated, copy the NEW one."
xdg-open "$CONSOLE_URL" </dev/null >/dev/null 2>&1 || echo " (open manually: $CONSOLE_URL)"
echo "2. Paste it here and press Enter (not shown):"
local secret
# -s needs a terminal; piped input (tests) falls back to a plain read.
if [[ -t 0 ]]; then read -rs secret; echo; else read -r secret; fi
if [[ ! $secret =~ ^GOCSPX-[A-Za-z0-9_-]{28}$ ]]; then
echo "Error: that does not look like a Web client secret (GOCSPX-…, 35 chars)" >&2
exit 1
fi
mkdir -p "$CONFIG_DIR"
(umask 077; printf '%s' "$secret" > "$SECRET_FILE")
echo "3. Saved to $SECRET_FILE. Running the seed -- pick the sync account in the browser."
local app_args=()
for app in "${APPS[@]}"; do app_args+=(--app "$app"); done
(
cd "$REPO_DIR"
python3 -m tool.seed_session \
--client-id "$CLIENT_ID" \
--client-secret "$secret" \
"${app_args[@]}"
)
}
main "$@"