From ae34b59676b3a49f4f3e84a8a98afa5a531fb247 Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:58:01 -0700 Subject: [PATCH 1/5] Add license page, update LICENSE file, and link license page on the base layout instead of ext. MIT license. Add src/pages/license.astro to render LICENSE with basic escaping and newline-to-
handling. Update LICENSE formatting, and also add new third-party icon attributions. --- LICENSE | 34 +++++++++++++++++++++++++++++----- src/pages/license.astro | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 src/pages/license.astro diff --git a/LICENSE b/LICENSE index 3dd6b7c..e027e8c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ Copyright 2026 badblocks Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the “Software”), to deal in +this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do @@ -10,7 +10,7 @@ so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -19,11 +19,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This software also contains various permissively-licensed 3rd-party -components, the licenses for which are listed below. +components, the licenses for which are listed below: -––– public/goat.js; ISC license –––––––––––––––––––––––––––––––––––––––––––––– +--- public/goat.js; ISC license --- -Copyright © Martin Tournoij +Copyright Martin Tournoij Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice @@ -36,3 +36,27 @@ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- public/crt.svg; CC BY 3.0 license --- + +Icon made by Delapouite: https://game-icons.net + +Used under CC BY 3.0 https://creativecommons.org/licenses/by/3.0/ + +Modified from original + +--- public/hi-res.svg; CC BY 4.0 license --- + +Icon made by Streamline professional team: https://streamlinehq.com + +Used under CC BY 4.0 https://creativecommons.org/licenses/by/4.0/ + +Modified from original + +--- public/mit.svg; PD --- + +Icon made by ZyMOS: https://commons.wikimedia.org/wiki/User:ZyMOS + +Released by author into the Public Domain + +Modified from original diff --git a/src/pages/license.astro b/src/pages/license.astro new file mode 100644 index 0000000..0ae1770 --- /dev/null +++ b/src/pages/license.astro @@ -0,0 +1,35 @@ +--- +import Layout from "@layouts/BaseLayout.astro"; +import fs from "node:fs"; + +const txtToHtml = (string: string) => { + return string + .replace(/[^\x0A\x20-\x7E]/g, "") //remove non-printable ascii chars, except newline + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'") + .replaceAll("\n\n", "

"); +}; + +const license_file_contents = txtToHtml( + fs.readFileSync("./LICENSE", { + encoding: "utf8", + }), +); +--- + + + LICENSE + +
+

+ MIT License +

+
+

+ + 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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}

+ )}