#!/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."