diff --git a/mcp-gateway/scripts/wave-token-refresh.sh b/mcp-gateway/scripts/wave-token-refresh.sh new file mode 100644 index 0000000..b6641f7 --- /dev/null +++ b/mcp-gateway/scripts/wave-token-refresh.sh @@ -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."