#!/usr/bin/env bash # bms-auth.sh — Kaseya BMS authentication helper # Obtains and caches JWT tokens. Called by bms.sh. set -euo pipefail BMS_API_BASE="${BMS_API_BASE:-https://api.bms.kaseya.com}" BMS_TOKEN_FILE="${BMS_TOKEN_FILE:-$HOME/.bms_token.json}" # ─── Helpers ──────────────────────────────────────────────────────────────── die() { echo "ERROR: $*" >&2; exit 1; } require_env() { local var="$1" [[ -n "${!var:-}" ]] || die "Environment variable $var is required. See SKILL.md for setup." } token_is_valid() { # Returns 0 (true) if cached token exists and has not expired (with 60s buffer) [[ -f "$BMS_TOKEN_FILE" ]] || return 1 local exp exp=$(jq -r '.expires_at // 0' "$BMS_TOKEN_FILE" 2>/dev/null) || return 1 local now now=$(date +%s) [[ $((exp - 60)) -gt $now ]] } save_token() { local response="$1" local access_token refresh_token expires_in expires_at access_token=$(echo "$response" | jq -r '.result.AccessToken // .result.accessToken // .result.access_token // empty') refresh_token=$(echo "$response" | jq -r '.result.RefreshToken // .result.refreshToken // .result.refresh_token // empty') expires_in=$(echo "$response" | jq -r '.result.ExpiresIn // .result.expires_in // 3600') expires_at=$(( $(date +%s) + expires_in )) [[ -n "$access_token" ]] || die "No access token in auth response: $response" jq -n \ --arg at "$access_token" \ --arg rt "${refresh_token:-}" \ --argjson ea "$expires_at" \ '{access_token: $at, refresh_token: $rt, expires_at: $ea}' \ > "$BMS_TOKEN_FILE" chmod 600 "$BMS_TOKEN_FILE" } # ─── Auth Actions ──────────────────────────────────────────────────────────── cmd_auth_login() { require_env BMS_TENANT local response if [[ -n "${BMS_CLIENT_ID:-}" && -n "${BMS_CLIENT_SECRET:-}" ]]; then # OAuth2 client credentials flow echo "Authenticating with client credentials..." >&2 response=$(curl -sf -X POST "${BMS_API_BASE}/v2/security/authenticate" \ -F "GrantType=client_credentials" \ -F "ClientId=${BMS_CLIENT_ID}" \ -F "ClientSecret=${BMS_CLIENT_SECRET}" \ -F "Tenant=${BMS_TENANT}") || die "Authentication request failed" else # Password flow require_env BMS_USERNAME require_env BMS_PASSWORD echo "Authenticating with username/password..." >&2 local curl_args=(-s -X POST "${BMS_API_BASE}/v2/security/authenticate" \ -F "GrantType=password" \ -F "UserName=${BMS_USERNAME}" \ -F "Password=${BMS_PASSWORD}" \ -F "Tenant=${BMS_TENANT}") [[ -n "${BMS_MFA_CODE:-}" ]] && curl_args+=(-F "MFACode=${BMS_MFA_CODE}") response=$(curl "${curl_args[@]}") || die "Authentication request failed" fi save_token "$response" echo "Authenticated successfully. Token cached at $BMS_TOKEN_FILE" >&2 } cmd_auth_refresh() { [[ -f "$BMS_TOKEN_FILE" ]] || die "No cached token. Run: bms auth" local access_token refresh_token access_token=$(jq -r '.access_token' "$BMS_TOKEN_FILE") refresh_token=$(jq -r '.refresh_token // empty' "$BMS_TOKEN_FILE") [[ -n "$refresh_token" ]] || { cmd_auth_login; return; } local response response=$(curl -sf -X POST "${BMS_API_BASE}/v2/security/refreshtoken" \ -H "Content-Type: application/json" \ -d "{\"AccessToken\":\"${access_token}\",\"RefreshToken\":\"${refresh_token}\"}") \ || { echo "Refresh failed, re-authenticating..." >&2; cmd_auth_login; return; } save_token "$response" echo "Token refreshed." >&2 } cmd_auth_status() { if [[ ! -f "$BMS_TOKEN_FILE" ]]; then echo "No token cached." return fi local expires_at now remaining expires_at=$(jq -r '.expires_at // 0' "$BMS_TOKEN_FILE") now=$(date +%s) remaining=$((expires_at - now)) if [[ $remaining -gt 0 ]]; then echo "Token valid. Expires in ${remaining}s (at $(date -d "@${expires_at}" 2>/dev/null || date -r "${expires_at}" 2>/dev/null || echo "unknown"))" else echo "Token expired ${remaining#-}s ago." fi } # ─── Public: get_token ─────────────────────────────────────────────────────── # Prints the current access token, refreshing/authenticating as needed. get_token() { if ! token_is_valid; then if [[ -f "$BMS_TOKEN_FILE" ]]; then cmd_auth_refresh else cmd_auth_login fi fi jq -r '.access_token' "$BMS_TOKEN_FILE" } # ─── Dispatch ──────────────────────────────────────────────────────────────── case "${1:-}" in login|"") cmd_auth_login ;; refresh) cmd_auth_refresh ;; status) cmd_auth_status ;; get-token) get_token ;; *) echo "Usage: bms-auth.sh [login|refresh|status|get-token]" >&2; exit 1 ;; esac