All checks were successful
check / check (push) Successful in 5s
Extracts HMAC-SHA256 request signing out of `internal/imgcache/` into its own `internal/signature/` package, per the plan in [issue #39](#39). This is one of the remaining "easily separable" extractions (`imageprocessor`, `allowlist`, `magic`, and `httpfetcher` already landed). Only the signer is moved here so the diff stays reviewable. ## What moved From `internal/imgcache/signature.go` and its tests into `internal/signature/`: - `Signer` type, its `New` constructor, `Sign`, `Verify`, `GenerateSignedURL` - `ParseParams` (query-string signature/expiration parsing) - Signature error sentinels ## One-way import edge To keep the import edge one-way (`imgcache` depends on `signature`, never the reverse), the package defines a standalone `Request` type carrying just the fields the signature covers, instead of importing `imgcache.ImageRequest`. `imgcache` projects its `ImageRequest` onto `signature.Request` via a small unexported `signatureRequest` helper. This mirrors how the `magic` extraction defined its own `ImageFormat` type. ## Renames (no stuttering) - `NewSigner` -> `signature.New` - `ParseSignatureParams` -> `signature.ParseParams` - `ErrSignatureRequired`/`Invalid`/`Expired` -> `signature.ErrRequired`/`Invalid`/`Expired` The `ErrRequired` message is updated from "non-whitelisted host" to "non-allowlisted host" for inclusive terminology, consistent with the `allowlist` rename. ## Rework (post-review) Three commits added after review feedback: - `d69019b` — golden known-answer test pinning the exact HMAC signatures and signed URL paths for three fixed vectors (resized, resized+query, orig size), cross-validated against an independent HMAC implementation. Any change to the signed byte format now fails loudly. - `43b9f1c` — whitelist→allowlist rename completed across `internal/imgcache` and `internal/handlers` (`ServiceConfig.Allowlist`, `Allowlist` interface, `IsAllowlisted`, test helpers and test names). - `3dc1999` — one-pass config surface rename, no back-compat alias: YAML key `whitelist_hosts` → `allowlist_hosts`, `Config.WhitelistHosts` → `Config.AllowlistHosts`, `config.example.yml`, `scripts/manual-test.sh`, and `README.md` (which also documented a nonexistent `source_host_whitelist` key — now fixed to the real one). ## Behavior Pure refactor apart from the config key rename above. The bytes fed to the HMAC are unchanged (`host:path:query:width:height:format:expiration`), so previously issued signatures remain valid — now enforced by the golden test. All existing tests move with the package. `script/cibuild` passes at head `3dc1999` (fmt-check, lint, test, build). refs #39 Co-authored-by: sneak <sneak@sneak.berlin> Co-authored-by: Jeffrey Paul <sneak@noreply.example.org> Reviewed-on: #46 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
148 lines
4.5 KiB
Bash
Executable File
148 lines
4.5 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Manual test script for pixa server
|
|
# Requires: server running on localhost:8080
|
|
#
|
|
set -e
|
|
|
|
BASE_URL="${BASE_URL:-http://localhost:8080}"
|
|
SIGNING_KEY="${SIGNING_KEY:-test-signing-key-for-development-only}"
|
|
TEST_IMAGE_URL="https://s3.sneak.cloud/sneak-public/2021/2021-04-18.untitled.a7r4.07723.jpg"
|
|
COOKIE_JAR=$(mktemp)
|
|
|
|
cleanup() {
|
|
rm -f "$COOKIE_JAR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
pass() {
|
|
echo "✓ PASS: $1"
|
|
}
|
|
|
|
fail() {
|
|
echo "✗ FAIL: $1"
|
|
exit 1
|
|
}
|
|
|
|
echo "=== Pixa Manual Test Suite ==="
|
|
echo "Base URL: $BASE_URL"
|
|
echo ""
|
|
|
|
# Test 1: Healthcheck
|
|
echo "--- Test 1: Healthcheck endpoint ---"
|
|
HEALTH=$(curl -sf "$BASE_URL/.well-known/healthcheck.json")
|
|
if echo "$HEALTH" | grep -q '"status"'; then
|
|
pass "Healthcheck returns status"
|
|
else
|
|
fail "Healthcheck did not return expected response"
|
|
fi
|
|
|
|
# Test 2: Login page displays
|
|
echo "--- Test 2: Login page (GET /) ---"
|
|
LOGIN_PAGE=$(curl -sf "$BASE_URL/")
|
|
if echo "$LOGIN_PAGE" | grep -qi "password\|login\|sign"; then
|
|
pass "Login page displays password form"
|
|
else
|
|
fail "Login page did not display expected content"
|
|
fi
|
|
|
|
# Test 3: Wrong password shows error
|
|
echo "--- Test 3: Login with wrong password ---"
|
|
WRONG_LOGIN=$(curl -sf -X POST "$BASE_URL/" -d "key=wrong-key" -c "$COOKIE_JAR")
|
|
if echo "$WRONG_LOGIN" | grep -qi "invalid\|error\|incorrect\|wrong"; then
|
|
pass "Wrong password shows error message"
|
|
else
|
|
fail "Wrong password did not show error"
|
|
fi
|
|
|
|
# Test 4: Correct password redirects to generator
|
|
echo "--- Test 4: Login with correct signing key ---"
|
|
curl -sf -X POST "$BASE_URL/" -d "key=$SIGNING_KEY" -c "$COOKIE_JAR" -b "$COOKIE_JAR" -L -o /dev/null
|
|
GENERATOR_PAGE=$(curl -sf "$BASE_URL/" -b "$COOKIE_JAR")
|
|
if echo "$GENERATOR_PAGE" | grep -qi "generate\|url\|source\|logout"; then
|
|
pass "Correct password shows generator page"
|
|
else
|
|
fail "Generator page not displayed after login"
|
|
fi
|
|
|
|
# Test 5: Generate encrypted URL
|
|
echo "--- Test 5: Generate encrypted URL ---"
|
|
GEN_RESULT=$(curl -sf -X POST "$BASE_URL/generate" -b "$COOKIE_JAR" \
|
|
-d "url=$TEST_IMAGE_URL" \
|
|
-d "width=800" \
|
|
-d "height=600" \
|
|
-d "format=jpeg" \
|
|
-d "quality=85" \
|
|
-d "fit=cover" \
|
|
-d "ttl=3600")
|
|
if echo "$GEN_RESULT" | grep -q "/v1/e/"; then
|
|
pass "Encrypted URL generated"
|
|
# Extract the encrypted URL
|
|
ENC_URL=$(echo "$GEN_RESULT" | grep -o '/v1/e/[^"<]*' | head -1)
|
|
echo " Generated URL: $ENC_URL"
|
|
else
|
|
fail "Failed to generate encrypted URL"
|
|
fi
|
|
|
|
# Test 6: Fetch image via encrypted URL
|
|
echo "--- Test 6: Fetch image via encrypted URL ---"
|
|
if [ -n "$ENC_URL" ]; then
|
|
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "$BASE_URL$ENC_URL")
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
pass "Encrypted URL returns image (HTTP 200)"
|
|
else
|
|
fail "Encrypted URL returned HTTP $HTTP_CODE"
|
|
fi
|
|
else
|
|
fail "No encrypted URL to test"
|
|
fi
|
|
|
|
# Test 7: Fetch image via allowlisted host (direct proxy)
|
|
echo "--- Test 7: Fetch image via direct proxy (allowlisted host) ---"
|
|
# URL format: /v1/image/<host>/<path>/<WxH>.<format>
|
|
PROXY_PATH="/v1/image/s3.sneak.cloud/sneak-public/2021/2021-04-18.untitled.a7r4.07723.jpg/400x300.jpeg"
|
|
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "$BASE_URL$PROXY_PATH")
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
pass "Direct proxy returns image (HTTP 200)"
|
|
else
|
|
fail "Direct proxy returned HTTP $HTTP_CODE"
|
|
fi
|
|
|
|
# Test 8: Logout
|
|
echo "--- Test 8: Logout ---"
|
|
curl -sf "$BASE_URL/logout" -b "$COOKIE_JAR" -c "$COOKIE_JAR" -L -o /dev/null
|
|
AFTER_LOGOUT=$(curl -sf "$BASE_URL/" -b "$COOKIE_JAR")
|
|
if echo "$AFTER_LOGOUT" | grep -qi "password\|login"; then
|
|
pass "Logout redirects to login page"
|
|
else
|
|
fail "Logout did not redirect to login"
|
|
fi
|
|
|
|
# Test 9: Generate short-TTL URL and verify expiration
|
|
echo "--- Test 9: Expired URL returns 410 ---"
|
|
# Login again
|
|
curl -sf -X POST "$BASE_URL/" -d "key=$SIGNING_KEY" -c "$COOKIE_JAR" -b "$COOKIE_JAR" -L -o /dev/null
|
|
# Generate URL with 1 second TTL
|
|
GEN_RESULT=$(curl -sf -X POST "$BASE_URL/generate" -b "$COOKIE_JAR" \
|
|
-d "url=$TEST_IMAGE_URL" \
|
|
-d "width=100" \
|
|
-d "height=100" \
|
|
-d "format=jpeg" \
|
|
-d "ttl=1")
|
|
SHORT_URL=$(echo "$GEN_RESULT" | grep -o '/v1/e/[^"<]*' | head -1)
|
|
if [ -n "$SHORT_URL" ]; then
|
|
echo " Waiting 2 seconds for URL to expire..."
|
|
sleep 2
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$SHORT_URL")
|
|
if [ "$HTTP_CODE" = "410" ]; then
|
|
pass "Expired URL returns 410 Gone"
|
|
else
|
|
fail "Expired URL returned HTTP $HTTP_CODE (expected 410)"
|
|
fi
|
|
else
|
|
fail "Could not generate short-TTL URL"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== All tests passed! ==="
|