#!/bin/bash
#
# program to: TeXt Approval for any program - ask "OK to go ahead?" by text (2026-09-29).
# Asks the UserAlertX account owner by text:
#     "UserAlertX: <app> wants approval: <description>
#      Reply Y to allow, N to deny, LOCK to lock until you decide. Expires in N min."
# and waits for the reply. Use it in front of anything that should need a
# "yes" from your phone first - summarize@xtlab.net uses it, and so can the
# PC-login example, a deploy script, a cron job...
#
# usage: uxapprove [-a app] [-t minutes] [-q] "what needs approving"
#   -a app      short name shown in the text and used for LOCK (default: "app")
#   -t minutes  how long to wait for the reply, 1-30 (default 10)
#   -q          quiet - no status line, just the exit code
#
# exit codes:  0 = allowed (Y)      1 = denied (N)
#              2 = locked (LOCK now, or the app was already locked -
#                  unlock it on your useralertx.com account page)
#              3 = no answer in time  4 = error (no key, network, API error)
#
#   if uxapprove -a backup "Delete old backups on nas1?"; then rm ...; fi
#
# API key: USERALERTX_API_KEY env var, else ~/.useralertx_key (same as the
# other UserAlertX example programs). Needs a phone on the account.
# Server side: itapproval.cgi mode=reply, replies handled by ithook.cgi.

URL="${USERALERTX_APPROVE_URL:-https://useralertx.com/cgi-bin/itapproval.cgi}"
KEYFILE="${USERALERTX_KEY_FILE:-$HOME/.useralertx_key}"
APP="app"
MINUTES=10
QUIET=0
while [ $# -gt 0 ]; do
  case "$1" in
    -a) APP="$2"; shift 2 ;;
    -t) MINUTES="$2"; shift 2 ;;
    -q) QUIET=1; shift ;;
    -h|--help) sed -n '3,26p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
    --) shift; break ;;
    -*) echo "uxapprove: unknown option $1 (see -h)" >&2; exit 4 ;;
    *) break ;;
  esac
done
WHAT="$*"
say() { [ "$QUIET" = 1 ] || echo "uxapprove: $*" >&2; }
[ -z "$WHAT" ] && { echo "usage: uxapprove [-a app] [-t minutes] [-q] \"what needs approving\"" >&2; exit 4; }

KEY="${USERALERTX_API_KEY:-}"
[ -z "$KEY" ] && [ -f "$KEYFILE" ] && KEY="$(tr -d '[:space:]' < "$KEYFILE")"
[ -z "$KEY" ] && { say "no API key - set USERALERTX_API_KEY or put it in $KEYFILE"; exit 4; }

# one field out of the API's flat JSON reply
field() { printf '%s' "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p"; }

post() {
  curl -s -m 70 -A 'uxapprove/1.0' -H "X-Api-Key: $KEY" -w '\n%{http_code}' "$URL" "$@"
}

R="$(post --data-urlencode "action=request" --data-urlencode "mode=reply" --data-urlencode "app=$APP" \
          --data-urlencode "expires_minutes=$MINUTES" --data-urlencode "description=$WHAT")"
CODE="${R##*$'\n'}"; BODY="${R%$'\n'*}"
case "$CODE" in
  200) ;;
  423) say "$APP is locked - unlock it on your useralertx.com account page"; exit 2 ;;
  *)   say "request failed (HTTP ${CODE:-none}): $(field "$BODY" error) $(field "$BODY" detail)"; exit 4 ;;
esac
ID="$(field "$BODY" approval_id)"
[ -z "$ID" ] && { say "no approval id in the reply"; exit 4; }
say "texted $(field "$BODY" target) - waiting for Y / N / LOCK (up to $MINUTES min)"

DEADLINE=$(( $(date +%s) + MINUTES * 60 + 60 ))
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
  R="$(post --data-urlencode "action=status" --data-urlencode "approval_id=$ID" --data-urlencode "wait=50")"
  CODE="${R##*$'\n'}"; BODY="${R%$'\n'*}"
  if [ "$CODE" != 200 ]; then sleep 5; continue; fi
  case "$(field "$BODY" status)" in
    approved) say "allowed"; exit 0 ;;
    denied)   say "denied"; exit 1 ;;
    locked)   say "denied and $APP LOCKED until you reply Y or N (or Unlock it on your account page)"; exit 2 ;;
    expired|failed) say "no answer in time"; exit 3 ;;
    pending)  ;;
    *) say "unexpected reply: $BODY"; exit 4 ;;
  esac
done
say "no answer in time"
exit 3
