From 27417f6806d70c39eeacdbf90cfb1be00e4bbe5f Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:47:26 -0700 Subject: [PATCH 1/4] Remove CI/CD stuffs from being directly in project (migrated to separate CI/CD infrastructure) --- .forgejo/workflows/build-and-deploy.yml | 72 ----------- cicd/scripts/build.sh | 30 ----- cicd/scripts/deploy.sh | 47 ------- deploy/certs/renewal-hooks/deploy/merge.sh | 4 - deploy/docker-compose.yml | 39 +----- deploy/haproxy/challenge.html | 143 --------------------- deploy/haproxy/haproxy.cfg | 85 ------------ 7 files changed, 2 insertions(+), 418 deletions(-) delete mode 100644 .forgejo/workflows/build-and-deploy.yml delete mode 100755 cicd/scripts/build.sh delete mode 100755 cicd/scripts/deploy.sh delete mode 100644 deploy/certs/renewal-hooks/deploy/merge.sh delete mode 100644 deploy/haproxy/challenge.html delete mode 100644 deploy/haproxy/haproxy.cfg diff --git a/.forgejo/workflows/build-and-deploy.yml b/.forgejo/workflows/build-and-deploy.yml deleted file mode 100644 index 274e94a..0000000 --- a/.forgejo/workflows/build-and-deploy.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Build And Deploy -on: - push: - branches: - - main - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Install dependencies - run: | - apt-get update && apt-get install gettext dnsutils iputils-ping -y - - name: Check out repository - uses: actions/checkout@v4 - - name: Substitute environment variables in .env.example and write to .env - env: - CERTBOT_EMAIL: ${{secrets.CERTBOT_EMAIL}} - CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}} - ANDROID_SMS_GATEWAY_LOGIN: ${{secrets.ANDROID_SMS_GATEWAY_LOGIN}} - ANDROID_SMS_GATEWAY_PASSWORD: ${{secrets.ANDROID_SMS_GATEWAY_PASSWORD}} - ANDROID_SMS_GATEWAY_RECIPIENT_PHONE: ${{secrets.ANDROID_SMS_GATEWAY_RECIPIENT_PHONE}} - OTP_SUPER_SECRET_SALT: ${{secrets.OTP_SUPER_SECRET_SALT}} - SSH_USER: ${{secrets.SSH_USER}} - SSH_KNOWN_HOST: ${{secrets.SSH_KNOWN_HOST}} - ASTRO_DB_REMOTE_URL: ${{secrets.ASTRO_DB_REMOTE_URL}} - SSH_KEY: ${{secrets.SSH_KEY}} - WIREGUARD_PRIVATE_KEY: ${{secrets.WIREGUARD_PRIVATE_KEY}} - WIREGUARD_PUBLIC_KEY: ${{secrets.WIREGUARD_PUBLIC_KEY}} - DNS_SERVER: ${{vars.DNS_SERVER}} - DNS_ADDRESS: ${{vars.DNS_ADDRESS}} - DOMAIN: ${{vars.DOMAIN}} - PUBLIC_IP: ${{vars.PUBLIC_IP}} - ANDROID_SMS_GATEWAY_IP: ${{vars.ANDROID_SMS_GATEWAY_IP}} - ANDROID_SMS_GATEWAY_URL: ${{vars.ANDROID_SMS_GATEWAY_URL}} - IMAGE_FILENAME: ${{vars.IMAGE_FILENAME}} - IMAGE_NAME: ${{vars.IMAGE_NAME}} - SSH_PORT: ${{vars.SSH_PORT}} - SSH_HOST: ${{vars.SSH_HOST}} - WIREGUARD_ALLOWED_IPS: ${{vars.WIREGUARD_ALLOWED_IPS}} - WIREGUARD_ADDRESSES: ${{vars.WIREGUARD_ADDRESSES}} - WIREGUARD_ENDPOINT_HOST: ${{vars.WIREGUARD_ENDPOINT_HOST}} - WIREGUARD_ENDPOINT_PORT: ${{vars.WIREGUARD_ENDPOINT_PORT}} - HEALTH_TARGET_ADDRESSES: ${{vars.HEALTH_TARGET_ADDRESSES}} - HEALTH_ICMP_TARGET_IPS: ${{vars.HEALTH_ICMP_TARGET_IPS}} - VERSION_INFORMATION: ${{vars.VERSION_INFORMATION}} - PUBLICIP_ENABLED: ${{vars.PUBLICIP_ENABLED}} - run: | - envsubst < .env.example > .env - # - name: Export secrets and variables to $GITHUB_ENV - # env: - # SECRETS_CONTEXT: ${{ toJSON(secrets) }} - # VARS_CONTEXT: ${{ toJSON(vars) }} - # run: | - # EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - # to_envs() { jq -r "to_entries[] | \"\(.key)<<$EOF\n\(.value)\n$EOF\n\""; } - # echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV - # echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV - # - name: Update .env with secrets and variables - # run: | - # envsubst < .env.example > .env - # cat .env - - name: Run build script - run: | - cd cicd/scripts - chmod +x ./build.sh - ./build.sh - - name: Run deploy script - run: | - cd cicd/scripts - chmod +x ./deploy.sh - ./deploy.sh diff --git a/cicd/scripts/build.sh b/cicd/scripts/build.sh deleted file mode 100755 index 8c105a4..0000000 --- a/cicd/scripts/build.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -set -eu - -####################### -# VARIABLES # -####################### -ROOT_DIR=$(dirname $(dirname $(dirname $(realpath $0)))) -GIT_REF=${GIT_REF:-main} - -### NO EDITS BELOW THIS LINE ### -cd ${ROOT_DIR} -source .env -git checkout ${GIT_REF} -GIT_SHA=$(git rev-parse --short HEAD) - -if [[ "${GIT_REF}" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)(-.*)?$ ]]; then - VERSION="${BASH_REMATCH[1]}" - if [[ -n "${BASH_REMATCH[2]}" ]]; then - VERSION="${VERSION}${BASH_REMATCH[2]}" - fi - echo "Using git tag version: ${VERSION}" -else - VERSION=$(node -p "require('./package.json').version || '0.0.0'") - GIT_SHA_SHORT="${GIT_SHA:0:7}" - VERSION="${VERSION}-${GIT_SHA_SHORT}" - echo "Using package.json + SHA version: ${VERSION}" -fi - -docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:v${VERSION} --build-arg VERSION=${VERSION} . -docker save -o ${IMAGE_FILENAME} ${IMAGE_NAME}:latest diff --git a/cicd/scripts/deploy.sh b/cicd/scripts/deploy.sh deleted file mode 100755 index 2dec7ad..0000000 --- a/cicd/scripts/deploy.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -set -eu - -####################### -# VARIABLES # -####################### -ROOT_DIR=$(dirname $(dirname $(dirname $(realpath $0)))) - -### NO EDITS BELOW THIS LINE ### -cd ${ROOT_DIR} -source .env - -mkdir -p ${HOME}/.ssh -chmod 700 ${HOME}/.ssh -echo "${SSH_KEY}" > ${HOME}/.ssh/id_ed25519-${SSH_HOST//./_} -echo "${SSH_KNOWN_HOST}" > ${HOME}/.ssh/known_hosts-${SSH_HOST//./_} -chmod -R 600 ${HOME}/.ssh/ -chmod 700 ${HOME}/.ssh - -grep -q "Host ${SSH_HOST}" ${HOME}/.ssh/config 2>&1 1>/dev/null || cat >> ${HOME}/.ssh/config <&2 && exit 1 -echo "WIREGUARD_ENDPOINT_IP=${WIREGUARD_ENDPOINT_IP}" >> .env - -DOCKER_HOST=ssh://${SSH_HOST} docker load -i ${IMAGE_FILENAME} - -ssh ${SSH_HOST} "mkdir -p /srv/${IMAGE_NAME#*/}/" -ssh ${SSH_HOST} "cd /srv/${IMAGE_NAME#*/}/ && docker compose down" -scp .env ${SSH_HOST}:/srv/${IMAGE_NAME#*/}/.env -cd deploy -scp -r . ${SSH_HOST}:/srv/${IMAGE_NAME#*/}/ -ssh ${SSH_HOST} "cd /srv/${IMAGE_NAME#*/}/ && docker compose up -d" diff --git a/deploy/certs/renewal-hooks/deploy/merge.sh b/deploy/certs/renewal-hooks/deploy/merge.sh deleted file mode 100644 index c71d27b..0000000 --- a/deploy/certs/renewal-hooks/deploy/merge.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -cat /etc/letsencrypt/live/badblocks.dev/fullchain.pem /etc/letsencrypt/live/badblocks.dev/privkey.pem > /etc/letsencrypt/fullcert.pem -chmod 755 /etc/letsencrypt/ -chmod 644 /etc/letsencrypt/fullcert.pem diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index f752649..a4cbf23 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -5,8 +5,6 @@ services: container_name: badblocks-personal-site volumes: - ./db:/db - networks: - - proxynet env_file: - .env # healthcheck: @@ -41,46 +39,13 @@ services: devices: - /dev/net/tun:/dev/net/tun restart: unless-stopped - networks: - - proxynet # healthcheck: # test: ["CMD", "ping", "-c", "1", "-W", "3", "$$ANDROID_SMS_GATEWAY_IP"] # interval: 30s # timeout: 15s # retries: 3 # start_period: 60s - certbot: - image: serversideup/certbot-dns-cloudflare - container_name: certbot - volumes: - - ./certs:/etc/letsencrypt - environment: - CLOUDFLARE_API_TOKEN: "${CLOUDFLARE_API_TOKEN}" - CERTBOT_EMAIL: "${CERTBOT_EMAIL}" - CERTBOT_DOMAINS: "${DOMAIN}" - haproxy: - image: haproxy:3.2 - stop_signal: SIGTERM - container_name: haproxy - env_file: - - .env - command: ["haproxy", "-f", "/usr/local/etc/haproxy"] - ports: - - "${PUBLIC_IP}:80:80" - - "${PUBLIC_IP}:443:443" - - "${PUBLIC_IP}:8404:8404" - volumes: - - ./haproxy:/usr/local/etc/haproxy:ro - - ./certs:/certs:ro - restart: always - networks: - - proxynet - # healthcheck: - # test: ["CMD", "haproxy", "-c", "-f", "/usr/local/etc/haproxy"] - # interval: 30s - # timeout: 10s - # retries: 3 networks: - proxynet: + default: name: proxynet - driver: bridge + external: true diff --git a/deploy/haproxy/challenge.html b/deploy/haproxy/challenge.html deleted file mode 100644 index 73eeca0..0000000 --- a/deploy/haproxy/challenge.html +++ /dev/null @@ -1,143 +0,0 @@ - - -Challenge Accepted! - -
- - - - -
-
- -
-
- - - diff --git a/deploy/haproxy/haproxy.cfg b/deploy/haproxy/haproxy.cfg deleted file mode 100644 index c3a6d7b..0000000 --- a/deploy/haproxy/haproxy.cfg +++ /dev/null @@ -1,85 +0,0 @@ -global - daemon - log stdout format raw local0 info - maxconn 2000 - # For normalize-uri - expose-experimental-directives - -defaults - mode http - log global - timeout connect 5s - timeout client 30s - timeout server 30s - timeout check 5s - retries 3 - option httplog - option dontlognull - option redispatch - -frontend http - bind :80 - mode http - - http-request redirect scheme https unless { ssl_fc } - -frontend www - bind :443 ssl crt /certs/fullcert.pem - - # 2 general purpose tags in this stick-table (name defaults to frontend name, i.e. www) - stick-table type ipv6 size 1m expire 2d store gpt(2) - http-request track-sc0 src - http-request normalize-uri path-merge-slashes - http-request normalize-uri path-strip-dot - http-request normalize-uri path-strip-dotdot - - # Drop the connection immediately if the requester previously requested the honeypot path) - http-request silent-drop if { sc_get_gpt(0,0) gt 0 } - - # Protect all paths except /robots.txt, /.well-known/*, and /favicon.ico - acl unprotected_path path -m reg ^/(robots.txt|\.well-known/.*|favicon\.ico|_challenge)$ - # Matches the default config of anubis of triggering on "Mozilla" - acl protected_ua hdr(User-Agent) -m beg Mozilla/ - # Set stick table index 0 to 1 if request is for honeypot path - http-request sc-set-gpt(0,0) 1 if { path -m beg /blokmeplz/ } - http-request silent-drop if { path -m beg /blokmeplz/ } - - acl accepted sc_get_gpt(1,0) gt 0 - http-request return status 200 content-type "text/html; charset=UTF-8" hdr "Cache-control" "max-age=0, no-cache" lf-file /usr/local/etc/haproxy/challenge.html if !unprotected_path protected_ua !accepted - use_backend challenge if { path -m beg /_challenge } - - http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" - default_backend main - -backend challenge - mode http - option http-buffer-request - # The parameter to table must match the stick table used in the frontend. - http-request track-sc0 src table www - acl challenge_req method POST - http-request set-var(txn.tries) req.body_param(tries) - http-request set-var(txn.ts) req.body_param(ts) - http-request set-var(txn.host) hdr(Host),host_only - http-request set-var(txn.hash) src,concat(;,txn.host,),concat(;,txn.ts,),concat(;,txn.tries),sha2,hex - acl ts_recent date,neg,add(txn.ts) ge -60 - # 4 is the difficulty, should match "diff" in challenge.html. - acl hash_good var(txn.hash) -m reg 0{4}.* - http-request sc-set-gpt(1,0) 1 if challenge_req ts_recent hash_good - http-request return status 200 if challenge_req hash_good - http-request return status 400 content-type "text/html; charset=UTF-8" hdr "Cache-control" "max-age=0" string "Bad request" if !challenge_req OR !hash_good - -backend main - mode http - balance leastconn - option httpchk GET /health - http-check expect status 200 - - server badblocks-personal-site badblocks-personal-site:4321 check resolvers docker resolve-prefer ipv4 init-addr none - -resolvers docker - nameserver dns1 127.0.0.11:53 - resolve_retries 3 - timeout resolve 1s - timeout retry 1s - hold valid 10s - hold obsolete 30s From e010ac2fd7a9e5ed890965802c10275226028098 Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:47:45 -0700 Subject: [PATCH 2/4] Add .env to docker ignore --- .dockerignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 60f958b..a1c1a89 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,4 @@ docker-compose* README.md LICENSE .vscode -.env.* +.env From 6e7977997e35105697fa358d53802e7c3b828cb5 Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:48:35 -0700 Subject: [PATCH 3/4] Fix contact form action processing for "reset" being nested incorrectly --- src/actions/contact.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/actions/contact.ts b/src/actions/contact.ts index b69426c..6822f4b 100644 --- a/src/actions/contact.ts +++ b/src/actions/contact.ts @@ -167,20 +167,20 @@ const submitActionDefinition = { return { nextAction: "complete", }; - } else if (input.action === "reset") { - context.session?.delete("phone"); - context.session?.delete("name"); - context.session?.delete("msg"); - - return { - nextAction: "send_otp", - }; } throw new ActionError({ code: "SERVICE_UNAVAILABLE", message: "Message failed to send.", }); + } else if (input.action === "reset") { + context.session?.delete("phone"); + context.session?.delete("name"); + context.session?.delete("msg"); + + return { + nextAction: "send_otp", + }; } }, }; From 07dda5bc38bc433408aed6589e1ed791df73a3ff Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Fri, 10 Jul 2026 00:21:45 -0700 Subject: [PATCH 4/4] dev work --- Dockerfile | 6 +-- astro.config.mjs | 14 ++--- bun.lockb | Bin 254191 -> 286548 bytes deploy/docker-compose.yml | 29 +++++------ docker-compose.yml | 2 +- package.json | 34 ++++++------ public/.well-known/mta-sts.txt | 6 +++ public/crt.svg | 6 +++ public/hi-res.svg | 10 ++++ src/actions/contact.ts | 92 ++++++++++++++++++++++++--------- src/lib/Otp.ts | 56 ++++++++++++++++---- src/middleware.ts | 54 +++++++++++++++---- src/pages/ai.astro | 8 +++ src/pages/cap/redeem.ts | 17 +++++- src/pages/contact.astro | 20 ++++--- src/pages/index.astro | 2 +- 16 files changed, 257 insertions(+), 99 deletions(-) create mode 100644 public/.well-known/mta-sts.txt create mode 100644 public/crt.svg create mode 100644 public/hi-res.svg create mode 100644 src/pages/ai.astro diff --git a/Dockerfile b/Dockerfile index c3a7015..d50b2d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1 AS base +FROM oven/bun:1@sha256:50317d83cd5a5ae1d8b35b3379c69f57ce1a0dbf4def91f0965653d767851834 AS base WORKDIR /usr/src/app # install dependencies into temp directory @@ -24,10 +24,10 @@ ENV NODE_ENV=production #RUN bun test RUN bun run build-remote -# copy production dependencies and source code into final image +# copy production dependencies and source code into final image # and run with node to avoid some random issues with bun # (e.g. bun's fetch doesn't support dispatchers) -FROM node:20-slim AS release +FROM node:24-slim@sha256:44a253901c13323953b4a0e1df5afe08bf8ac4ac086edd500422b54cb6e6eeb0 AS release WORKDIR /usr/src/app COPY --from=install /temp/prod/node_modules node_modules COPY --from=prerelease /usr/src/app/dist ./dist diff --git a/astro.config.mjs b/astro.config.mjs index 427afd4..9c6cf56 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,6 @@ import htmx from "astro-htmx"; // @ts-check -import { defineConfig, envField } from "astro/config"; +import { defineConfig, sessionDrivers, envField } from "astro/config"; import alpinejs from "@astrojs/alpinejs"; import sitemap from "@astrojs/sitemap"; import bun from "@nurodev/astro-bun"; @@ -23,13 +23,11 @@ export default defineConfig({ prefetch: { prefetchAll: true, }, - security: { - checkOrigin: false, - }, session: { - driver: "lru-cache", - ttl: 3600, - maxEntries: 1000, + driver: sessionDrivers.lruCache({ + ttl: 3600, + maxEntries: 1000, + }), }, server: { host: true, @@ -61,8 +59,6 @@ export default defineConfig({ }, integrations: [alpinejs(), sitemap(), htmx(), db()], experimental: { - preserveScriptOrder: true, chromeDevtoolsWorkspace: true, - failOnPrerenderConflict: true, }, }); diff --git a/bun.lockb b/bun.lockb index e82ed005fead897f91ac5a1c750ab6705549925d..c5f177cee28573ede9d19c2c89303d30079b151f 100755 GIT binary patch delta 74339 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 R2dQ_r&}(e-QCDc^{{h)hg+>4X delta 54730 zcmeFZd0b8F`~Sb!u1!l)+f*Tysgg9$!*1RUN@+%Do;B0NE<_otrB-DOks%}+5QQiS zAqt_)LsX_h{H}Yiwa&pDs(<2O7WzrWu5;q2GCp7*`3d%o^_t?jsXf&HmLGH0$z z=8^YRyMkoc$))$-AFjRU5oA;FO+|U_(VhAZyLMEM`nvR)2m^hha=ql4CCRx-8a{eV z24gIPk-iU97IX~c7zH{W?9rfGKqr7IfKCLB4h~8P2xTx5gQNMbd_)-xHt@ip_=Gqh z!|)%v&z-<9X`UZIlrKKiFD?eO7F>^q)B~W%>sC)7AW3CN@1pH~ zG5!td&0r&k?O-E^HK1~!MWFJa>p)S(qd=uW2*%WAL4BGK1=MC0Zpxksqg- z)cqZh4~@nR(9sMYBVC4eC`uRb5o(4C3Lys{|^CGKkaQ z3wT8tyJ)Q^x<#dVpW(LAKy`Gf;>JL+Xdu4B+(sR*s7JLs1r!xO0E!0Y1t=QvQJ~0c zp#g(23v?wYDs>?!OpQt72Iq>Ilc}y z>YqwbRKRvnwC?oHsF`pQ6y<*n6-5KnMOV0pLk)lw(Z-Ti83qeg_YEjyNKdk$ye|hu z%^P7!jmkBGJwlqd0Tva?T?JX$paWLaT~9z!S^I&b^gdWtsPV@kKjOu-CeZiCMMp*^ z#xNM7_Ee)^+fi+70Yz=907a8!JI#|Q&P!hk7iczFfWneXpH3?i6!mHkmvVTC)&rm@ zUj`_eR7*i&=BJy3qRvvJRRk2}du>Zqpa~SEAEWJD(8*}I1jh%h2wI*HuLuAwWTz3- zv}giFw*N?KmZn4pGSJ6`wu7UWCq#rLFkIcJ>7-3-j9)}F%Etj4Ei)f?%AYdWDDcRF zN`eLk`!N_q>Bw=i67-#Bv}Y4fs$^}VZKO0$-HRH5!1$m9TTw+XIS5s-3RCi4Rq-pgre-qv?N=R)>Yu>^eZ(MWCpjTPbx&Uk4Xx;sghV#i4>z zXgd}ZE!R+5_bsMvZIp|_Sh0kvNGPpKiCQs^Z2dBd3c7hIeYZ8i#^n`r{Hdm|1Ld#L z0BUq)K_^35D+yOz9ZMuKaB1G0U@Gr)P*jf?waBn27?Ag1qXwV>@(T!JgojWio&iOC z5ES_ij`T}N2n%8a#rr3QMFuh|!>K8{6BI4K^`H|$V`yDSt2L#(^tm*U1w|`Ol-9m5 zYK=UmwTaf#wC<&KGp$Lq2GZ&ZIu87q(W*+T46P$T$HJ;g{}M_~u^w7)(|RG4I5ldn z?D=?V;FkL>j}C*$8y6Is91{csNDPfq;yp~Hy6r3|>W)<~tTJ_IvUa9;bLGc+JHIwCC4FD@>~FAyeo zjNt5mP8KkE7=jrjm?2chGt%h+;-w2lS}+iTDI^$q!4wjVq+q59223zB1XD~fLj+S$ zFy#a@KrnNtfrj8Em_qcNOczWM!BiAXEx}Y3OtA~}ObHBH;TM@01ZPz0v?Mqs2~IhJ z)05z|BRDPbPdzY|(gVYSVYx9F&)`2aXZ<2$!j=bx$1mMXMWYj-XtuOLI*LvgK~Yp( zxrJIYaX|raPG&F+w^9SF4vJa~%J&Tu}L&bq4P!#kca;a>KLHWTH zIGVf`c~rVP*iv9;KygUN6I5~WVF^J|elZL|EI1Dw73UWRK>2^BkMkfR< zU%^y%1=K3i07dN$3Q`LWi;GW~1vV-mDl95U zEzqAanNI&gQu$wzM9SD%JSiHF1jQjc#C*ORsbZ?QOi;8L!IMFJY$StmZ3nfh9Ro#i zaXY9C=qgYYyFx%Gg1XXb0y+U~Wl+>l6G2fsaZvEfOBbBs)1V+!flyGC@xmU;@nKNp zAZ9O>z7k>!`s@LY+UHP0b-Xz!vOj~O`+I0U7Zeq^82qAW!vaP96ulq$6NS@J5fMIa zs{i*=%Fi26G_XPO0e+Ewfk6S0VTlm-7=oxJh(;-gsE%m{Mdc=I8Z1ZkiiiwKj04>T zeo)6uhig>-IY)?hp)R7+I5U-R@8Co zzCKWN-z!j5yf-K+#t{^a7l%&w3txe1#khKse?Kq15-!ks35MCPmKYZn1E+2XY;lp% zfqwC@IFtRNBGCY+fiVC{?4%lcYd@oQHHGsUR0fNXB7#)_# zi-*`H2sOJeQa5nxshVyCMFo8XMUI|oKoPkGR`k11`5aqN64|jf#AldMv8~ULsk5e z8LQ}gfwYQTqk7`oRmyJr}ZP&9X-SNRp?S0A;Ezxar#iK-eJTNB?zKV0;ELuhaM zZjEb^dn^|&n_be>-=Vf7d~ZeL1G(}Gk8V7#TkD|Ra^kMXqCG^@MDt21>Clmt=~HV& z6byQeihM_IV5{jpf6uk%HH<&4(W5qM+QAepGe0UMqd$ zqm83y?$uM7k80#6PfC*5>3DhHNQH>Tj-xv^MGVZSo8F*dziwUTyVAXS#~ztUze!qg zZ_B*wNyOkR4MIaHi>OypsC=Vjj=5C!7Rl@^``Y|=!R9N{GHs*xw-hGt3BMCIQq+D` z%~q3^oNuLj&&r9ck(jGv+5Oepm2>dzFRiJ|zxtHDzh+%>V`ZE}?Z-JCYDe`OHdPf^ zb>AY)Rb<5SR)=nXJ-=>>Vm-$ciS5q7i9r5-QQSa3?Uc1wNF3e#PN z&i9C%*#=VnC-6tJS43v!kA7vgyK~YLqG`5*7-MprTBU!cL!PPzPm^npBH>4u7WYq__MIMO&<-R{@mqVWS?4vyW)%nQ!hz5bk4 z+3=WyE7t|Tnma#Hchs)#hwDZ@*SgsHNjKR=_`85*FYWPefFb941exN2mSrla8 zgsYl9{*=x;n#grE7iBUSHpCV+E7m!>xVuonSkOHa#xu z57<bb;%*7%J z6%#IYh=?`eGM`K&&YIX_Dg%(LQV*z1mVZ}0=0&@yoVJQUL zk_+Yez&A#ez;__=){=`i!nWs|@qNn4 z3w-(z-PTvTNx5{HddG+(QnViGr>mXiSS3T9W1IYXarT1s6MC4@Fm!eT)JYCocX~1>S)DRnq*~494QW^3@RC zkmCn1D`4UT+tZwgb>`v)s)C+E5nu;_&FA8{8iTP6axsYv4|5_H$a5g`_ypxrClp<{ zckh3Hc2#l?TaqKp%y;4!dBLjSTlz)Jeda)Ds^AHULsq7N5Oh6LdDPrU{N_5 zY&iI2Q^8oE^_)srEac*Cz=A1{(1ry>8?Y;8ln-hKk0<;Vaaq0`v}z=rtgwSb{~|6v z6&5`5O!b&Qk+B%Y9~d+X)*|!=eiJOT4#WrxH*+xySkin0O}_O+i7$i~V5s|We)ml! zG?#Gk6_!-@jo?r44zN(*6D1nFIQSTtZOA=Uq%GmPl#6c%=D^Q^Ceuiw8<-E&>#s$E z9VTLzaq;iKsOqAo8xxox7vE$<<%1vv)qMn(EqG$_L&svG(~pbIAQb(%tW0|bV-eBi zXT^F3S3dkJLnzb(U13`ZivTY6mZF)PZ2R zb8rO@1_Kr~>;Qg_9L$+$i{xVIglrTSJ5BgSaanzkZbhh>SYh@=e-sP`EKRf@@FN@6 zNMtOB%?+3ipP@BpNwA~2SPtO>-}OXEG?)3&i+C4pi)j#=F zE|x&#!gmGH8P8>RFQTR_98LHi5wvUJa*OG`jNjG}VkiM7oG*2j8AVj&axg6-I}t_J zwnQ$oX$diDg)N*?{8n&r+ojZ;W}r^PRulceP6I>D#s~-@_#3b^z=91fnuE(Mqw>Rw zfOX_Tu#;e0ry14pH^D+Po$AQ3#M>k;HjmIu=Hl!9sA}@d!f%0vR+9+PeV>Dm^rtN9 z@WvrrQ(z7Pvn1qJSh1eMl@XzqYK3_cDydxd`2Z@=3(k#`iT*#&@Q1F%F@)ii`7ts6K_FH8}V=upoHTqtOc%nwDZH0Kooa zv6_pgLbyURh2J5pGhpcvzK&MRZ^6VQo-L+B`0==SQV4?qkqwH2!y{e~7OILYzt=i{w_;6;U@+#RD=d~M$>3ra_}}7$<{B>M&HpY%->iX1 z2E&n`&y;Xo%VlLnF&OrI4{d@gbjDuG#RB->$N1l0`QK&)mdV97pl{agXa9Xu9wpRq!&f(8^{I{e~R; z$zN8Cta zXMq0G!7xyCbp5+q`*r_Tg>695$>i^OJAqpNgUV(8TWLW+|5;kaKk~+!8%P0!bHe{9 zjJy8d91uYN*<+nR|Jh*04gXf%AfW%uTMqQ^)kPHp_|L*PS^w5I*+Bp4whidt-J;1L zm;G;fgMj{{cUgyl>JeRAt=LcD3auIudQc|t-*=4NNJo>uQD>lL|H!);=z@RHC;#eU z(k5zV{k^p1K+!Vz8^z0jqIC`%9UMYBz(T{xk7evxo9PF2x_**Go2EW%IZz8i;*u5f z>}F!rC0q7q1cV$*5Q-c1ah)yHQyPRLXAX1o7Gm~gTXr8n{^FxO!rrq{pT*q@y+_DJ zS>c=D3PlCjWz;$N7qC!n{$mb%_BJX;(6=(AP^`g&fNDV={+WeMf`xWpK^}-FbM*0h zK#^B?$kTwBl|zjcMrRsDv?c2^kLD0R8g1D!xl|&PP820F`t93>3uicG?mlGGgE>F@4)Y(o2VF=)GM)n8G3i3b}loLfKzT>dGa#RJQn&o9Cin#)WA z&II@{oeM?<&I`Jvpi`lO%vh3eXtl-508%%gM47Eam$3x)A+w9xoCOvCd!^F<*Hpi){97IW)h}v~%x*Yy{%dNRi3Um(o?Cw_m30tO z4av5#%yGnR8x8y^m}cOKKX2G_d;T7wMxuCwdjaJ~XZ|v|50(omo9IvC;IsD9@tj~^ z;xLo<60_TF@p6FZgaAjdesy^HEQa1_vIl_j*D&23)DzUA5;#snUO1X-a`1y-@$;ZV z4?c3A;Bj6tiX-L)7Fy@RuSxL|fM}1PB2+I}Xfn|yF!(ctt+bzB^faE0Vn1>Fw5SBi zD_FE5C<@`%4^STXry?f1lz4dGmhE5q_rQnq1J;>RBK(0ZOY9(23!ThZ3*gF@@YS%w z3*iba4LEl}Gj4;07K0dn%`y)`3FvhSZUR@R@8O&ck$(eNmSFME-uPXxP)9&L;Mq^I zj9Ne}{xb~+EaVT)ahEvkHDICfVA8FVLZO7&d6>BV(3WjH@VvGMe zPQ@k>{zNo6L3J4axQw3!YXRKIf5F6No}{`~&_NEFM#*#?MEM5~pDJq9 zU}Jc~!HC;omEUQI_{{tNa7uuv#~4uvP4(KTQR)sq=pLlnQV#an@+ zb;96#kgTO676BmwUkw&oHKP1=auzHdu&CENgLK+R{@!PKmYQcQ{+iqd7Jp5$n&C=^ zh>Ebnhv5pXPS|$gU5xQLdg#z8jg<?L?3bauZkkTGJ-TXPr?hwROdiJnd((|yS0_W ziU!M?kVvr>Ll^u^XqGdt2nH8D;hSF}7WUe*)&SHXc5Sx8Pr((c7xirX3M|S$9Eq5- z8i|yTw#?*4;?hT3{9>aZtt8f56b!w;+RD_uN)&&x#nY}*RTm|&eH>i&8ue&^p|>B* z71xNkK3it(HR3FYcoV_ww`B%45f1%u5^JJQl|;!a4&DV8YBKx+_DUc2R8itye_>_9 z0Q|oHV(6p@4=Tu*2_uJr&H_arq{vpHRT*?L*j9A9u;Rey(fOR{bYVrvg-&+`odk9S zoez`;=HI~o&o))QaE<;yR{(7R@z6phXd>P66||j1+sUBthmppEZ&A1qEFPq&>oaIw zL+e^l^buBs*25RlZ1`ekQaa%voq!bGc!ai*ii3TIwuKcrtfDzmWLMKRQq&{o zY5U(5#{b_F{?5^uUZEW}(+-iMiru5_|5u9U-~XK(QIDX%6oEcS(d2pvidy`Lw!1*# z591llyJ`L$LHPWeh9L(pAOl+8f9MPi+Unj@oW{%?xPl7w`mV?mMs zaY)HsOiZ3V9!zCAS(T4{MKEbHQUsIXFGWo{O$QYIFbv=ewbck2w2lU~1she#k>E39)b@p-$l)ST)W#*C@Q2|KU#LPMpeQ{QL0S=y zqHUz8LD6)+7+PaRp?s7OPiGWXgjT{A(o{MfDRP_+itbwjiV9v!>pEI9LD5H8QNHzb z`UX0E0}n2cqik9?(FsV=4V!5jDYCcFHd3?;6wo%(5n%76ZKTNWZcudJ9#FKN4$>v= zMJ>1j`A~t^==}dDMFlrOKBVX~nL*s|FZl1qO!Y>D2(=pjyD{^3?Eh}eXj=T+M*ZK7 z83msIZp{DPn5hjKZ72WTn1wg$|8E=fZ^_>nStW;sn4il1_I`rS=Xtv(iD`0YB>GEF zUMs)<>D++G_7{IH{m!1;rC@DPtHbS7mhXHxqxMNoPr6z~WwerO)X&GkJNC)OkiJtf z87JGLGrfZ5yqNTIyL@)atPs_WOHRym+x56^Ov;h>fz{3HT%AqTCNNHpP1@cPmGZG! zPUb`Pg#+7xX|TM0lN0&*m-3_wg7K$J27d9oIP8U*B204S1SDgZ)N0JI`7 zos^voKxQ_8RkH!iB%2XvLO@LwfD)Ok3Lr@pKo;!<#AvM)N z)W~ckbIEQb>ZIXZ$ZRkdGVhuTnKj9`2)seSNgaSTS)>l2P#wT90=lHV1^_z^0OcA0 z^vNLv1`+Vp1Yk%W)&x+d2|z*%fHCQ#1;ASiKpg_6B(4oWOdCLyHUJJ;i$DzmaykGk z$S@rMp*jFs5wId`4`U0LuCRw&(+J zBs&r4K)}=hfD@T*03gc%KraIGNkc;b28IB383J%6-y-k^0Vg8>Ze)=WfI=ex!w7hg z_QnA0i~*Dz1Mnh;5Ew+j*93qMdDsL%nF#<1Qvi!dA5#F{rU2>?@Fj6G0AgkUqRap+ zC2J9=K|qcJz>f^$00`v(Xhk4^lr;w+V-8@IIe;Lt8G$AQ)GPpmkjWMRk}LppArMBY zSOQSC1hB;tKm^%|KnDV*Rsf>NY%2g+Rsebth$aoK0T@^V*kuhMmVArA8w8we0K}6; zHUJ8301P9LNZQ*1u!FzUNS50ISV;~cFo=LJ7eF$3mBhZ9^+B^Uo z$mDqdlI8*ELLi$|aRQ+11YnC3fK6m40v!mLIs@23W;+AOat6?gz&6rwJ^+LH0Cvp> zkW0Qr;0*#!E&vF!$OS;53xHt+@=1GF0Cuhb%3T4F31lP(&VH0HACE00}n$ zJ4hcl0N!o@>JZpP;_d*%+yO+n1K2~>B2a^XoCkmsGRy-&s0V;n1oo4%o&aP#0j%-_ zP)ar<(1d`R7l1=#vKN3PF92N#941w~0VsO|*y0V~DA|cX2Lh%(0Lsa19{^cC0D2KP zK^iUuV6YItu7v<9$hQc*LBMGdfJ(Ax5rD!)0EQ7bL)tF}V7C}R`CAK} zs3i~k0x0tZAh86%Inrkd0PiIL>JX?S@ud*GE|7jmE|Rq%WX)1YmRknN4P@9d;Frh- zB$r89KaeYAERsgD8Oc>r(I4a*nG8ZE`NOSU{%~tEsS*JEI+=l_h3o_&I|3ltG!T+o z$?QPjH_2`!w@AYvklSP~k~Z=!k~^eDFvwl92+2LNA4xlD9|Df-LcmdZ2snB`4k0jz zfNv;(PV#UlfU-~k5@7%ykv?Gnyu$$0A<#wQ;Q++K0YrrZcuLkHP=kP61b}WbECN7i z1b|iqo|CeX0AwNotcnEil59qx2?4bz0I$j9C;&-O0J;!(OR6jfpu8NwmgNB6lbr~3 zAYd8|pqI>!29OmEpcjEpq+tvIgBSq2VgU4$ZxMKdfKx1h&ty?7fWlY+!w7sO?c)I0 z#Q`Xf1Mr<3LSPU9-*^B+)U`N&q5c>`DMKD*?14z#wrY$dVWjK-p>lK|BDH$qEF#c>pG) z1CS&A(gBF21877*o@8eLs6ikx1ArpgfIw&lfSGFmOebU40FYS&pdEpkq~cltO$cPJ z1)xN>A&|5dfbKc~DrCkw0LtqCyhK2i)Xan_K8MUkqDFRukXf0KY_lGc)ydrTz%|IX zNHj@{4Io-%5fW{(ABhfWp9P{zmLSn1hmh!#uGt_42Sda}!EJ@idAXa275^J&fz#tdER|H&1i#z~t5Gc(9 z;70Z%P?!h6g8<+`mJk5!2mq|@0K7=o?eH~-z!?O5NGu;f*>(Uy`2ZG?6$p6e1DH?% zz?bwZ03cQXpb>$kB%1_KgFqq)z>jP|Ae00!vk*W48CwWIrVv0o0zsr=5r8HHGK&C& zkZlMg6#>vK1`tMO6a!E$2JjMr2vTzgfDQ!mcL0bYyAjCR0l;P_fM_yzCjf(;0KOs+ zOIqv#@CJd>T>#?Aegq140r1!jAdxKD4Zvw0#_2Ws*+SwZiWGlZF#4dGI z$hhlK^X1-y_{Qc3c2Bo{T5so0_U?vbe|b9uon(xRy9Gg~Y!9@+w*_-_N);|DE$(zv zhyK8A{~1Skp5n86O!2Yx2iKGg^~ETCa=!CP^8mA|vtr>Nai+vR^+OwyR`rdytUua+ zul&KKhBRGr!D7?9SNKDHJ)o2Kn{c7c~|Bgd2+gRsKa)t z|Es&NGtJYrw`-?NY!!`}EB|4f`wqR_rJwJ%zTZ4#^lf`g+QiIfYp#eT$35>eZ(nmW z?(&q1VxbP^kqwt&+-gdo8)jaCfy^LlO8|uKgMo~;D7VsDbN$7JN!Omg`n6@CCAIif ziu*K!#b)Xg7yfipi^vc1zM}T};K~<$H;yKy+54aWx^KVL2?$}az_(ebbGa01^O0KGlsBQ@#sB@YYgtpdE0nA)nD#F8h5AI=c{RY!541i zF1C0$_{u8uM48{BXB8v5rYl{v*wL}{>YvE;&$^Sc>Xx-IE{%fs;7}{EbeLb6!qw~P zv0Z;-`N2okhSxnr4E=u&PCVOa+fcxDc0c<>X7$h;>#H_y{mRpL?B9QC)NjQtyk%zD z9bB>(H|T2f*tGxHyfS{f#Tdx(dSS=rr}8GeY<0XcVbRqM{liydq-Q)T>wdjKx|{D;l<#)~M*qGHWiUiX zMkIgpDDbapla%b;I&c9oy!mE@>EB9C08Zv zdca%tI!Wzbg2nQ2b3z~PpFc}nrn0NVZ{WGsz21}UO2#%Xha0BQHAa<0%UbXe)l;b- zJL*qL8{5=aR;g-Tkoc#yeMZx++WxAMZTX~I|9r*zm#Ge?s#D9m+-$m=i9zdMzW` z8YRnf&2>9k7_gXkqN;xGy9w+vIfeHjJBFg`um1{G@r}APZ7y1I+;O~Te01KtkPcNho_Mdv=I3a5^VHxEKMW+jnhG#eKKD6hhiv8y=Jtm{$ zo7K1Pr7J82J+W2Tueiba7TtT^>tuM$JAWR{J+TjC5A1k+^8JjL*JIQ(4!nvBm(Q2Y zc8OEmCc3Y-mh1cM=(F1@SvrF|bM~t`S{yY*tu+; zHF56(ugynaPT90tLrwWG@6y9HPs9th+xYPPrt|&g3i;)Urp#&BrJs@fWBO^m;a$o} z7rwf5Ep_4DetOsKX@tmQt`@fkTd<~b$K{?+$@A9kE!8JvYJc9dpEt8~TSkCi;(RH- zW2(Y=LXJflKedX|jZTKh_T>B^UTQ`f+?=3#YtbyfEoIxTFMayyvBNdd*T+}$Toxp@ zy(<2c@~GIZWpa#8#E=_a9Q{vTivAOu+p6{7Kk6K?Sdp>ix*yp7;k?)wvcU&OsxbpC= zi-9v7-r3hzX{}P<+W`L{hvR`}O(!yr$jD`6<#hc}?2J8q%d~M#?8K2nVNTJElJr$M zIrE(y+#)nNBY5Bo{U?Tgq#=bJCm8B1y?OVezmZ#9ou_X3l$Y=7MR3u(D^65B*=#)J zhpE1uU9V)@d!wZa?k;gjt&?UsDld3B!R=l{SV7fsu`krJ6XgerLSerfw|8iVJg-ic z!q%SHfAsN{ohx}eCo*mfKAWdvY;GqzxqiHH+O7qYpA>{fM7~*~6}vV6K<0vs2P2Oj z8XWWPxw6c6aE2<3e!ndec6>gWvu@0vdn0FTj%+(J_vG?z`@@vuZ8yto+;{v@G;VGxEKn5HhKAwaCtQ12Zqjt5)zXJ^H@x5tr&>QGN(QCkw$x)aIkrgb!oVBgrt8>;U zC99&wKl?(rPqmQSp?tA{@m?66JOW>Bs&k}fKiq=;j?>+E$_F}v*wk%X)O$i8fn;!IHRpG+$bCW}7 zVS8d- zffC{B-3Y&LJTc)^~jyZd*E|8mhBRVQYAYRURuDr(0$uPx*k{@s}W1V~ZC zY^8ZhYVXQUmNMQ+Joujd+I6MDk99uIX21Qk2F%vm>=8R^SiB&~DXm8|I;8nvOMOIX zx!tv_+0|E6RU9={{e&DJ5U%jr!-rl)7rt3$|9o^XYh&^D6AKQRXXZNW(bv^_n8#a| zZ#Qj^Sy5^5!-X0R-QRBixw7GgcgON}BZi#e zrXq8)=7(o`zXY~yQ$zatTaWh6D~dD@8*_8qfeV}Shs}SV-ZgiA6}!o7<=v^r)9>wB z9nr*phFeDense}PjuUdu4IgT^luEHRNqJ^p(C;0%S;u6B(Gy*5*|Rr`j%#qHeBPOV zcZoyK(8!TV;}%NG>@)Jder;@^$87BJ;G-s?3Lhe|8<-cf3?uXIVx9{R3m0>1tUNxx zVN!ph*t8~tD`T#V&Ny4Pqft@AabIwI`865Ih&87?;xs3c%*PhGwwPaDf0lWLj-%%} zmzT#~=HI_#FI3DCjI_Lmc`}b;WHHiX7&(Bn93vgtL62kP52PnBa>0GjlNfmfX$3|y zAAp|1$c0ENG4d4B(-=9b1L}K5xW4h7DuH)W_tm63KThpFSbXibuW{7Yg7i(x)1qDG zx=dQ~TxybK{`AiuB+kfhZQHVZY2jWij^VRM&-YK`O~^R7{DDw?t1$8`@?S0NU*)5v z{^*e3n?GzgaBarX;%0jb=k?WRr(fO_KDMFpmD7WLN>*1-yquB9T>4FVUb@VqX|{E% zW$)}sRP;N%M|U9gv5@~7j12Dt|Fy#YlYiTD=m3oFwzPPbHoSUcQ z+weeIWl81cn?Ii`6m-35?3n&3$0}~vVfRJv@5yf()W<&^RdQ9x|5=Qj^bq>#96X|f zlHOJDC_2V)KPx!tbY4Z;?K9FP1Iw(PrhFUw;lfRm#cRsnt6jJ9btPBwbd znHqs5%cCdy72>!140yH?OCrZ6h|W?b7jC;4n2kA^#5YfAJHGj0v4SAF|$c=(9C0;_b8X`^-s2!uYZ0mb+7V^ zoQi$U!_TTuUTd=jg%#+t3Tyb?= z?EtA2Xf)~enjb2h3;APKmYzO(EyOhAqwcWMrunl^btX!P9L;_HeE3}6g#AVHlj@3( zjT|$3)xyBhp?)VfqT`#O;+KT|UVd+<)%yNu@E(gtlC#{_uFn)z)tujcta!hQb|T)~ zcRD-L;QJ**irx!%F-R*F&bP=nQV%-E!8^TE-fo8RvYOzYTGT#rd+aTe%{mikbqa%#7|1~KI+ ztRWQ61RqhX@=oyO5$Bq`Oy$VNCBruRP7O>wxzQkM-$C1C?x10k-esSPC5w|h zKSMYM(#s;17a1@R)|&Iu(q|nf_i|oLA(OOyaVy-Ip|%C^hbIR;tSGtj2!y{^ghG`qz^Fi z64DM>zb`>MVf}*g(8IdmBbu#Mdq-E{x@m36iRSEtEe{4J94Z=D_;cUh+UQj0P2+A| zYxO(vxY#3oT9#MaHD2-SwRqt+Zt`-|q(SAFxb?lZ&p)nSFSIu+9ba|Av*4rg zU7^lrCF%(wm<4{GEG`%Y-6|J)Ow^>|M4e3#4AV>LZ^^1HB7J&iwgS zk6&@hF*+~_KB8R?`VQ&0W`6oSBCTllChcr?d8O{dv5$s6Ruz=zORJUi5q?cqV%$?6^?zj)u6%yi>Kycz^O6S0=eC zWXo0iM~(lMp`MYW^Zxj=!xNYN36K4G>ccwYE5|HPbgoF-G^Ov!F1uq>TFhhfD)|+r zp7pwg{i-O17~DADe=GPvw}Z>PTc>!|#nOq6tG({HD8z){db?-o2d@!>(ZtJ$w z<>rD9nk(l_o$ZvRJLgK#5w(FNl_#!R&61^WikLwmdQCmN2({0TkCnQo3to?*r_<-c zj$L<4uavDh*!$KZ=~G(aDRI5fLw^E>pXtpqEYaMnm>N4_&Z@l+T;sZbX)V|jIo}=k zlM5_u&7S*Vhe_*<+SEVJbcK=M7s7sXZgWGk$MV)*lzBEOeTCR#Zdk@fPp`_1jvRd7 z>r1Pj1;$8>@OwEh-lz8VdHJQ^b6)sZj_`PWsO+>&K=@+kut@YO8x0+L)_W=J_?f(H z{;Jms+OHG8t9=MqawkqdV9dVd8$`1kcZqNKWO4e~Z~gFLsj9uI%1UFCc>208E;xF9 zGC!k~vA8%KYjLh~p=*pBzY=!5M{R>&M9Oyetc13Bmj(FUP5Txd>e;pOhR*K3>qQL7 z%kNe!Kg5`_bZ0iyIqvGuHB}S-85ho{Y!XLdM)rv+P{NeW|A5q9+kd> zc=Q%x+B=9)%2(k$V6aqvg6M(B3!i3gi&*`7uc~|M$w8@i7V~oS4rXzu=sNZ!OIDvf z|8(>MqX$PM$;QSke0A}yMNt-KbnqR*9~f9FI0{zGkbex>!0U+>F(w-67SU9%udg_ zH?o>+y^4Ngcn=lsg<$(01=(xR1m6$vTfisbq7tQ22d=mNUb6RaL}|GEf)mn(d!%MBg6u-oTj}pPNBKli!8p5( z3aNMJq`MRyf0BAn=0L1pVV=gBJlUWq{k7Y}i#j;!k`0{g1?Rnkc0^ac+;Qu{%*<4U z+|Cf8>U|cj-fyjEMUs%KQ6nMb#Xi^W~v3?*=U86waKH6O6<@oQ-1#r^m?l4z{f-8Q%MOmfeG# z)$9ZLid7qZ-EG%qog_`KL-k74m2`@#a>n^ws@y*>ZfU~dNp3yIUGeJ7mm<0Gt$nH;^E**0Q^vgpUqeOv$JPssjJ>#o90{c*lWW59Gvda9GGpYX|JB-sgGF)>o} zGYC#*BN;_@BM~PJzhGWIqtXApL4QPFTz}V+vnkEfea*ZQ-Msqt4Yg~1*F^vP;ePW@ zrZRX4HX&y3DV*#lrn}a0^vyt zm$H>Rf0&(8KOimTbiHb2+Wj^2_f(}{NL-M8BuCD`vLofw{$sx1a;F@*-{7*XaCPaj zy31;d@yVy}p1oJT-b`v*jZi64!hRjc7JZ)9T^f8YfV^mJ5~VwIZrl0DC3`;dBD9Wf z)V;Fmi_?KG2R$BqwydyeAydiAt))9JL?)RvAD&e_r1VZ>P9 zA=Hi&F6oQ!tESHERx957ivi8o=3PA+m+)n+^@yVmrhR6LsijTUX7P1nB#aGj?Nwa! z&UdQ#56MhB8?Pn97m6<=IoxS9BDdUwpneAYGAA&}fo7O2cI{A-#30NKY0_s9iXKFu z4gpyb9|BNzA3)R)fXQSn0^Sb*$o&8yM~3|XAl3mp*~LVN4dP3kUvJ3?3N!li>Wu%e`ubj&p{J6U8^=^X(6srH?6Ua$tZ5;6FAwK-cCK)1QFuE)phBp6io(^KW18C=dO~7j zqTQhEi7C9hQB{rZJ*N#`Jv}mN|DGMIGHP?|lrBGWmNj+lx)kwtMj6NE=Nz6?)BQ~+ zB{N@0Y05{EzMb$>c=;EsWp(Zjw|n0P?oXYME!(a7kaKCeQ_Y19c+6^v=zUc-`g+r9 zPrkLv56J17G`-EM_;;$W#+*yFyAA%ZFa0_xRK4k>;x8B!nTIeaBi9@jG4l%F3_>oSTa1bdb-}{9UFhwO)1|nYKi;&GlZnC_NKkL6D)2-e(#YEl9he# zcXe*V2uHmQvO9msuVHPOaq)1!g!LKqY_jelwAw|(@%7Z}2Ro0?BrNgxnWI+DTeqmR z`e(q;jt56$x$U`m_nb%n@i1JcW^_zDIcj@^IK28CL!MCSg1 zRwq4zx!L<0<|h0_plGO#^>mLB>`4&w0>mpAu ziTqNgqm!m^i(8jmdi(bES5DN~FGkrjwU#;R_e_7lJu#nmUuI!J1vpJbe# zuW~oD!?$+7+tEYS#jNT#o768n(wMq*9oh8+mYxU3M1QBb1cP!4pTYorxE`h6s1tXH zwNgvF{>8O@Dqi2GS{!?LDz%C?_u09e>FkYLtTpExu`X|rVu^fgY&)7oJR5!2^Of&< zQN4Mp9dm_xKv%fBrfKpcCeFPhWyz5g*<)emtv9*%(D%e??cQb8QzCvn+hEgelDSt+ zOSY2jdplh7a_yaq>;7;wajVHPC+#xDj_8S!rq5v2G>>?Y{^_QDzE#JjX>%98`C0Mw ztwfqs@pU^#xxF{8Ck>EsUC+*Ql+`ZhBy7HVws>eFXPv|+1>>REc$fIJ>#^sBs;4hp zJ^zNosfioroRIaOtLAN>r=<2IYRb3r6b(t)mn+x5FqhMbz4bLj(p+|7%;`;!dq)?R zZ~W;}x6f2%-@xo@vvm_rUnhG}_1+UdH<^*I6;<1BOj+|qGwPH^uf2ZyhuVYI z*Y&(xUv1Cn>G_qGQa$aoj_vP`Wv6yM+dND0$T7wxyG|3~MQtctz2`3bR@)h+UKz-r z?R&;(iJIH&It6Plo!--_SFFtjAG`|*sh7Cuq$B(6isSQyQ6`2CUmH&7?cNd;>^{Qt zs_*95-Q=!rsNTTw_OT-PqBUt}uC;U274F_$Y&F6+8nZqkebJ{#dAh;tKM7YQ#hvj@ zUX!Ktja%m5B(=p9o?U$YjkEJ~Tb{F#Q12NFSC7%7V9(fnaGH1haJ_A8#n1c@=iUvh z4e^lygD-pf?*I6HL+(>x^(oopu1N8Ykf_T$zkfNZJ>Agyi)v2fV3@kke$uH2s@M2- zL4@M9rw5mftMXj$eXcZWaNYe@^78(RdHKbbRy|U_lBs_E`Y*nHn{=|$tSE77Fgq}V z>|b~K&g`>&+D&$uPm@&MXyv{T z(a9qZmS{#jUNGkC!H1Eu#gkc0d&d?H1`gaIzJ4xvGHFapfih25@s3weeWg#x&6Fv^ z)#C_P??QRai1L9QxmPTswkXg3-W=1n<>n{nhH;rvuk&`TRehLvc3<0|=11A%(XEC? zI?BthElV^`DRu}py>b2ETn*_T56SZ9P(53_`DVK+kMFHFm;AQ5Bjn}-N#>#--7Ed3 z?SA-aYm!K@`t*~_2^qByjX!H2GtQ+wyQi;W>ZE!r(W70d_{cW{;n-y%T)kIICNytP zdsD^RZsOxKb${7K#(1Z^+Zm1;!)LLY4{9zVS62Fs{H`M_>p1eK+OTWizOKig``)_L z3>^w3Ht*HEElT>nfa>YJ+G2lv&-MdDGY=G6X@wHbc&pY|u`$JyCJ&Vl+`s4o4g1$zvMrqfX&=Oc33RDOQC zaoMU&>5(TYF}{^_cF{=Q)qs_T{z%9o_O5 zb#%S7VoB(jvkf9LTDR=Kd2-XZgBMpnjw|;hcoOYG_9#xk{NJp2#9wyo6BSbCw^h^E zPnggoZE!-B!R3E>SZ~v<@?9q0bz0-yYpD5%yE)tHJX)Atf5bO^t?%FXO6oo#V`u%F znvP4hHp;i6^qYv+Mig4+H7_3T$S0$O+rAHdeDfPF92m1MU|@1q+wU7b?qoIpmM+8g z%La4jFCEhD+V#dy+^a3$b5gp}Bzo)--)~3P88_NHaebb9t6~+=qIkR`d}=v=v*kjk z)Ift}m({CYBrM)K;+olr@*TeoUfOuU{K&wKr}w{pdd&K#K}~Dl^UzrNzP4SsU}u9> z+Mv5qYNKhc{Gif!sA)+XM4^+m63_#t@7uhw{DrQgGyS$#ICQX$-aB`BWy|H&TIT#@yYGf?d+XWr`*iT!fXkI9 zT9>F-Q5`$qT;}z@+$PIh^NaR&O_whZuamVqez)bNDf_3KtH_}ZK0jFpoDfAA12wfG*JM7`<@W(9NXg%ClT zQ1GjxP}|z<=Z5$l9R7gKFS5o3cfryu8lNb#D}b!0$U@$j)DR-rY9{~gA;o$NA&7OQ zAcx7XgfM&K5r*R#He->$q;)E=sVrn@wZjS)2M84fe7(j=*3AqMEG@5RE5Fr4Z5jx5 zWO4U;$f7_F)r8h&uXFJWKb-muCDU)K3zoJEhAKbfgWjh(KeXUCM4a;b^)uulGTm>w)1CW}i(0I@TR3)(jj9%+?Aan|`)llVuIs_ap#?1fwlu1Rp?QycmC zeJ(@a9Oa7$^6wCF%SG52e|o5>A~MisRT1rH;{Rj+o9!VaTVEccjKTTCDe9;YnfLyK z@{ey3T;Roh#+DuUXLNIjC49|`f3B(yfB#{bDsxk1?A*;B_@`54X0Y=&bNHWLmGM_I zycAiU#vMZZCx3KAuPXA{l{3OGR6Sr%{PR*}{0R}?J1sd^PvJ^p0P2IPpv zl~fsjQN>)%G#|y}*D`D%Jgi1oMOEY)7^lj7Rhb=Ri#Y;N6*4yYwtg4voKXWz{Kvo4 zrP;$4{0oH11#tv>RT-a%*jf&p%u|ISFj-OlZj736s46QDc{Rwmfm?kFanlyTIhkcz}JXtg9;XgzPlzT=H&;teM6Ojz((2-Bm?z$T(q6 zEKZgAz~fR;Ez~U0^Fxv`CDIZuv7rtpYJ1! z)-cNf?#nO0E#Lw7W0+4sA*cxVOPII84R8mPhwK$h2biy6z5!0K^RK*n0(bBhGCP=B zP!_xs@T~4Vgx+wNfoHHk1m>{cg(<)+14Li}xT&2152{D-=YjMb*ud=wbifK!hpI2A z3aSC_9o#_N`bSW^{Kx3~z<%yqKLY-|%yz)vwb=+(fR$hs_ztWF>ygj~z@yRvSc5Xa z67bg^{D3oX1#UnG^uQf>08iirynzp>2=Xf7f0aRBM9g2aItUH{9_2^CP{>!qTm!xb zYr#6O9&7*`!6vX7@MpUg0shX{LNFKbH^K;f0eF`99Au&vvH(_VvO6in<*kBaHXtw; z%meemH(&u+2o`~*U>W!pL?UB6a%+Jqh|~fm|7F59upR6GJHc+iQw&cjSco;T&}|Fa zf%c#ScfyXabON107tj@S1KmL!=mC0yULYPMfJBf4l0gdS4f=po&=eDpOP95Mu3rkb#g%> zNCruu6KD^3F7yWh;4Ct82Al>*!7tz>I0cS_6W~|y8#o4j26+Ye-!`xV@HgAGfC=DB zFbPZs<541PdNcxZECLDeW2ATBIXDLRh0kn|0|o#c&;w`i3SnOZe7aM!1MCF5Ky3l@ zvM+uAyU@+y5aQeqvOqQ%O0K5`U0!n}-wZScEkH}q6nqBo;aH6i;15Re$E3aoYr(Gw z&L1ip55|G9ARlCbY|tC@0jYpLWEKVL0G{5@AzuEV8-Kj*FYca~VA+F2?w}lZ!F})m z@Cy({1z&)1fM2$l0QglN{woeI z;KT1T@bR}2s0{dZ8DDT8kv;$d>>`lBd$^B-6W}Dch+5!<$PuODZ;0`C!uTU&{9&?m z&>zHrXwU|<23JtntKbRZyNNh%A$`q3IQVjy-_08i_%*LQuoUpgk>AXo2l!>DAi$3} z>H}-=JHnSk#Qf2+(SY9_;#UY-gILfObOaf|4)9~JG0<~j6B)_ltbWWLW}e)XKfv4qxKncHTm@EwfwbzZP_Z&^6?uoo8)LTfuFhPw^Y^bbBNYDmu?g-o=S^<2pS<@8eU6@T^RtMEUWv~%4 z9tk`w+<_}72ON2C>;Mb*oHJ1R3~*1j^a4Ermyk=sktTsekf4$bljWQ!@1oKH*D#040R4;E zSuc-;*&qwBqEX3c&nYJ7035*pkPD2NVEI76iE^g7WE`IT**yes##qK~)*lW~usqFF zShyBOf?;4N;L>maZf~}8DcPUx9B_ncp8|7|Dnn$Nv0w~f{ZU{v;BZ_+9Pj5K9^?Zh z4W!HgSy>U7OqOvbI5IZX9BU@R{w3gqCV=t4nBX|rzW~O>@?kf|%Q{9ohn3x2a*n)6 zpt?Cz#RC+#Pln%DfUA=E2FwOLyrzQ&faNoa*}2MRfti47oHNR`oyQ%6J01t92j+u$ zU@n*gz6J!iDmkN^v5zzRDLLzMEplR?17qn~&SBurD~fC^1o42Yj!VmSV*u_zOTiMb z7%T(}0GE(6&JnUsardH+WFPx0Zqz>~$dT~)wz&24$GNm9Zq-?FveE^`wxKqxM0=TgW-fY_nc#nxE44K&Vn;w7frq-^vv50 z>pAczxCw56>);x=3a)^Q;1ak1{!-nSRX3Y%pb$I&lfXT27u*52!F|>K3?^scC3pvr z4G%dloBb_UdRHgJQLSp3_koy7NS#bV$0p2Y%2VA9Z z!S6KfvQRGXGl}JLm@5fv%tnFz#SG zt8(7Wb^z@`C*GW}a!-%|;z2Kv2y#F_z~%1?dIQc(3g`n;0cVW2#_XR7`hzr(4p^S0 z+Kn05l-)e?zJs|MtOZ;jBpUHC_^ANYb8a0na* zKZBnDH`YLylVF~M`3E=)&VUo(7?{l4mtSEy3Vs2c+J z2K!_1NHw3rd;&NuExs&NvVQ@aQJb^H(wcc3hq<84xn?lmz|Bo4!TtgE_n;@>!n}j| z7U1qgo>W-QlZzH^wi|D0iZ!FA8T`2An!vONd}kj5f7xcV6rY#4t7q26Le7hS?&ls>$w3h;0ZjycAlj;B3Hl@gEMaVcVqaW*WVc5Mr4-PJ^QGq=T za90DqfR(BOKTsVMFR?$|H30iFfgk{I9KkS+aeiD=QShq=a0#Z#tA+oCfpB1pfPA}J z6GVbKfbSL?f%>2U;J^(*W5CtS8RSvQC2a~j*I@CgH~N1DIcJD7$Nj4X+-rE1;<+Lj zjwH|%bOh0$IpnQia);~yvpr}B+JYF+8pMJ&pgZ7E*%@Xh)!r3m7tjsFfgXT$dcjOo z?Fm9nERAra0?t4m&>N%x?)fbMXD$u!NIAk6D)<*%fg0Wys=Kn?8fTB6&8zKd893^h zrM5?qA;b`=p;7mR7Gh095dC&v2xuQ@2!&8SAY&=W-!kWQb z%=RVe5F+oFv1H^|AJIm4apuZj;S*^HGlXg=_JQCMf!S;n!rDW_ zwx)Z`&V{S`K_kQvVF*+X=#KE|_2Jo`!;XE;;1gsB3Pggm=z$OF5MDTBC0NbT%QN|3jzv z*tJ*n#YY~?`3^K-iB%&i5ubJmHF+e2;H{1azM?BleNI#l9 zYl{`65OgoP_!femlmU5IA9MMBDL*fCZq^&yJH3<&ia@nzsXi@d)obpsx9=p`2bsj8 zOS?GAG1MSQbQZ%jQ|K|f=TKH1(N(*QevO2&p6dTCxQ6dUEM-yBy1kDl4IkIH5@HEP z3!plWz=xZ4e_qU$j7vLuz$XOtj2xaerzw96F8Zs`;Lf6dnO|n}o;9PO5sIM5!6S3} ztFGuGyf>!{(A3#jC>`Lgd5v9t|GK_H&a15HPFi#1#-FkhMQ8UwO+9FGhj3fhV7{GA z=4(M4g6@haHEk&FIhrOxRjB!()|@ztaSn2}g2SMaOVgmB9Y?EKZ!Yw(+sV(~c5%0Nkr+6pyb`!xnW<&o6> zmo;9lJMv(O$!89|;~2J5-8X`({xtOT&^x^3{qb%$Z!D9vL55%qBrKek)aoS?wxfry zq3=(5FJVN}4tDjU#cYhDUsG*eiuAA0C{LkL0jafq zvSd$+VSuwq!-`tJLf`X74`+qf6B|?PAqjO84o?RB9XJf$N?!-w1Ak+F|j%)(^qTF#Lh`ph?u;L{FfEXVW$q znc)|aVPrc$F*|Qa*9?!{=pLBMxO?1!k3D>r-cQ)lvg1aPI}SY1>0Xql?r+h4S_*PR zcekOhc#ONzs<-Gy@`w+?ryCvKL8y+1rqtveUR*NvxE?eEPAy)p5fENaS6EeX>H0Zi ztw*we;!t+Pkk;5Y15^%6G)%`iBo0Br#Zc=tI4nQGM1lOhWoj73Jeh zeqsHvp9Y(4iAPMByEuKNRq2Q%x)~eVj`rChjBh1n7=|}{vGnXK&-td*>rjxHoM+jH zrUq7Usg0pziPo8k?iioHnxVqgj$=WKEudG9JlQBIR0At2`qNdF_2M-Q8~md-wQ}P` z3u@(RN76A@=*a~RDUvFY2!($1ofV?CrHiNzolgyAjc&U7^R#h;4;?g>!H*oQ5leUS zV`DIlDhorcQl&2cW$>zcN)zGFHsq)jp}x=$=LB?@6V`k$<+EdFvMpYSLAIk$_N zityAxNoCMU)GP}h40M#eT_~$O@}fo_K#y%vkR~`8cq7*5hkj_?&-=Y(>IO>c|I$y? zvZc^G6tSpF=F_nfC0^`=MpF_iSq&e#R28+mshLz<#**mwVkzfYFQ!F<>iIFh%r#~9 zVi0B87L`%=42qZ`^Q+wbLSNuzUdE(YhIXOYB`X1++HGA9b@3VYa=aEV%Svi!lXqgJ zDO8D~YCkTbJV4YOmuTsas{Ft5lk2P0$VwB^Ob3kYP+I4JCNRztM)PeLy>}2@bk^9* zb8T!+?0wPPBXFwxDBVf zC|fbP6FvzieL1v%GCi=q_wA{ssY+H+gzvc$ac)s<&9)%Ft99D&92C8#ObHb|9>N z4WvBhZ~1WliM89$l_f2XZy|hwJJOUMd5bP#w^WTY!8<-oe$q*6(h!^RgCg9I?>FuXI zPzbvU1)dxue7~<5{N{L5lY;P>GWS(Z8Pj9*pAQe4d@7PnMa10z3Y^LIf9+X3?%60e zlR{5w00nfBZmc&GdOW?l4{fczG5mDCNpAyv1%DWHepm&pDv91W0{K-Ed+S`=DLZ-BBh#!dj$OZ6?h3e}!i6DyUkN!1 zp@7QDiL5?dVL>dtse~qz<*vFe?UlV(r?&Go?;pPY8R7YU3q4uZbH$y@h|1y?sE?|G zz;!4ZhED#o4rIT{w4@zFaw50`^5pfThOs8NYZpi6HV^EViTPI6Me-D z-IDG~9v&SSkvaeD(Ni27-=~G(t5Ue|i56COr#e-!WB;)`9gl{2ygOyWt-p$Ze2hsS zH(GPO#pc;gy!j31Y&`Bx9x)gPmb9y?=vvM%PC0b=Wi6?FZmrV>C;SciovNa@E)yQS z1XGWR85P?co#~`)0138n`6Fpz45s`UaTHZebO~P!G2ipN5rd{T&Kfn?Nn5WjMn!}n zXg7Qu;4^kWHRU;_NCVB z+|rxud!Tt<_Ezf5Dx=P!yRmb32-=ZIncHV~A6np#T0P!JIqRivtFtoHJpCqLU*Ik( zC|Gk<^|>`W_sQb=&v%%7p7o*o2&*$sRl6?d|G+apAOmwOx$KwaBa;ShM zt48)Mw5)v6q*YJ#885Br)6=2WV3SWQr5jM}Bq;Fuc*LR&HNBm_-=r{@7DGXpluA1d zDCZ(o@z2pIO@n5i-)BNBJE{fd1j&mC{_d6Y`^ z0#Ht=ud;?N&;@Qkbtkc_NyC-00?=y=G!cfbG4%Mh$;P?yods(LV)8Q9Wmnn*h44&O zA+6Vv4`-%rYhY6NLiGuD7}4>{_O=NopZR^sHV|ESQ(vkYC{`ENWmEe=vA=OBqYEgv zr1UIMysz7W2Io8OX#rg)SJ_fG(n;%?qfD4}=}i!_+MLPOkBM|A|@;ayL=4t%fQA(^5L9Q*d*Roa8dU?yZ684ML(ry&RE>R(wg2wf zYWgqG2sPklH&AnI03C;h?!2ne;ne*~Lo@8Vm@?*0v7soeFMb0I?Xn$uJafED)2`3- zX?z}f>UEc@mreaxZ^S^QyR~1p@I(#!%eSGY&Z*sLAr!(s9Z@Mh>P50UJxmZQc%bOn zh@RW#ep1&C7whb8jOf*^*f8=ALw37UZ8n@Kj_;hjC?iblE$qypKf=Tmby`T#r4Lss zE->fBfp+gZyh1FzJ3#ycXVR2H`6p1TM zjS$^veP#U5>9Np^>>@>P8va-yiEEs^EfHbB6TwA9FQFDsgiT_&A$ayv2yl^4x~D=Q zPHly61P|)_R7jMC$DayqN_#etUK@q`56=cd|9^Zo5c{vs1}v1h)M<0zRex)rZ_%vk zEz+kR7XA{t9@_lxb`Q!d z)_YKXW2?xry`8jOIb{st()SYqw5>G;P?zdrztVOVIc>xcI^PBw`O)|v7JFy2pdgJ} zt!+`QyVWK4ufO(uvpRTJq`V67lQK=IE1~RTP*;w&^^W`L-5pk#!pLi+lb5Vj>JldV zC`)yoY%g^IlYJ(si{ySyVY4cCgeX%grEVbPFr_Zavd=%a1bOmK4h8M17H4(S)VS>W zzVl59&4&+P`Q+Pc*@VLJlZEZcyA5HU9e*+v;hEYe_T8AavYd0X7m~J< zv92DeYh!`LW$=hsiUxGs^RsS}GUvYkdvBztUj?}?@B~+*(F~eQ?K_CmtuIYh4n0+-(4!96Pz6j; z;=F!;yW3u`cPFsb$4dyjFNmB%^*Z8~L4HDA9wRM8(|)S*m~WQ(@ddtlnfST@UXlpr z^F)iT%5mkDLvp|Op})?7PY@1*L1CICgyGHj$ln$Xv2I_c6%xRcT|;n)W-xpl;Iqdr zH{sBof;%RkoaFR@$=TRFH^0%*W_qP-h|LO}&43%u2~#PmFG~8)XysTV@W#nBYSSO9 zQom`ld&6ijou+mY%j@7XBtAV2RiO!eU3OQeSsVDlelS1YroB+G;!PDw;xvQ)>LmJE zpr?fyBEreOvltq_bB0n&GgkXW3rXu{%f$=kTKahB%s#wJ41}`src0g0F#XtBO2>8z^|<@QxoVP}s2osT za9!hbaLTk`JNyE}~Abm`x+QpbG3~)7M?ZR(joRrFTTn*&T7s zdeT}`PO8kF-4&f7P*teeBzZ}K=-f?KKr7;fq}deHRh%o_B)SVt;Te%fw_>%_rW=-~ z1y2Px-P<|JMsxbeJ~MB}b?vF<8a1oD@FAGZrA6IDr#x&wHJ^S7!+x$(c%L@T&))48 zxUEohoD9Rd!-u!!0e%~jqaTjM(^Si**a z_eOHuxa+{t4L**DJJ!*Eh~4pi^?1?@!&^7F$bk>%XL6GdSrex}dtmaZ1E2EnN!MDP z88{wAe0@X^7iV0_ow?d`fnn|uz#hwrqiCe>fu^i;QClh5$E)B_g+trjj+ z`v27D%NK2T7e7bo`Rx>>w+cR7k~?iD9dLFzQC6-OdCJ)hAI{092XkEJul)Nb*$3~L z&>wN4UZ1&0X_2MBd_81g-;=5E#f=8nr#f!(&O^f*;rryQ{qWjub*g+vBzOPo@ZlN% zc$icBE*G9z@RbfOFm8FBdkPE`RyU4GH-{cELDoW!)9K!PDidba?)1E80LQ&s?V_dy?-6=c+bb=Gk7Tt?}=$| zG&Fd99gw>ARI?^cpO`f4=hAxCmu3}rpmjCG3bZa>46!smO`*H-VoZ3cXY^p+U4MG^ zy1ktGC*aon1(M{xb^T)O;nWB2%8JA9m}$-rP&z@eONgS&1>?#86DgyU$+rWp;lAqC zKuo~?C9XgJ=%jXCD3lsh-YyMYLG==G3-Z$nWk8(h{N3^9&lcXm>dRL-7!b;WU+aIn z;GbMcdlSV9HADVo>A$d2ndFlCRUUYApv5`U(*I;7*(Qk<(n2&=tCSiFs=uuA@X-!# z@<2j^1#2AP!-Jr2$Mwsu*^lPC48C$_ExW`f0AN4%qgo@ofj+8+oX?zmSw z-;1ki|93l+Poq^dH3{nxYjuDYPri{GdQQGM>zhp`t$y&~t~sH{;-ClH%M^=wG<>ST z$1keZ;K_5>W|_jQfsZ46HZ+?4s-R)hA51>G;lp!r!*A->o3x= zk!;*{Inm@~(Nn1L9j%2-n6{dZ^PWc81n~v*H>)WyMc(XiDC4F_Z~O*PmzD9BbLs2~ zrEwz>KkD~#KMa>o@1O81+Y5OAr0%rTeUyI8_sYETXCvJ^+j;?b=dmWXe&}kPhu_jM zfLiqy4W4{MjhAvWlHzl40@-_YVBK#bV}2L#JPG@f;2``|wOGLdleJvXMSYNtTBz{) zYn64~a?sx=#*Ry9f;v&B)Ryq!_I_hg?qF1z&E|4@V{?YLir10{;?So1%Ii zWb^u@KB-fxyhMx=UbO$7EK{+^`t)O+P3tK$6)_)LuPhMf##eb?uiJPlQ#sWgtJ*~L zZC|lGdG-~}^rdE3u6#9!y64sVY*JS8$E$9~+P7P>MXo9NIi9gKi$(5cY+B?OGE5DREhEO1d=&8efrh6c7g0YbOGVc2_75zb&05G2%iTpu-9<2(H4ErH zhlnXq>UC_pmrbot^1CQqkI)cQ_am4o=X0PFMTW7hK1IDV6d&e!hX!WrlHWKkoj@Zn411z+rF zwDO%T-vgrHJfEZ|XZKEq`RrTA`NH&+sd5-OXSoQ&gWz8Jfq9*8#VR-DfrdN`n}qE6 z#N-@|U#CCJxBqyHUj*jfkAzBn#D$#{9-yny)qT2m!_W3%eGmqd0sDk*SEdfBb8!1# z&A&S+XHMS9eE}aUWTAS%XK!D2b9%yI@|1m4YL0*Jw2TDJpfM>o4Xp=+!Hc(Lh%Re~ zl5gv8uACoZag9%*SQgkP1qY%m^q7HHTbv4vnH&e7Amy>*bA%~}gnlg>{d?Cf53BLU zUO5=Y>{Pn?IE!ivqL*%5A>?tOAo*_s)ao`h1fK@Nkia4`)4eY0qUpv{DY zC;kb0=n-qof}6Wbh0V)n+}YM@IZDLyJi6tAJyaF)R|4My)-EkAp-X*aTZ=* zSJ<=p7XuJNO})-u+6zs6GiY#}p2F20v4!%Xku|Un`f@MXW?@%)85*|G=oI^Vblj7l z_`OzjVJd_VkD#9>54Uv|P7hWVcvP@v`F`r2g?wz>Pg4O zc@Gl9T$7Sh;s>PVMrGt?rllqXU{%aX&h;M{6h!;RA(DxML|Y0PBI;=8An{w8HyGLO zJ6H^&vxCL1G-r@lv7pru5kIb%HcYHc?!&~0f=R=~jvWg;PKw=ps7t7%BM2Nw>nQoq zmxuBELl`1@kyCZaKn5SFGJWYG#S^r3cqr!UB7Lq6CWqsq4;^)p%F_OP=pH*QI?*6k zsXL`j#g5DH0>1k5q7z;ei`|Y#Zy6pEehP3BH0%B)mRF^-Mahb$-Vn{Ht{|2BNT~bB zZB8-QL}!!GN43&rQL>@#mqi;|a2fAss0l*qKy$7YrNwI1O`)ym>f92GvScPzDel$DOezY>sy#1>W@NZ7Iu=*DXC}E9cSo9+3^dkUDyj#L&Ai7zj!f=G|3X8KvDUs&BbO5Ehll3K5L3G^J=IIZ@_Sv4Sy& zR;ChKN!HZXN-C)U&G8TU)c>NL8towxD5k$ro4Xba^-WQ%nuX zf~MDyI%~rV?!&G{L7w?b*I}G9NbO-ft|C>XjsfU)OWRA;=qzm7Kq?H7GU-$t3Nt!T za>TnTm{MqNpj5|Pz8nozVh_BCKG`@(%0quY5F}*?K?d4z4V||`u++j#{!_Yw<-t;p zWW#@MXUOe6G$}bb)1N9wNbMxP3Z!8X(i&}WLDia4u$F^*)1unQ@!6VEIhtBtGNZYX zl5;VS>a{SEZbVA%C1t+FoI`41B$}c+RMOeICPsl*Z4}q2VoS-jCAT7HCB=55wsfX8 zALx8 z+~nW^{>4MeBL6_Td`7HTqS|B!HLP4xv3fb2PG%?;~qOXK0U{j_B711{-yaK*3!iOA+-LGSh2EP>f*5* zYofS}GAD>ts{W%cpek2Di3;+^>sCup!F%dT&Ptz?7m=5BCA_3+2rO`^Cne%H2|Gw$ zI=OQurKY5CMBGu??$3*LFcs97zQ$i%wuzGT^j$MaS3ptHVu5NklDcD-|GJS>+ln`@ z@|L&YR3oX5K=$p>lVh4n5!#@FFPci$S|L1wrZG~{=oX% zB3AOJ*=?kL1%p~+4I%SJl5Tda)V5$=tW-;D%mix9jpjDSS_|6(I_@o*wcv9Eidj^_ z@_>-%lPK;lyx6DY7;#sdOR*p=oSV48cue_c$M^3|8#al0LyET>HpTu%RXwsJKo}VtoIkRC@e}=pI(Q`D8JdliN~p_{)bpjv#wLe{RS4 zw6x?jT3k(XcPZKMYA|#G3K}X_4lS+^zv50gSMpSrH6VFFvcKE~a^rLQ$))xFNQl56 zNyyd}D-1fOWRu0o@|l(6q{rPP9|!gP$Z0AkNxJe2wv)&FF&P|fhJiS|5hfx&94bTc zZ96Q^5!9i*G*RM9P}&eFdC~V(C09$iF%nYyQ+%N0Q()Uc+G|PXgRp;n7AI{~^y2GF MZrVs%*+W|MKXWn;{Qv*} diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index a4cbf23..fa39057 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -7,21 +7,20 @@ services: - ./db:/db env_file: - .env - # healthcheck: - # test: - # [ - # "CMD", - # "curl", - # "-f", - # "-s", - # "--max-time", - # "5", - # "http://localhost:4321/health", - # ] - # interval: 30s - # timeout: 15s - # retries: 3 - # start_period: 120s + healthcheck: + test: + [ + "node", + "-e", + "fetch('http://localhost:4321/health').then(r=>process.exit(r.ok?0:1))", + ] + interval: 30s + timeout: 15s + retries: 3 + start_period: 120s + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + read_only: true wireguard: image: qmcgaw/gluetun cap_add: diff --git a/docker-compose.yml b/docker-compose.yml index cffe336..81c2714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: personal-site: build: . ports: - - 4321:4321 + - 127.0.0.1:4321:4321 restart: no logging: options: diff --git a/package.json b/package.json index 89a5b01..09296bd 100644 --- a/package.json +++ b/package.json @@ -14,34 +14,34 @@ "astro": "astro" }, "dependencies": { - "@astrojs/alpinejs": "^0.4.9", - "@astrojs/check": "^0.9.6", - "@astrojs/db": "^0.18.3", - "@astrojs/node": "^9.5.4", - "@astrojs/partytown": "^2.1.4", - "@astrojs/sitemap": "^3.7.0", - "@astrojs/ts-plugin": "^1.10.6", + "@astrojs/alpinejs": "^1.0.0", + "@astrojs/check": "^0.9.9", + "@astrojs/db": "^0.21.3", + "@astrojs/node": "^11.0.2", + "@astrojs/partytown": "^2.1.7", + "@astrojs/sitemap": "^3.7.3", + "@astrojs/ts-plugin": "^1.10.10", "@cap.js/server": "^4.0.5", - "@cap.js/widget": "^0.1.35", + "@cap.js/widget": "^0.1.56", "@nurodev/astro-bun": "^2.1.2", "@types/alpinejs": "^3.13.11", - "alpinejs": "^3.15.8", + "alpinejs": "^3.15.12", "android-sms-gateway": "^3.0.0", - "astro": "^5.18.0", + "astro": "^7.0.6", "astro-htmx": "^1.0.6", - "htmx.org": "^2.0.8", + "htmx.org": "^2.0.10", "iconify-icon": "^3.0.2", "ofetch": "^1.5.1", - "otplib": "^12.0.1", - "typescript": "^5.9.3", - "undici": "^7.22.0", - "validator": "^13.15.26" + "otplib": "12.0.1", + "typescript": "^6.0.3", + "undici": "^8.6.0", + "validator": "^13.15.35" }, "devDependencies": { - "@types/bun": "^1.3.9", + "@types/bun": "^1.3.14", "@types/validator": "^13.15.10", "doiuse": "^6.0.6", - "prettier": "^3.8.1", + "prettier": "^3.9.4", "prettier-plugin-astro": "^0.14.1" } } diff --git a/public/.well-known/mta-sts.txt b/public/.well-known/mta-sts.txt new file mode 100644 index 0000000..bcf6c8e --- /dev/null +++ b/public/.well-known/mta-sts.txt @@ -0,0 +1,6 @@ +version: STSv1 +mode: enforce +mx: mx.zoho.com +mx: mx2.zoho.com +mx: mx3.zoho.com +max_age: 604800 diff --git a/public/crt.svg b/public/crt.svg new file mode 100644 index 0000000..40b88e8 --- /dev/null +++ b/public/crt.svg @@ -0,0 +1,6 @@ + + CRT Icon + An icon of a CRT monitor, representing retro technology. + + + diff --git a/public/hi-res.svg b/public/hi-res.svg new file mode 100644 index 0000000..32862e0 --- /dev/null +++ b/public/hi-res.svg @@ -0,0 +1,10 @@ + + Hi-Res Icon + An icon of a modern monitor, indicating high resolution. + + + + + + + diff --git a/src/actions/contact.ts b/src/actions/contact.ts index 6822f4b..b908a72 100644 --- a/src/actions/contact.ts +++ b/src/actions/contact.ts @@ -3,13 +3,15 @@ import { z } from "astro/zod"; import type { ActionAPIContext } from "astro:actions"; import validator from "validator"; import SmsClient from "@lib/SmsGatewayClient.ts"; -import Otp, { verifyOtp } from "@lib/Otp.ts"; +import Otp, { verifyOtp, normalizePhone } from "@lib/Otp.ts"; import { createCap } from "@lib/CapAdapter"; import { OTP_SUPER_SECRET_SALT, ANDROID_SMS_GATEWAY_RECIPIENT_PHONE, } from "astro:env/server"; +const MAX_OTP_VERIFY_ATTEMPTS = 5; + const isValidMobilePhone: [(data: string) => any, { message: string }] = [ (value: string) => validator.isMobilePhone(value, ["en-US", "en-CA"]) && @@ -39,7 +41,11 @@ const captcha_input = z.string().trim().nonempty(); const sendOtpAction = z.object({ action: z.literal("send_otp"), - name: z.string().trim().min(5).max(32).transform(stripDisallowedCharacters), + name: z + .string() + .trim() + .transform(stripDisallowedCharacters) + .pipe(z.string().min(5).max(32)), phone: z .string() .trim() @@ -47,11 +53,16 @@ const sendOtpAction = z.object({ msg: z .string() .trim() - .min(25) - .max(512) .transform(stripDisallowedCharacters) - .refine(...noYelling) - .refine(...noExcessiveRepetitions), + .pipe( + z + .string() + .min(25) + .max(512) + + .refine(...noYelling) + .refine(...noExcessiveRepetitions), + ), captcha: captcha_input, }); @@ -63,6 +74,7 @@ const sendMsgAction = z.object({ const resetAction = z.object({ action: z.literal("reset"), + captcha: captcha_input, }); const formAction = z.discriminatedUnion("action", [ @@ -73,7 +85,10 @@ const formAction = z.discriminatedUnion("action", [ const submitActionDefinition = { input: formAction, - handler: async (input: any, context: ActionAPIContext) => { + handler: async ( + input: z.infer, + context: ActionAPIContext, + ) => { if (!OTP_SUPER_SECRET_SALT || !ANDROID_SMS_GATEWAY_RECIPIENT_PHONE) { throw new ActionError({ code: "INTERNAL_SERVER_ERROR", @@ -83,12 +98,10 @@ const submitActionDefinition = { const cap = createCap(context.session ?? null); - if ( - !( - /^[a-fA-F0-9]{16}:[a-fA-F0-9]{30}$/.test(input.captcha) && - (await cap.validateToken(input.captcha)) - ) - ) { + if (!( + /^[a-fA-F0-9]{16}:[a-fA-F0-9]{30}$/.test(input.captcha) && + (await cap.validateToken(input.captcha)) + )) { throw new ActionError({ code: "BAD_REQUEST", message: "Invalid Captcha Token.", @@ -96,7 +109,19 @@ const submitActionDefinition = { } if (input.action === "send_otp") { - const { name, phone, msg } = input; + const { name, msg } = input; + const phone = normalizePhone(input.phone); + + if ( + Otp.isRateLimitedGlobally() || + Otp.isRateLimitedForOtp(phone) || + Otp.isRateLimitedForMsgs(phone) + ) { + throw new ActionError({ + code: "TOO_MANY_REQUESTS", + message: "Too many requests. Try again later.", + }); + } const otp = Otp.generateOtp(phone, OTP_SUPER_SECRET_SALT); const stepSeconds = Otp.getOtpStep(); @@ -107,6 +132,9 @@ const submitActionDefinition = { remainingSeconds != 0 ? " " + remainingSeconds + " seconds." : "." }`; + Otp.recordOtpRequest(phone); + Otp.recordGlobalOtpSend(); + const result = await new SmsClient().sendSMS(phone, message); if (result.success) { @@ -118,15 +146,16 @@ const submitActionDefinition = { nextAction: "send_msg", }; } else { + console.error("OTP SMS send failed:", result.message); throw new ActionError({ code: "SERVICE_UNAVAILABLE", - message: "Verification code failed to send: " + result.message, + message: "Verification code failed to send. Try again later.", }); } } else if (input.action === "send_msg") { const { otp } = input; const name = await context.session?.get("name"); - const phone = await context.session?.get("phone"); + const phone = normalizePhone((await context.session?.get("phone")) ?? ""); const msg = await context.session?.get("msg"); if (!name || !otp || !msg || !phone) { @@ -136,20 +165,38 @@ const submitActionDefinition = { }); } + if (Otp.isRateLimitedForOtp(phone) || Otp.isRateLimitedForMsgs(phone)) { + throw new ActionError({ + code: "TOO_MANY_REQUESTS", + message: "Too many requests. Try again later.", + }); + } + const isVerified = verifyOtp(phone, OTP_SUPER_SECRET_SALT, otp); if (!isVerified) { + const attempts = ((await context.session?.get("otpAttempts")) ?? 0) + 1; + if (attempts >= MAX_OTP_VERIFY_ATTEMPTS) { + context.session?.delete("phone"); + context.session?.delete("name"); + context.session?.delete("msg"); + context.session?.delete("otpAttempts"); + return { + nextAction: "send_otp", + error: "Too many incorrect codes. Please start over.", + field: "otp", + }; + } + context.session?.set("otpAttempts", attempts); return { nextAction: "send_msg", error: "Invalid or expired verification code.", field: "otp", }; - // throw new ActionError({ - // code: "BAD_REQUEST", - // message: "Invalid or expired verification code.", - // }); } - const message = `Web message from ${name} ( ${phone} ):\n\n${msg}`; + const message = `Web message from ${name} (${phone}):\n\n${msg}`; + + Otp.recordMsgSubmission(phone); const smsClient = new SmsClient(); const result = await smsClient.sendSMS( @@ -158,8 +205,6 @@ const submitActionDefinition = { ); if (result.success) { - Otp.recordMsgSubmission(phone); - context.session?.delete("phone"); context.session?.delete("name"); context.session?.delete("msg"); @@ -169,6 +214,7 @@ const submitActionDefinition = { }; } + console.error("Contact SMS send failed:", result.message); throw new ActionError({ code: "SERVICE_UNAVAILABLE", message: "Message failed to send.", diff --git a/src/lib/Otp.ts b/src/lib/Otp.ts index 347a02d..31373d2 100644 --- a/src/lib/Otp.ts +++ b/src/lib/Otp.ts @@ -1,16 +1,44 @@ import { authenticator } from "otplib"; import { createHash } from "crypto"; -const submissionTimestamps = new Map(); -const otpRequestTimestamps = new Map(); +const submissionTimestamps = new Map(); +const otpRequestTimestamps = new Map(); const ONE_WEEK_IN_MS: number = 7 * 24 * 60 * 60 * 1000; const ONE_HOUR_IN_MS: number = 60 * 60 * 1000; const MAX_OTP_REQUESTS_PER_HOUR: number = 3; const MAX_MESSAGES_PER_WEEK: number = 3; const OTP_STEP_IN_SEC: number = 300; const VALID_PAST_OTP_STEPS: number = 1; -const VALID_FUTURE_OTP_STEPS: number = 1; +const VALID_FUTURE_OTP_STEPS: number = 0; const OTP_NUM_DIGITS: number = 6; +const MAX_GLOBAL_OTP_SENDS_PER_HOUR: number = 10; +let globalOtpSendTimestamps: number[] = []; + +export function isRateLimitedGlobally(): boolean { + const now = Date.now(); + globalOtpSendTimestamps = globalOtpSendTimestamps.filter( + (t) => now - t < ONE_HOUR_IN_MS, + ); + return globalOtpSendTimestamps.length >= MAX_GLOBAL_OTP_SENDS_PER_HOUR; +} + +export function recordGlobalOtpSend() { + globalOtpSendTimestamps.push(Date.now()); +} + +function cleanupStaleEntries(map: Map, windowMs: number) { + const now = Date.now(); + for (const [key, timestamps] of map) { + const recent = timestamps.filter((t) => now - t < windowMs); + if (recent.length === 0) map.delete(key); + else if (recent.length !== timestamps.length) map.set(key, recent); + } +} + +setInterval(() => { + cleanupStaleEntries(submissionTimestamps, ONE_WEEK_IN_MS); + cleanupStaleEntries(otpRequestTimestamps, ONE_HOUR_IN_MS); +}, ONE_HOUR_IN_MS).unref?.(); authenticator.options = { step: OTP_STEP_IN_SEC, @@ -30,15 +58,14 @@ function getUserSecret(phoneNumber: string, salt: string): string { } export function normalizePhone(phone: string) { - const result = phone.replace(/[^\d]/g, "").trim().startsWith("1") - ? phone.substring(1) - : phone; - - if (result.length !== 10) { + let digits = phone.replace(/\D/g, ""); + if (digits.length === 11 && digits.startsWith("1")) { + digits = digits.slice(1); + } + if (digits.length !== 10) { throw new Error("Invalid phone number."); } - - return result; + return digits; } export function isValidPhone(phone: string): boolean { @@ -46,9 +73,13 @@ export function isValidPhone(phone: string): boolean { const match = phone.match(/(\d{3})(\d{3})(\d{4})/); const [, prefix, exchange, station] = match ?? []; const isValidNANPFormat = - /^[2-7][0-8][0-9]$/.test(prefix) && /^[2-9][0-9]{2}$/.test(exchange); + /^[2-9][0-9]{2}$/.test(prefix) && /^[2-9][0-9]{2}$/.test(exchange); const isNotAllSameDigit = !/^(.)\1{6}$/.test(exchange + station); const isNot911Number = prefix !== "911" && exchange !== "911"; + const isNotTollFreeNumber = !( + /^[8-9][0-9]{2}$/.test(prefix) && + /^(99|88|77|66|55|44|33|22|11|00)$/.test(prefix.slice(1, 2)) + ); const isNot555Number = prefix !== "555" && exchange !== "555"; const isNotPopSongNumber = exchange !== "867" && station !== "5309"; @@ -57,6 +88,7 @@ export function isValidPhone(phone: string): boolean { isNotAllSameDigit && isNot911Number && isNot555Number && + isNotTollFreeNumber && isNotPopSongNumber ); } @@ -153,4 +185,6 @@ export default { recordMsgSubmission, isRateLimitedForOtp, isRateLimitedForMsgs, + isRateLimitedGlobally, + recordGlobalOtpSend, }; diff --git a/src/middleware.ts b/src/middleware.ts index a97291a..0621cf3 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,7 +1,29 @@ import { defineMiddleware } from "astro:middleware"; import { getActionContext } from "astro:actions"; +import type { APIContext, MiddlewareNext } from "astro"; -export const onRequest = defineMiddleware(async (context, next) => { +// htmz -> frame-ancestors 'self' & X-Frame-Options SAMEORIGIN +// astro -> 'unsafe-inline' +const SECURITY_HEADERS: Record = { + "Content-Security-Policy": [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: https://badblocks.goatcounter.com", + "font-src 'self'", + "connect-src 'self' https://badblocks.goatcounter.com https://api.iconify.design", + "object-src 'none'", + "frame-ancestors 'self'", + "base-uri 'self'", + "form-action 'self'", + ].join("; "), + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "SAMEORIGIN", + "Referrer-Policy": "strict-origin-when-cross-origin", + "Permissions-Policy": "camera=(), microphone=(), geolocation=()", +}; + +async function handle(context: APIContext, next: MiddlewareNext) { if (context.isPrerendered) return next(); const { action, setActionResult, serializeActionResult } = @@ -10,10 +32,11 @@ export const onRequest = defineMiddleware(async (context, next) => { const currentAction = await context.session?.get("currentAction"); if (currentAction) { - const { actionName, actionResult } = JSON.parse(currentAction); - setActionResult(actionName, actionResult); - context.session?.delete("currentAction"); + try { + const { actionName, actionResult } = JSON.parse(currentAction); + setActionResult(actionName, actionResult); + } catch {} return next(); } @@ -39,13 +62,14 @@ export const onRequest = defineMiddleware(async (context, next) => { context.session?.set("contactFormDraft", draft); - const referer = context.request.headers.get("Referer"); - if (!referer) { - throw new Error( - "Internal: Referer unexpectedly missing from Action POST request.", - ); - } - return context.redirect(referer); + let redirectPath = context.originPathname; + try { + const referer = new URL(context.request.headers.get("Referer") ?? ""); + if (referer.origin === context.url.origin) { + redirectPath = referer.pathname; + } + } catch {} + return context.redirect(redirectPath); } context.session?.delete("contactFormDraft"); @@ -53,4 +77,12 @@ export const onRequest = defineMiddleware(async (context, next) => { } return next(); +} + +export const onRequest = defineMiddleware(async (context, next) => { + const response = await handle(context, next); + for (const [header, value] of Object.entries(SECURITY_HEADERS)) { + response.headers.set(header, value); + } + return response; }); diff --git a/src/pages/ai.astro b/src/pages/ai.astro new file mode 100644 index 0000000..4122804 --- /dev/null +++ b/src/pages/ai.astro @@ -0,0 +1,8 @@ +--- +import Layout from "@layouts/BaseLayout.astro"; +--- + + + AI Policy + + diff --git a/src/pages/cap/redeem.ts b/src/pages/cap/redeem.ts index ed93e1c..e07e650 100644 --- a/src/pages/cap/redeem.ts +++ b/src/pages/cap/redeem.ts @@ -10,8 +10,21 @@ export const POST: APIRoute = async (context) => { ); } - const { token, solutions } = await context.request.json(); - if (!token || !solutions) { + let body: { token?: unknown; solutions?: unknown }; + try { + body = await context.request.json(); + } catch { + return new Response(JSON.stringify({ success: false }), { status: 400 }); + } + + const { token, solutions } = body ?? {}; + if ( + typeof token !== "string" || + token.length > 256 || + !Array.isArray(solutions) || + solutions.length > 128 || + !solutions.every((s) => typeof s === "number") + ) { return new Response(JSON.stringify({ success: false }), { status: 400 }); } diff --git a/src/pages/contact.astro b/src/pages/contact.astro index 933fb9e..67f89d1 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -168,8 +168,11 @@ const msgValue = pickValue("msg"); name="name" aria-describedby="name" placeholder="Alice Bob" + value={nameValue} /> - {error.name &&

{error.name.join(",")}

} + {"name" in error && error.name && ( +

{error.name}

+ )}