Barazo AppView backend
barazo.forum
1#!/usr/bin/env bash
2# ---------------------------------------------------------------------------
3# test-oauth.sh -- Manual OAuth verification for Barazo API (M3 Checkpoint)
4#
5# This script starts the API (with dependencies) and walks you through
6# testing the full AT Protocol OAuth flow against bsky.social.
7#
8# Prerequisites:
9# - Docker (for PostgreSQL + Valkey)
10# - Node.js 24+, pnpm
11# - A Bluesky account (handle + app password or browser login)
12#
13# Usage:
14# chmod +x scripts/test-oauth.sh
15# ./scripts/test-oauth.sh [handle]
16#
17# handle: Your Bluesky handle (e.g. jay.bsky.team). If omitted,
18# the script will prompt for it.
19# ---------------------------------------------------------------------------
20set -euo pipefail
21
22API_DIR="$(cd "$(dirname "$0")/.." && pwd)"
23API_PORT="${PORT:-3000}"
24API_BASE="http://localhost:${API_PORT}"
25PG_CONTAINER="barazo-test-pg"
26VK_CONTAINER="barazo-test-valkey"
27
28# Colors (if terminal supports them)
29if [ -t 1 ]; then
30 BOLD="\033[1m"
31 GREEN="\033[0;32m"
32 YELLOW="\033[0;33m"
33 RED="\033[0;31m"
34 CYAN="\033[0;36m"
35 RESET="\033[0m"
36else
37 BOLD="" GREEN="" YELLOW="" RED="" CYAN="" RESET=""
38fi
39
40info() { echo -e "${GREEN}[INFO]${RESET} $*"; }
41warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
42error() { echo -e "${RED}[ERROR]${RESET} $*"; }
43step() { echo -e "\n${BOLD}${CYAN}>> $*${RESET}"; }
44
45cleanup() {
46 step "Cleaning up"
47 if [ -n "${API_PID:-}" ] && kill -0 "$API_PID" 2>/dev/null; then
48 info "Stopping API (PID $API_PID)"
49 kill "$API_PID" 2>/dev/null || true
50 wait "$API_PID" 2>/dev/null || true
51 fi
52 # Leave Docker containers running so you can re-run quickly.
53 # To stop them: docker stop barazo-test-pg barazo-test-valkey
54 info "Docker containers left running for quick re-runs."
55 info "To stop: docker stop $PG_CONTAINER $VK_CONTAINER"
56}
57trap cleanup EXIT
58
59# ---------------------------------------------------------------------------
60# 1. Check prerequisites
61# ---------------------------------------------------------------------------
62step "Checking prerequisites"
63
64command -v docker >/dev/null 2>&1 || { error "Docker not found. Install Docker first."; exit 1; }
65command -v node >/dev/null 2>&1 || { error "Node.js not found. Install Node.js 24+."; exit 1; }
66command -v pnpm >/dev/null 2>&1 || { error "pnpm not found. Install: npm i -g pnpm"; exit 1; }
67
68NODE_MAJOR=$(node -v | sed 's/v//' | cut -d. -f1)
69if [ "$NODE_MAJOR" -lt 24 ]; then
70 warn "Node.js $NODE_MAJOR detected; Node.js 24+ recommended."
71fi
72
73info "All prerequisites met."
74
75# ---------------------------------------------------------------------------
76# 2. Start PostgreSQL + Valkey via Docker (if not already running)
77# ---------------------------------------------------------------------------
78step "Starting Docker dependencies"
79
80if docker ps --format '{{.Names}}' | grep -q "^${PG_CONTAINER}$"; then
81 info "PostgreSQL container already running."
82else
83 info "Starting PostgreSQL 16 + pgvector..."
84 docker run -d --name "$PG_CONTAINER" \
85 -e POSTGRES_USER=barazo \
86 -e POSTGRES_PASSWORD=barazo_dev \
87 -e POSTGRES_DB=barazo \
88 -p 5432:5432 \
89 pgvector/pgvector:pg16 >/dev/null 2>&1 || {
90 # Container might exist but be stopped
91 docker start "$PG_CONTAINER" >/dev/null 2>&1
92 }
93fi
94
95if docker ps --format '{{.Names}}' | grep -q "^${VK_CONTAINER}$"; then
96 info "Valkey container already running."
97else
98 info "Starting Valkey..."
99 docker run -d --name "$VK_CONTAINER" \
100 -p 6379:6379 \
101 valkey/valkey:8 >/dev/null 2>&1 || {
102 docker start "$VK_CONTAINER" >/dev/null 2>&1
103 }
104fi
105
106# Wait for PostgreSQL to accept connections
107info "Waiting for PostgreSQL to be ready..."
108for i in $(seq 1 30); do
109 if docker exec "$PG_CONTAINER" pg_isready -U barazo >/dev/null 2>&1; then
110 break
111 fi
112 if [ "$i" -eq 30 ]; then
113 error "PostgreSQL did not become ready in time."
114 exit 1
115 fi
116 sleep 1
117done
118info "PostgreSQL ready."
119
120# ---------------------------------------------------------------------------
121# 3. Install dependencies + run migrations
122# ---------------------------------------------------------------------------
123step "Installing dependencies and running migrations"
124
125cd "$API_DIR"
126pnpm install --frozen-lockfile 2>/dev/null || pnpm install
127
128# Create .env if missing
129if [ ! -f .env ]; then
130 info "Creating .env from .env.example..."
131 cp .env.example .env
132 # Set a real session secret for testing
133 if [[ "$OSTYPE" == "darwin"* ]]; then
134 sed -i '' 's/your-session-secret-minimum-32-characters-long/barazo-dev-session-secret-for-local-testing-only/' .env
135 else
136 sed -i 's/your-session-secret-minimum-32-characters-long/barazo-dev-session-secret-for-local-testing-only/' .env
137 fi
138fi
139
140info "Running database migrations..."
141pnpm db:migrate 2>&1 || {
142 warn "Migration command failed. This may be expected if tables already exist."
143}
144
145# ---------------------------------------------------------------------------
146# 4. Start the API
147# ---------------------------------------------------------------------------
148step "Starting Barazo API on port ${API_PORT}"
149
150# Source .env for the API process
151set -a
152# shellcheck disable=SC1091
153source .env
154set +a
155
156pnpm dev &
157API_PID=$!
158
159# Wait for API to be healthy
160info "Waiting for API to respond..."
161for i in $(seq 1 30); do
162 if curl -sf "${API_BASE}/api/health" >/dev/null 2>&1; then
163 break
164 fi
165 if ! kill -0 "$API_PID" 2>/dev/null; then
166 error "API process exited unexpectedly. Check logs above."
167 exit 1
168 fi
169 if [ "$i" -eq 30 ]; then
170 error "API did not respond within 30 seconds."
171 exit 1
172 fi
173 sleep 1
174done
175info "API is running at ${API_BASE}"
176
177# ---------------------------------------------------------------------------
178# 5. Check setup status
179# ---------------------------------------------------------------------------
180step "Checking community setup status"
181
182SETUP_STATUS=$(curl -sf "${API_BASE}/api/setup/status")
183echo " Response: ${SETUP_STATUS}"
184
185# ---------------------------------------------------------------------------
186# 6. Initiate OAuth login
187# ---------------------------------------------------------------------------
188step "OAuth Login Flow"
189
190HANDLE="${1:-}"
191if [ -z "$HANDLE" ]; then
192 echo -en "\n ${BOLD}Enter your Bluesky handle${RESET} (e.g. jay.bsky.team): "
193 read -r HANDLE
194fi
195
196if [ -z "$HANDLE" ]; then
197 error "No handle provided. Exiting."
198 exit 1
199fi
200
201info "Initiating OAuth login for: ${HANDLE}"
202LOGIN_RESPONSE=$(curl -sf "${API_BASE}/api/auth/login?handle=${HANDLE}" 2>&1) || {
203 error "Login request failed. Response: ${LOGIN_RESPONSE:-empty}"
204 error "Make sure your handle is correct and the API can reach bsky.social."
205 exit 1
206}
207
208AUTH_URL=$(echo "$LOGIN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])" 2>/dev/null || true)
209
210if [ -z "$AUTH_URL" ]; then
211 error "Could not extract authorization URL from response: ${LOGIN_RESPONSE}"
212 exit 1
213fi
214
215info "Authorization URL obtained."
216
217# Open in browser
218echo ""
219echo -e " ${BOLD}Opening your browser to authorize with Bluesky...${RESET}"
220echo ""
221echo " If the browser doesn't open, copy this URL manually:"
222echo -e " ${CYAN}${AUTH_URL}${RESET}"
223echo ""
224
225if [[ "$OSTYPE" == "darwin"* ]]; then
226 open "$AUTH_URL" 2>/dev/null || true
227elif command -v xdg-open >/dev/null 2>&1; then
228 xdg-open "$AUTH_URL" 2>/dev/null || true
229fi
230
231# ---------------------------------------------------------------------------
232# 7. Print verification checklist
233# ---------------------------------------------------------------------------
234echo ""
235echo -e "${BOLD}==========================================================================${RESET}"
236echo -e "${BOLD} M3 Authentication -- Manual Verification Checklist${RESET}"
237echo -e "${BOLD}==========================================================================${RESET}"
238echo ""
239echo -e " ${BOLD}Step 1: Authorize in browser${RESET}"
240echo " - Your browser should show the Bluesky authorization page."
241echo " - Log in / authorize the Barazo application."
242echo " - After authorizing, you'll be redirected to the callback URL."
243echo ""
244echo -e " ${BOLD}Step 2: Check the callback response${RESET}"
245echo " - The callback returns JSON with:"
246echo " { accessToken, expiresAt, did, handle }"
247echo " - Copy the accessToken value for the next steps."
248echo ""
249echo -e " If the callback shows a connection error (the frontend isn't running),"
250echo -e " copy the full callback URL from the browser address bar and run:"
251echo ""
252echo -e " ${CYAN}curl -v '\${CALLBACK_URL}'${RESET}"
253echo ""
254echo -e " ${BOLD}Step 3: Verify GET /api/auth/me${RESET}"
255echo " - Replace ACCESS_TOKEN with the token from step 2:"
256echo ""
257echo -e " ${CYAN}curl -s ${API_BASE}/api/auth/me \\\\${RESET}"
258echo -e " ${CYAN} -H 'Authorization: Bearer ACCESS_TOKEN' | python3 -m json.tool${RESET}"
259echo ""
260echo " - Expected: { \"did\": \"did:plc:...\", \"handle\": \"did:plc:...\" }"
261echo " (Handle currently shows DID -- this is a known TODO for M3.)"
262echo ""
263echo -e " ${BOLD}Step 4: Verify POST /api/auth/refresh${RESET}"
264echo " - The callback set an HTTP-only cookie. Use the cookie jar:"
265echo ""
266echo -e " ${CYAN}curl -s -X POST ${API_BASE}/api/auth/refresh \\\\${RESET}"
267echo -e " ${CYAN} -b 'barazo_refresh=SESSION_ID' | python3 -m json.tool${RESET}"
268echo ""
269echo " - Expected: { \"accessToken\": \"...\", \"expiresAt\": ... }"
270echo " - (You'll need to capture the cookie from the callback response"
271echo " headers to test this. Use curl -v on the callback to see it.)"
272echo ""
273echo -e " ${BOLD}Step 5: Verify setup wizard${RESET}"
274echo " - Check status (should be uninitialized):"
275echo ""
276echo -e " ${CYAN}curl -s ${API_BASE}/api/setup/status | python3 -m json.tool${RESET}"
277echo ""
278echo " - Initialize as first admin (requires access token from step 2):"
279echo ""
280echo -e " ${CYAN}curl -s -X POST ${API_BASE}/api/setup/initialize \\\\${RESET}"
281echo -e " ${CYAN} -H 'Authorization: Bearer ACCESS_TOKEN' \\\\${RESET}"
282echo -e " ${CYAN} -H 'Content-Type: application/json' \\\\${RESET}"
283echo -e " ${CYAN} -d '{\"communityName\": \"My Test Forum\"}' | python3 -m json.tool${RESET}"
284echo ""
285echo " - Expected: { \"initialized\": true, \"adminDid\": \"did:plc:...\","
286echo " \"communityName\": \"My Test Forum\" }"
287echo ""
288echo " - Verify status is now initialized:"
289echo ""
290echo -e " ${CYAN}curl -s ${API_BASE}/api/setup/status | python3 -m json.tool${RESET}"
291echo ""
292echo " - Second initialize attempt should return 409:"
293echo ""
294echo -e " ${CYAN}curl -s -X POST ${API_BASE}/api/setup/initialize \\\\${RESET}"
295echo -e " ${CYAN} -H 'Authorization: Bearer ACCESS_TOKEN' \\\\${RESET}"
296echo -e " ${CYAN} -H 'Content-Type: application/json' \\\\${RESET}"
297echo -e " ${CYAN} -d '{}' | python3 -m json.tool${RESET}"
298echo ""
299echo " - Expected: { \"error\": \"Community already initialized\" }"
300echo ""
301echo -e " ${BOLD}Step 6: Verify logout${RESET}"
302echo ""
303echo -e " ${CYAN}curl -s -X DELETE ${API_BASE}/api/auth/session \\\\${RESET}"
304echo -e " ${CYAN} -b 'barazo_refresh=SESSION_ID' -w '\\nHTTP %{http_code}\\n'${RESET}"
305echo ""
306echo " - Expected: HTTP 204 (no content)"
307echo ""
308echo -e " ${BOLD}Step 7: Verify token is invalidated after logout${RESET}"
309echo ""
310echo -e " ${CYAN}curl -s ${API_BASE}/api/auth/me \\\\${RESET}"
311echo -e " ${CYAN} -H 'Authorization: Bearer ACCESS_TOKEN' | python3 -m json.tool${RESET}"
312echo ""
313echo " - Expected: { \"error\": \"Invalid or expired token\" }"
314echo ""
315echo -e "${BOLD}==========================================================================${RESET}"
316echo -e " ${GREEN}Pass criteria:${RESET} Steps 1-7 all return expected responses."
317echo -e " ${YELLOW}Known limitations:${RESET}"
318echo -e " - Handle shows DID (not resolved yet -- TODO for identity layer)"
319echo -e " - No Tap service required for auth testing (firehose is separate)"
320echo -e "${BOLD}==========================================================================${RESET}"
321echo ""
322echo " The API is still running. Press Ctrl+C when done testing."
323echo ""
324
325# Keep script alive until user terminates
326wait "$API_PID" 2>/dev/null || true