Add scripts/wave-token-refresh.sh
This commit is contained in:
parent
49c26e0365
commit
1d13495c93
1 changed files with 227 additions and 0 deletions
227
scripts/wave-token-refresh.sh
Normal file
227
scripts/wave-token-refresh.sh
Normal 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."
|
||||
Loading…
Reference in a new issue