c89010a4f1
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from f976bd5 on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
76 lines
4.3 KiB
Bash
76 lines
4.3 KiB
Bash
#!/usr/bin/env bash
|
|
# Photos on disk: stored as files rather than base64, served only to the owning facility, and
|
|
# still whole in a backup.
|
|
set -u
|
|
B=${BASE:-http://127.0.0.1:3111}
|
|
# Refuses early, with the fix, when the server under test is in production mode with
|
|
# Turnstile refusing every auth route — otherwise the first signup fails and every check
|
|
# after it reports a security-check error instead of what it was testing.
|
|
. "$(dirname "$0")/e2e-preflight.sh"; e2e_preflight "$B"
|
|
T=${TMP:-/tmp}; J="$T/tc-ph-cj.txt"; K="$T/tc-ph-cj2.txt"; rm -f "$J" "$K"
|
|
PASS=0; FAIL=0
|
|
ok() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
fail() { FAIL=$((FAIL+1)); echo " ✗ $1 :: $2"; }
|
|
mut() { curl -s -b "$J" -c "$J" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"$1\",\"payload\":$2}"; }
|
|
check(){ local name=$1 out=$2 pat=$3; if echo "$out" | grep -q "$pat"; then ok "$name"; else fail "$name" "$(echo "$out" | head -c 200)"; fi; }
|
|
py() { python3 -c "import sys,json; d=json.load(sys.stdin); $1"; }
|
|
|
|
# A real 1x1 JPEG, so what is written is a genuine image rather than random bytes.
|
|
JPEG_B64=$(python3 -c "
|
|
import base64
|
|
# smallest valid JPEG
|
|
b = bytes.fromhex('ffd8ffe000104a46494600010100000100010000ffdb004300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00011080001000101011100ffc40014000100000000000000000000000000000009ffc40014100100000000000000000000000000000000ffda0008010100003f0054df')
|
|
print(base64.b64encode(b).decode())
|
|
")
|
|
DATAURL="data:image/jpeg;base64,$JPEG_B64"
|
|
|
|
TS=$(date +%s)
|
|
echo "== setup"
|
|
check "signup" "$(curl -s -c "$J" -X POST "$B/api/auth/signup" -H 'content-type: application/json' \
|
|
-H "x-forwarded-for: 10.17.$((RANDOM%250)).$((RANDOM%250))" \
|
|
-d "{\"first\":\"Pia\",\"last\":\"Photo\",\"facility\":\"Photo Hospital $TS\",\"email\":\"ph$TS@example.com\",\"password\":\"password123\"}")" '"ok":true'
|
|
check "the facility names its staff groups" "$(e2e_groups "$B" "$J")" '"ok":true'
|
|
|
|
echo "== storing"
|
|
R=$(mut photo.put "{\"kind\":\"sig\",\"data\":\"$DATAURL\"}")
|
|
check "photo accepted" "$R" '"id"'
|
|
PID=$(echo "$R" | py "print(d['result']['id'])")
|
|
|
|
echo "== it is on disk, not in the database"
|
|
ROW=$(curl -s -b "$J" "$B/api/backup" | py "
|
|
ph = [x for x in d['photos'] if x['id']=='$PID'][0]
|
|
print('path=' + (ph.get('path') or '') + ' mime=' + (ph.get('mime') or '') + ' bytes=' + str(ph.get('bytes')))
|
|
")
|
|
# Named after the row id we generated, never after anything the request supplied — the trailing
|
|
# space keeps the match inside the path field, so an empty path cannot borrow the slash in the mime.
|
|
check "the row records a file path" "$ROW" "path=[A-Za-z0-9_-]\{1,\}/$PID\.jpg "
|
|
check "and the mime type" "$ROW" 'mime=image/jpeg'
|
|
# The decoded image is 143 bytes (192 as base64). Pinning the number is what catches bytes ever
|
|
# recording the encoded length instead of the real size of the file on disk.
|
|
check "and the size" "$ROW" 'bytes=143$'
|
|
|
|
echo "== serving"
|
|
CT=$(curl -s -b "$J" -o /dev/null -w '%{content_type} %{http_code}' "$B/api/photo/$PID")
|
|
check "served as a jpeg" "$CT" 'image/jpeg 200'
|
|
check "signed out is refused" "$(curl -s -o /dev/null -w '%{http_code}' "$B/api/photo/$PID")" '401'
|
|
|
|
echo "== another facility cannot read it"
|
|
check "second facility signs up" "$(curl -s -c "$K" -X POST "$B/api/auth/signup" -H 'content-type: application/json' \
|
|
-H "x-forwarded-for: 10.18.$((RANDOM%250)).$((RANDOM%250))" \
|
|
-d "{\"first\":\"Oth\",\"last\":\"Er\",\"facility\":\"Other Photo $TS\",\"email\":\"pho$TS@example.com\",\"password\":\"password123\"}")" '"ok":true'
|
|
check "their request 404s" "$(curl -s -b "$K" -o /dev/null -w '%{http_code}' "$B/api/photo/$PID")" '404'
|
|
|
|
echo "== the backup is still whole"
|
|
# Byte-identical to what went in, not merely a data URL with the right prefix: the prefix is built
|
|
# from the mime column, so a truncated or wrong file on disk still produces one.
|
|
check "backup embeds the image" "$(curl -s -b "$J" "$B/api/backup" | py "
|
|
ph = [x for x in d['photos'] if x['id']=='$PID'][0]
|
|
print('same' if ph.get('data') == '$DATAURL' else 'DIFFERENT')
|
|
")" 'same'
|
|
|
|
echo "== rubbish is refused"
|
|
check "not an image" "$(mut photo.put '{"kind":"sig","data":"data:text/html;base64,PGgxPmhp"}')" 'JPEG or PNG'
|
|
check "empty" "$(mut photo.put '{"kind":"sig","data":""}')" 'JPEG or PNG'
|
|
|
|
echo; echo "PASS=$PASS FAIL=$FAIL"; [ "$FAIL" -eq 0 ]
|