Add mcp-gateway/scripts/wave-token-refresh.sh

This commit is contained in:
Zac Gaetano 2026-03-31 15:29:42 -04:00
parent 43cc287d1f
commit f13f512376

View file

@ -0,0 +1,227 @@
#!/usr/bin/env bash
# =============================================================================
# Wave Finance Token Refresh Script
# =============================================================================
# Refreshes a Wave OAuth2 access token using a stored refresh token,
# then updates the .env file and optionally restarts the wave-mcp container.
#
# Usage:
# ./wave-token-refresh.sh [--restart] [--first-time]
#
# Options:
# --restart Restart the wave-mcp Docker container after updating the token
# --first-time Run the interactive first-time OAuth2 authorization flow
#
# Environment / .env variables used:
# WAVE_CLIENT_ID Your Wave application Client ID
# WAVE_CLIENT_SECRET Your Wave application Client Secret
# WAVE_REFRESH_TOKEN Current refresh token (updated in-place after each refresh)
# WAVE_ACCESS_TOKEN Current access token (updated in-place after each refresh)
#
# First-time setup:
# ./wave-token-refresh.sh --first-time
#
# Cron (every 50 minutes, tokens expire in 60):
# */50 * * * * /opt/mcp-gateway/scripts/wave-token-refresh.sh --restart >> /var/log/wave-refresh.log 2>&1
# =============================================================================
set -euo pipefail
# ── Paths ─────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GATEWAY_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="$GATEWAY_DIR/.env"
LOG_PREFIX="[wave-refresh $(date '+%Y-%m-%d %H:%M:%S')]"
# ── Args ──────────────────────────────────────────────────────────────────────
RESTART_CONTAINER=false
FIRST_TIME=false
for arg in "$@"; do
case "$arg" in
--restart) RESTART_CONTAINER=true ;;
--first-time) FIRST_TIME=true ;;
esac
done
# ── Helper: update or insert a KEY=VALUE in .env ──────────────────────────────
update_env_var() {
local file="$1"
local key="$2"
local value="$3"
if grep -q "^${key}=" "$file"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$file"
else
echo "" >> "$file"
echo "${key}=${value}" >> "$file"
fi
}
# ── Load .env ─────────────────────────────────────────────────────────────────
if [[ ! -f "$ENV_FILE" ]]; then
echo "$LOG_PREFIX ERROR: .env file not found at $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source <(grep -v '^#' "$ENV_FILE" | grep '=')
set +a
# ── Validate credentials ──────────────────────────────────────────────────────
WAVE_CLIENT_ID="${WAVE_CLIENT_ID:-}"
WAVE_CLIENT_SECRET="${WAVE_CLIENT_SECRET:-}"
WAVE_REFRESH_TOKEN="${WAVE_REFRESH_TOKEN:-}"
if [[ -z "$WAVE_CLIENT_ID" || -z "$WAVE_CLIENT_SECRET" ]]; then
echo "$LOG_PREFIX ERROR: WAVE_CLIENT_ID and WAVE_CLIENT_SECRET must be set in .env" >&2
echo "$LOG_PREFIX Add these lines to $ENV_FILE:" >&2
echo " WAVE_CLIENT_ID=jWVuzSgq98ILhX23Az6J_wqpNWsGb3-OJFSgikeE" >&2
echo " WAVE_CLIENT_SECRET=tYz1m8NOUd7z2eLx2Lem7pGGebcu7D4A3amWyZgJaoSozLYdTrrQJPK2jwOq7RQtFUAKQrk8b4klTgDmmJnMQ8TeAHvHNGEXLkhVT70MaXUPFYx6kZIvXLzPBn9XIiFb" >&2
exit 1
fi
# ── Parse JSON helper (uses python3, falls back to grep) ──────────────────────
json_field() {
local json="$1"
local field="$2"
echo "$json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$field',''))" 2>/dev/null || \
echo "$json" | grep -o "\"${field}\":\"[^\"]*\"" | cut -d'"' -f4 || \
echo ""
}
# ── First-time authorization flow ─────────────────────────────────────────────
if [[ "$FIRST_TIME" == "true" ]]; then
REDIRECT_URI="http://localhost:8080/callback"
SCOPE="account%3A%2A%20business%3Aread%20business%3Awrite"
echo ""
echo "======================================================"
echo " Wave OAuth2 — Initial Authorization"
echo "======================================================"
echo ""
echo "1. Open this URL in your browser and click Authorize:"
echo ""
echo " https://api.waveapps.com/oauth2/authorize/?client_id=${WAVE_CLIENT_ID}&response_type=code&scope=${SCOPE}&redirect_uri=${REDIRECT_URI}"
echo ""
echo "2. After approving, you'll be redirected to:"
echo " http://localhost:8080/callback?code=XXXX"
echo ""
echo "3. Copy the 'code' value from the URL (it expires in ~60 seconds)."
echo ""
read -r -p " Paste authorization code here: " AUTH_CODE
if [[ -z "$AUTH_CODE" ]]; then
echo "$LOG_PREFIX ERROR: No authorization code entered." >&2
exit 1
fi
echo ""
echo "$LOG_PREFIX Exchanging code for tokens..."
RESPONSE=$(curl -s -X POST "https://api.waveapps.com/oauth2/token/" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=$WAVE_CLIENT_ID" \
--data-urlencode "client_secret=$WAVE_CLIENT_SECRET" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI")
NEW_ACCESS_TOKEN=$(json_field "$RESPONSE" "access_token")
NEW_REFRESH_TOKEN=$(json_field "$RESPONSE" "refresh_token")
EXPIRES_IN=$(json_field "$RESPONSE" "expires_in")
if [[ -z "$NEW_ACCESS_TOKEN" ]]; then
echo "$LOG_PREFIX ERROR: Token exchange failed." >&2
echo "$LOG_PREFIX Response: $RESPONSE" >&2
exit 1
fi
update_env_var "$ENV_FILE" "WAVE_ACCESS_TOKEN" "$NEW_ACCESS_TOKEN"
update_env_var "$ENV_FILE" "WAVE_REFRESH_TOKEN" "$NEW_REFRESH_TOKEN"
echo "$LOG_PREFIX Success! Tokens saved to .env"
echo "$LOG_PREFIX Access token expires in: ${EXPIRES_IN:-3600} seconds"
echo ""
echo "======================================================"
echo " Setup complete."
echo ""
echo " To auto-refresh, add this cron job:"
echo " */50 * * * * $(realpath "$0") --restart >> /var/log/wave-refresh.log 2>&1"
echo "======================================================"
exit 0
fi
# ── Normal refresh flow ───────────────────────────────────────────────────────
if [[ -z "$WAVE_REFRESH_TOKEN" ]]; then
echo "$LOG_PREFIX ERROR: WAVE_REFRESH_TOKEN is not set in $ENV_FILE" >&2
echo "$LOG_PREFIX Run the first-time setup: $0 --first-time" >&2
exit 1
fi
echo "$LOG_PREFIX Refreshing Wave access token..."
RESPONSE=$(curl -s -X POST "https://api.waveapps.com/oauth2/token/" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=$WAVE_CLIENT_ID" \
--data-urlencode "client_secret=$WAVE_CLIENT_SECRET" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "refresh_token=$WAVE_REFRESH_TOKEN")
NEW_ACCESS_TOKEN=$(json_field "$RESPONSE" "access_token")
NEW_REFRESH_TOKEN=$(json_field "$RESPONSE" "refresh_token")
ERROR=$(json_field "$RESPONSE" "error")
ERROR_DESC=$(json_field "$RESPONSE" "error_description")
if [[ -z "$NEW_ACCESS_TOKEN" ]]; then
echo "$LOG_PREFIX ERROR: Token refresh failed." >&2
if [[ -n "$ERROR_DESC" ]]; then
echo "$LOG_PREFIX Reason: $ERROR_DESC" >&2
elif [[ -n "$ERROR" ]]; then
echo "$LOG_PREFIX Error code: $ERROR" >&2
else
echo "$LOG_PREFIX Raw response: $RESPONSE" >&2
fi
echo "$LOG_PREFIX The refresh token may be expired. Re-run: $0 --first-time" >&2
exit 1
fi
# Update both tokens (Wave rotates the refresh token on each use)
update_env_var "$ENV_FILE" "WAVE_ACCESS_TOKEN" "$NEW_ACCESS_TOKEN"
if [[ -n "$NEW_REFRESH_TOKEN" ]]; then
update_env_var "$ENV_FILE" "WAVE_REFRESH_TOKEN" "$NEW_REFRESH_TOKEN"
fi
echo "$LOG_PREFIX Access token refreshed successfully."
[[ -n "$NEW_REFRESH_TOKEN" ]] && echo "$LOG_PREFIX Refresh token rotated and saved."
# ── Optionally restart wave-mcp container ────────────────────────────────────
if [[ "$RESTART_CONTAINER" == "true" ]]; then
echo "$LOG_PREFIX Restarting wave-mcp container to pick up new token..."
if ! command -v docker &>/dev/null; then
echo "$LOG_PREFIX WARNING: docker not found — skipping container restart." >&2
elif docker ps --format '{{.Names}}' | grep -q '^mcp-wave$'; then
# Inject new token without full container restart using docker exec + env
# This avoids downtime — restart is still needed for a full env reload
(
cd "$GATEWAY_DIR"
docker compose up -d --no-deps wave-mcp
)
echo "$LOG_PREFIX Container restarted with updated token."
else
echo "$LOG_PREFIX Container 'mcp-wave' is not running — skipping restart." >&2
fi
fi
echo "$LOG_PREFIX Done."