135 lines
4.8 KiB
Bash
Executable File
135 lines
4.8 KiB
Bash
Executable File
#!/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 '.AccessToken // .access_token // empty')
|
|
refresh_token=$(echo "$response" | jq -r '.RefreshToken // .refresh_token // empty')
|
|
expires_in=$(echo "$response" | jq -r '.ExpiresIn // .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
|
|
response=$(curl -sf -X POST "${BMS_API_BASE}/v2/security/authenticate" \
|
|
-F "GrantType=password" \
|
|
-F "UserName=${BMS_USERNAME}" \
|
|
-F "Password=${BMS_PASSWORD}" \
|
|
-F "Tenant=${BMS_TENANT}") || 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
|