#!/usr/bin/env bash # m13a_smoke.sh — End-to-end smoke for the M13a auth gate. # # Walks through: # 1. authd /health and /metrics # 2. login (super_admin) → access + refresh # 3. /v1/users/me with Bearer → user info # 4. refresh → new pair (rotated) # 5. re-use OLD refresh → session_killed # 6. invite → magic-link token (will not email; we just verify # the route is wired) # 7. admind /v1/dlq (gated): no token → 401, valid token → 200 # 8. ingestd /v1/admin/ingest (gated): no token → 401, valid # token → 200 (we don't check the full ingest pipeline here, # just that the gate lets the request through) # # Requires: # - authd running on $BA_AUTHD_HTTP (default http://127.0.0.1:8804) # - admind running on $BA_ADMIND_HTTP (default http://127.0.0.1:8803) # - ingestd running on $BA_INGESTD_HTTP (default http://127.0.0.1:8800) # - $BA_AUTHD_JWT_SECRET set # - super_admin user in Postgres with a known password # (created by scripts/bootstrap-super-admin.sh) # # Run: # bash scripts/m13a_smoke.sh # # Exits 0 if all steps pass, non-zero with a summary table on failure. set -euo pipefail cd "$(dirname "$0")/.." AUTHD="${BA_AUTHD_HTTP:-http://127.0.0.1:8804}" ADMIND="${BA_ADMIND_HTTP:-http://127.0.0.1:8803}" INGESTD="${BA_INGESTD_HTTP:-http://127.0.0.1:8800}" SUPER_EMAIL="${BA_SMOKE_SUPER_EMAIL:-super@broad-announce.test}" SUPER_PASSWORD="${BA_SMOKE_SUPER_PASSWORD:-test-password-123}" TENANT_SLUG="${BA_SMOKE_TENANT_SLUG:-acme}" INVITE_EMAIL="${BA_SMOKE_INVITE_EMAIL:-invite-$(date +%s)@acme.test}" PASS=0 FAIL=0 RESULTS=() check() { local name="$1" local actual="$2" local want="$3" if [[ "$actual" == "$want" ]]; then PASS=$((PASS+1)) RESULTS+=("OK $name") else FAIL=$((FAIL+1)) RESULTS+=("FAIL $name (got $actual, want $want)") fi } # --------------------------------------------------------------------------- # 1. authd /health # --------------------------------------------------------------------------- status=$(curl -s -o /dev/null -w "%{http_code}" "$AUTHD/health") check "1. authd /health" "$status" "200" # --------------------------------------------------------------------------- # 2. login # --------------------------------------------------------------------------- login_resp=$(curl -s -X POST "$AUTHD/v1/auth/login" \ -H 'Content-Type: application/json' \ -d "{\"email\":\"$SUPER_EMAIL\",\"password\":\"$SUPER_PASSWORD\"}") login_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/auth/login" \ -H 'Content-Type: application/json' \ -d "{\"email\":\"$SUPER_EMAIL\",\"password\":\"$SUPER_PASSWORD\"}") check "2. login (super_admin)" "$login_code" "200" ACCESS=$(echo "$login_resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))") REFRESH=$(echo "$login_resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('refresh_token',''))") if [[ -z "$ACCESS" || -z "$REFRESH" ]]; then echo "FATAL: login response missing tokens" >&2 echo "$login_resp" >&2 exit 1 fi # --------------------------------------------------------------------------- # 3. /v1/users/me with Bearer # --------------------------------------------------------------------------- me_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $ACCESS" "$AUTHD/v1/users/me") check "3. /v1/users/me (with Bearer)" "$me_code" "200" me_no_auth_code=$(curl -s -o /dev/null -w "%{http_code}" "$AUTHD/v1/users/me") check "3a. /v1/users/me (no Bearer)" "$me_no_auth_code" "401" # --------------------------------------------------------------------------- # 4. refresh → new pair # --------------------------------------------------------------------------- refresh_resp=$(curl -s -X POST "$AUTHD/v1/auth/refresh" \ -H 'Content-Type: application/json' \ -d "{\"refresh_token\":\"$REFRESH\"}") refresh_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/auth/refresh" \ -H 'Content-Type: application/json' \ -d "{\"refresh_token\":\"$REFRESH\"}") check "4. refresh" "$refresh_code" "200" NEW_ACCESS=$(echo "$refresh_resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))") NEW_REFRESH=$(echo "$refresh_resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('refresh_token',''))") if [[ "$NEW_ACCESS" == "$ACCESS" || "$NEW_REFRESH" == "$REFRESH" ]]; then FAIL=$((FAIL+1)) RESULTS+=("FAIL 4a. refresh rotated (got same tokens)") else PASS=$((PASS+1)) RESULTS+=("OK 4a. refresh rotated (new JTI + new refresh)") fi # --------------------------------------------------------------------------- # 5. re-use OLD refresh → session_killed # --------------------------------------------------------------------------- reuse_resp=$(curl -s -X POST "$AUTHD/v1/auth/refresh" \ -H 'Content-Type: application/json' \ -d "{\"refresh_token\":\"$REFRESH\"}") reuse_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/auth/refresh" \ -H 'Content-Type: application/json' \ -d "{\"refresh_token\":\"$REFRESH\"}") check "5. re-use old refresh" "$reuse_code" "401" if echo "$reuse_resp" | grep -q "session_killed"; then PASS=$((PASS+1)) RESULTS+=("OK 5a. reuse → session_killed (family killed)") else FAIL=$((FAIL+1)) RESULTS+=("FAIL 5a. reuse → expected 'session_killed', got: $reuse_resp") fi # After kill, NEW_REFRESH is also revoked post_kill_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/auth/refresh" \ -H 'Content-Type: application/json' \ -d "{\"refresh_token\":\"$NEW_REFRESH\"}") check "5b. new refresh after family kill" "$post_kill_code" "401" # --------------------------------------------------------------------------- # 6. invite # --------------------------------------------------------------------------- invite_resp=$(curl -s -X POST "$AUTHD/v1/users/invite" \ -H "Authorization: Bearer $ACCESS" \ -H 'Content-Type: application/json' \ -d "{\"tenant_slug\":\"$TENANT_SLUG\",\"email\":\"$INVITE_EMAIL\",\"role\":\"tenant_admin\"}") invite_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/users/invite" \ -H "Authorization: Bearer $ACCESS" \ -H 'Content-Type: application/json' \ -d "{\"tenant_slug\":\"$TENANT_SLUG\",\"email\":\"another-$(date +%s)@acme.test\",\"role\":\"viewer\"}") check "6. invite (super_admin)" "$invite_code" "200" invite_no_auth_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$AUTHD/v1/users/invite" \ -H 'Content-Type: application/json' \ -d '{"email":"x@y.com","role":"viewer"}') check "6a. invite (no Bearer)" "$invite_no_auth_code" "401" # --------------------------------------------------------------------------- # 7. admind /v1/dlq (gated) # --------------------------------------------------------------------------- dlq_no_auth_code=$(curl -s -o /dev/null -w "%{http_code}" "$ADMIND/v1/dlq") check "7. admind /v1/dlq (no auth)" "$dlq_no_auth_code" "401" # Need a fresh token (the family was killed above) login_resp=$(curl -s -X POST "$AUTHD/v1/auth/login" \ -H 'Content-Type: application/json' \ -d "{\"email\":\"$SUPER_EMAIL\",\"password\":\"$SUPER_PASSWORD\"}") ACCESS=$(echo "$login_resp" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") dlq_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $ACCESS" "$ADMIND/v1/dlq") check "7a. admind /v1/dlq (super_admin Bearer)" "$dlq_code" "200" # --------------------------------------------------------------------------- # 8. ingestd /v1/admin/ingest (gated) # --------------------------------------------------------------------------- ingest_no_auth_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$INGESTD/v1/admin/ingest") check "8. ingestd /v1/admin/ingest (no auth)" "$ingest_no_auth_code" "401" ingest_auth_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$INGESTD/v1/admin/ingest" \ -H "Authorization: Bearer $ACCESS" \ -H 'Content-Type: application/json' \ -d '{}') # We expect 4xx (invalid alert) or 5xx (deps nil), not 401. The point is # the gate let us through. if [[ "$ingest_auth_code" == "401" ]]; then FAIL=$((FAIL+1)) RESULTS+=("FAIL 8a. ingestd /v1/admin/ingest (auth Bearer) — gate rejected the token") else PASS=$((PASS+1)) RESULTS+=("OK 8a. ingestd /v1/admin/ingest (auth Bearer) — gate passed ($ingest_auth_code)") fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo echo "=== M13a smoke summary ===" for r in "${RESULTS[@]}"; do echo " $r" done echo echo " $PASS passed, $FAIL failed" echo if [[ $FAIL -gt 0 ]]; then exit 1 fi echo "all M13a smoke checks passed"