From 47251539625bab6815ef6fd525f0b5dd82b99007 Mon Sep 17 00:00:00 2001 From: xJayMorex <3513159+xJayMorex@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:49:17 +0200 Subject: [PATCH 01/76] fix: only chown /data/keys.json if necessary --- docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh index 4236c90a2..1306e7035 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh @@ -49,7 +49,9 @@ done # Ensure the JWT key file is owned by the runtime user, even when the /data # directory ownership already matches PUID:PGID (chownit skips recursion then) if [ -f /data/keys.json ]; then - chown "$PUID:$PGID" /data/keys.json + if [ "$(stat -c '%u:%g' '/data/keys.json')" != "$PUID:$PGID" ]; then + chown "$PUID:$PGID" /data/keys.json + fi fi if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then From ccbffa76ff4ed82efbb908a5f8dfd84cc0f17f37 Mon Sep 17 00:00:00 2001 From: JUNG JI YONG Date: Fri, 25 Sep 2026 23:19:18 +0900 Subject: [PATCH 02/76] Update Korean translation - Add missing strings (2FA, logs, per-location access lists, etc.) - Fix translations that didn't match the UI context - Minor wording fixes in Korean help docs --- .../src/locale/src/HelpDoc/ko/AccessLists.md | 4 +- .../src/locale/src/HelpDoc/ko/Certificates.md | 2 +- .../src/locale/src/HelpDoc/ko/DeadHosts.md | 4 +- .../locale/src/HelpDoc/ko/RedirectionHosts.md | 2 +- frontend/src/locale/src/HelpDoc/ko/Streams.md | 4 +- frontend/src/locale/src/ko.json | 267 ++++++++++++++---- 6 files changed, 215 insertions(+), 68 deletions(-) diff --git a/frontend/src/locale/src/HelpDoc/ko/AccessLists.md b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md index 8e9670122..ead647eb2 100644 --- a/frontend/src/locale/src/HelpDoc/ko/AccessLists.md +++ b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md @@ -1,9 +1,9 @@ ## 접근 정책이란? 접근 정책은 특정 클라이언트 IP 주소를 허용하거나 거부할 수 있으며, -프록시 호스트에 기본 HTTP 인증(Basic Auth) 을 적용할 수 있는 기능입니다. +프록시 호스트에 기본 HTTP 인증(Basic Auth)을 적용할 수 있는 기능입니다. -하나의 접근 목록에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤 +하나의 접근 정책에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤 이를 하나 이상의 프록시 호스트에 적용할 수 있습니다. 이 기능은 인증 기능이 없는 웹 서비스에 인증을 추가하거나, diff --git a/frontend/src/locale/src/HelpDoc/ko/Certificates.md b/frontend/src/locale/src/HelpDoc/ko/Certificates.md index 660f39620..f0b3a0637 100644 --- a/frontend/src/locale/src/HelpDoc/ko/Certificates.md +++ b/frontend/src/locale/src/HelpDoc/ko/Certificates.md @@ -24,5 +24,5 @@ DNS 검증 방식의 인증서는 DNS 공급자 플러그인을 사용해야 합 ### 사용자 지정 인증서 -이 옵션을 사용하면 직접 보유한 인증 기관(CA)에서 발급한 SSL 인증서를 직접 업로드하여 사용할 수 있습니다. +이 옵션을 사용하면 사용 중인 인증 기관(CA)에서 발급받은 SSL 인증서를 직접 업로드하여 사용할 수 있습니다. diff --git a/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md index 032a23c94..3b13b2ee7 100644 --- a/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md +++ b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md @@ -4,7 +4,7 @@ 이 기능은 도메인이 검색 엔진에 이미 색인되어 있을 때, 더 깔끔한 오류 페이지를 제공하거나 해당 페이지가 더 이상 존재하지 않음을 -검색 엔진에게 명확하게 알려야 할 때 유용합니다. +검색 엔진에 명확하게 알려야 할 때 유용합니다. -또한 404 호스트를 사용하면 접근 로그를 확인하고, 어떤 경로(Referrer)를 통해 들어왔는지 추적할 수 있다는 장점도 있습니다. +또한 이 호스트로 들어오는 요청을 로그로 추적하고 리퍼러(유입 경로)를 확인할 수 있다는 장점도 있습니다. diff --git a/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md index 7494885de..d5c575e5e 100644 --- a/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md +++ b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md @@ -3,5 +3,5 @@ 리다이렉션 호스트는 외부에서 들어오는 도메인 요청을 다른 도메인으로 자동 이동(리다이렉트)시키는 역할을 합니다. 이 유형의 호스트는 주로 웹사이트의 도메인이 변경되었지만, -검색 엔진이나 다른 사이트에 이전 도메인 링크가 남아 있을 때 사용하면 가장 효과적입니다. +검색 엔진이나 다른 사이트의 링크가 여전히 이전 도메인을 가리키고 있을 때 사용합니다. diff --git a/frontend/src/locale/src/HelpDoc/ko/Streams.md b/frontend/src/locale/src/HelpDoc/ko/Streams.md index 45107b2c3..0d50a979e 100644 --- a/frontend/src/locale/src/HelpDoc/ko/Streams.md +++ b/frontend/src/locale/src/HelpDoc/ko/Streams.md @@ -1,6 +1,6 @@ -## 호스트 스트림이란? +## 스트림이란? -호스트 스트림은 비교적 최근에 Nginx에 추가된 기능으로, +스트림은 비교적 최근에 Nginx에 추가된 기능으로, TCP/UDP 트래픽을 네트워크 내의 다른 컴퓨터로 직접 전달하는 데 사용됩니다. 게임 서버나 FTP, SSH 서버 등을 운영할 때 유용하게 사용할 수 있습니다. diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json index 9c0093591..fc54b5201 100644 --- a/frontend/src/locale/src/ko.json +++ b/frontend/src/locale/src/ko.json @@ -1,12 +1,69 @@ { + "2fa.backup-codes-remaining": { + "defaultMessage": "남은 백업 코드: {count}개" + }, + "2fa.backup-warning": { + "defaultMessage": "백업 코드를 안전한 곳에 보관하세요. 각 코드는 한 번만 사용할 수 있습니다." + }, + "2fa.disable": { + "defaultMessage": "2단계 인증 비활성화" + }, + "2fa.disable-confirm": { + "defaultMessage": "2FA 비활성화" + }, + "2fa.disable-warning": { + "defaultMessage": "2단계 인증을 비활성화하면 계정 보안이 약해집니다." + }, + "2fa.disabled": { + "defaultMessage": "비활성화" + }, + "2fa.done": { + "defaultMessage": "백업 코드를 저장했습니다" + }, + "2fa.enable": { + "defaultMessage": "2단계 인증 활성화" + }, + "2fa.enabled": { + "defaultMessage": "활성화" + }, + "2fa.enter-code": { + "defaultMessage": "인증 코드 입력" + }, + "2fa.enter-code-disable": { + "defaultMessage": "비활성화하려면 인증 코드를 입력하세요" + }, + "2fa.regenerate": { + "defaultMessage": "재발급" + }, + "2fa.regenerate-backup": { + "defaultMessage": "백업 코드 재발급" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "새 백업 코드를 발급하려면 인증 코드를 입력하세요. 기존 코드는 더 이상 사용할 수 없게 됩니다." + }, + "2fa.secret-key": { + "defaultMessage": "비밀 키" + }, + "2fa.setup-instructions": { + "defaultMessage": "인증 앱으로 이 QR 코드를 스캔하거나 비밀 키를 직접 입력하세요." + }, + "2fa.status": { + "defaultMessage": "상태" + }, + "2fa.title": { + "defaultMessage": "2단계 인증" + }, + "2fa.verify-enable": { + "defaultMessage": "확인 후 활성화" + }, "access-list": { "defaultMessage": "접근 정책" }, "access-list.access-count": { - "defaultMessage": "{count}개의 정책" + "defaultMessage": "규칙 {count}개" }, "access-list.auth-count": { - "defaultMessage": "{count}명의 사용자" + "defaultMessage": "사용자 {count}명" }, "access-list.help-rules-last": { "defaultMessage": "규칙이 하나라도 있으면 아래 ‘전체 거부’ 규칙이 마지막에 추가됩니다." @@ -14,6 +71,12 @@ "access-list.help.rules-order": { "defaultMessage": "허용/거부 규칙은 정의된 순서대로 적용됩니다." }, + "access-list.inherit": { + "defaultMessage": "프록시 호스트 설정 상속" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "프록시 호스트의 접근 정책을 사용합니다" + }, "access-list.pass-auth": { "defaultMessage": "인증 정보를 원본 서버로 전달" }, @@ -24,13 +87,13 @@ "defaultMessage": "기본 인증 필요 없음" }, "access-list.rule-source.placeholder": { - "defaultMessage": "192.168.1.100 / 192.168.1.0/24 / IPv6" + "defaultMessage": "192.168.1.100 또는 192.168.1.0/24 또는 2001:0db8::/32" }, "access-list.satisfy-any": { "defaultMessage": "조건 중 하나라도 충족" }, "access-list.subtitle": { - "defaultMessage": "{users}명 {users, plural, one {사용자} other {사용자}}, {rules}개 {rules, plural, one {규칙} other {규칙}} - 생성일: {date}" + "defaultMessage": "사용자 {users}명, 규칙 {rules}개 - 생성일: {date}" }, "access-lists": { "defaultMessage": "접근 정책" @@ -44,6 +107,9 @@ "action.allow": { "defaultMessage": "허용" }, + "action.clear": { + "defaultMessage": "지우기" + }, "action.close": { "defaultMessage": "닫기" }, @@ -65,6 +131,9 @@ "action.enable": { "defaultMessage": "활성화" }, + "action.logs": { + "defaultMessage": "로그" + }, "action.permissions": { "defaultMessage": "권한" }, @@ -102,13 +171,13 @@ "defaultMessage": "지정된 인증서 없음" }, "certificate.none.subtitle.for-http": { - "defaultMessage": "이 호스트는 HTTPS를 사용하지 않습니다." + "defaultMessage": "이 호스트는 HTTPS를 사용하지 않습니다" }, "certificate.none.title": { "defaultMessage": "없음" }, "certificate.not-in-use": { - "defaultMessage": "사용 안 함" + "defaultMessage": "미사용" }, "certificate.renew": { "defaultMessage": "인증서 갱신" @@ -123,19 +192,19 @@ "defaultMessage": "비밀번호로 보호된 키 파일은 지원되지 않습니다." }, "certificates.dns.credentials": { - "defaultMessage": "DNS 자격 증명 입력" + "defaultMessage": "자격 증명 파일 내용" }, "certificates.dns.credentials-note": { - "defaultMessage": "이 플러그인은 API 토큰 등이 포함된 설정 파일이 필요합니다." + "defaultMessage": "이 플러그인을 사용하려면 DNS 공급자의 API 토큰 또는 기타 자격 증명이 포함된 설정 파일이 필요합니다." }, "certificates.dns.credentials-warning": { "defaultMessage": "입력한 정보는 데이터베이스와 파일에 평문으로 저장됩니다." }, "certificates.dns.propagation-seconds": { - "defaultMessage": "DNS 전파 시간" + "defaultMessage": "DNS 전파 대기 시간(초)" }, "certificates.dns.propagation-seconds-note": { - "defaultMessage": "비워두면 기본값을 사용합니다. DNS 전파를 기다리는 시간(초)입니다." + "defaultMessage": "비워 두면 플러그인 기본값을 사용합니다. DNS 전파를 기다릴 시간(초)입니다." }, "certificates.dns.provider": { "defaultMessage": "DNS 공급자" @@ -144,37 +213,37 @@ "defaultMessage": "공급자를 선택하세요..." }, "certificates.dns.warning": { - "defaultMessage": "이 기능을 사용하려면 Certbot과 DNS 플러그인에 대한 기본적인 이해가 필요합니다. 자세한 내용은 관련 문서를 참고해 주세요." + "defaultMessage": "이 항목을 사용하려면 Certbot과 DNS 플러그인에 대한 지식이 어느 정도 필요합니다. 각 플러그인의 문서를 참고하세요." }, "certificates.http.reachability-404": { - "defaultMessage": "해당 도메인에서 서버가 탐지되었지만 Nginx Proxy Manager가 아닌 것으로 보입니다. 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요." + "defaultMessage": "해당 도메인에서 서버가 발견되었지만 Nginx Proxy Manager가 아닌 것으로 보입니다. 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요." }, "certificates.http.reachability-failed-to-check": { - "defaultMessage": "site24x7.com과의 통신 오류로 인해 도달 가능 여부를 확인할 수 없습니다." + "defaultMessage": "site24x7.com과 통신하는 중 오류가 발생해 접근 가능 여부를 확인하지 못했습니다." }, "certificates.http.reachability-not-resolved": { - "defaultMessage": "해당 도메인에 접근 가능한 서버가 없습니다. 도메인이 존재하며 NPM이 실행되는 IP를 가리키고, 필요하면 라우터에서 80포트가 포워딩되어 있는지 확인하세요." + "defaultMessage": "해당 도메인에서 사용 가능한 서버를 찾을 수 없습니다. 도메인이 존재하고 NPM이 실행 중인 IP를 가리키는지, 필요한 경우 라우터에서 80번 포트가 포워딩되어 있는지 확인하세요." }, "certificates.http.reachability-ok": { - "defaultMessage": "서버에 정상적으로 접근할 수 있으며 인증서 발급이 가능합니다." + "defaultMessage": "서버에 정상적으로 접근할 수 있어 인증서를 발급할 수 있을 것으로 보입니다." }, "certificates.http.reachability-other": { - "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상치 못한 상태 코드 {code}를 반환했습니다. NPM 서버가 맞는지 확인하세요." + "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상하지 못한 상태 코드({code})를 반환했습니다. NPM 서버가 맞나요? 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요." }, "certificates.http.reachability-wrong-data": { - "defaultMessage": "서버가 응답했지만 예상치 못한 데이터를 반환했습니다. NPM 서버가 맞는지 확인하세요." + "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상하지 못한 데이터를 반환했습니다. NPM 서버가 맞나요? 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요." }, "certificates.http.test-results": { "defaultMessage": "테스트 결과" }, "certificates.http.warning": { - "defaultMessage": "도메인이 이 서버를 가리키도록 설정되어 있어야 합니다." + "defaultMessage": "입력한 도메인은 미리 이 서버를 가리키도록 설정되어 있어야 합니다." }, "certificates.key-type": { "defaultMessage": "키 유형" }, "certificates.key-type-description": { - "defaultMessage": "RSA는 호환성이 넓고, ECDSA는 더 빠르고 안전하지만 오래된 시스템에서 지원되지 않을 수 있습니다" + "defaultMessage": "RSA는 호환성이 좋고, ECDSA는 더 빠르고 안전하지만 오래된 시스템에서는 지원되지 않을 수 있습니다" }, "certificates.key-type-ecdsa": { "defaultMessage": "ECDSA 256" @@ -183,16 +252,16 @@ "defaultMessage": "RSA 2048" }, "certificates.request.subtitle": { - "defaultMessage": "Let's Encrypt 사용" + "defaultMessage": "Let's Encrypt로 발급" }, "certificates.request.title": { "defaultMessage": "새 인증서 요청" }, "column.access": { - "defaultMessage": "접근 정책" + "defaultMessage": "접근" }, "column.authorization": { - "defaultMessage": "인증 사용자" + "defaultMessage": "인증" }, "column.authorizations": { "defaultMessage": "인증 사용자" @@ -209,6 +278,9 @@ "column.email": { "defaultMessage": "이메일" }, + "column.error": { + "defaultMessage": "오류" + }, "column.event": { "defaultMessage": "이벤트" }, @@ -231,13 +303,13 @@ "defaultMessage": "공급자" }, "column.roles": { - "defaultMessage": "권한" + "defaultMessage": "역할" }, "column.rules": { - "defaultMessage": "IP 정책" + "defaultMessage": "접근 규칙" }, "column.satisfy": { - "defaultMessage": "조건 방식" + "defaultMessage": "충족 조건" }, "column.satisfy-all": { "defaultMessage": "모두 충족" @@ -270,7 +342,7 @@ "defaultMessage": "404 호스트" }, "dead-hosts.count": { - "defaultMessage": "{count}개의 404 호스트" + "defaultMessage": "404 호스트 {count}개" }, "disabled": { "defaultMessage": "비활성화" @@ -282,14 +354,17 @@ "defaultMessage": "최대 {count}개의 도메인 이름" }, "domain-names.placeholder": { - "defaultMessage": "도메인을 입력해주세요." + "defaultMessage": "추가할 도메인을 입력하세요..." }, "domain-names.wildcards-not-permitted": { - "defaultMessage": "HTTP 방식으로는 와일드카드 인증서를 발급할 수 없습니다." + "defaultMessage": "이 방식에서는 와일드카드 도메인을 사용할 수 없습니다." }, "domain-names.wildcards-not-supported": { "defaultMessage": "이 인증 기관(CA)은 와일드카드를 지원하지 않습니다." }, + "domains.advanced": { + "defaultMessage": "고급" + }, "domains.force-ssl": { "defaultMessage": "SSL 강제 적용" }, @@ -302,6 +377,9 @@ "domains.http2-support": { "defaultMessage": "HTTP/2 지원" }, + "domains.trust-forwarded-proto": { + "defaultMessage": "앞단 프록시의 X-Forwarded-Proto 헤더 신뢰" + }, "domains.use-dns": { "defaultMessage": "DNS 챌린지 사용" }, @@ -318,7 +396,7 @@ "defaultMessage": "활성화" }, "error.access.at-least-one": { - "defaultMessage": "인증 또는 접근 규칙 중 하나는 반드시 필요합니다." + "defaultMessage": "인증 사용자 또는 접근 규칙이 하나 이상 필요합니다." }, "error.access.duplicate-usernames": { "defaultMessage": "인증 사용자 이름은 중복될 수 없습니다." @@ -348,7 +426,7 @@ "defaultMessage": "최솟값은 {min}입니다." }, "error.passwords-must-match": { - "defaultMessage": "비밀번호가 일치해야 합니다." + "defaultMessage": "비밀번호가 일치하지 않습니다." }, "error.required": { "defaultMessage": "필수 항목입니다." @@ -360,7 +438,7 @@ "defaultMessage": "GitHub에서 포크하기" }, "host.flags.block-exploits": { - "defaultMessage": "일반적인 공격 차단" + "defaultMessage": "일반적인 취약점 공격 차단" }, "host.flags.cache-assets": { "defaultMessage": "정적 에셋 캐싱" @@ -381,7 +459,7 @@ "defaultMessage": "프로토콜" }, "hosts": { - "defaultMessage": "호스트 목록" + "defaultMessage": "호스트" }, "http-only": { "defaultMessage": "HTTP 전용" @@ -398,8 +476,74 @@ "loading": { "defaultMessage": "불러오는 중…" }, + "location.advanced-config": { + "defaultMessage": "사용자 지정 Nginx 설정 있음" + }, + "location.filter": { + "defaultMessage": "경로 또는 전달 대상으로 필터링" + }, + "login.2fa-code": { + "defaultMessage": "인증 코드" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "코드 입력" + }, + "login.2fa-description": { + "defaultMessage": "인증 앱에 표시된 코드를 입력하세요" + }, + "login.2fa-title": { + "defaultMessage": "2단계 인증" + }, + "login.2fa-verify": { + "defaultMessage": "확인" + }, "login.title": { - "defaultMessage": "로그인" + "defaultMessage": "계정에 로그인" + }, + "logs": { + "defaultMessage": "로그" + }, + "logs.channel.access": { + "defaultMessage": "접근" + }, + "logs.channel.error": { + "defaultMessage": "오류" + }, + "logs.download": { + "defaultMessage": "다운로드" + }, + "logs.empty": { + "defaultMessage": "아직 로그가 없습니다." + }, + "logs.level": { + "defaultMessage": "레벨" + }, + "logs.level.all": { + "defaultMessage": "모든 레벨" + }, + "logs.lines": { + "defaultMessage": "줄 수" + }, + "logs.live": { + "defaultMessage": "실시간" + }, + "logs.paused": { + "defaultMessage": "일시 정지" + }, + "logs.refresh": { + "defaultMessage": "새로고침" + }, + "logs.search-placeholder": { + "defaultMessage": "로그 검색…" + }, + "logs.source": { + "defaultMessage": "소스" + }, + "logs.source.system": { + "defaultMessage": "시스템" + }, + "logs.truncated-warning": { + "defaultMessage": "이 파일의 최근 내용 일부만 표시됩니다." }, "nginx-config.label": { "defaultMessage": "사용자 지정 Nginx 설정" @@ -459,28 +603,28 @@ "defaultMessage": "{objects}이(가) 없습니다." }, "object.event.created": { - "defaultMessage": "{object}이(가) 생성됨" + "defaultMessage": "{object} 생성" }, "object.event.deleted": { - "defaultMessage": "{object}이(가) 삭제됨" + "defaultMessage": "{object} 삭제" }, "object.event.disabled": { - "defaultMessage": "{object}이(가) 비활성화됨" + "defaultMessage": "{object} 비활성화" }, "object.event.enabled": { - "defaultMessage": "{object}이(가) 활성화됨" + "defaultMessage": "{object} 활성화" }, "object.event.renewed": { - "defaultMessage": "{object}이(가) 갱신됨" + "defaultMessage": "{object} 갱신" }, "object.event.updated": { - "defaultMessage": "{object}이(가) 업데이트됨" + "defaultMessage": "{object} 수정" }, "offline": { - "defaultMessage": "비활성화" + "defaultMessage": "오프라인" }, "online": { - "defaultMessage": "활성화" + "defaultMessage": "온라인" }, "options": { "defaultMessage": "옵션" @@ -495,7 +639,7 @@ "defaultMessage": "비밀번호 숨기기" }, "password.show": { - "defaultMessage": "비밀번호 표시" + "defaultMessage": "비밀번호 보기" }, "permissions.hidden": { "defaultMessage": "숨김" @@ -510,10 +654,10 @@ "defaultMessage": "모든 항목" }, "permissions.visibility.title": { - "defaultMessage": "항목 표시 설정" + "defaultMessage": "항목 표시 범위" }, "permissions.visibility.user": { - "defaultMessage": "내가 만든 항목만" + "defaultMessage": "본인이 만든 항목만" }, "proxy-host": { "defaultMessage": "프록시 호스트" @@ -525,7 +669,7 @@ "defaultMessage": "프록시 호스트" }, "proxy-hosts.count": { - "defaultMessage": "{count}개의 프록시 호스트" + "defaultMessage": "프록시 호스트 {count}개" }, "public": { "defaultMessage": "공개" @@ -534,7 +678,7 @@ "defaultMessage": "리다이렉션 호스트" }, "redirection-host.forward-domain": { - "defaultMessage": "전달할 도메인" + "defaultMessage": "리다이렉트할 도메인" }, "redirection-host.forward-http-code": { "defaultMessage": "HTTP 코드" @@ -543,25 +687,25 @@ "defaultMessage": "리다이렉션 호스트" }, "redirection-hosts.count": { - "defaultMessage": "{count}개의 리다이렉션 호스트" + "defaultMessage": "리다이렉션 호스트 {count}개" }, "redirection-hosts.http-code.300": { - "defaultMessage": "300 Multiple Choices" + "defaultMessage": "300 여러 선택 항목" }, "redirection-hosts.http-code.301": { - "defaultMessage": "301 Moved permanently" + "defaultMessage": "301 영구 이동" }, "redirection-hosts.http-code.302": { - "defaultMessage": "302 Moved temporarily" + "defaultMessage": "302 임시 이동" }, "redirection-hosts.http-code.303": { - "defaultMessage": "303 See other" + "defaultMessage": "303 기타 위치 보기" }, "redirection-hosts.http-code.307": { - "defaultMessage": "307 Temporary redirect" + "defaultMessage": "307 임시 리다이렉션" }, "redirection-hosts.http-code.308": { - "defaultMessage": "308 Permanent redirect" + "defaultMessage": "308 영구 리다이렉션" }, "role.admin": { "defaultMessage": "관리자" @@ -597,7 +741,7 @@ "defaultMessage": "사용자 지정 HTML" }, "settings.default-site.html.placeholder": { - "defaultMessage": "" + "defaultMessage": "" }, "settings.default-site.redirect": { "defaultMessage": "리다이렉트" @@ -615,22 +759,22 @@ "defaultMessage": "SSL 인증서" }, "stream": { - "defaultMessage": "호스트 스트림" + "defaultMessage": "스트림" }, "stream.forward-host": { "defaultMessage": "전달할 호스트" }, "stream.forward-host.placeholder": { - "defaultMessage": "example.com / 10.0.0.1 / IPv6" + "defaultMessage": "example.com 또는 10.0.0.1 또는 2001:db8:3333:4444:5555:6666:7777:8888" }, "stream.incoming-port": { "defaultMessage": "수신 포트" }, "streams": { - "defaultMessage": "호스트 스트림" + "defaultMessage": "스트림" }, "streams.count": { - "defaultMessage": "{count}개의 호스트 스트림" + "defaultMessage": "스트림 {count}개" }, "streams.tcp": { "defaultMessage": "TCP" @@ -660,10 +804,10 @@ "defaultMessage": "프로필 편집" }, "user.full-name": { - "defaultMessage": "전체 이름" + "defaultMessage": "이름" }, "user.login-as": { - "defaultMessage": "{name}으로 로그인" + "defaultMessage": "{name} 계정으로 로그인" }, "user.logout": { "defaultMessage": "로그아웃" @@ -686,6 +830,9 @@ "user.switch-light": { "defaultMessage": "라이트 모드로 전환" }, + "user.two-factor": { + "defaultMessage": "2단계 인증" + }, "username": { "defaultMessage": "사용자 이름" }, From a54b73f1535efc357cea46961e84c73f76661e7c Mon Sep 17 00:00:00 2001 From: Amit Yadav Date: Fri, 25 Sep 2026 20:35:32 +0530 Subject: [PATCH 03/76] fix: omit certificate_key from certificate API responses The private key of custom certificates was returned in meta by GET /api/nginx/certificates and GET /api/nginx/certificates/{id}. Add meta.certificate_key to omissions(), the same way meta.dns_provider_credentials is hidden. The key stays in the database and is still written to /data/custom_ssl on upload. --- backend/internal/certificate.js | 2 +- test/cypress/e2e/api/Certificates.cy.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 963d2bb6a..9a14f5893 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -25,7 +25,7 @@ const certbotLogsDir = "/data/logs"; const certbotWorkDir = "/tmp/letsencrypt-lib"; const omissions = () => { - return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials"]; + return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials", "meta.certificate_key"]; }; const internalCertificate = { diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 25d1b8c41..7d75d40cf 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -69,6 +69,9 @@ describe('Certificates endpoints', () => { }).then((data) => { cy.validateSwaggerSchema('get', 200, '/nginx/certificates', data); expect(data.length).to.be.greaterThan(0); + const cert = data.find((c) => c.id === certID); + expect(cert.meta).to.have.property('certificate'); + expect(cert.meta).to.not.have.property('certificate_key'); // Delete cert cy.task('backendApiDelete', { From c699dc96d41693b3f40c907c3b78d747e5eee2a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 07:19:54 +0000 Subject: [PATCH 04/76] build(deps): bump ip-address from 10.5.0 to 10.7.2 in /backend Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.5.0 to 10.7.2. - [Release notes](https://github.com/beaugunderson/ip-address/releases) - [Commits](https://github.com/beaugunderson/ip-address/compare/v10.5.0...v10.7.2) --- updated-dependencies: - dependency-name: ip-address dependency-version: 10.7.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/yarn.lock b/backend/yarn.lock index 39efe544a..1d7daab0a 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1150,9 +1150,9 @@ interpret@^2.2.0: integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== ip-address@^10.1.1: - version "10.5.0" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.5.0.tgz#fcd6d7dfe9e68416b7cb71afe9bc960b3e38c13b" - integrity sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g== + version "10.7.2" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.2.tgz#5b3b2b7d46c6293861b82dfcd49cc203aa8f2c5c" + integrity sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w== ipaddr.js@1.9.1: version "1.9.1" From 77dfc3d3c1fb01b968f4b373c8d85a050f187b92 Mon Sep 17 00:00:00 2001 From: Prassanth <80441547+PrassanthVG@users.noreply.github.com> Date: Fri, 2 Oct 2026 02:08:28 +0530 Subject: [PATCH 05/76] Don't start IP ranges renewal timer when fetching is disabled IP_RANGES_FETCH_ENABLED=false only skipped the initial fetch; the renewal timer still fetched the ranges every 6 hours. Fixes #5748 --- backend/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/index.js b/backend/index.js index 002856672..a6afda7bb 100644 --- a/backend/index.js +++ b/backend/index.js @@ -26,7 +26,9 @@ async function appStart() { }) .then(() => { internalCertificate.initTimer(); - internalIpRanges.initTimer(); + if (IP_RANGES_FETCH_ENABLED) { + internalIpRanges.initTimer(); + } const server = app.listen(3000, () => { logger.info(`Backend PID ${process.pid} listening on port 3000 ...`); From e7e69abb84fa619f5379363cfa71de239f380662 Mon Sep 17 00:00:00 2001 From: nglinh28 Date: Sat, 3 Oct 2026 09:28:36 +0700 Subject: [PATCH 06/76] i18n(vi): complete Vietnamese locale and add vi tests - Translate the keys added for the logs feature and access list inheritance so the Vietnamese UI has full key coverage - Update Vietnamese help docs for access lists, certificates, hosts and streams - Use a 24-hour clock for Vietnamese date and time formatting - Add tests that check Vietnamese key coverage, ICU placeholders and that strings are translated Co-Authored-By: Claude Sonnet 5 --- frontend/src/locale/Utils.test.tsx | 11 + frontend/src/locale/Utils.ts | 1 + .../src/locale/src/HelpDoc/vi/AccessLists.md | 13 +- .../src/locale/src/HelpDoc/vi/Certificates.md | 29 +- .../src/locale/src/HelpDoc/vi/DeadHosts.md | 9 +- .../src/locale/src/HelpDoc/vi/ProxyHosts.md | 8 +- .../locale/src/HelpDoc/vi/RedirectionHosts.md | 6 +- frontend/src/locale/src/HelpDoc/vi/Streams.md | 7 +- frontend/src/locale/src/vi.json | 337 ++++++++++++++---- frontend/src/locale/vi.test.ts | 68 ++++ 10 files changed, 374 insertions(+), 115 deletions(-) create mode 100644 frontend/src/locale/vi.test.ts diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx index d1c4b2648..77db2b6b6 100644 --- a/frontend/src/locale/Utils.test.tsx +++ b/frontend/src/locale/Utils.test.tsx @@ -112,3 +112,14 @@ describe("formatDateTime Estonian locale", () => { } }); }); + +describe("formatDateTime Vietnamese locale", () => { + it("uses 24-hour clock for vi and vi-VN", () => { + const value = "2024-06-15T15:30:00.000Z"; + for (const locale of ["vi", "vi-VN"]) { + const text = formatDateTime(value, locale); + // Vietnamese 12-hour output marks the period with SA (sáng) / CH (chiều) + expect(text).not.toMatch(/\b(SA|CH)\b/); + } + }); +}); diff --git a/frontend/src/locale/Utils.ts b/frontend/src/locale/Utils.ts index 9def46f80..d274dca2c 100644 --- a/frontend/src/locale/Utils.ts +++ b/frontend/src/locale/Utils.ts @@ -26,6 +26,7 @@ const DATE_LOCALE_BY_LANG: Record = { const HOUR_CYCLE_BY_LANG: Record = { et: "h23", + vi: "h23", }; const formatDateTime = (value: string | number, locale = "en-US"): string => { diff --git a/frontend/src/locale/src/HelpDoc/vi/AccessLists.md b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md index 50e5375a4..0b0b8e77e 100644 --- a/frontend/src/locale/src/HelpDoc/vi/AccessLists.md +++ b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md @@ -1,11 +1,10 @@ -## Khái niệm Access List là gì? +## Danh sách truy cập là gì? -Access List (Danh sách truy cập) cung cấp cơ chế chặn (blacklist) hoặc cho phép (whitelist) các địa chỉ IP của client, đồng thời hỗ trợ xác thực Basic HTTP Authentication cho các Proxy Host. +Danh sách truy cập (Access List) cung cấp danh sách chặn hoặc danh sách cho phép theo địa chỉ IP của client, đồng thời hỗ trợ xác thực HTTP cơ bản (Basic HTTP Authentication) cho các máy chủ proxy. -Bạn có thể cấu hình nhiều quy tắc client, nhiều tên người dùng và mật khẩu trong một Access List duy nhất, sau đó áp dụng Access List đó cho một hoặc nhiều Proxy Host. +Bạn có thể cấu hình nhiều quy tắc client, nhiều tên người dùng và mật khẩu trong một danh sách truy cập, sau đó áp dụng nó cho một hoặc nhiều _máy chủ proxy_. -Tính năng này đặc biệt hữu ích đối với: +Tính năng này đặc biệt hữu ích khi: -các dịch vụ web được forward mà không có cơ chế xác thực tích hợp, hoặc - -khi bạn muốn bảo vệ tài nguyên khỏi những client không xác định. +- dịch vụ web được chuyển tiếp không có sẵn cơ chế xác thực, hoặc +- bạn muốn chặn các client không xác định. diff --git a/frontend/src/locale/src/HelpDoc/vi/Certificates.md b/frontend/src/locale/src/HelpDoc/vi/Certificates.md index d25ef0078..e633a3a75 100644 --- a/frontend/src/locale/src/HelpDoc/vi/Certificates.md +++ b/frontend/src/locale/src/HelpDoc/vi/Certificates.md @@ -1,25 +1,24 @@ -## Hỗ trợ Chứng chỉ +## Trợ giúp về chứng chỉ -### Chứng chỉ HTTP (HTTP Certificate) +### Chứng chỉ HTTP -Chứng chỉ được xác thực qua HTTP nghĩa là máy chủ của Let's Encrypt sẽ cố gắng truy cập vào tên miền của bạn thông qua HTTP (không phải HTTPS!). Nếu kiểm tra thành công, chứng chỉ sẽ được cấp. +Chứng chỉ xác thực qua HTTP nghĩa là máy chủ của Let's Encrypt sẽ truy cập vào tên miền của bạn qua HTTP (không phải HTTPS!). Nếu kiểm tra thành công, chứng chỉ sẽ được cấp. -Với phương thức này, bạn phải tạo trước một Proxy Host cho tên miền, có thể truy cập qua HTTP và trỏ về đúng cài đặt Nginx này. -Sau khi chứng chỉ được cấp, bạn có thể chỉnh sửa Proxy Host để sử dụng chứng chỉ đó cho kết nối HTTPS. +Với phương thức này, bạn phải tạo trước một _máy chủ proxy_ cho tên miền, truy cập được qua HTTP và trỏ về máy chủ Nginx này. +Sau khi chứng chỉ được cấp, bạn có thể chỉnh sửa _máy chủ proxy_ để dùng chứng chỉ đó cho kết nối HTTPS. +Tuy nhiên, _máy chủ proxy_ vẫn phải truy cập được qua HTTP thì chứng chỉ mới gia hạn được. -Tuy nhiên, Proxy Host vẫn phải hỗ trợ truy cập HTTP để việc gia hạn chứng chỉ diễn ra bình thường. +Phương thức này _không hỗ trợ_ tên miền wildcard (ví dụ `*.example.com`). -Phương thức này _không hỗ trợ_ wildcard domain. +### Chứng chỉ DNS -### Chứng chỉ DNS (DNS Certificate) +Chứng chỉ xác thực qua DNS yêu cầu bạn dùng plugin của nhà cung cấp DNS. +Plugin sẽ tạo các bản ghi tạm thời cho tên miền của bạn, sau đó Let's Encrypt truy vấn các bản ghi này để xác nhận bạn là chủ sở hữu. Nếu hợp lệ, chứng chỉ sẽ được cấp. -Chứng chỉ được xác thực qua DNS yêu cầu bạn sử dụng plugin của DNS Provider. -Plugin này sẽ tạo các bản ghi tạm thời trong DNS của bạn để Let's Encrypt kiểm tra quyền sở hữu tên miền. Nếu hợp lệ, chứng chỉ sẽ được cấp. +Bạn không cần tạo _máy chủ proxy_ trước khi yêu cầu loại chứng chỉ này, cũng không cần cấu hình _máy chủ proxy_ cho truy cập HTTP. -Khi dùng phương thức này: Bạn không cần tạo sẵn Proxy Host trước và bạn không cần mở HTTP cho Proxy Host. +Phương thức này _có hỗ trợ_ tên miền wildcard. -Phương thức DNS _có hỗ trợ_ wildcard domain. +### Chứng chỉ tùy chỉnh -### Chứng chỉ tùy chỉnh (Custom Certificate) - -Tùy chọn này cho phép bạn tải lên chứng chỉ SSL của riêng mình, được cung cấp bởi Certificate Authority (CA) mà bạn tự chọn. +Dùng tùy chọn này để tải lên chứng chỉ SSL của riêng bạn, do tổ chức cấp chứng chỉ (CA) của bạn cung cấp. diff --git a/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md index 4c0aae86c..d380f3564 100644 --- a/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md +++ b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md @@ -1,8 +1,7 @@ -## 404 Host là gì? +## Máy chủ 404 là gì? -404 Host đơn giản là một host được thiết lập để hiển thị trang 404. +Máy chủ 404 đơn giản là một cấu hình máy chủ chỉ hiển thị trang 404. -Điều này có thể hữu ích khi tên miền của bạn vẫn xuất hiện trên các công cụ tìm kiếm và bạn muốn hiển thị một trang lỗi đẹp hơn, hoặc muốn thông báo rõ ràng cho các trình thu thập dữ liệu tìm kiếm rằng các trang thuộc tên miền đó không còn tồn tại. +Nó hữu ích khi tên miền của bạn vẫn xuất hiện trên các công cụ tìm kiếm và bạn muốn hiển thị một trang lỗi đẹp hơn, hoặc muốn báo rõ cho các trình thu thập dữ liệu rằng các trang thuộc tên miền đó không còn tồn tại. -Một lợi ích khác của việc có 404 Host là bạn có thể theo dõi nhật ký truy cập vào nó và -xem các nguồn giới thiệu (referrers). \ No newline at end of file +Một lợi ích khác là bạn có thể theo dõi nhật ký các lượt truy cập vào đó và xem trang giới thiệu (referrer). diff --git a/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md index 969f8a773..e04552c65 100644 --- a/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md +++ b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md @@ -1,7 +1,7 @@ -## Proxy Host là gì? +## Máy chủ proxy là gì? -Proxy Host là điểm truy cập đầu vào cho một dịch vụ web mà bạn muốn chuyển tiếp. +Máy chủ proxy là điểm tiếp nhận yêu cầu cho một dịch vụ web mà bạn muốn chuyển tiếp. -Nó cung cấp khả năng kết thúc SSL (SSL termination) tùy chọn cho các dịch vụ vốn không hỗ trợ SSL tích hợp. +Nó có thể đảm nhận phần mã hóa SSL (SSL termination) cho những dịch vụ không có sẵn hỗ trợ SSL. -Proxy Host là loại cấu hình phổ biến nhất trong Nginx Proxy Manager. \ No newline at end of file +Máy chủ proxy là tính năng được dùng nhiều nhất trong Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md index 433992364..ecda55c10 100644 --- a/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md +++ b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md @@ -1,5 +1,5 @@ -## Redirection Host là gì? +## Máy chủ chuyển hướng là gì? -Redirection Host sẽ chuyển hướng các yêu cầu từ tên miền truy cập vào và đưa người xem sang một tên miền khác +Máy chủ chuyển hướng sẽ chuyển các yêu cầu tới tên miền này sang một tên miền khác. -Lý do phổ biến nhất để sử dụng loại host này là khi trang web của bạn đổi sang tên miền mới nhưng vẫn còn các liên kết từ công cụ tìm kiếm hoặc nguồn giới thiệu trỏ về tên miền cũ. \ No newline at end of file +Lý do phổ biến nhất để dùng loại máy chủ này là khi trang web của bạn đổi sang tên miền mới nhưng các công cụ tìm kiếm hoặc trang giới thiệu vẫn còn liên kết trỏ về tên miền cũ. diff --git a/frontend/src/locale/src/HelpDoc/vi/Streams.md b/frontend/src/locale/src/HelpDoc/vi/Streams.md index 43cf97b2f..c6b451bc3 100644 --- a/frontend/src/locale/src/HelpDoc/vi/Streams.md +++ b/frontend/src/locale/src/HelpDoc/vi/Streams.md @@ -1,6 +1,5 @@ -## Stream là gì? +## Luồng TCP/UDP là gì? -Stream là một tính năng tương đối mới của Nginx, dùng để chuyển tiếp lưu lượng -TCP/UDP trực tiếp tới một máy khác trong mạng. +Luồng TCP/UDP (Stream) là một tính năng tương đối mới của Nginx, dùng để chuyển tiếp lưu lượng TCP/UDP trực tiếp tới một máy khác trong mạng. -Nếu bạn đang vận hành các máy chủ game, FTP hoặc SSH thì tính năng này sẽ rất hữu ích. \ No newline at end of file +Tính năng này rất hữu ích nếu bạn đang chạy máy chủ game, FTP hoặc SSH. diff --git a/frontend/src/locale/src/vi.json b/frontend/src/locale/src/vi.json index 32d26d559..f0a180038 100644 --- a/frontend/src/locale/src/vi.json +++ b/frontend/src/locale/src/vi.json @@ -1,4 +1,61 @@ { + "2fa.backup-codes-remaining": { + "defaultMessage": "Số mã dự phòng còn lại: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Hãy lưu các mã dự phòng này ở nơi an toàn. Mỗi mã chỉ dùng được một lần." + }, + "2fa.disable": { + "defaultMessage": "Tắt xác thực hai lớp" + }, + "2fa.disable-confirm": { + "defaultMessage": "Tắt 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Tắt xác thực hai lớp sẽ khiến tài khoản của bạn kém an toàn hơn." + }, + "2fa.disabled": { + "defaultMessage": "Đã tắt" + }, + "2fa.done": { + "defaultMessage": "Tôi đã lưu các mã dự phòng" + }, + "2fa.enable": { + "defaultMessage": "Bật xác thực hai lớp" + }, + "2fa.enabled": { + "defaultMessage": "Đã bật" + }, + "2fa.enter-code": { + "defaultMessage": "Nhập mã xác minh" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Nhập mã xác minh để tắt" + }, + "2fa.regenerate": { + "defaultMessage": "Tạo lại" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Tạo lại mã dự phòng" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Nhập mã xác minh để tạo mã dự phòng mới. Các mã cũ sẽ không còn hiệu lực." + }, + "2fa.secret-key": { + "defaultMessage": "Khóa bí mật" + }, + "2fa.setup-instructions": { + "defaultMessage": "Quét mã QR này bằng ứng dụng xác thực, hoặc nhập khóa bí mật thủ công." + }, + "2fa.status": { + "defaultMessage": "Trạng thái" + }, + "2fa.title": { + "defaultMessage": "Xác thực hai lớp" + }, + "2fa.verify-enable": { + "defaultMessage": "Xác minh và bật" + }, "access-list": { "defaultMessage": "Danh sách truy cập" }, @@ -9,13 +66,19 @@ "defaultMessage": "{count} người dùng" }, "access-list.help-rules-last": { - "defaultMessage": "Quy tắc từ chối tất cả này sẽ được thêm vào cuối khi tồn tại ít nhất 1 quy tắc" + "defaultMessage": "Khi có ít nhất 1 quy tắc, quy tắc từ chối tất cả này sẽ được thêm vào cuối" }, "access-list.help.rules-order": { - "defaultMessage": "Các quy tắc cho phép và từ chối sẽ được thực thi theo thứ tự được xác định." + "defaultMessage": "Lưu ý: các quy tắc cho phép và từ chối được áp dụng theo đúng thứ tự khai báo." + }, + "access-list.inherit": { + "defaultMessage": "Kế thừa từ máy chủ proxy" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "Sử dụng danh sách truy cập của máy chủ proxy" }, "access-list.pass-auth": { - "defaultMessage": "Chuyển xác thực lên thượng nguồn" + "defaultMessage": "Chuyển tiếp thông tin xác thực tới máy chủ đích" }, "access-list.public": { "defaultMessage": "Có thể truy cập công khai" @@ -23,6 +86,9 @@ "access-list.public.subtitle": { "defaultMessage": "Không cần xác thực cơ bản" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 hoặc 192.168.1.0/24 hoặc 2001:0db8::/32" + }, "access-list.satisfy-any": { "defaultMessage": "Thỏa mãn điều kiện bất kỳ" }, @@ -36,7 +102,13 @@ "defaultMessage": "Thêm" }, "action.add-location": { - "defaultMessage": "Thêm Vị trí" + "defaultMessage": "Thêm đường dẫn" + }, + "action.allow": { + "defaultMessage": "Cho phép" + }, + "action.clear": { + "defaultMessage": "Xóa bộ lọc" }, "action.close": { "defaultMessage": "Đóng" @@ -44,6 +116,9 @@ "action.delete": { "defaultMessage": "Xóa" }, + "action.deny": { + "defaultMessage": "Từ chối" + }, "action.disable": { "defaultMessage": "Tắt" }, @@ -56,6 +131,9 @@ "action.enable": { "defaultMessage": "Bật" }, + "action.logs": { + "defaultMessage": "Nhật ký" + }, "action.permissions": { "defaultMessage": "Quyền" }, @@ -63,10 +141,13 @@ "defaultMessage": "Gia hạn" }, "action.view-details": { - "defaultMessage": "Xem Chi tiết" + "defaultMessage": "Xem chi tiết" }, "auditlogs": { - "defaultMessage": "Nhật ký kiểm tra" + "defaultMessage": "Nhật ký hoạt động" + }, + "auto": { + "defaultMessage": "Tự động" }, "cancel": { "defaultMessage": "Hủy" @@ -75,19 +156,19 @@ "defaultMessage": "Chứng chỉ" }, "certificate.custom-certificate": { - "defaultMessage": "Certificate (crt)" + "defaultMessage": "Chứng chỉ" }, "certificate.custom-certificate-key": { - "defaultMessage": "Certificate Key" + "defaultMessage": "Khóa riêng của chứng chỉ" }, "certificate.custom-intermediate": { - "defaultMessage": "Intermediate Certificate" + "defaultMessage": "Chứng chỉ trung gian" }, "certificate.in-use": { "defaultMessage": "Đang sử dụng" }, "certificate.none.subtitle": { - "defaultMessage": "Không có chứng chỉ nào được chỉ định" + "defaultMessage": "Chưa gán chứng chỉ nào" }, "certificate.none.subtitle.for-http": { "defaultMessage": "Máy chủ này sẽ không sử dụng HTTPS" @@ -96,19 +177,19 @@ "defaultMessage": "Không có" }, "certificate.not-in-use": { - "defaultMessage": "Không được dùng" + "defaultMessage": "Chưa sử dụng" }, "certificate.renew": { - "defaultMessage": "Gia hạn Chứng chỉ" + "defaultMessage": "Gia hạn chứng chỉ" }, "certificates": { - "defaultMessage": "Danh sách chứng chỉ" + "defaultMessage": "Chứng chỉ" }, "certificates.custom": { "defaultMessage": "Chứng chỉ tùy chỉnh" }, "certificates.custom.warning": { - "defaultMessage": "Các tệp chính được bảo vệ bằng cụm mật khẩu không được hỗ trợ." + "defaultMessage": "Không hỗ trợ tệp khóa có đặt cụm mật khẩu bảo vệ." }, "certificates.dns.credentials": { "defaultMessage": "Nội dung tệp thông tin xác thực" @@ -120,40 +201,43 @@ "defaultMessage": "Dữ liệu này sẽ được lưu trữ dưới dạng bản rõ trong cơ sở dữ liệu và trong một tệp!" }, "certificates.dns.propagation-seconds": { - "defaultMessage": "Thời gian lan truyền (Giây)" + "defaultMessage": "Thời gian lan truyền (giây)" }, "certificates.dns.propagation-seconds-note": { - "defaultMessage": "Để trống để sử dụng giá trị mặc định của plugin. Số giây chờ truyền DNS." + "defaultMessage": "Để trống để dùng giá trị mặc định của plugin. Số giây chờ bản ghi DNS lan truyền." }, "certificates.dns.provider": { "defaultMessage": "Nhà cung cấp DNS" }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Chọn nhà cung cấp…" + }, "certificates.dns.warning": { "defaultMessage": "Phần này yêu cầu một số kiến thức về Certbot và các plugin DNS của nó. Vui lòng tham khảo tài liệu plugin tương ứng." }, "certificates.http.reachability-404": { - "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng có vẻ như nó không phải là NPM. Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng có vẻ không phải Nginx Proxy Manager. Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM." }, "certificates.http.reachability-failed-to-check": { "defaultMessage": "Không thể kiểm tra khả năng truy cập do lỗi giao tiếp với site24x7.com." }, "certificates.http.reachability-not-resolved": { - "defaultMessage": "Không có máy chủ có sẵn tại tên miền này. Vui lòng đảm bảo rằng miền của bạn tồn tại và trỏ đến IP nơi phiên bản NPM của bạn đang chạy và nếu cần, cổng 80 sẽ được chuyển tiếp trong bộ định tuyến của bạn." + "defaultMessage": "Không có máy chủ nào tại tên miền này. Vui lòng đảm bảo tên miền tồn tại, trỏ đến IP của máy đang chạy NPM và nếu cần, cổng 80 đã được chuyển tiếp (port forwarding) trên router." }, "certificates.http.reachability-ok": { - "defaultMessage": "Máy chủ của bạn có thể truy cập được và có thể tạo chứng chỉ." + "defaultMessage": "Máy chủ của bạn truy cập được, có thể tiến hành tạo chứng chỉ." }, "certificates.http.reachability-other": { - "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về mã trạng thái không mong muốn {code}. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng nó trả về mã trạng thái không mong đợi {code}. Đây có phải máy chủ NPM không? Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM." }, "certificates.http.reachability-wrong-data": { - "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về một dữ liệu không mong muốn. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng nó trả về dữ liệu không mong đợi. Đây có phải máy chủ NPM không? Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM." }, "certificates.http.test-results": { "defaultMessage": "Kết quả kiểm tra" }, "certificates.http.warning": { - "defaultMessage": "Các miền này phải được cấu hình sẵn để trỏ đến cài đặt này." + "defaultMessage": "Các tên miền này phải được cấu hình sẵn để trỏ đến máy chủ NPM này." }, "certificates.key-type": { "defaultMessage": "Loại khóa" @@ -177,16 +261,16 @@ "defaultMessage": "Truy cập" }, "column.authorization": { - "defaultMessage": "Ủy quyền" + "defaultMessage": "Xác thực" }, "column.authorizations": { - "defaultMessage": "Danh sách ủy quyền" + "defaultMessage": "Xác thực" }, "column.custom-locations": { - "defaultMessage": "Quy tắc đường dẫn tùy chỉnh (Vị trí)" + "defaultMessage": "Đường dẫn tùy chỉnh" }, "column.destination": { - "defaultMessage": "Mục tiêu" + "defaultMessage": "Đích" }, "column.details": { "defaultMessage": "Chi tiết" @@ -194,6 +278,9 @@ "column.email": { "defaultMessage": "Email" }, + "column.error": { + "defaultMessage": "Lỗi" + }, "column.event": { "defaultMessage": "Sự kiện" }, @@ -201,10 +288,10 @@ "defaultMessage": "Hết hạn" }, "column.http-code": { - "defaultMessage": "HTTP Code" + "defaultMessage": "Mã HTTP" }, "column.incoming-port": { - "defaultMessage": "Cổng đến" + "defaultMessage": "Cổng vào" }, "column.name": { "defaultMessage": "Tên" @@ -228,10 +315,10 @@ "defaultMessage": "Tất cả" }, "column.satisfy-any": { - "defaultMessage": "Bất kì" + "defaultMessage": "Bất kỳ" }, "column.scheme": { - "defaultMessage": "Scheme" + "defaultMessage": "Giao thức" }, "column.source": { "defaultMessage": "Nguồn" @@ -243,7 +330,7 @@ "defaultMessage": "Trạng thái" }, "created-on": { - "defaultMessage": "Đã tạo: {date}" + "defaultMessage": "Tạo lúc: {date}" }, "dashboard": { "defaultMessage": "Bảng điều khiển" @@ -255,19 +342,19 @@ "defaultMessage": "Máy chủ 404" }, "dead-hosts.count": { - "defaultMessage": "Số trang lỗi {count}" + "defaultMessage": "{count} máy chủ 404" }, "disabled": { "defaultMessage": "Đã tắt" }, "domain-names": { - "defaultMessage": "Danh sách tên miền" + "defaultMessage": "Tên miền" }, "domain-names.max": { "defaultMessage": "Tối đa {count} tên miền" }, "domain-names.placeholder": { - "defaultMessage": "Nhập tên miền vào đây..." + "defaultMessage": "Nhập để thêm tên miền…" }, "domain-names.wildcards-not-permitted": { "defaultMessage": "Ký tự đại diện không được phép cho loại này" @@ -275,6 +362,9 @@ "domain-names.wildcards-not-supported": { "defaultMessage": "Ký tự đại diện không được hỗ trợ cho CA này" }, + "domains.advanced": { + "defaultMessage": "Nâng cao" + }, "domains.force-ssl": { "defaultMessage": "Bắt buộc SSL" }, @@ -282,13 +372,16 @@ "defaultMessage": "Bật HSTS" }, "domains.hsts-subdomains": { - "defaultMessage": "Tên miền phụ HSTS" + "defaultMessage": "HSTS cho tên miền con" }, "domains.http2-support": { "defaultMessage": "Hỗ trợ HTTP/2" }, + "domains.trust-forwarded-proto": { + "defaultMessage": "Tin cậy header X-Forwarded-Proto từ proxy phía trước" + }, "domains.use-dns": { - "defaultMessage": "Dùng thử thách DNS" + "defaultMessage": "Xác thực qua DNS" }, "email-address": { "defaultMessage": "Địa chỉ email" @@ -297,19 +390,19 @@ "defaultMessage": "Không có kết quả nào" }, "empty-subtitle": { - "defaultMessage": "Tại sao bạn không tạo một cái luôn?" + "defaultMessage": "Hãy bắt đầu bằng cách tạo mới." }, "enabled": { "defaultMessage": "Đã bật" }, "error.access.at-least-one": { - "defaultMessage": "Yêu cầu ít nhất một quy tắc ủy quyền hoặc truy cập" + "defaultMessage": "Cần ít nhất một tài khoản xác thực hoặc một quy tắc truy cập" }, "error.access.duplicate-usernames": { - "defaultMessage": "Tên người dùng được ủy quyền phải là duy nhất" + "defaultMessage": "Tên người dùng xác thực không được trùng nhau" }, "error.invalid-auth": { - "defaultMessage": "Email hoặc Mật khẩu không hợp lệ" + "defaultMessage": "Email hoặc mật khẩu không hợp lệ" }, "error.invalid-domain": { "defaultMessage": "Tên miền không hợp lệ: {domain}" @@ -336,7 +429,7 @@ "defaultMessage": "Mật khẩu phải khớp" }, "error.required": { - "defaultMessage": "Điều này là bắt buộc" + "defaultMessage": "Trường này là bắt buộc" }, "expires.on": { "defaultMessage": "Hết hạn: {date}" @@ -348,10 +441,10 @@ "defaultMessage": "Chặn các hoạt động khai thác phổ biến" }, "host.flags.cache-assets": { - "defaultMessage": "Cache tài nguyên" + "defaultMessage": "Bộ nhớ đệm tài nguyên" }, "host.flags.preserve-path": { - "defaultMessage": "Bảo toàn đường dẫn" + "defaultMessage": "Giữ nguyên đường dẫn" }, "host.flags.protocols": { "defaultMessage": "Giao thức" @@ -360,16 +453,16 @@ "defaultMessage": "Hỗ trợ Websockets" }, "host.forward-port": { - "defaultMessage": "Chuyển tiếp cổng" + "defaultMessage": "Cổng đích" }, "host.forward-scheme": { - "defaultMessage": "Scheme" + "defaultMessage": "Giao thức" }, "hosts": { "defaultMessage": "Máy chủ" }, "http-only": { - "defaultMessage": "HTTP Only" + "defaultMessage": "Chỉ HTTP" }, "lets-encrypt": { "defaultMessage": "Let's Encrypt" @@ -381,28 +474,94 @@ "defaultMessage": "Let's Encrypt qua HTTP" }, "loading": { - "defaultMessage": "Đang tải..." + "defaultMessage": "Đang tải…" + }, + "location.advanced-config": { + "defaultMessage": "Có cấu hình Nginx tùy chỉnh" + }, + "location.filter": { + "defaultMessage": "Lọc theo đường dẫn hoặc đích" + }, + "login.2fa-code": { + "defaultMessage": "Mã xác minh" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Nhập mã" + }, + "login.2fa-description": { + "defaultMessage": "Nhập mã từ ứng dụng xác thực của bạn" + }, + "login.2fa-title": { + "defaultMessage": "Xác thực hai lớp" + }, + "login.2fa-verify": { + "defaultMessage": "Xác minh" }, "login.title": { "defaultMessage": "Đăng nhập vào tài khoản của bạn" }, + "logs": { + "defaultMessage": "Nhật ký" + }, + "logs.channel.access": { + "defaultMessage": "Truy cập" + }, + "logs.channel.error": { + "defaultMessage": "Lỗi" + }, + "logs.download": { + "defaultMessage": "Tải xuống" + }, + "logs.empty": { + "defaultMessage": "Chưa có mục nhật ký nào." + }, + "logs.level": { + "defaultMessage": "Mức độ" + }, + "logs.level.all": { + "defaultMessage": "Tất cả mức độ" + }, + "logs.lines": { + "defaultMessage": "Số dòng" + }, + "logs.live": { + "defaultMessage": "Trực tiếp" + }, + "logs.paused": { + "defaultMessage": "Đã tạm dừng" + }, + "logs.refresh": { + "defaultMessage": "Làm mới" + }, + "logs.search-placeholder": { + "defaultMessage": "Tìm trong nhật ký…" + }, + "logs.source": { + "defaultMessage": "Nguồn" + }, + "logs.source.system": { + "defaultMessage": "Hệ thống" + }, + "logs.truncated-warning": { + "defaultMessage": "Chỉ hiển thị phần mới nhất của tệp này." + }, "nginx-config.label": { "defaultMessage": "Cấu hình Nginx tùy chỉnh" }, "nginx-config.placeholder": { - "defaultMessage": "# Nhập cấu hình Nginx tùy chỉnh của bạn vào đây và bạn phải tự chịu rủi ro!" + "defaultMessage": "# Nhập cấu hình Nginx tùy chỉnh tại đây, bạn tự chịu rủi ro!" }, "no-permission-error": { - "defaultMessage": "Bạn không có quyền truy cập trang này." + "defaultMessage": "Bạn không có quyền xem nội dung này." }, "notfound.action": { "defaultMessage": "Về trang chủ" }, "notfound.content": { - "defaultMessage": "Chúng tôi xin lỗi nhưng trang bạn đang tìm kiếm không được tìm thấy" + "defaultMessage": "Rất tiếc, không tìm thấy trang bạn yêu cầu" }, "notfound.title": { - "defaultMessage": "Rất tiếc… Bạn vừa tìm thấy một trang lỗi" + "defaultMessage": "Ối… Bạn vừa lạc vào một trang lỗi" }, "notification.error": { "defaultMessage": "Lỗi" @@ -417,7 +576,7 @@ "defaultMessage": "{object} đã được bật" }, "notification.object-renewed": { - "defaultMessage": "{object} đã được làm mới" + "defaultMessage": "{object} đã được gia hạn" }, "notification.object-saved": { "defaultMessage": "{object} đã được lưu" @@ -435,13 +594,13 @@ "defaultMessage": "Xóa {object}" }, "object.delete.content": { - "defaultMessage": "Bạn có chắc muốn xóa {object} không?" + "defaultMessage": "Bạn có chắc muốn xóa {object} này không?" }, "object.edit": { "defaultMessage": "Chỉnh sửa {object}" }, "object.empty": { - "defaultMessage": "Không có {objects}" + "defaultMessage": "Chưa có {objects} nào" }, "object.event.created": { "defaultMessage": "Đã tạo {object}" @@ -477,10 +636,10 @@ "defaultMessage": "Tạo mật khẩu ngẫu nhiên" }, "password.hide": { - "defaultMessage": "Ẩn Mật khẩu" + "defaultMessage": "Ẩn mật khẩu" }, "password.show": { - "defaultMessage": "Hiện Mật khẩu" + "defaultMessage": "Hiện mật khẩu" }, "permissions.hidden": { "defaultMessage": "Ẩn" @@ -495,16 +654,16 @@ "defaultMessage": "Tất cả các mục" }, "permissions.visibility.title": { - "defaultMessage": "Khả năng hiển thị mục" + "defaultMessage": "Phạm vi hiển thị" }, "permissions.visibility.user": { - "defaultMessage": "Chỉ các mục đã tạo" + "defaultMessage": "Chỉ các mục do mình tạo" }, "proxy-host": { "defaultMessage": "Máy chủ proxy" }, "proxy-host.forward-host": { - "defaultMessage": "Chuyển tiếp Hostname / IP" + "defaultMessage": "Hostname / IP đích" }, "proxy-hosts": { "defaultMessage": "Máy chủ proxy" @@ -516,25 +675,43 @@ "defaultMessage": "Công khai" }, "redirection-host": { - "defaultMessage": "Redirection Host" + "defaultMessage": "Máy chủ chuyển hướng" }, "redirection-host.forward-domain": { - "defaultMessage": "Chuyển tiếp Tên miền" + "defaultMessage": "Tên miền đích" }, "redirection-host.forward-http-code": { - "defaultMessage": "HTTP Code" + "defaultMessage": "Mã HTTP" }, "redirection-hosts": { - "defaultMessage": "Redirection Hosts" + "defaultMessage": "Máy chủ chuyển hướng" }, "redirection-hosts.count": { "defaultMessage": "{count} máy chủ chuyển hướng" }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Nhiều lựa chọn" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Chuyển hướng vĩnh viễn" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Chuyển hướng tạm thời" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Xem trang khác" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Chuyển hướng tạm thời (giữ phương thức)" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Chuyển hướng vĩnh viễn (giữ phương thức)" + }, "role.admin": { "defaultMessage": "Quản trị viên" }, "role.standard-user": { - "defaultMessage": "Người dùng bình thường" + "defaultMessage": "Người dùng thường" }, "save": { "defaultMessage": "Lưu" @@ -555,10 +732,10 @@ "defaultMessage": "Không có phản hồi (444)" }, "settings.default-site.congratulations": { - "defaultMessage": "Trang chào mừng" + "defaultMessage": "Trang chúc mừng" }, "settings.default-site.description": { - "defaultMessage": "Hiển thị gì khi Nginx gặp phải Máy chủ không xác định" + "defaultMessage": "Nội dung hiển thị khi Nginx nhận yêu cầu tới tên miền không xác định" }, "settings.default-site.html": { "defaultMessage": "HTML tùy chỉnh" @@ -582,19 +759,22 @@ "defaultMessage": "Chứng chỉ SSL" }, "stream": { - "defaultMessage": "Stream" + "defaultMessage": "Luồng TCP/UDP" }, "stream.forward-host": { - "defaultMessage": "Chuyển tiếp Host" + "defaultMessage": "Máy chủ đích" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com hoặc 10.0.0.1 hoặc 2001:db8:3333:4444:5555:6666:7777:8888" }, "stream.incoming-port": { "defaultMessage": "Cổng vào" }, "streams": { - "defaultMessage": "Danh sách các Stream" + "defaultMessage": "Luồng TCP/UDP" }, "streams.count": { - "defaultMessage": "Số Stream {count}" + "defaultMessage": "{count} luồng TCP/UDP" }, "streams.tcp": { "defaultMessage": "TCP" @@ -606,16 +786,16 @@ "defaultMessage": "Kiểm tra" }, "update-available": { - "defaultMessage": "Cập nhật khả dụng: {latestVersion}" + "defaultMessage": "Đã có bản cập nhật: {latestVersion}" }, "user": { "defaultMessage": "Người dùng" }, "user.change-password": { - "defaultMessage": "Đổi Mật khẩu" + "defaultMessage": "Đổi mật khẩu" }, "user.confirm-password": { - "defaultMessage": "Xác nhận Mật khẩu" + "defaultMessage": "Xác nhận mật khẩu" }, "user.current-password": { "defaultMessage": "Mật khẩu hiện tại" @@ -624,10 +804,10 @@ "defaultMessage": "Chỉnh sửa hồ sơ" }, "user.full-name": { - "defaultMessage": "Tên" + "defaultMessage": "Họ và tên" }, "user.login-as": { - "defaultMessage": "Đăng nhập bằng {name}" + "defaultMessage": "Đăng nhập với tư cách {name}" }, "user.logout": { "defaultMessage": "Đăng xuất" @@ -639,7 +819,7 @@ "defaultMessage": "Tên hiển thị" }, "user.set-password": { - "defaultMessage": "Đặt Mật khẩu" + "defaultMessage": "Đặt mật khẩu" }, "user.set-permissions": { "defaultMessage": "Đặt quyền cho {name}" @@ -650,10 +830,13 @@ "user.switch-light": { "defaultMessage": "Chuyển sang chế độ sáng" }, + "user.two-factor": { + "defaultMessage": "Xác thực hai lớp" + }, "username": { "defaultMessage": "Tên người dùng" }, "users": { - "defaultMessage": "Danh sách người dùng" + "defaultMessage": "Người dùng" } } diff --git a/frontend/src/locale/vi.test.ts b/frontend/src/locale/vi.test.ts new file mode 100644 index 000000000..3b8cfcb85 --- /dev/null +++ b/frontend/src/locale/vi.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import en from "./src/en.json"; +import vi from "./src/vi.json"; + +const english = en as Record; +const vietnamese = vi as Record; + +// Brand names, codes and ICU-only strings can stay the same as English. +const allowedIdentical = new Set([ + "certificates.key-type-ecdsa", + "certificates.key-type-rsa", + "column.email", + "column.ssl", + "lets-encrypt", + "object.actions-title", + "streams.tcp", + "streams.udp", +]); + +describe("Vietnamese UI locale", () => { + it("covers every English UI key", () => { + const missing = Object.keys(english).filter((key) => !(key in vietnamese)); + expect(missing).toEqual([]); + }); + + it("does not add keys that are missing from English", () => { + const extra = Object.keys(vietnamese).filter((key) => !(key in english)); + expect(extra).toEqual([]); + }); + + it("is translated, not an English copy", () => { + const identical = Object.keys(english).filter( + (key) => vietnamese[key]?.defaultMessage === english[key]?.defaultMessage, + ); + const unexpected = identical.filter((key) => !allowedIdentical.has(key)); + expect(unexpected).toEqual([]); + }); + + it("keeps ICU data placeholders from English strings", () => { + const argNames = [ + "count", + "object", + "objects", + "id", + "date", + "users", + "rules", + "max", + "min", + "domain", + "code", + "latestVersion", + "name", + ]; + const usedArgs = (message: string) => argNames.filter((name) => message.includes(`{${name}`)); + const broken: string[] = []; + + for (const key of Object.keys(english)) { + const expected = usedArgs(english[key].defaultMessage).join(","); + const actual = usedArgs(vietnamese[key]?.defaultMessage ?? "").join(","); + if (expected !== actual) { + broken.push(`${key}: expected {${expected}} got {${actual}}`); + } + } + + expect(broken).toEqual([]); + }); +}); From 092d212ad70d00c57d6f15f43a8026c9e8c42c13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:38:20 +0000 Subject: [PATCH 07/76] build(deps): bump serialize-javascript from 7.1.1 to 7.1.2 in /test Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 7.1.1 to 7.1.2. - [Release notes](https://github.com/yahoo/serialize-javascript/releases) - [Commits](https://github.com/yahoo/serialize-javascript/compare/v7.1.1...v7.1.2) --- updated-dependencies: - dependency-name: serialize-javascript dependency-version: 7.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- test/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/yarn.lock b/test/yarn.lock index 4eb1a544f..5f24217e9 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -1928,9 +1928,9 @@ semver@^7.6.3: integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== serialize-javascript@^7.0.2: - version "7.1.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.1.tgz#c3b0a7ac13df6cf2fcda71cbb142ba9392a710b9" - integrity sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ== + version "7.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.2.tgz#0161010a0815dd7b7e011cbc5a3644850b8c2289" + integrity sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ== shebang-command@^2.0.0: version "2.0.0" From f5e6dfecfbdcf6742b58d5805891c4fccf3c29c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:34:18 +0000 Subject: [PATCH 08/76] build(deps): bump fast-uri from 3.1.7 to 3.1.8 in /backend Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.7 to 3.1.8. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.7...v3.1.8) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/yarn.lock b/backend/yarn.lock index 1d7daab0a..8f4238ad5 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -899,9 +899,9 @@ fast-fifo@^1.2.0, fast-fifo@^1.3.2: integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== fast-uri@^3.0.1: - version "3.1.7" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.7.tgz#743157d957f3cbb4c65310e033dc2ad4ad7dc60a" - integrity sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg== + version "3.1.8" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.8.tgz#f7db8d942e20eead3cfe93b0beeb4eb0169e938b" + integrity sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg== fdir@^6.5.0: version "6.5.0" From 2afd723cd8ab4b0b3f7a45cb6230c6b42b09cb02 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 5 Oct 2026 21:59:46 +1000 Subject: [PATCH 09/76] Fix access list rules and SSL missing from re-rendered host configs Some paths rendered host configs without fully loading the relations the templates depend on, silently dropping access rules (and SSL): - Enabling a proxy host loaded the access list without its clients or auth items, so the host came back with no access restrictions. - Requesting a Let's Encrypt certificate re-rendered every host sharing its domains from bare rows, dropping access rules and SSL. generateConfig now loads any referenced certificate or access list that isn't fully loaded, so no caller can render a partial config. The two callers above also fetch the relations themselves, and a warning is logged when a host's access list is missing or has no rules. Adds integration tests covering access lists across disable/enable. Fixes #5919, #4286, #5710 Co-Authored-By: Claude Opus 5.5 --- backend/internal/host.js | 9 +- backend/internal/nginx.js | 52 ++++- backend/internal/proxy-host.js | 2 +- docker/docker-compose.ci.yml | 1 + docker/docker-compose.dev.yml | 1 + .../e2e/api/AccessListHostToggle.cy.js | 206 ++++++++++++++++++ test/docker/nginx/conf.d/website123.conf | 2 +- 7 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 test/cypress/e2e/api/AccessListHostToggle.cy.js diff --git a/backend/internal/host.js b/backend/internal/host.js index 748716262..81efe160e 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -79,15 +79,18 @@ const internalHost = { redirection_hosts: [], }; - const proxyRes = await proxyHostModel.query().where("is_deleted", 0); + const proxyRes = await proxyHostModel + .query() + .where("is_deleted", 0) + .withGraphFetched("[certificate, access_list.[clients,items]]"); responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames); responseObject.total_count += responseObject.proxy_hosts.length; - const redirRes = await redirectionHostModel.query().where("is_deleted", 0); + const redirRes = await redirectionHostModel.query().where("is_deleted", 0).withGraphFetched("[certificate]"); responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames); responseObject.total_count += responseObject.redirection_hosts.length; - const deadRes = await deadHostModel.query().where("is_deleted", 0); + const deadRes = await deadHostModel.query().where("is_deleted", 0).withGraphFetched("[certificate]"); responseObject.dead_hosts = internalHost._getHostsWithDomains(deadRes, domainNames); responseObject.total_count += responseObject.dead_hosts.length; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index a50e0780d..378b53591 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -7,6 +7,7 @@ import errs from "../lib/error.js"; import utils from "../lib/utils.js"; import { debug, nginx as logger } from "../logger.js"; import accessListModel from "../models/access_list.js"; +import certificateModel from "../models/certificate.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -209,11 +210,13 @@ const internalNginx = { * @param {Object} host * @returns {Promise} */ - generateConfig: (host_type, host_row) => { + generateConfig: async (host_type, host_row) => { // Prevent modifying the original object: const host = JSON.parse(JSON.stringify(host_row)); const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); + await internalNginx.loadRenderRelations(nice_host_type, host); + debug(logger, `Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); const renderEngine = utils.getRenderEngine(); @@ -299,6 +302,53 @@ const internalNginx = { }); }, + /** + * The templates rely on the certificate and access list relations being loaded. + * Not every caller fetches them (or fetches them fully), and when they're missing + * the config silently renders without SSL or without any access rules (#5919). + * Load anything that is referenced but not loaded onto the given host object. + * + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + loadRenderRelations: async (host_type, host) => { + if (host.certificate_id > 0 && host.certificate?.id !== host.certificate_id) { + const certificate = await certificateModel + .query() + .where("is_deleted", 0) + .andWhere("id", host.certificate_id) + .first(); + host.certificate = certificate ? JSON.parse(JSON.stringify(certificate)) : null; + } + + if (host_type === "proxy_host" && host.access_list_id > 0) { + if ( + host.access_list?.id !== host.access_list_id || + !Array.isArray(host.access_list.clients) || + !Array.isArray(host.access_list.items) + ) { + const accessList = await accessListModel + .query() + .where("is_deleted", 0) + .andWhere("id", host.access_list_id) + .withGraphFetched("[clients,items]") + .first(); + host.access_list = accessList ? JSON.parse(JSON.stringify(accessList)) : null; + } + + if (!host.access_list) { + logger.warn( + `Proxy Host #${host.id} references Access List #${host.access_list_id} which does not exist`, + ); + } else if (!host.access_list.clients.length && !host.access_list.items.length) { + logger.warn( + `Proxy Host #${host.id} uses Access List #${host.access_list_id} which has no clients or authorizations, no access restrictions will apply`, + ); + } + } + }, + /** * This generates a temporary nginx config listening on port 80 for the domain names listed * in the certificate setup. It allows the letsencrypt acme challenge to be requested by letsencrypt diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index eac0c148a..f079dab02 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -323,7 +323,7 @@ const internalProxyHost = { .then(() => { return internalProxyHost.get(access, { id: data.id, - expand: ["certificate", "owner", "access_list"], + expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) .then(async (row) => { diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index be6f517d3..2aef4e8ff 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -37,6 +37,7 @@ services: - website1.example.com - website2.example.com - website3.example.com + - website4.example.com examplesite: image: "${IMAGE}-examplesite:ci-${BUILD_NUMBER}" diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index d6b07ec01..20e01e6be 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -16,6 +16,7 @@ services: - website1.example.com - website2.example.com - website3.example.com + - website4.example.com environment: TZ: "${TZ:-Australia/Brisbane}" PUID: 1000 diff --git a/test/cypress/e2e/api/AccessListHostToggle.cy.js b/test/cypress/e2e/api/AccessListHostToggle.cy.js new file mode 100644 index 000000000..b566a3a98 --- /dev/null +++ b/test/cypress/e2e/api/AccessListHostToggle.cy.js @@ -0,0 +1,206 @@ +/// + +// Regression tests for #5919, #4286 and #5710: access list rules must survive +// every path that re-renders a proxy host config, not just the initial save. +describe('Access Lists across Proxy Host disable/enable', () => { + const domain = 'website4.example.com'; + + const creds = { + username: 'toggle-user', + password: 'toggle-pass', + }; + + let token; + let authListId; + let clientListId; + let hostId; + + /** + * Requests a path on the proxy host directly (bypassing squid) + * and yields the HTTP status code + * + * @param {object} [auth] + * @param {string} auth.username + * @param {string} auth.password + */ + const request = (auth) => { + const authArg = auth ? `-u '${auth.username}:${auth.password}'` : ''; + return cy.exec(`curl --noproxy '*' -s -o /dev/null -w '%{http_code}' ${authArg} http://${domain}/`) + .then((result) => { + expect(result.exitCode).to.eq(0); + return result.stdout.trim(); + }); + }; + + // nginx reloads are signalled and return immediately, so the old + // config can still be served for a moment after an API change. + // Retry until the expected status is seen. + const expectStatus = (auth, status) => { + let last = null; + cy.waitUntil(() => request(auth).then((res) => { + last = res; + return res === status; + }), { + timeout: 15000, + interval: 500, + errorMsg: () => `${domain} did not return ${status}, last status: ${last}`, + }); + }; + + const createAccessList = (data) => { + return cy.task('backendApiPost', { + token: token, + path: '/api/nginx/access-lists', + data: { + satisfy_any: false, + pass_auth: false, + items: [], + clients: [], + ...data, + } + }).then((res) => { + cy.validateSwaggerSchema('post', 201, '/nginx/access-lists', res); + expect(res).to.have.property('id'); + expect(res.id).to.be.greaterThan(0); + return cy.wrap(res.id); + }); + }; + + const setHostAccessList = (accessListId) => { + cy.task('backendApiPut', { + token: token, + path: `/api/nginx/proxy-hosts/${hostId}`, + data: { + access_list_id: accessListId, + } + }).then((data) => { + // No swagger validation here: with a host access list set, the expanded + // access_list in the response has no proxy_host_count, which the schema requires + expect(data).to.have.property('access_list_id', accessListId); + }); + }; + + const toggleHost = () => { + cy.task('backendApiPost', { + token: token, + path: `/api/nginx/proxy-hosts/${hostId}/disable`, + }).then((data) => { + cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/disable', data); + expect(data).to.be.equal(true); + }); + + cy.task('backendApiPost', { + token: token, + path: `/api/nginx/proxy-hosts/${hostId}/enable`, + }).then((data) => { + cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/enable', data); + expect(data).to.be.equal(true); + }); + }; + + before(() => { + cy.resetUsers(); + cy.getToken().then((tok) => { + token = tok; + }); + }); + + it('Should be able to create an auth access list and a client access list', () => { + createAccessList({ + name: 'Toggle Auth', + items: [creds], + }).then((id) => { + authListId = id; + }); + + // The test runner is never in this range, so it must always be denied + createAccessList({ + name: 'Toggle Clients', + clients: [ + { + directive: 'allow', + address: '203.0.113.0/24', + } + ], + }).then((id) => { + clientListId = id; + }); + }); + + it('Should be able to create a proxy host without an access list', () => { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/proxy-hosts', + data: { + domain_names: [domain], + forward_scheme: 'http', + forward_host: 'examplesite', + forward_port: 80, + access_list_id: 0, + certificate_id: 0, + meta: { + dns_challenge: false + }, + advanced_config: '', + locations: [], + block_exploits: false, + caching_enabled: false, + allow_websocket_upgrade: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + hostId = data.id; + }); + + expectStatus(null, '200'); + }); + + it('Should enforce an auth access list applied to an existing host', () => { + setHostAccessList(authListId); + expectStatus(null, '401'); + expectStatus(creds, '200'); + }); + + it('Should still enforce the auth access list after the host is disabled and enabled', () => { + toggleHost(); + expectStatus(null, '401'); + expectStatus(creds, '200'); + }); + + it('Should enforce a client access list applied to an existing host', () => { + setHostAccessList(clientListId); + expectStatus(null, '403'); + }); + + it('Should still enforce the client access list after the host is disabled and enabled', () => { + toggleHost(); + expectStatus(null, '403'); + }); + + it('Should be able to delete the proxy host and access lists', () => { + cy.task('backendApiDelete', { + token: token, + path: `/api/nginx/proxy-hosts/${hostId}`, + }).then((data) => { + cy.validateSwaggerSchema('delete', 200, '/nginx/proxy-hosts/{hostID}', data); + expect(data).to.be.equal(true); + }); + + [authListId, clientListId].forEach((id) => { + cy.task('backendApiDelete', { + token: token, + path: `/api/nginx/access-lists/${id}`, + }).then((data) => { + cy.validateSwaggerSchema('delete', 200, '/nginx/access-lists/{listID}', data); + expect(data).to.be.equal(true); + }); + }); + }); + +}); diff --git a/test/docker/nginx/conf.d/website123.conf b/test/docker/nginx/conf.d/website123.conf index 656f3ade4..eefb233bc 100644 --- a/test/docker/nginx/conf.d/website123.conf +++ b/test/docker/nginx/conf.d/website123.conf @@ -1,6 +1,6 @@ server { listen 80; - server_name website1.example.com website2.example.com website3.example.com; + server_name website1.example.com website2.example.com website3.example.com website4.example.com; root /www; index index.html; From 81e7044a174d7bcccbdc9134255c720d61a1a3ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:34:59 +0000 Subject: [PATCH 10/76] build(deps): bump proxy-addr from 2.0.7 to 2.0.8 in /backend Bumps [proxy-addr](https://github.com/jshttp/proxy-addr) from 2.0.7 to 2.0.8. - [Release notes](https://github.com/jshttp/proxy-addr/releases) - [Changelog](https://github.com/jshttp/proxy-addr/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/proxy-addr/compare/v2.0.7...v2.0.8) --- updated-dependencies: - dependency-name: proxy-addr dependency-version: 2.0.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/yarn.lock b/backend/yarn.lock index 8f4238ad5..a9d6c2b51 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1873,9 +1873,9 @@ process@^0.11.1, process@^0.11.10: integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + version "2.0.8" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.8.tgz#624e760fd8ab06b1b8320c2f8506d4422cf8e932" + integrity sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ== dependencies: forwarded "0.2.0" ipaddr.js "1.9.1" From 3270873cc83b3b91d29f68e313339e7f3ead0db6 Mon Sep 17 00:00:00 2001 From: Juice-de-Orange <200407033+Juice-de-Orange@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:59:42 +0200 Subject: [PATCH 11/76] Add missing German (de) and Italian (it) translations --- frontend/src/locale/src/de.json | 66 ++++++++++++ frontend/src/locale/src/it.json | 183 ++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json index 6cdb64cf3..e5ce5355a 100644 --- a/frontend/src/locale/src/de.json +++ b/frontend/src/locale/src/de.json @@ -71,6 +71,12 @@ "access-list.help.rules-order": { "defaultMessage": "Beachten Sie, dass die Anweisungen „Erlauben“ und „Verbieten“ in der Reihenfolge ihrer Definition angewendet werden." }, + "access-list.inherit": { + "defaultMessage": "Vom Proxy Host übernehmen" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "Verwendet die Zugriffsliste des Proxy Hosts" + }, "access-list.pass-auth": { "defaultMessage": "Authentifizierung an Upstream weiterleiten" }, @@ -101,6 +107,9 @@ "action.allow": { "defaultMessage": "Erlauben" }, + "action.clear": { + "defaultMessage": "Leeren" + }, "action.close": { "defaultMessage": "Schließen" }, @@ -122,6 +131,9 @@ "action.enable": { "defaultMessage": "Aktivieren" }, + "action.logs": { + "defaultMessage": "Logs" + }, "action.permissions": { "defaultMessage": "Berechtigungen" }, @@ -266,6 +278,9 @@ "column.email": { "defaultMessage": "E-Mail" }, + "column.error": { + "defaultMessage": "Fehler" + }, "column.event": { "defaultMessage": "Ereignis" }, @@ -461,6 +476,12 @@ "loading": { "defaultMessage": "Laden…" }, + "location.advanced-config": { + "defaultMessage": "Hat eine eigene Nginx-Konfiguration" + }, + "location.filter": { + "defaultMessage": "Nach Pfad oder Ziel filtern" + }, "login.2fa-code": { "defaultMessage": "Verifizierungscode" }, @@ -479,6 +500,51 @@ "login.title": { "defaultMessage": "Anmelden" }, + "logs": { + "defaultMessage": "Logs" + }, + "logs.channel.access": { + "defaultMessage": "Zugriff" + }, + "logs.channel.error": { + "defaultMessage": "Fehler" + }, + "logs.download": { + "defaultMessage": "Herunterladen" + }, + "logs.empty": { + "defaultMessage": "Noch keine Log-Einträge." + }, + "logs.level": { + "defaultMessage": "Level" + }, + "logs.level.all": { + "defaultMessage": "Alle Level" + }, + "logs.lines": { + "defaultMessage": "Zeilen" + }, + "logs.live": { + "defaultMessage": "Live" + }, + "logs.paused": { + "defaultMessage": "Pausiert" + }, + "logs.refresh": { + "defaultMessage": "Aktualisieren" + }, + "logs.search-placeholder": { + "defaultMessage": "Log durchsuchen…" + }, + "logs.source": { + "defaultMessage": "Quelle" + }, + "logs.source.system": { + "defaultMessage": "System" + }, + "logs.truncated-warning": { + "defaultMessage": "Es wird nur der neueste Teil dieser Datei angezeigt." + }, "nginx-config.label": { "defaultMessage": "Benutzerdefinierte Nginx Konfiguration" }, diff --git a/frontend/src/locale/src/it.json b/frontend/src/locale/src/it.json index 7e5ca7711..d8506bd95 100644 --- a/frontend/src/locale/src/it.json +++ b/frontend/src/locale/src/it.json @@ -1,4 +1,61 @@ { + "2fa.backup-codes-remaining": { + "defaultMessage": "Codici di backup rimanenti: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Conserva questi codici di backup in un luogo sicuro. Ogni codice può essere usato una sola volta." + }, + "2fa.disable": { + "defaultMessage": "Disattiva Autenticazione a Due Fattori" + }, + "2fa.disable-confirm": { + "defaultMessage": "Disattiva 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Disattivare l'autenticazione a due fattori renderà il tuo account meno sicuro." + }, + "2fa.disabled": { + "defaultMessage": "Disattivata" + }, + "2fa.done": { + "defaultMessage": "Ho salvato i miei codici di backup" + }, + "2fa.enable": { + "defaultMessage": "Attiva Autenticazione a Due Fattori" + }, + "2fa.enabled": { + "defaultMessage": "Attivata" + }, + "2fa.enter-code": { + "defaultMessage": "Inserisci il codice di verifica" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Inserisci il codice di verifica per disattivarla" + }, + "2fa.regenerate": { + "defaultMessage": "Rigenera" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Rigenera Codici di Backup" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Inserisci un codice di verifica per generare nuovi codici di backup. I vecchi codici non saranno più validi." + }, + "2fa.secret-key": { + "defaultMessage": "Chiave Segreta" + }, + "2fa.setup-instructions": { + "defaultMessage": "Scansiona questo codice QR con la tua app di autenticazione, oppure inserisci la chiave segreta manualmente." + }, + "2fa.status": { + "defaultMessage": "Stato" + }, + "2fa.title": { + "defaultMessage": "Autenticazione a Due Fattori" + }, + "2fa.verify-enable": { + "defaultMessage": "Verifica e Attiva" + }, "access-list": { "defaultMessage": "Lista di Accesso" }, @@ -14,6 +71,12 @@ "access-list.help.rules-order": { "defaultMessage": "Nota che le direttive di allow e deny saranno applicate nell'ordine in cui sono definite." }, + "access-list.inherit": { + "defaultMessage": "Eredita dal Proxy Host" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "Usa la lista di accesso del proxy host" + }, "access-list.pass-auth": { "defaultMessage": "Passa Autenticazione all'Upstream" }, @@ -23,6 +86,9 @@ "access-list.public.subtitle": { "defaultMessage": "Nessuna autenticazione base richiesta" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 o 192.168.1.0/24 o 2001:0db8::/32" + }, "access-list.satisfy-any": { "defaultMessage": "Soddisfa Qualsiasi" }, @@ -38,12 +104,21 @@ "action.add-location": { "defaultMessage": "Aggiungi Percorso" }, + "action.allow": { + "defaultMessage": "Consenti" + }, + "action.clear": { + "defaultMessage": "Svuota" + }, "action.close": { "defaultMessage": "Chiudi" }, "action.delete": { "defaultMessage": "Elimina" }, + "action.deny": { + "defaultMessage": "Nega" + }, "action.disable": { "defaultMessage": "Disabilita" }, @@ -56,6 +131,9 @@ "action.enable": { "defaultMessage": "Abilita" }, + "action.logs": { + "defaultMessage": "Log" + }, "action.permissions": { "defaultMessage": "Permessi" }, @@ -68,6 +146,9 @@ "auditlogs": { "defaultMessage": "Log di Audit" }, + "auto": { + "defaultMessage": "Auto" + }, "cancel": { "defaultMessage": "Annulla" }, @@ -128,6 +209,9 @@ "certificates.dns.provider": { "defaultMessage": "Provider DNS" }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Seleziona un Provider..." + }, "certificates.dns.warning": { "defaultMessage": "Questa sezione richiede conoscenze su Certbot e i relativi plugin DNS. Consulta la documentazione del plugin." }, @@ -194,6 +278,9 @@ "column.email": { "defaultMessage": "Email" }, + "column.error": { + "defaultMessage": "Errore" + }, "column.event": { "defaultMessage": "Evento" }, @@ -275,6 +362,9 @@ "domain-names.wildcards-not-supported": { "defaultMessage": "Wildcard non supportate per questa CA" }, + "domains.advanced": { + "defaultMessage": "Avanzate" + }, "domains.force-ssl": { "defaultMessage": "Forza SSL" }, @@ -287,6 +377,9 @@ "domains.http2-support": { "defaultMessage": "Supporto HTTP/2" }, + "domains.trust-forwarded-proto": { + "defaultMessage": "Considera Attendibili gli Header Forwarded Proto dell'Upstream" + }, "domains.use-dns": { "defaultMessage": "Usa Challenge DNS" }, @@ -383,9 +476,75 @@ "loading": { "defaultMessage": "Caricamento…" }, + "location.advanced-config": { + "defaultMessage": "Ha una configurazione Nginx personalizzata" + }, + "location.filter": { + "defaultMessage": "Filtra per percorso o destinazione" + }, + "login.2fa-code": { + "defaultMessage": "Codice di Verifica" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Inserisci il codice" + }, + "login.2fa-description": { + "defaultMessage": "Inserisci il codice della tua app di autenticazione" + }, + "login.2fa-title": { + "defaultMessage": "Autenticazione a Due Fattori" + }, + "login.2fa-verify": { + "defaultMessage": "Verifica" + }, "login.title": { "defaultMessage": "Accedi al tuo account" }, + "logs": { + "defaultMessage": "Log" + }, + "logs.channel.access": { + "defaultMessage": "Accessi" + }, + "logs.channel.error": { + "defaultMessage": "Errori" + }, + "logs.download": { + "defaultMessage": "Scarica" + }, + "logs.empty": { + "defaultMessage": "Nessuna voce di log per ora." + }, + "logs.level": { + "defaultMessage": "Livello" + }, + "logs.level.all": { + "defaultMessage": "Tutti i Livelli" + }, + "logs.lines": { + "defaultMessage": "Righe" + }, + "logs.live": { + "defaultMessage": "Live" + }, + "logs.paused": { + "defaultMessage": "In Pausa" + }, + "logs.refresh": { + "defaultMessage": "Aggiorna" + }, + "logs.search-placeholder": { + "defaultMessage": "Cerca nel log…" + }, + "logs.source": { + "defaultMessage": "Origine" + }, + "logs.source.system": { + "defaultMessage": "Sistema" + }, + "logs.truncated-warning": { + "defaultMessage": "Viene mostrata solo la parte più recente di questo file." + }, "nginx-config.label": { "defaultMessage": "Configurazione Nginx Personalizzata" }, @@ -530,6 +689,24 @@ "redirection-hosts.count": { "defaultMessage": "{count} {count, plural, one {Host di Reindirizzamento} other {Host di Reindirizzamento}}" }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Scelte multiple" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Spostato permanentemente" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Spostato temporaneamente" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Vedi altro" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Reindirizzamento temporaneo" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Reindirizzamento permanente" + }, "role.admin": { "defaultMessage": "Amministratore" }, @@ -587,6 +764,9 @@ "stream.forward-host": { "defaultMessage": "Host di Destinazione" }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com o 10.0.0.1 o 2001:db8:3333:4444:5555:6666:7777:8888" + }, "stream.incoming-port": { "defaultMessage": "Porta in Ingresso" }, @@ -650,6 +830,9 @@ "user.switch-light": { "defaultMessage": "Passa alla modalità Chiara" }, + "user.two-factor": { + "defaultMessage": "Autenticazione 2FA" + }, "username": { "defaultMessage": "Nome Utente" }, From 6b26999e343f6daca608475a6783d610595055a9 Mon Sep 17 00:00:00 2001 From: lennondotw Date: Thu, 8 Oct 2026 15:50:24 +0800 Subject: [PATCH 12/76] fix(proxy): preserve client ports in forwarded Host headers Keep the incoming Host authority for default and custom locations, with the existing hostname fallback when Host is absent. Add a built-image regression that validates Host against Origin and Referer and run it after the CI image build. --- backend/templates/_location.conf | 2 +- .../etc/nginx/conf.d/include/proxy.conf | 2 +- docker/rootfs/etc/nginx/nginx.conf | 6 + scripts/ci/test-and-build | 2 + test/README.md | 12 ++ test/host-header-smoke.py | 167 ++++++++++++++++++ 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 test/host-header-smoke.py diff --git a/backend/templates/_location.conf b/backend/templates/_location.conf index a2ecb166d..550a87e3b 100644 --- a/backend/templates/_location.conf +++ b/backend/templates/_location.conf @@ -1,7 +1,7 @@ location {{ path }} { {{ advanced_config }} - proxy_set_header Host $host; + proxy_set_header Host $forward_host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; diff --git a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf index fe2c2f213..fdba1bd7c 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf @@ -1,5 +1,5 @@ add_header X-Served-By $host; -proxy_set_header Host $host; +proxy_set_header Host $forward_host; proxy_set_header X-Forwarded-Scheme $x_forwarded_scheme; proxy_set_header X-Forwarded-Proto $x_forwarded_proto; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index bdba3b305..471d22536 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -52,6 +52,12 @@ http { # Dynamically generated resolvers file include /etc/nginx/conf.d/include/resolvers[.]conf; + # Preserve the client authority, falling back when a request has no Host header. + map $http_host $forward_host { + default $http_host; + "" $host; + } + # Default upstream scheme map $host $forward_scheme { default http; diff --git a/scripts/ci/test-and-build b/scripts/ci/test-and-build index a0de34140..fec906d60 100755 --- a/scripts/ci/test-and-build +++ b/scripts/ci/test-and-build @@ -28,3 +28,5 @@ docker build --pull --no-cache --compress \ --build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \ . echo -e "${BLUE}❯ ${GREEN}Building Complete${RESET}" + +python3 test/host-header-smoke.py "${IMAGE:-nginx-proxy-manager}:${BRANCH_LOWER:-unknown}-ci-${BUILD_NUMBER:-0000}" diff --git a/test/README.md b/test/README.md index 63a2453b7..57b9eb36a 100644 --- a/test/README.md +++ b/test/README.md @@ -1,5 +1,17 @@ # Cypress Test Suite +## Host Header Regression Test + +Run `python3 test/host-header-smoke.py ` from the repository root. +The backend CI build runs this check against the image it builds. It creates a +disposable NPM container and renders a proxy host through the backend's normal +configuration generator, including a custom location. A synthetic HTTPS upstream +checks that the forwarded Host matches Origin and Referer, and echoes the request +method, path and body. Coverage includes HTTP and HTTPS, explicit default and +nonstandard ports, mixed-case names, IPv6 authorities, and HTTP/1.0 without Host. +All test traffic and published Docker ports use loopback; the container and its +anonymous volumes are removed on completion. + ## Running Locally ``` diff --git a/test/host-header-smoke.py b/test/host-header-smoke.py new file mode 100644 index 000000000..a823b8977 --- /dev/null +++ b/test/host-header-smoke.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Check forwarded Host headers against a real upstream in a disposable image.""" + +import http.client +import json +import socket +import ssl +import subprocess +import sys +import time +import uuid + + +BACKEND = r''' +import http.server, json, ssl +from urllib.parse import urlsplit + +class Backend(http.server.BaseHTTPRequestHandler): + def do_GET(self): + host = self.headers.get("Host") + origin = self.headers.get("Origin") + referer = self.headers.get("Referer") + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + self.send_response(200) + if origin and (host != urlsplit(origin).netloc or host != urlsplit(referer).netloc): + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(b"") + else: + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"host": host, "origin": origin, "referer": referer, + "method": self.command, "path": self.path, + "body": body.decode()}).encode()) + + do_POST = do_GET + + def log_message(self, *args): + pass + +context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +context.load_cert_chain("/data/custom_ssl/npm-999/fullchain.pem", + "/data/custom_ssl/npm-999/privkey.pem") +server = http.server.ThreadingHTTPServer(("127.0.0.1", 19443), Backend) +server.socket = context.wrap_socket(server.socket, server_side=True) +server.serve_forever() +''' + +RENDER = r''' +import nginx from "./internal/nginx.js"; + +const host = { + id: 999, enabled: true, domain_names: ["host-port.example.test", "[::1]"], + forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443, + certificate_id: 999, certificate: {provider: "other"}, access_list_id: 0, + ssl_forced: false, hsts_enabled: false, http2_support: false, + block_exploits: false, caching_enabled: false, allow_websocket_upgrade: false, + advanced_config: "", locations: [{path: "/custom/", advanced_config: "", + forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443}] +}; +await nginx.generateConfig("proxy_host", host); +process.exit(0); +''' + + +def docker(*args, **kwargs): + return subprocess.check_output(["docker", *args], text=True, **kwargs).strip() + + +def request(port, tls=False, path="/", method="GET", headers=None, body=None): + connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5) + try: + if tls: + connection.sock = ssl._create_unverified_context().wrap_socket( + socket.create_connection(("127.0.0.1", port), timeout=5), + server_hostname="host-port.example.test") + connection.request(method, path, headers=headers or {}, body=body) + response = connection.getresponse() + return response.status, response.getheader("Content-Type"), response.read() + finally: + connection.close() + + +def check_image(image): + name = f"npm-host-header-smoke-{uuid.uuid4().hex[:10]}" + backend = None + checked = 0 + try: + docker("run", "-d", "--name", name, "-e", "IP_RANGES_FETCH_ENABLED=false", + "-p", "127.0.0.1::80", "-p", "127.0.0.1::443", "-p", "127.0.0.1::81", + "--mount", "type=volume,destination=/etc/letsencrypt", image) + ports = {p: int(docker("port", name, f"{p}/tcp").rsplit(":", 1)[1]) + for p in (80, 443, 81)} + deadline = time.monotonic() + 120 + while True: + try: + status, _, body = request(ports[81], path="/api/") + if status == 200 and json.loads(body).get("status") == "OK": + break + except (OSError, ValueError, http.client.HTTPException): + pass + if time.monotonic() >= deadline: + raise AssertionError("Manager health endpoint did not become ready") + time.sleep(1) + docker("exec", name, "mkdir", "-p", "/data/custom_ssl/npm-999") + docker("exec", name, "openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", "/data/custom_ssl/npm-999/privkey.pem", + "-out", "/data/custom_ssl/npm-999/fullchain.pem", + "-days", "1", "-subj", "/CN=host-port.example.test", stderr=subprocess.DEVNULL) + backend = subprocess.Popen(["docker", "exec", name, "python3", "-S", "-c", BACKEND], + stdout=subprocess.DEVNULL) + subprocess.run(["docker", "exec", "-i", "-w", "/app", name, + "node", "--input-type=module"], input=RENDER, text=True, check=True) + docker("exec", name, "nginx", "-t") + docker("exec", name, "nginx", "-s", "reload") + time.sleep(1) + for tls, authorities in [ + (False, ["host-port.example.test", "host-port.example.test:80", + "host-port.example.test:232"]), + (True, ["host-port.example.test", "host-port.example.test:443", + "host-port.example.test:233", "HOST-PORT.EXAMPLE.TEST:233", + "[::1]:233"]), + ]: + scheme = "https" if tls else "http" + for authority in authorities: + for prefix in ("/", "/custom/"): + for method in ("GET", "POST"): + origin = f"{scheme}://{authority}" + referer = origin + "/settings" + path = prefix + "apply?one=1&two=2" + payload = "rule=example" if method == "POST" else None + try: + status, content_type, body = request( + ports[443 if tls else 80], tls, path, method, + {"Host": authority, "Origin": origin, "Referer": referer}, payload) + except (OSError, http.client.HTTPException) as error: + raise AssertionError((authority, path, method, str(error))) from error + assert status == 200 and content_type == "application/json", ( + authority, path, method, status, body) + assert json.loads(body) == {"host": authority, "origin": origin, + "referer": referer, "method": method, "path": path, + "body": payload or ""}, body + checked += 1 + # HTTP/1.0 permits no Host. An absolute target selects our generated host. + for prefix in ("/", "/custom/"): + with socket.create_connection(("127.0.0.1", ports[80]), timeout=5) as sock: + sock.sendall(f"GET http://host-port.example.test{prefix}fallback HTTP/1.0\r\n\r\n".encode()) + response = http.client.HTTPResponse(sock) + response.begin() + assert response.status == 200, response.status + assert json.loads(response.read())["host"] == "host-port.example.test" + checked += 1 + print(json.dumps({"image": image, "cases": checked, "nginx": "passed", + "default_and_custom_locations": "passed", "http_and_https": "passed", + "origin_and_referer_validation": "passed", "missing_host_fallback": "passed"}), + flush=True) + except Exception: + subprocess.run(["docker", "logs", "--tail", "30", name], check=False) + raise + finally: + subprocess.run(["docker", "rm", "-fv", name], stdout=subprocess.DEVNULL, check=False) + if backend is not None: + backend.wait(timeout=10) + + +if __name__ == "__main__": + check_image(sys.argv[1]) From 717877db3feea710eb45b3dddc7da342b9d61d17 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Fri, 9 Oct 2026 21:21:13 +1000 Subject: [PATCH 13/76] Fix flakey cypress test --- test/cypress/e2e/api/AccessListHostToggle.cy.js | 4 ++++ test/cypress/plugins/backendApi/client.mjs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/test/cypress/e2e/api/AccessListHostToggle.cy.js b/test/cypress/e2e/api/AccessListHostToggle.cy.js index b566a3a98..ee1bee0dc 100644 --- a/test/cypress/e2e/api/AccessListHostToggle.cy.js +++ b/test/cypress/e2e/api/AccessListHostToggle.cy.js @@ -192,7 +192,11 @@ describe('Access Lists across Proxy Host disable/enable', () => { expect(data).to.be.equal(true); }); + // Each delete signals an nginx reload and returns immediately. A request + // made while the workers are being swapped can be reset by the nginx + // serving the API (502 from squid), so let each reload settle first. [authListId, clientListId].forEach((id) => { + cy.wait(1500); cy.task('backendApiDelete', { token: token, path: `/api/nginx/access-lists/${id}`, diff --git a/test/cypress/plugins/backendApi/client.mjs b/test/cypress/plugins/backendApi/client.mjs index 78618abb2..5c0daa5c0 100644 --- a/test/cypress/plugins/backendApi/client.mjs +++ b/test/cypress/plugins/backendApi/client.mjs @@ -1,3 +1,4 @@ +import http from "node:http"; import axios from "axios"; import logger from "./logger.mjs"; @@ -8,6 +9,10 @@ const BackendApi = function (config, token) { this.axios = axios.create({ baseURL: config.baseUrl, timeout: 90000, + // Don't reuse sockets: a pooled keep-alive connection that squid or the + // backend has since closed fails with a 502 (ERR_READ_ERROR 104), which + // made tests flaky. + httpAgent: new http.Agent({ keepAlive: false }), }); }; From 06b02f77319cf3e0a536a389bc59b78ed11c5afa Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 11 Oct 2026 21:53:42 +0200 Subject: [PATCH 14/76] Add missing FR translations --- frontend/src/locale/src/fr.json | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json index 0911eedc3..cbdf3cd1b 100644 --- a/frontend/src/locale/src/fr.json +++ b/frontend/src/locale/src/fr.json @@ -71,6 +71,12 @@ "access-list.help.rules-order": { "defaultMessage": "Notez que les directives autoriser et refuser seront appliquées dans l'ordre où elles sont définies." }, + "access-list.inherit": { + "defaultMessage": "Hériter de l'hôte proxy" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "Utilise la liste d'accès de l'hôte proxy" + }, "access-list.pass-auth": { "defaultMessage": "Transmettre l'authentification au serveur en amont" }, @@ -101,6 +107,9 @@ "action.allow": { "defaultMessage": "Autoriser" }, + "action.clear": { + "defaultMessage": "Effacer" + }, "action.close": { "defaultMessage": "Fermer" }, @@ -122,6 +131,9 @@ "action.enable": { "defaultMessage": "Activer" }, + "action.logs": { + "defaultMessage": "Journaux" + }, "action.permissions": { "defaultMessage": "Permissions" }, @@ -266,6 +278,9 @@ "column.email": { "defaultMessage": "e-mail" }, + "column.error": { + "defaultMessage": "Erreurs" + }, "column.event": { "defaultMessage": "Évènement" }, @@ -461,6 +476,12 @@ "loading": { "defaultMessage": "Chargement…" }, + "location.advanced-config": { + "defaultMessage": "Configuration Nginx personnalisée" + }, + "location.filter": { + "defaultMessage": "Filtrer par chemin ou destination" + }, "login.2fa-code": { "defaultMessage": "Code de vérification" }, @@ -479,6 +500,51 @@ "login.title": { "defaultMessage": "Connectez-vous à votre compte" }, + "logs": { + "defaultMessage": "Journaux" + }, + "logs.channel.access": { + "defaultMessage": "Accès" + }, + "logs.channel.error": { + "defaultMessage": "Erreurs" + }, + "logs.download": { + "defaultMessage": "Télécharger" + }, + "logs.empty": { + "defaultMessage": "Aucune entrée pour le moment." + }, + "logs.level": { + "defaultMessage": "Niveau" + }, + "logs.level.all": { + "defaultMessage": "Tous les niveaux" + }, + "logs.lines": { + "defaultMessage": "Lignes" + }, + "logs.live": { + "defaultMessage": "En direct" + }, + "logs.paused": { + "defaultMessage": "En pause" + }, + "logs.refresh": { + "defaultMessage": "Actualiser" + }, + "logs.search-placeholder": { + "defaultMessage": "Rechercher dans les journaux…" + }, + "logs.source": { + "defaultMessage": "Source" + }, + "logs.source.system": { + "defaultMessage": "Système" + }, + "logs.truncated-warning": { + "defaultMessage": "Seule la partie la plus récente de ce fichier est affichée." + }, "nginx-config.label": { "defaultMessage": "Configuration Nginx personnalisée" }, From 41713715ca6da426049e54531f41e4e0c270f877 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 11 Oct 2026 22:55:39 +0200 Subject: [PATCH 15/76] Improve existing french translations --- frontend/src/locale/src/fr.json | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json index cbdf3cd1b..88add74bd 100644 --- a/frontend/src/locale/src/fr.json +++ b/frontend/src/locale/src/fr.json @@ -1,6 +1,6 @@ { "2fa.backup-codes-remaining": { - "defaultMessage": "Codes de secours restants : {count}" + "defaultMessage": "Codes de secours restants : {count}" }, "2fa.backup-warning": { "defaultMessage": "Sauvegardez ces codes de secours dans un endroit sûr. Chaque code ne peut être utilisé qu'une seule fois." @@ -90,10 +90,10 @@ "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" }, "access-list.satisfy-any": { - "defaultMessage": "Valide n'importe quelle règle" + "defaultMessage": "Satisfaire au moins une condition" }, "access-list.subtitle": { - "defaultMessage": "{utilisateurs} {utilisateurs, plural, one {Utilisateur} other {Utilisateurs}}, {règles} {règles, plural, one {Règle} other {Règles}} - Crée : {date}" + "defaultMessage": "{users} {users, plural, one {Utilisateur} other {Utilisateurs}}, {rules} {rules, plural, one {Règle} other {Règles}} - Créée le {date}" }, "access-lists": { "defaultMessage": "Listes d'accès" @@ -102,7 +102,7 @@ "defaultMessage": "Ajouter" }, "action.add-location": { - "defaultMessage": "Ajouter localisation" + "defaultMessage": "Ajouter un emplacement" }, "action.allow": { "defaultMessage": "Autoriser" @@ -141,7 +141,7 @@ "defaultMessage": "Renouveler" }, "action.view-details": { - "defaultMessage": "Voir les Détails" + "defaultMessage": "Voir les détails" }, "auditlogs": { "defaultMessage": "Journaux d'audit" @@ -159,7 +159,7 @@ "defaultMessage": "Certificat" }, "certificate.custom-certificate-key": { - "defaultMessage": "Clé du Certificat" + "defaultMessage": "Clé du certificat" }, "certificate.custom-intermediate": { "defaultMessage": "Certificat intermédiaire" @@ -180,7 +180,7 @@ "defaultMessage": "Non utilisé" }, "certificate.renew": { - "defaultMessage": "Renouveler Certificat" + "defaultMessage": "Renouveler le certificat" }, "certificates": { "defaultMessage": "Certificats" @@ -201,7 +201,7 @@ "defaultMessage": "Ces données seront stockées en clair dans la base de données et dans un fichier !" }, "certificates.dns.propagation-seconds": { - "defaultMessage": "Propagation Seconds" + "defaultMessage": "Délai de propagation (secondes)" }, "certificates.dns.propagation-seconds-note": { "defaultMessage": "Laisser vide pour utiliser la valeur par défaut du plugin. Nombre de secondes à attendre pour la propagation DNS." @@ -228,10 +228,10 @@ "defaultMessage": "Votre serveur est accessible et la création de certificats devrait être possible." }, "certificates.http.reachability-other": { - "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé un code d'état inattendu {code}. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." + "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé un code d'état inattendu {code}. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." }, "certificates.http.reachability-wrong-data": { - "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé des données inattendues. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." + "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé des données inattendues. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." }, "certificates.http.test-results": { "defaultMessage": "Résultats du test" @@ -267,7 +267,7 @@ "defaultMessage": "Autorisations" }, "column.custom-locations": { - "defaultMessage": "Emplacement personnalisé" + "defaultMessage": "Emplacements personnalisés" }, "column.destination": { "defaultMessage": "Destination" @@ -309,13 +309,13 @@ "defaultMessage": "Règles" }, "column.satisfy": { - "defaultMessage": "Valide" + "defaultMessage": "Conditions requises" }, "column.satisfy-all": { - "defaultMessage": "All" + "defaultMessage": "Toutes" }, "column.satisfy-any": { - "defaultMessage": "Any" + "defaultMessage": "Au moins une" }, "column.scheme": { "defaultMessage": "Schéma" @@ -330,7 +330,7 @@ "defaultMessage": "Statut" }, "created-on": { - "defaultMessage": "Créé : {date}" + "defaultMessage": "Création : {date}" }, "dashboard": { "defaultMessage": "Tableau de bord" @@ -357,10 +357,10 @@ "defaultMessage": "Commencez à écrire pour ajouter un domaine…" }, "domain-names.wildcards-not-permitted": { - "defaultMessage": "Les Wildcards ne sont pas permises dans ce cas" + "defaultMessage": "Les wildcards ne sont pas permises dans ce cas" }, "domain-names.wildcards-not-supported": { - "defaultMessage": "Les Wildcards ne sont pas prises en charge par cette autorité de certification." + "defaultMessage": "Les wildcards ne sont pas prises en charge par cette autorité de certification." }, "domains.advanced": { "defaultMessage": "Avancé" @@ -390,7 +390,7 @@ "defaultMessage": "Aucun résultat trouvé" }, "empty-subtitle": { - "defaultMessage": "Pourquoi n'en créez-vous pas un ?" + "defaultMessage": "Pourquoi n'en créez-vous pas un ?" }, "enabled": { "defaultMessage": "Activé" @@ -405,7 +405,7 @@ "defaultMessage": "Adresse e-mail ou mot de passe invalide" }, "error.invalid-domain": { - "defaultMessage": "Domaine invalide : {domain}" + "defaultMessage": "Domaine invalide : {domain}" }, "error.invalid-email": { "defaultMessage": "Adresse e-mail invalide" @@ -432,16 +432,16 @@ "defaultMessage": "Ceci est obligatoire" }, "expires.on": { - "defaultMessage": "Expire : {date}" + "defaultMessage": "Expire le {date}" }, "footer.github-fork": { - "defaultMessage": "Forkez-moi sur Github" + "defaultMessage": "Forkez-moi sur GitHub" }, "host.flags.block-exploits": { "defaultMessage": "Bloquer les exploits courants" }, "host.flags.cache-assets": { - "defaultMessage": "Ressources du cache" + "defaultMessage": "Mise en cache des ressources" }, "host.flags.preserve-path": { "defaultMessage": "Préserver le chemin" @@ -450,10 +450,10 @@ "defaultMessage": "Protocoles" }, "host.flags.websockets-upgrade": { - "defaultMessage": "Prise en charge de Websockets" + "defaultMessage": "Prise en charge de WebSockets" }, "host.forward-port": { - "defaultMessage": "Port de redirection" + "defaultMessage": "Port de destination" }, "host.forward-scheme": { "defaultMessage": "Schéma" @@ -549,7 +549,7 @@ "defaultMessage": "Configuration Nginx personnalisée" }, "nginx-config.placeholder": { - "defaultMessage": "# Mettez ici votre configuration Nginx personnalisé à vos risques et périls !" + "defaultMessage": "# Mettez ici votre configuration Nginx personnalisée à vos risques et périls !" }, "no-permission-error": { "defaultMessage": "Vous n'avez pas la permission de voir ce contenu." @@ -663,7 +663,7 @@ "defaultMessage": "Hôte proxy" }, "proxy-host.forward-host": { - "defaultMessage": "Nom d'hôte de redirection / IP" + "defaultMessage": "Nom d'hôte / IP de destination" }, "proxy-hosts": { "defaultMessage": "Hôtes proxy" @@ -672,7 +672,7 @@ "defaultMessage": "{count} {count, plural, one {Hôte proxy} other {Hôtes proxy}}" }, "public": { - "defaultMessage": "Publique" + "defaultMessage": "Public" }, "redirection-host": { "defaultMessage": "Hôte de redirection" @@ -735,7 +735,7 @@ "defaultMessage": "Page de félicitations" }, "settings.default-site.description": { - "defaultMessage": "ce qu'il faut afficher lorsqu'un hôte inconnu est détecté par Nginx" + "defaultMessage": "Ce qu'il faut afficher lorsqu'un hôte inconnu est détecté par Nginx" }, "settings.default-site.html": { "defaultMessage": "HTML personnalisé" @@ -762,7 +762,7 @@ "defaultMessage": "Stream" }, "stream.forward-host": { - "defaultMessage": "Hôte destinataire" + "defaultMessage": "Hôte de destination" }, "stream.forward-host.placeholder": { "defaultMessage": "example.com ou 10.0.0.1 ou 2001:db8:3333:4444:5555:6666:7777:8888" @@ -786,7 +786,7 @@ "defaultMessage": "Test" }, "update-available": { - "defaultMessage": "Mise à jour disponible : {latestVersion}" + "defaultMessage": "Mise à jour disponible : {latestVersion}" }, "user": { "defaultMessage": "Utilisateur" @@ -825,10 +825,10 @@ "defaultMessage": "Définir les autorisations pour {name}" }, "user.switch-dark": { - "defaultMessage": "Passer au mode Sombre" + "defaultMessage": "Passer au mode sombre" }, "user.switch-light": { - "defaultMessage": "Passer au mode Lumineux" + "defaultMessage": "Passer au mode clair" }, "user.two-factor": { "defaultMessage": "Auth. à deux facteurs" From 9972e78dc8e75374f4e36a60ff26df6dc6a8c7f5 Mon Sep 17 00:00:00 2001 From: Hafiz Ahmed Zia <246515321+hafizahmedzia@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:36:32 +0500 Subject: [PATCH 16/76] fix: preserve hostname keys in HTTP certificate test results Preserve raw response keys for the HTTP certificate test endpoint so hostname punctuation and distinct result entries survive normalization. Add API and mounted-modal regression coverage for the exact candidate reviewed against develop 56ab784117abb89dc7b81e39d4e7e23e33989084. Related: NginxProxyManager/nginx-proxy-manager#5849 AI assistance: Codex assisted with the implementation, tests, independent review and contribution text. --- frontend/src/api/backend/base.ts | 9 +- .../api/backend/testHttpCertificate.test.ts | 92 +++++++++++++++++++ .../src/api/backend/testHttpCertificate.ts | 1 + .../src/modals/HTTPCertificateModal.test.tsx | 57 ++++++++++++ 4 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 frontend/src/api/backend/testHttpCertificate.test.ts create mode 100644 frontend/src/modals/HTTPCertificateModal.test.tsx diff --git a/frontend/src/api/backend/base.ts b/frontend/src/api/backend/base.ts index d56f120c7..e91376d44 100644 --- a/frontend/src/api/backend/base.ts +++ b/frontend/src/api/backend/base.ts @@ -46,7 +46,7 @@ function buildBody(data?: Record): string | undefined { } } -async function processResponse(response: Response) { +async function processResponse(response: Response, preserveResponseKeys = false) { const payload = await response.json(); if (!response.ok) { if (response.status === 401) { @@ -59,7 +59,7 @@ async function processResponse(response: Response) { typeof payload.error.messageI18n !== "undefined" ? payload.error.messageI18n : payload.error.message, ); } - return camelizeKeys(payload) as any; + return (preserveResponseKeys ? payload : camelizeKeys(payload)) as any; } interface GetArgs { @@ -97,9 +97,10 @@ interface PostArgs { params?: queryString.StringifiableRecord; data?: any; noAuth?: boolean; + preserveResponseKeys?: boolean; } -export async function post({ url, params, data, noAuth }: PostArgs, abortController?: AbortController) { +export async function post({ url, params, data, noAuth, preserveResponseKeys }: PostArgs, abortController?: AbortController) { const apiUrl = buildUrl({ url, params }); const method = "POST"; @@ -126,7 +127,7 @@ export async function post({ url, params, data, noAuth }: PostArgs, abortControl const signal = abortController?.signal; const response = await fetch(apiUrl, { method, headers, body, signal }); - return processResponse(response); + return processResponse(response, preserveResponseKeys); } interface PutArgs { diff --git a/frontend/src/api/backend/testHttpCertificate.test.ts b/frontend/src/api/backend/testHttpCertificate.test.ts new file mode 100644 index 000000000..ecbe1dbed --- /dev/null +++ b/frontend/src/api/backend/testHttpCertificate.test.ts @@ -0,0 +1,92 @@ +import AuthStore from "src/modules/AuthStore"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import * as api from "./base"; +import { testHttpCertificate } from "./testHttpCertificate"; + +const fetchMock = vi.fn(); +const reply = (payload: unknown, status = 200) => { + fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(payload), { status })); +}; +beforeEach(() => { + localStorage.clear(); + vi.stubGlobal("fetch", fetchMock); +}); +afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + fetchMock.mockReset(); +}); +describe("certificate HTTP result keys", () => { + it.each([ + "test-domain.example", + "test--domain.example", + "xn--bcher-kva.example", + "one.two-three.example", + "_acme-challenge.example", + "Test-Domain.example", + "plain.example", + ])("preserves the exact response key %s", async (domain) => { + const result = { [domain]: "wrong-data" }; + reply(result); + expect(await testHttpCertificate([domain])).toEqual(result); + }); + it("keeps colliding spellings and their distinct statuses", async () => { + const result = { "test-domain.example": "ok", "test--domain.example": "no-host" }; + reply(result); + expect(await testHttpCertificate(Object.keys(result))).toEqual(result); + }); + it("sends unchanged domains with the normal request headers", async () => { + const domains = ["test-domain.example", "test--domain.example"]; + reply({}); + await testHttpCertificate(domains); + expect(fetchMock).toHaveBeenCalledWith("/api/nginx/certificates/test-http", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ domains }), + signal: undefined, + }); + }); + it("preserves an empty result", async () => { + reply({}); + expect(await testHttpCertificate([])).toEqual({}); + }); + it.each([ + [400, { message: "invalid domain" }, "invalid domain"], + [500, { message: "fallback", messageI18n: "certificate-error" }, "certificate-error"], + ])("preserves error handling for status %s", async (status, error, message) => { + reply({ error }, status); + await expect(testHttpCertificate(["test-domain.example"])).rejects.toThrow(message); + }); + it("still clears authentication on 401", async () => { + const clear = vi.spyOn(AuthStore, "clear"); + const reload = vi.spyOn(window.location, "reload").mockImplementation(() => {}); + reply({ error: { message: "unauthorized" } }, 401); + await expect(testHttpCertificate(["test-domain.example"])).rejects.toThrow("unauthorized"); + expect(clear).toHaveBeenCalledOnce(); + expect(reload).toHaveBeenCalledOnce(); + }); + it.each([ + ["get", api.get], + ["post", api.post], + ["put", api.put], + ["del", api.del], + ] as const)("keeps default %s response normalization", async (_name, method) => { + reply({ domain_names: ["test-domain.example"], nested_value: { created_on: "today" } }); + expect(await method({ url: "/control" })).toEqual({ + domainNames: ["test-domain.example"], + nestedValue: { createdOn: "today" }, + }); + }); + it("preserves POST abort forwarding and request normalization", async () => { + const controller = new AbortController(); + reply({ domain_names: [] }); + await api.post({ url: "/control", data: { domainNames: ["a-b.example"] } }, controller); + expect(fetchMock).toHaveBeenCalledWith( + "/api/control", + expect.objectContaining({ + body: JSON.stringify({ domain_names: ["a-b.example"] }), + signal: controller.signal, + }), + ); + }); +}); diff --git a/frontend/src/api/backend/testHttpCertificate.ts b/frontend/src/api/backend/testHttpCertificate.ts index 23dfa4b7d..9687d55af 100644 --- a/frontend/src/api/backend/testHttpCertificate.ts +++ b/frontend/src/api/backend/testHttpCertificate.ts @@ -3,6 +3,7 @@ import * as api from "./base"; export async function testHttpCertificate(domains: string[]): Promise> { return await api.post({ url: "/nginx/certificates/test-http", + preserveResponseKeys: true, data: { domains, }, diff --git a/frontend/src/modals/HTTPCertificateModal.test.tsx b/frontend/src/modals/HTTPCertificateModal.test.tsx new file mode 100644 index 000000000..65d1aaed0 --- /dev/null +++ b/frontend/src/modals/HTTPCertificateModal.test.tsx @@ -0,0 +1,57 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import EasyModal from "ez-modal-react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { showHTTPCertificateModal } from "./HTTPCertificateModal"; + +const fetchMock = vi.fn(); +beforeEach(() => { + localStorage.clear(); + vi.stubGlobal("fetch", fetchMock); +}); +afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + fetchMock.mockReset(); +}); +async function openAndTest(domains: string[], payload: Record) { + fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(payload), { status: 200 })); + render( + + + , + ); + await act(async () => { + showHTTPCertificateModal(); + }); + const input = await screen.findByRole("combobox", { expanded: false }); + for (const domain of domains) { + fireEvent.change(input, { target: { value: domain } }); + fireEvent.keyDown(input, { key: "Enter", code: "Enter", keyCode: 13 }); + await waitFor(() => expect(input).toHaveValue("")); + } + fireEvent.click(screen.getByRole("button", { name: "Test" })); + await waitFor(() => expect(fetchMock).toHaveBeenCalledOnce()); + expect(JSON.parse(String(fetchMock.mock.calls[0][1]?.body))).toEqual({ domains }); +} +describe("mounted HTTP certificate modal", () => { + it("renders a hyphenated result without changing its label", async () => { + await openAndTest(["test-domain.example"], { "test-domain.example": "wrong-data" }); + expect(await screen.findByText("test-domain.example:")).toBeVisible(); + expect(screen.queryByText("testDomain.example:")).not.toBeInTheDocument(); + }); + it("renders both collision-prone domains with distinct results", async () => { + await openAndTest(["test-domain.example", "test--domain.example"], { + "test-domain.example": "ok", + "test--domain.example": "no-host", + }); + const first = await screen.findByText("test-domain.example:"); + const second = await screen.findByText("test--domain.example:"); + expect( + first.parentElement?.querySelector('[data-translation-id="certificates.http.reachability-ok"]'), + ).not.toBeNull(); + expect( + second.parentElement?.querySelector('[data-translation-id="certificates.http.reachability-not-resolved"]'), + ).not.toBeNull(); + }); +}); From 8c5b4d8ffd44901096f592e125a58ab39c2c4b90 Mon Sep 17 00:00:00 2001 From: jeezat Date: Tue, 13 Oct 2026 07:57:28 +0200 Subject: [PATCH 17/76] fix(certbot): update Akamai Edge DNS plugin and credentials for v0.3.0 --- backend/certbot/dns-plugins.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index 4467a27af..f22da5af7 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -224,12 +224,12 @@ "version": "~=0.1.2" }, "edgedns": { - "credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf", - "dependencies": "", - "full_plugin_name": "edgedns", - "name": "Akamai Edge DNS", - "package_name": "certbot-plugin-edgedns", - "version": "~=0.1.0" + "credentials": "client_token = YOUR_CLIENT_TOKEN\naccess_token = YOUR_ACCESS_TOKEN\nclient_secret = YOUR_CLIENT_SECRET\nhost = YOUR_HOST.luna.akamaiapis.net\n# Optional: account_key = YOUR_ACCOUNT_KEY", + "dependencies": "", + "full_plugin_name": "edgedns", + "name": "Akamai Edge DNS", + "package_name": "certbot-plugin-edgedns", + "version": "==0.3.0" }, "edgeone": { "credentials": "dns_edgeone_secret_id = YOUR_TENCENTCLOUD_SECRET_ID\ndns_edgeone_secret_key = YOUR_TENCENTCLOUD_SECRET_KEY", From 82529d6402b63090592bf34ddc760e9d328c741c Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Tue, 13 Oct 2026 18:34:20 +0200 Subject: [PATCH 18/76] Require certificates:create permission to validate certificates --- backend/internal/certificate.js | 8 +++++--- backend/routes/nginx/certificates.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 9a14f5893..9f81b764d 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -545,13 +545,15 @@ const internalCertificate = { /** * Validates that the certs provided are good. - * No access required here, nothing is changed or stored. + * Nothing is changed or stored, but the files go through openssl, so it needs the same access as creating one. * + * @param {Access} access * @param {Object} data * @param {Object} data.files * @returns {Promise} */ - validate: (data) => { + validate: async (access, data) => { + await access.can("certificates:create"); // Put file contents into an object const files = {}; _.map(data.files, (file, name) => { @@ -600,7 +602,7 @@ const internalCertificate = { throw new error.ValidationError("Cannot upload certificates for this type of provider"); } - const validations = await internalCertificate.validate(data); + const validations = await internalCertificate.validate(access, data); if (typeof validations.certificate === "undefined") { throw new error.ValidationError("Certificate file was not provided"); } diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js index dea809423..90f279c37 100644 --- a/backend/routes/nginx/certificates.js +++ b/backend/routes/nginx/certificates.js @@ -161,7 +161,7 @@ router } try { - const result = await internalCertificate.validate({ + const result = await internalCertificate.validate(res.locals.access, { files: req.files, }); res.status(200).send(result); From 6acf34626214e59dfca8ac546d8c00af32111d81 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Tue, 13 Oct 2026 18:35:50 +0200 Subject: [PATCH 19/76] Add test for unauthenticated certificate validation --- test/cypress/e2e/api/Certificates.cy.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 7d75d40cf..80fc9f395 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -35,6 +35,19 @@ describe('Certificates endpoints', () => { }); }); + it('Unauthenticated user cannot validate custom certificate', () => { + cy.task('backendApiPostFiles', { + path: '/api/nginx/certificates/validate', + files: { + certificate: certFile, + certificate_key: keyFile, + }, + returnOnError: true, + }).then((data) => { + expect(data.error.code).to.equal(403); + }); + }); + it('Custom certificate lifecycle', () => { // Create custom cert cy.task('backendApiPost', { From f1bd1574380f3e749dcfcd539c429bcbc6d57292 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Tue, 13 Oct 2026 19:32:53 +0200 Subject: [PATCH 20/76] Add test for certificate validation with view-only permission --- test/cypress/e2e/api/Certificates.cy.js | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 80fc9f395..cc197bc36 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -48,6 +48,50 @@ describe('Certificates endpoints', () => { }); }); + it('User with certificates view permission cannot validate custom certificate', () => { + const viewer = { + email: 'viewer@example.com', + secret: 'changeme', + }; + + cy.task('backendApiPost', { + token: token, + path: '/api/users', + data: { + name: 'Certificate Viewer', + nickname: 'Viewer', + email: viewer.email, + roles: ['user'], + auth: { + type: 'password', + secret: viewer.secret, + }, + }, + }).then((user) => { + cy.task('backendApiPut', { + token: token, + path: `/api/users/${user.id}/permissions`, + data: { + certificates: 'view', + }, + }); + + cy.getToken(null, {identity: viewer.email, secret: viewer.secret}).then((viewerToken) => { + cy.task('backendApiPostFiles', { + token: viewerToken, + path: '/api/nginx/certificates/validate', + files: { + certificate: certFile, + certificate_key: keyFile, + }, + returnOnError: true, + }).then((data) => { + expect(data.error.code).to.equal(403); + }); + }); + }); + }); + it('Custom certificate lifecycle', () => { // Create custom cert cy.task('backendApiPost', { From 3a455595e7ac80d7346b93438b4e1ff89e3d0fe1 Mon Sep 17 00:00:00 2001 From: lennondotw Date: Wed, 14 Oct 2026 14:48:22 +0800 Subject: [PATCH 21/76] fix(proxy): isolate cached assets by forwarded authority --- .../etc/nginx/conf.d/include/assets.conf | 2 +- test/README.md | 11 ++ test/host-header-smoke.py | 140 +++++++++++++----- 3 files changed, 119 insertions(+), 34 deletions(-) diff --git a/docker/rootfs/etc/nginx/conf.d/include/assets.conf b/docker/rootfs/etc/nginx/conf.d/include/assets.conf index 5a90beb8a..9157d172d 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/assets.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/assets.conf @@ -3,7 +3,7 @@ location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map| # use the public cache proxy_cache public-cache; - proxy_cache_key $host$request_uri; + proxy_cache_key $forward_host$request_uri; # ignore these headers for media proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires; diff --git a/test/README.md b/test/README.md index 57b9eb36a..20be040e7 100644 --- a/test/README.md +++ b/test/README.md @@ -56,3 +56,14 @@ The ESLint extension only works on JavaScript files by default, so add the follo "editor.formatOnSave": false, }, ``` + +## Forwarded Host image test + +Run `python3 test/host-header-smoke.py ` from the repository root. +The test waits for the manager, generated HTTP/HTTPS proxy host, both location +paths and its synthetic upstream. It verifies Host/Origin/Referer consistency, +request bodies and HTTP/1.0 fallback, then uses a dynamic upstream counter to +prove cache hits and isolation between client authorities on ports 232 and 233. +Requests run through `docker exec`, so a remote Docker daemon or containerized +CI agent needs no shared loopback interface or published ports. The CI image +build script invokes this test against the image it just built. diff --git a/test/host-header-smoke.py b/test/host-header-smoke.py index a823b8977..a1397d878 100644 --- a/test/host-header-smoke.py +++ b/test/host-header-smoke.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 """Check forwarded Host headers against a real upstream in a disposable image.""" -import http.client +import base64 import json -import socket -import ssl import subprocess import sys import time @@ -16,7 +14,11 @@ import http.server, json, ssl from urllib.parse import urlsplit class Backend(http.server.BaseHTTPRequestHandler): + requests = 0 + def do_GET(self): + Backend.requests += 1 + request_number = Backend.requests host = self.headers.get("Host") origin = self.headers.get("Origin") referer = self.headers.get("Referer") @@ -28,10 +30,13 @@ class Backend(http.server.BaseHTTPRequestHandler): self.wfile.write(b"") else: self.send_header("Content-Type", "application/json") + self.send_header("X-Upstream-Request", str(request_number)) self.end_headers() - self.wfile.write(json.dumps({"host": host, "origin": origin, "referer": referer, - "method": self.command, "path": self.path, - "body": body.decode()}).encode()) + response = {"host": host, "origin": origin, "referer": referer, + "method": self.command, "path": self.path, "body": body.decode()} + if self.path.endswith(".js"): + response["request_number"] = request_number + self.wfile.write(json.dumps(response).encode()) do_POST = do_GET @@ -52,9 +57,9 @@ import nginx from "./internal/nginx.js"; const host = { id: 999, enabled: true, domain_names: ["host-port.example.test", "[::1]"], forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443, - certificate_id: 999, certificate: {provider: "other"}, access_list_id: 0, + certificate_id: 999, certificate: {id: 999, provider: "other"}, access_list_id: 0, ssl_forced: false, hsts_enabled: false, http2_support: false, - block_exploits: false, caching_enabled: false, allow_websocket_upgrade: false, + block_exploits: false, caching_enabled: true, allow_websocket_upgrade: false, advanced_config: "", locations: [{path: "/custom/", advanced_config: "", forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443}] }; @@ -67,18 +72,69 @@ def docker(*args, **kwargs): return subprocess.check_output(["docker", *args], text=True, **kwargs).strip() -def request(port, tls=False, path="/", method="GET", headers=None, body=None): - connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5) - try: - if tls: +# Keep all traffic on the tested container's loopback interface so this also +# works when the CI agent talks to a Docker daemon on another host. +REQUEST = r''' +import base64, http.client, json, socket, ssl, sys +args = json.load(sys.stdin) +connection = None +sock = None +try: + if args["http10"]: + sock = socket.create_connection(("127.0.0.1", 80), timeout=5) + target = "http://host-port.example.test" + args["path"] + sock.sendall(f"GET {target} HTTP/1.0\r\n\r\n".encode()) + response = http.client.HTTPResponse(sock) + response.begin() + else: + port = 443 if args["tls"] else 80 + if args["admin"]: + port = 81 + connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5) + if args["tls"]: connection.sock = ssl._create_unverified_context().wrap_socket( socket.create_connection(("127.0.0.1", port), timeout=5), server_hostname="host-port.example.test") - connection.request(method, path, headers=headers or {}, body=body) + connection.request(args["method"], args["path"], headers=args["headers"], body=args["body"]) response = connection.getresponse() - return response.status, response.getheader("Content-Type"), response.read() - finally: + print(json.dumps({"status": response.status, "headers": dict(response.getheaders()), + "body": base64.b64encode(response.read()).decode()})) +finally: + if connection is not None: connection.close() + if sock is not None: + sock.close() +''' + + +def request(container, tls=False, path="/", method="GET", headers=None, body=None, admin=False, http10=False): + payload = {"tls": tls, "path": path, "method": method, "headers": headers or {}, + "body": body, "admin": admin, "http10": http10} + try: + result = json.loads(docker("exec", "-i", container, "python3", "-S", "-c", REQUEST, + input=json.dumps(payload), stderr=subprocess.DEVNULL, timeout=15)) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as error: + raise OSError("Request inside the tested container failed") from error + return result["status"], result["headers"], base64.b64decode(result["body"]) + + +def wait_for_generated_host(container): + deadline = time.monotonic() + 60 + while True: + try: + for tls in (False, True): + for prefix in ("/", "/custom/"): + path = prefix + "readiness" + status, headers, body = request(container, tls, path, + headers={"Host": "host-port.example.test:233"}) + data = json.loads(body) + assert status == 200 and headers.get("Content-Type") == "application/json" + assert data["host"] == "host-port.example.test:233" and data["path"] == path + return + except (OSError, ValueError, AssertionError, KeyError): + if time.monotonic() >= deadline: + raise AssertionError("Generated HTTP/HTTPS host and upstream did not become ready") + time.sleep(0.5) def check_image(image): @@ -87,17 +143,14 @@ def check_image(image): checked = 0 try: docker("run", "-d", "--name", name, "-e", "IP_RANGES_FETCH_ENABLED=false", - "-p", "127.0.0.1::80", "-p", "127.0.0.1::443", "-p", "127.0.0.1::81", "--mount", "type=volume,destination=/etc/letsencrypt", image) - ports = {p: int(docker("port", name, f"{p}/tcp").rsplit(":", 1)[1]) - for p in (80, 443, 81)} deadline = time.monotonic() + 120 while True: try: - status, _, body = request(ports[81], path="/api/") + status, _, body = request(name, path="/api/", admin=True) if status == 200 and json.loads(body).get("status") == "OK": break - except (OSError, ValueError, http.client.HTTPException): + except (OSError, ValueError): pass if time.monotonic() >= deadline: raise AssertionError("Manager health endpoint did not become ready") @@ -113,7 +166,7 @@ def check_image(image): "node", "--input-type=module"], input=RENDER, text=True, check=True) docker("exec", name, "nginx", "-t") docker("exec", name, "nginx", "-s", "reload") - time.sleep(1) + wait_for_generated_host(name) for tls, authorities in [ (False, ["host-port.example.test", "host-port.example.test:80", "host-port.example.test:232"]), @@ -130,12 +183,12 @@ def check_image(image): path = prefix + "apply?one=1&two=2" payload = "rule=example" if method == "POST" else None try: - status, content_type, body = request( - ports[443 if tls else 80], tls, path, method, + status, headers, body = request( + name, tls, path, method, {"Host": authority, "Origin": origin, "Referer": referer}, payload) - except (OSError, http.client.HTTPException) as error: + except OSError as error: raise AssertionError((authority, path, method, str(error))) from error - assert status == 200 and content_type == "application/json", ( + assert status == 200 and headers.get("Content-Type") == "application/json", ( authority, path, method, status, body) assert json.loads(body) == {"host": authority, "origin": origin, "referer": referer, "method": method, "path": path, @@ -143,16 +196,37 @@ def check_image(image): checked += 1 # HTTP/1.0 permits no Host. An absolute target selects our generated host. for prefix in ("/", "/custom/"): - with socket.create_connection(("127.0.0.1", ports[80]), timeout=5) as sock: - sock.sendall(f"GET http://host-port.example.test{prefix}fallback HTTP/1.0\r\n\r\n".encode()) - response = http.client.HTTPResponse(sock) - response.begin() - assert response.status == 200, response.status - assert json.loads(response.read())["host"] == "host-port.example.test" - checked += 1 + status, _, body = request(name, path=prefix + "fallback", http10=True) + assert status == 200, status + assert json.loads(body)["host"] == "host-port.example.test" + checked += 1 + + cache_cases = 0 + for prefix in ("/", "/custom/"): + path = prefix + f"cache-{uuid.uuid4().hex}.js" + cached = {} + authorities = [(False, "host-port.example.test:232"), (True, "host-port.example.test:233")] + for _ in range(2): + for tls, authority in authorities: + origin = f"{'https' if tls else 'http'}://{authority}" + status, headers, body = request(name, tls, path, + headers={"Host": authority, "Origin": origin, "Referer": origin + "/settings"}) + data = json.loads(body) + assert status == 200 and data["host"] == authority, ("cache", prefix, authority, status, data) + serial = headers.get("X-Upstream-Request") + assert serial and str(data["request_number"]) == serial, (headers, data) + if authority in cached: + # A dynamic upstream counter must not change on a real cache hit. + assert (serial, body) == cached[authority], ("cache hit", authority, cached, serial, body) + else: + cached[authority] = (serial, body) + cache_cases += 1 + assert len({serial for serial, _ in cached.values()}) == 2, cached + checked += cache_cases print(json.dumps({"image": image, "cases": checked, "nginx": "passed", "default_and_custom_locations": "passed", "http_and_https": "passed", - "origin_and_referer_validation": "passed", "missing_host_fallback": "passed"}), + "origin_and_referer_validation": "passed", "missing_host_fallback": "passed", "cached_asset_cases": cache_cases, + "cache_port_isolation_and_hits": "passed", "generated_host_readiness": "passed"}), flush=True) except Exception: subprocess.run(["docker", "logs", "--tail", "30", name], check=False) From b7d1e99e8c4485b0b6d0642bfc5c44a5842a6555 Mon Sep 17 00:00:00 2001 From: CnpPt Date: Sat, 17 Oct 2026 11:50:38 +0800 Subject: [PATCH 22/76] Reload Nginx after renewing SSL certificate --- backend/internal/certificate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 9f81b764d..13ddd6af4 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -916,6 +916,7 @@ const internalCertificate = { : internalCertificate.renewLetsEncryptSsl; await renewMethod(certificate); + await internalNginx.reload(); const certInfo = await internalCertificate.getCertificateInfoFromFile( `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, ); From a57ffb0ea95167d7a077d165f837140da29ec7b3 Mon Sep 17 00:00:00 2001 From: CnpPt Date: Sat, 17 Oct 2026 12:20:45 +0800 Subject: [PATCH 23/76] Update certificate.js --- backend/internal/certificate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 13ddd6af4..814612be1 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -916,7 +916,6 @@ const internalCertificate = { : internalCertificate.renewLetsEncryptSsl; await renewMethod(certificate); - await internalNginx.reload(); const certInfo = await internalCertificate.getCertificateInfoFromFile( `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, ); @@ -933,6 +932,8 @@ const internalCertificate = { meta: updatedCertificate, }); + await internalNginx.reload(); + return updatedCertificate; } From b6e9038f0c801bce9848f514d979eeff31f9e481 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 18 Oct 2026 19:42:35 +0200 Subject: [PATCH 24/76] Update infomaniak plugin to 0.3.3 --- backend/certbot/dns-plugins.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index f22da5af7..3c51eea9c 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -381,7 +381,7 @@ "full_plugin_name": "dns-infomaniak", "name": "Infomaniak", "package_name": "certbot-dns-infomaniak", - "version": "~=0.2.2" + "version": "~=0.3.3" }, "inwx": { "credentials": "dns_inwx_url = https://api.domrobot.com/xmlrpc/\ndns_inwx_username = your_username\ndns_inwx_password = your_password\ndns_inwx_shared_secret = your_shared_secret optional", From d9c4d33a7c4039ff8f14379cc57c69ecaa643976 Mon Sep 17 00:00:00 2001 From: Lindsay Roberts Date: Sun, 18 Oct 2026 21:19:53 +0300 Subject: [PATCH 25/76] fix(certbot): update Hetzner plugin to v4.0.0 --- backend/certbot/dns-plugins.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index f22da5af7..4c70c8856 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -333,7 +333,7 @@ "full_plugin_name": "dns-hetzner", "name": "Hetzner", "package_name": "certbot-dns-hetzner", - "version": "~=1.0.4" + "version": "~=4.0.0" }, "hetzner-cloud": { "credentials": "dns_hetzner_cloud_api_token = your_api_token_here", From 518dd04679d617d68e8b340f605c346aa3a24a12 Mon Sep 17 00:00:00 2001 From: capitain-schos Date: Sat, 24 Oct 2026 14:15:25 +0200 Subject: [PATCH 26/76] added ipp certbot definitions --- backend/certbot/dns-plugins.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index f22da5af7..873256368 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -399,6 +399,14 @@ "package_name": "certbot-dns-ionos", "version": "==2022.11.24" }, + "ip-projects": { + "credentials": "dns_ipp_acme_api_key = ", + "dependencies": "", + "full_plugin_name": "dns-ipp", + "name": "ip-projects.de", + "package_name": "certbot-dns-ipp", + "version": "~=1.0.0" + }, "ispconfig": { "credentials": "dns_ispconfig_username = myremoteuser\ndns_ispconfig_password = verysecureremoteuserpassword\ndns_ispconfig_endpoint = https://localhost:8080", "dependencies": "", From 076c522ccc652139aca2630731487506e8fe16ce Mon Sep 17 00:00:00 2001 From: Juice-de-Orange <200407033+Juice-de-Orange@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:33:59 +0200 Subject: [PATCH 27/76] fix: apply the HSTS sub-domains setting per host _hsts_map.conf defined $hsts_header with the host's own value in every host config. A map is global to nginx, so one definition won for all hosts and the "HSTS Sub-domains" switch of the others was ignored. Define two fixed maps instead and let _hsts.conf pick one per host. They get new names, so configs generated before this change, which still define $hsts_header, can't override them. The maps keep HSTS limited to https. Fixes #5947 --- backend/templates/_hsts.conf | 6 +++++- backend/templates/_hsts_map.conf | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/templates/_hsts.conf b/backend/templates/_hsts.conf index 26c83ee8b..0e9829009 100644 --- a/backend/templates/_hsts.conf +++ b/backend/templates/_hsts.conf @@ -2,7 +2,11 @@ {% if ssl_forced == 1 or ssl_forced == true %} {% if hsts_enabled == 1 or hsts_enabled == true %} # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years) - add_header Strict-Transport-Security $hsts_header always; +{% if hsts_subdomains == 1 or hsts_subdomains == true %} + add_header Strict-Transport-Security $hsts_header_subdomains always; +{% else %} + add_header Strict-Transport-Security $hsts_header_plain always; +{% endif %} {% endif %} {% endif %} {% endif %} diff --git a/backend/templates/_hsts_map.conf b/backend/templates/_hsts_map.conf index 27dd1f8f4..f9f3339ef 100644 --- a/backend/templates/_hsts_map.conf +++ b/backend/templates/_hsts_map.conf @@ -1,3 +1,6 @@ -map $scheme $hsts_header { - https "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload"; +map $scheme $hsts_header_plain { + https "max-age=63072000; preload"; +} +map $scheme $hsts_header_subdomains { + https "max-age=63072000;includeSubDomains; preload"; } \ No newline at end of file From 9780e22792c514cee6f7e92b4ad34d09e233b7ab Mon Sep 17 00:00:00 2001 From: Junker der Provinz <133605895+junkerderprovinz@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:08:12 +0200 Subject: [PATCH 28/76] Skip propagation seconds option for Route 53 DNS challenge certbot deprecated --dns-route53-propagation-seconds in 2.5.0 and removed it in 3.0.0, so requesting a Route 53 certificate with a propagation value fails with "unrecognized arguments". The plugin waits for the record through the AWS GetChange API instead. Fixes #4702 Fixes #5414 Co-authored-by: Pascaru <23747773+pascaruchan@users.noreply.github.com> --- backend/internal/certificate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 9f81b764d..77d585f44 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -862,7 +862,8 @@ const internalCertificate = { if (hasConfigArg) { args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); } - if (certificate.meta.propagation_seconds !== undefined) { + // The route53 plugin waits for the change through the AWS API and has no propagation option + if (certificate.meta.propagation_seconds !== undefined && certificate.meta.dns_provider !== "route53") { args.push( `--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString(), From 26e27e35039f9f4749b9369b75c858e3c51afda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=BC=D0=B0=D0=BD=20=D0=A1=D0=B0=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=D1=81=D1=8F=D0=BD?= Date: Wed, 28 Oct 2026 02:09:01 +0700 Subject: [PATCH 29/76] updated beget target plugin --- backend/certbot/dns-plugins.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index f22da5af7..88c35f32a 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -48,12 +48,12 @@ "version": "~=0.1.1" }, "beget": { - "credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password", + "credentials": "# Beget API credentials used by Certbot\n# Find on Settings -> Security -> BegetAPI\ndns_beget_plugin_username = username\ndns_beget_plugin_password = password", "dependencies": "", - "full_plugin_name": "beget-plugin", + "full_plugin_name": "dns-beget-plugin", "name": "Beget", - "package_name": "certbot-beget-plugin", - "version": "~=1.0.0.dev9" + "package_name": "certbot-dns-beget-plugin", + "version": "~=1.0.0" }, "bunny": { "credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", From 6838868fdcdf9336cb441774dcbb2761cc71a597 Mon Sep 17 00:00:00 2001 From: mirfalltnixein <22786734+mirfalltnixein@users.noreply.github.com> Date: Wed, 28 Oct 2026 00:04:24 +0100 Subject: [PATCH 30/76] Prevent dismissing unsaved form modals --- .../src/components/Form/FormModal.test.tsx | 70 +++++++++++++++++++ frontend/src/components/Form/FormModal.tsx | 38 ++++++++++ frontend/src/components/Form/index.ts | 1 + frontend/src/locale/src/en.json | 3 + frontend/src/locale/src/et.json | 3 + frontend/src/modals/AccessListModal.tsx | 17 +++-- frontend/src/modals/ChangePasswordModal.tsx | 17 +++-- .../src/modals/CustomCertificateModal.tsx | 17 +++-- frontend/src/modals/DNSCertificateModal.tsx | 17 +++-- frontend/src/modals/DeadHostModal.tsx | 17 +++-- frontend/src/modals/HTTPCertificateModal.tsx | 17 +++-- frontend/src/modals/PermissionsModal.tsx | 17 +++-- frontend/src/modals/ProxyHostModal.tsx | 17 +++-- frontend/src/modals/RedirectionHostModal.tsx | 17 +++-- frontend/src/modals/SetPasswordModal.tsx | 17 +++-- frontend/src/modals/StreamModal.tsx | 17 +++-- frontend/src/modals/UserModal.tsx | 17 +++-- 17 files changed, 235 insertions(+), 84 deletions(-) create mode 100644 frontend/src/components/Form/FormModal.test.tsx create mode 100644 frontend/src/components/Form/FormModal.tsx diff --git a/frontend/src/components/Form/FormModal.test.tsx b/frontend/src/components/Form/FormModal.test.tsx new file mode 100644 index 000000000..7ed649dc4 --- /dev/null +++ b/frontend/src/components/Form/FormModal.test.tsx @@ -0,0 +1,70 @@ +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { Field, Form } from "formik"; +import Modal from "react-bootstrap/Modal"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { FormModal } from "./FormModal"; + +const renderModal = (onHide = vi.fn()) => { + render( + + {({ onHide: hide }) => ( +
+ + + + + + )} +
, + ); + return onHide; +}; + +describe("FormModal", () => { + beforeEach(() => { + vi.stubGlobal("confirm", vi.fn()); + }); + + afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }); + + it("dismisses a pristine form without confirmation", () => { + const onHide = renderModal(); + const confirm = vi.spyOn(window, "confirm"); + + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + + expect(confirm).not.toHaveBeenCalled(); + expect(onHide).toHaveBeenCalledOnce(); + }); + + it("keeps a dirty form open when dismissal is cancelled", () => { + const onHide = renderModal(); + const confirm = vi.spyOn(window, "confirm").mockReturnValue(false); + fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } }); + + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + fireEvent.click(screen.getByRole("button", { name: "Close" })); + fireEvent.click(document.querySelector(".modal") as Element); + fireEvent.keyDown(document, { key: "Escape", keyCode: 27 }); + + expect(confirm).toHaveBeenCalledTimes(4); + expect(onHide).not.toHaveBeenCalled(); + expect(screen.getByLabelText("Name")).toHaveValue("example.com"); + }); + + it("dismisses a dirty form after confirmation", () => { + const onHide = renderModal(); + vi.spyOn(window, "confirm").mockReturnValue(true); + fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } }); + + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + + expect(onHide).toHaveBeenCalledOnce(); + }); +}); diff --git a/frontend/src/components/Form/FormModal.tsx b/frontend/src/components/Form/FormModal.tsx new file mode 100644 index 000000000..8c305ac4d --- /dev/null +++ b/frontend/src/components/Form/FormModal.tsx @@ -0,0 +1,38 @@ +import { Formik, type FormikConfig, type FormikProps, type FormikValues } from "formik"; +import type { ReactNode } from "react"; +import Modal from "react-bootstrap/Modal"; +import { intl } from "src/locale"; + +interface FormModalProps extends Omit, "children"> { + show: boolean; + onHide: () => void; + children: (props: FormikProps & { onHide: () => void }) => ReactNode; +} + +const FormModal = ({ show, onHide, children, ...formikProps }: FormModalProps) => ( + + {(formikProps) => { + const handleHide = () => { + if ( + !formikProps.dirty || + window.confirm( + intl.formatMessage({ + id: "form.unsaved-changes", + defaultMessage: "You have unsaved changes. Are you sure you want to discard them?", + }), + ) + ) { + onHide(); + } + }; + + return ( + + {children({ ...formikProps, onHide: handleHide })} + + ); + }} + +); + +export { FormModal }; diff --git a/frontend/src/components/Form/index.ts b/frontend/src/components/Form/index.ts index f218b2e33..8530d6f6d 100644 --- a/frontend/src/components/Form/index.ts +++ b/frontend/src/components/Form/index.ts @@ -3,6 +3,7 @@ export * from "./AccessField"; export * from "./BasicAuthFields"; export * from "./DNSProviderFields"; export * from "./DomainNamesField"; +export * from "./FormModal"; export * from "./LocationsFields"; export * from "./NginxConfigField"; export * from "./SSLCertificateField"; diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 11d09ea71..14c1d5b44 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -437,6 +437,9 @@ "footer.github-fork": { "defaultMessage": "Fork me on Github" }, + "form.unsaved-changes": { + "defaultMessage": "You have unsaved changes. Are you sure you want to discard them?" + }, "host.flags.block-exploits": { "defaultMessage": "Block Common Exploits" }, diff --git a/frontend/src/locale/src/et.json b/frontend/src/locale/src/et.json index 316ede752..2301c5a46 100644 --- a/frontend/src/locale/src/et.json +++ b/frontend/src/locale/src/et.json @@ -437,6 +437,9 @@ "footer.github-fork": { "defaultMessage": "Tee GitHubis fork" }, + "form.unsaved-changes": { + "defaultMessage": "Sul on salvestamata muudatusi. Kas oled kindel, et soovid need tühistada?" + }, "host.flags.block-exploits": { "defaultMessage": "Blokeeri levinud ründed" }, diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx index 79537f5cd..559740d3c 100644 --- a/frontend/src/modals/AccessListModal.tsx +++ b/frontend/src/modals/AccessListModal.tsx @@ -1,6 +1,7 @@ import cn from "classnames"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; -import { Field, Form, Formik } from "formik"; +import { Field, Form } from "formik"; +import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; @@ -86,7 +87,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { const toggleEnabled = cn(toggleClasses, "bg-cyan"); return ( - + <> {!isLoading && error && ( {error?.message || "Unknown error"} @@ -94,7 +95,9 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { )} {isLoading && } {!isLoading && data && ( - { } onSubmit={onSubmit} > - {({ setFieldValue }: any) => ( + {({ onHide, setFieldValue }: any) => (
@@ -267,7 +270,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { -
@@ -206,8 +209,8 @@ const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPr )} - - + + ); }); diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx index d363de9ed..3c808abf0 100644 --- a/frontend/src/modals/PermissionsModal.tsx +++ b/frontend/src/modals/PermissionsModal.tsx @@ -1,7 +1,8 @@ import { useQueryClient } from "@tanstack/react-query"; import cn from "classnames"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; -import { Field, Form, Formik } from "formik"; +import { Field, Form } from "formik"; +import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; @@ -133,7 +134,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { const isAdmin = data?.roles.indexOf("admin") !== -1; return ( - + <> {!isLoading && error && ( {error?.message || "Unknown error"} @@ -141,7 +142,9 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { )} {isLoading && } {!isLoading && data && ( - { } onSubmit={onSubmit} > - {() => ( + {({ onHide }) => (
@@ -263,7 +266,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { )} -
- @@ -368,9 +371,9 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => { )} -
+ )} -
+ ); }); diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx index 9a0d95ce8..a621cf1f7 100644 --- a/frontend/src/modals/RedirectionHostModal.tsx +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -1,7 +1,8 @@ import { IconSettings } from "@tabler/icons-react"; import cn from "classnames"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; -import { Field, Form, Formik } from "formik"; +import { Field, Form } from "formik"; +import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; @@ -55,7 +56,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = }; return ( - + <> {!isLoading && error && ( {error?.message || "Unknown error"} @@ -63,7 +64,9 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = )} {isLoading && } {!isLoading && data && ( - - {() => ( + {({ onHide }) => (
@@ -326,7 +329,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = - - - - - )} - - + + {({ onHide }) => ( +
+ + + + + + + setError(null)} dismissible> + {error} + +
+ + {({ field, form }: any) => ( +
+ + + {form.errors.name ? ( +
+ {form.errors.current && form.touched.current + ? form.errors.current + : null} +
+ ) : null} +
+ )} +
+
+
+ + {({ field, form }: any) => ( +
+ + + {form.errors.new ? ( +
+ {form.errors.new && form.touched.new ? form.errors.new : null} +
+ ) : null} +
+ )} +
+
+
+ + {({ field, form }: any) => ( +
+ + {form.errors.confirm ? ( +
+ {form.errors.confirm && form.touched.confirm + ? form.errors.confirm + : null} +
+ ) : null} + +
+ )} +
+
+
+ + + + +
+ )} +
); }); diff --git a/frontend/src/modals/CustomCertificateModal.tsx b/frontend/src/modals/CustomCertificateModal.tsx index b77a56d4b..c373a8d36 100644 --- a/frontend/src/modals/CustomCertificateModal.tsx +++ b/frontend/src/modals/CustomCertificateModal.tsx @@ -2,12 +2,12 @@ import { IconAlertTriangle } from "@tabler/icons-react"; import { useQueryClient } from "@tanstack/react-query"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form } from "formik"; -import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; import { type Certificate, createCertificate, uploadCertificate, validateCertificate } from "src/api/backend"; import { Button } from "src/components"; +import { FormModal } from "src/components/Form"; import { T } from "src/locale"; import { validateString } from "src/modules/Validations"; import { showObjectSuccess } from "src/notifications"; @@ -58,177 +58,176 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal }; return ( - <> - - {({ onHide }) => ( -
- - - - - - - setErrorMsg(null)} dismissible> - {errorMsg} - -
-
-

- - -

- - {({ field, form }: any) => ( -
- - - {form.errors.niceName ? ( -
- {form.errors.niceName && form.touched.niceName - ? form.errors.niceName - : null} -
- ) : null} -
- )} -
- - {({ field, form }: any) => ( -
- - { - form.setFieldValue( - field.name, - event.currentTarget.files?.length - ? event.currentTarget.files[0] - : null, - ); - }} - /> - {form.errors.certificateKey ? ( -
- {form.errors.certificateKey && form.touched.certificateKey - ? form.errors.certificateKey - : null} -
- ) : null} -
- )} -
- - {({ field, form }: any) => ( -
- - { - form.setFieldValue( - field.name, - event.currentTarget.files?.length - ? event.currentTarget.files[0] - : null, - ); - }} - /> - {form.errors.certificate ? ( -
- {form.errors.certificate && form.touched.certificate - ? form.errors.certificate - : null} -
- ) : null} -
- )} -
- - {({ field, form }: any) => ( -
- - { - form.setFieldValue( - field.name, - event.currentTarget.files?.length - ? event.currentTarget.files[0] - : null, - ); - }} - /> - {form.errors.intermediateCertificate ? ( -
- {form.errors.intermediateCertificate && - form.touched.intermediateCertificate - ? form.errors.intermediateCertificate - : null} -
- ) : null} -
- )} -
-
+ + {({ onHide }) => ( + + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
+
+

+ + +

+ + {({ field, form }: any) => ( +
+ + + {form.errors.niceName ? ( +
+ {form.errors.niceName && form.touched.niceName + ? form.errors.niceName + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificateKey ? ( +
+ {form.errors.certificateKey && form.touched.certificateKey + ? form.errors.certificateKey + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificate ? ( +
+ {form.errors.certificate && form.touched.certificate + ? form.errors.certificate + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.intermediateCertificate ? ( +
+ {form.errors.intermediateCertificate && + form.touched.intermediateCertificate + ? form.errors.intermediateCertificate + : null} +
+ ) : null} +
+ )} +
- - - - - - - )} - - +
+
+ + + + + + )} +
); }); diff --git a/frontend/src/modals/DNSCertificateModal.tsx b/frontend/src/modals/DNSCertificateModal.tsx index 134aef62c..be4cf3c22 100644 --- a/frontend/src/modals/DNSCertificateModal.tsx +++ b/frontend/src/modals/DNSCertificateModal.tsx @@ -1,12 +1,12 @@ import { useQueryClient } from "@tanstack/react-query"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form } from "formik"; -import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; import { createCertificate } from "src/api/backend"; import { Button, DNSProviderFields, DomainNamesField } from "src/components"; +import { FormModal } from "src/components/Form"; import { T } from "src/locale"; import { showObjectSuccess } from "src/notifications"; @@ -37,79 +37,78 @@ const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPro }; return ( - <> - - {({ onHide }) => ( -
- - - - - - - setErrorMsg(null)} dismissible> - {errorMsg} - -
-
- - - {({ field }: any) => ( -
- - - - - -
- )} -
- -
+ + {({ onHide }) => ( + + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
+
+ + + {({ field }: any) => ( +
+ + + + + +
+ )} +
+
- - - - - - - )} - - +
+
+ + + + + + )} +
); }); diff --git a/frontend/src/modals/DeadHostModal.tsx b/frontend/src/modals/DeadHostModal.tsx index f0c0d96c0..8205936a5 100644 --- a/frontend/src/modals/DeadHostModal.tsx +++ b/frontend/src/modals/DeadHostModal.tsx @@ -1,7 +1,6 @@ import { IconSettings } from "@tabler/icons-react"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Form } from "formik"; -import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; @@ -13,6 +12,7 @@ import { SSLCertificateField, SSLOptionsFields, } from "src/components"; +import { FormModal } from "src/components/Form"; import { useDeadHost, useSetDeadHost } from "src/hooks"; import { T } from "src/locale"; import { showObjectSuccess } from "src/notifications"; @@ -55,16 +55,21 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => { return ( <> - {!isLoading && error && ( - - {error?.message || "Unknown error"} - + {(isLoading || (error && !data)) && ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + )} - {isLoading && } {!isLoading && data && ( - - {({ onHide }) => ( -
- - - - - - - setErrorMsg(null)} dismissible> - {errorMsg} - -
-
-

- - -

- { - setDomains(doms); - setTestResults(null); - }} - /> - - {({ field }: any) => ( -
- - - - - -
- )} -
+ + {({ onHide }) => ( + + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
+
+

+ + +

+ { + setDomains(doms); + setTestResults(null); + }} + /> + + {({ field }: any) => ( +
+ + + + + +
+ )} +
+
+ {testResults ? ( +
+
+ +
+ {parseTestResults()}
- {testResults ? ( -
-
- -
- {parseTestResults()} -
- ) : null} -
-
- -
+ + + +
+ -
- - -
- - - )} - - + +
+
+ + )} + ); }); diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx index 3c808abf0..4f6bc0fab 100644 --- a/frontend/src/modals/PermissionsModal.tsx +++ b/frontend/src/modals/PermissionsModal.tsx @@ -2,12 +2,12 @@ import { useQueryClient } from "@tanstack/react-query"; import cn from "classnames"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form } from "formik"; -import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; import { setPermissions } from "src/api/backend"; import { Button, Loading } from "src/components"; +import { FormModal } from "src/components/Form"; import { useUser } from "src/hooks"; import { T } from "src/locale"; import styles from "./PermissionsModal.module.css"; @@ -135,16 +135,21 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { return ( <> - {!isLoading && error && ( - - {error?.message || "Unknown error"} - + {(isLoading || (error && !data)) && ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + )} - {isLoading && } {!isLoading && data && ( { return ( <> - {!isLoading && (error || userError) && ( - - {error?.message || userError?.message || "Unknown error"} - + {(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && ( + + {!isLoading && (error || userError) && ( + + {error?.message || userError?.message || "Unknown error"} + + )} + {(isLoading || userIsLoading) && } + )} - {isLoading || (userIsLoading && )} {!isLoading && !userIsLoading && data && currentUser && ( - {!isLoading && error && ( - - {error?.message || "Unknown error"} - + {(isLoading || (error && !data)) && ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + )} - {isLoading && } {!isLoading && data && ( { }; return ( - <> - - {({ onHide }) => ( -
- - - - - - - setError(null)} dismissible> - {error} - -
- - {({ field, form }: any) => ( - <> -

- - { - e.preventDefault(); - form.setFieldValue( - field.name, - generate({ - length: 12, - numbers: true, - }), - ); - setShowPassword(true); - }} - > - - {" "} - —{" "} - { - e.preventDefault(); - setShowPassword(!showPassword); - }} - > - - - -

-
- - - {form.errors.new ? ( -
- {form.errors.new && form.touched.new ? form.errors.new : null} -
- ) : null} -
- - )} -
-
-
- - - - -
- )} -
- + + {({ onHide }) => ( +
+ + + + + + + setError(null)} dismissible> + {error} + +
+ + {({ field, form }: any) => ( + <> +

+ + { + e.preventDefault(); + form.setFieldValue( + field.name, + generate({ + length: 12, + numbers: true, + }), + ); + setShowPassword(true); + }} + > + + {" "} + —{" "} + { + e.preventDefault(); + setShowPassword(!showPassword); + }} + > + + + +

+
+ + + {form.errors.new ? ( +
+ {form.errors.new && form.touched.new ? form.errors.new : null} +
+ ) : null} +
+ + )} +
+
+
+ + + + +
+ )} +
); }); diff --git a/frontend/src/modals/StreamModal.tsx b/frontend/src/modals/StreamModal.tsx index b6f35622f..417ae3103 100644 --- a/frontend/src/modals/StreamModal.tsx +++ b/frontend/src/modals/StreamModal.tsx @@ -1,10 +1,10 @@ import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form } from "formik"; -import { FormModal } from "src/components/Form"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; import { Button, Loading, SSLCertificateField, SSLOptionsFields } from "src/components"; +import { FormModal } from "src/components/Form"; import { useSetStream, useStream } from "src/hooks"; import { intl, T } from "src/locale"; import { validateNumber, validateString } from "src/modules/Validations"; @@ -48,16 +48,21 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => { return ( <> - {!isLoading && error && ( - - {error?.message || "Unknown error"} - + {(isLoading || (error && !data)) && ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + )} - {isLoading && } {!isLoading && data && ( { const [setupData, setSetupData] = useState<{ secret: string; otpauthUrl: string } | null>(null); const [backupCodes, setBackupCodes] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); + const [formDirty, setFormDirty] = useState(false); + + const handleHide = () => { + if (!isSubmitting && confirmDiscardChanges(formDirty)) { + remove(); + } + }; const loadStatus = useCallback(async () => { try { @@ -172,8 +180,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => { /> - {() => ( + {({ dirty }) => (
+ {({ field, form }: any) => (
- {dnsProvider ? ` (DNS: ${dnsProvider})` : null}
{certificate.domainNames?.length ? ( <> From 66fd49ee4633adfccc342407896f3f2f549fb79a Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:31 -0800 Subject: [PATCH 38/76] Frontend tests: disable Node's built-in localStorage so vitest works on Node 25+ Co-Authored-By: Claude Opus 5.5 --- frontend/vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1e00d067f..6a00563af 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -57,6 +57,9 @@ export default defineConfig({ test: { environment: "happy-dom", setupFiles: ["./vitest-setup.js"], + // Node 25+ has a built-in global localStorage (undefined without + // --localstorage-file) that shadows happy-dom's; turn it off. + execArgv: ["--no-experimental-webstorage"], }, assetsInclude: ["**/*.md", "**/*.png", "**/*.svg"], }); From 2a27e542e945e73fe1f3334577b4f15984466eea Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:33 -0800 Subject: [PATCH 39/76] Add .gitattributes to keep LF line endings Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a12fac3d0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Force LF everywhere: these files run inside Linux containers +* text=auto eol=lf +*.png binary +*.jpg binary +*.ico binary +*.woff binary +*.woff2 binary From b28e0d5442786058893666e6eb9f13e519e20c16 Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:35 -0800 Subject: [PATCH 40/76] s6-overlay 3.2.3.2 Co-Authored-By: Claude Opus 5.5 --- docker/scripts/install-s6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/scripts/install-s6 b/docker/scripts/install-s6 index 55513fe1e..dc8eb4ae2 100755 --- a/docker/scripts/install-s6 +++ b/docker/scripts/install-s6 @@ -8,7 +8,7 @@ BLUE='\E[1;34m' GREEN='\E[1;32m' RESET='\E[0m' -S6_OVERLAY_VERSION=3.2.3.0 +S6_OVERLAY_VERSION=3.2.3.2 TARGETPLATFORM=${1:-linux/amd64} # Determine the correct binary file for the architecture given From c214a49afbf0340482c5cdaa52c6232f89fe52d4 Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:36 -0800 Subject: [PATCH 41/76] Backend: refresh yarn.lock within the declared ranges (fixes 15 of 16 advisories, incl. body-parser DoS) Co-Authored-By: Claude Opus 5.5 --- backend/package.json | 4 +- backend/yarn.lock | 435 +++++++++++++++++++++---------------------- 2 files changed, 213 insertions(+), 226 deletions(-) diff --git a/backend/package.json b/backend/package.json index b8e0ed9fc..adcbe5411 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,14 +20,14 @@ "bcrypt": "^6.0.0", "better-sqlite3": "^13.0.3", "body-parser": "^2.3.0", - "chalk": "5.6.2", + "chalk": "6.0.1", "compression": "^1.8.2", "express": "^5.2.1", "express-fileupload": "^1.5.2", "gravatar": "^1.8.2", "jsonwebtoken": "^9.0.3", "knex": "3.3.0", - "liquidjs": "10.29.0", + "liquidjs": "10.30.0", "lodash": "^4.18.1", "moment": "^2.31.0", "mysql2": "^3.24.4", diff --git a/backend/yarn.lock b/backend/yarn.lock index a9d6c2b51..66013beb5 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,20 +2,12 @@ # yarn lockfile v1 -"@apidevtools/json-schema-ref-parser@15.3.6": - version "15.3.6" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.3.6.tgz#9e6360264d90b209eb09f9e4dc9a32316d86f4ea" - integrity sha512-oPFDSviRrreUmCZn09LCD4S9QQa6j7zfEwBM1pkGa/kXY0O4sXsEXn4emLP/Tt6tik2+BtgwKk2eldAeOWZ2Rw== +"@apidevtools/json-schema-ref-parser@^16.0.2", "@apidevtools/json-schema-ref-parser@^16.0.3": + version "16.0.5" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.5.tgz#4f5ab1d20fe9dcde15d4167a3d5dd0434e0109a6" + integrity sha512-z3LiEMqTP2FNu6u8bdUgBYErVuwMSZReOJy1MCEIB0BDHMoNgBtkciby32HOlcrfWwrOSzRU8YIsZ0CogYRuYQ== dependencies: - js-yaml "^4.2.0" - -"@apidevtools/json-schema-ref-parser@^16.0.2": - version "16.0.2" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.2.tgz#21f55458a55d1b02bc62f19c731913d8e74976b3" - integrity sha512-oCwWUyRdMtt/hnp6ONPudFZzF7tSqiHy46lLx5VsnsLTacs8TSU558VPAiOueFJ8WddCH30PINWDA8ByxhGHmQ== - dependencies: - js-yaml "^5.4.1" - undici "^8.10.2" + js-yaml "^5.4.2" "@apidevtools/openapi-schemas@^2.1.0": version "2.1.0" @@ -28,71 +20,83 @@ integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== "@apidevtools/swagger-parser@^13.0.0": - version "13.0.0" - resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-13.0.0.tgz#a8363c086cc2ae0005b270cfc241f353a741e49c" - integrity sha512-TcvnmBzJD7DBREkc/2qaxMqbKpttOaHhOFsYRYKdYAv6HGY+iNlWY2IUA209C2/NmvL9z8RBZhkrESqvGkjqmw== + version "13.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-13.1.0.tgz#efc8ffcc573838e715d0bce83d917b79636ca172" + integrity sha512-dkCuqCWh5U7u0aurmmc/qvel2KeKDxHTwVtUdYonndIpD7mXV6AVaWNdvaja2HAy628z7FT/Qdz8sCgzwhgwKg== dependencies: - "@apidevtools/json-schema-ref-parser" "15.3.6" + "@apidevtools/json-schema-ref-parser" "^16.0.3" "@apidevtools/openapi-schemas" "^2.1.0" "@apidevtools/swagger-methods" "^3.0.2" "@types/json-schema" "^7.0.15" - ajv "^8.17.1" + ajv "^8.20.0" ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.2" "@biomejs/biome@^2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.5.10.tgz#c2a42fc6f9c7d9e12c1f77f63dfb6b5489c497b7" - integrity sha512-WRKXARA3kTuiV5sxqTpobJ/I0MVd4vk3pOL6wnp5az4LntFIhWTj1RWZq3DI9PCEN3lXcqy7p5aqUHzvq8AXyQ== + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.3.tgz#649bf3d08d702823a9ce62a7022fa16b4e7b64dc" + integrity sha512-va/0xPku7wnfzv+D8Zpdm5uKiGJWiV8neqoEPnRjvNQQwhl4tVXS4fETs4H0CCB2rGeqJ4cu9y4iOZOnOYKAUA== optionalDependencies: - "@biomejs/cli-darwin-arm64" "2.5.10" - "@biomejs/cli-darwin-x64" "2.5.10" - "@biomejs/cli-linux-arm64" "2.5.10" - "@biomejs/cli-linux-arm64-musl" "2.5.10" - "@biomejs/cli-linux-x64" "2.5.10" - "@biomejs/cli-linux-x64-musl" "2.5.10" - "@biomejs/cli-win32-arm64" "2.5.10" - "@biomejs/cli-win32-x64" "2.5.10" + "@biomejs/cli-android-arm64" "2.6.3" + "@biomejs/cli-android-x64" "2.6.3" + "@biomejs/cli-darwin-arm64" "2.6.3" + "@biomejs/cli-darwin-x64" "2.6.3" + "@biomejs/cli-linux-arm64" "2.6.3" + "@biomejs/cli-linux-arm64-musl" "2.6.3" + "@biomejs/cli-linux-x64" "2.6.3" + "@biomejs/cli-linux-x64-musl" "2.6.3" + "@biomejs/cli-win32-arm64" "2.6.3" + "@biomejs/cli-win32-x64" "2.6.3" -"@biomejs/cli-darwin-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.10.tgz#f1453a7f2e63f228a34645df1c33962f22a6c2a8" - integrity sha512-ItCrxKK6SXVT6flYs0qIuBd4AA3TTTl4d66Re6YI2FuGZnN85NmuYNzkiTJUyYw8qBLv69L5zTUB6uyWd++h3Q== +"@biomejs/cli-android-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.3.tgz#929aa987accb55957db62d1e11b76dbcc0a7f2e4" + integrity sha512-5JD7QQDEHxdeEb4sftEHG+lsuUyu8Cob/hVLqRELbgGlFl68Rldv/PPi2k4dRjERZ/EItJ3O6Xmv8lqsEtUBiw== -"@biomejs/cli-darwin-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.10.tgz#e21041e7bfab01cd453f609b940bf613713d0e24" - integrity sha512-yLsPU9pAmtChXDu8vhKAzErqe+LeeYuwuUB2FZMkRitsmdodxsYRa9KHrFispsUHzzOu+9HB3nP/TQxyia+Sjw== +"@biomejs/cli-android-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.3.tgz#f130f0ee898b437686d1cddd87e9ca5e5b7dc4de" + integrity sha512-70WOGKCgpT+Uqg6LFdTLqefV54/DypdZg+LnBQDO6AWvkgXO+9xhVClajQyDsIHYjnXMtI1pRO5sP+zMXlS/bw== -"@biomejs/cli-linux-arm64-musl@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.10.tgz#57fc8aee25c08ca6c14526ccc44e282e7ba96eb6" - integrity sha512-t1QAKZwQJRB4dvgJSgFiQ4BNfNPChg69BNonz854qLVxnjT3UvDzQg9mbkTJRu35ZqU0Rw10A73J8Urgbg2RPw== +"@biomejs/cli-darwin-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.3.tgz#b232a0fce9b30097f69d52382fc6e7074860b012" + integrity sha512-ezf+XEKeyJ2yYlBhrzyEEHLvfRYrhtNKc6maLzR/WcAcBccb4RVDeFxsymjBve2LO0bpIMRUAiS90D4bUBRT0A== -"@biomejs/cli-linux-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.10.tgz#aef789a71e5aa86f5a0e1aff6cb4c1b757ec5be1" - integrity sha512-VG8uQW/86a1roLaIFvtIbEigxIdzdJ190oGyg1tV7VYeQtOS+x10sflk7WbuXgw91EtZX5DlIIIej1YqkNLlcg== +"@biomejs/cli-darwin-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.3.tgz#8a31e6a26e26bba170a0ce36fff947038b733d72" + integrity sha512-Lg2tFdg4elJo+7i707PpLMs6N1hATVt4kqDo6MD/q/VnPs8rsKReKXsz1TA5GNdfRVAQNJ5GRiyK2EwNptK+MA== -"@biomejs/cli-linux-x64-musl@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.10.tgz#1aebe9f928dc72dc3c112e60f1211a4d646990c4" - integrity sha512-pgDDqp9JybHm2I0KRgzN6i4+lt8xu4iqxUwLzglUMmOmyRTU1AYBGKzh9sNMOtIjah7xoWvKHlLVetvyifzoiQ== +"@biomejs/cli-linux-arm64-musl@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.3.tgz#84defa2b38f3fcf04b8eee8c76cbf60b3fa5ef05" + integrity sha512-dYU70A89OFHzakQPE9VOPbE60JNcQt4mtxvUGQl+EgTkGtqCFxLD2RUn1SbiMu8Mu5xFscS1bfYDpoasaUrqNA== -"@biomejs/cli-linux-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.10.tgz#a82b414abf5d9668fb035ee50a710bf78a7c5fbf" - integrity sha512-4O6T0eq2heoHZN0a9UX+rWQoxXEBaKf+lRi2hbsGlHneUz9BWXM76nEWMK7Eeq8gzMxR1khQB6BFpAASpeXqGg== +"@biomejs/cli-linux-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.3.tgz#c7cb949571e8088b463385b15d60d4f1690c9242" + integrity sha512-8Stcvq98n1gs9VyLKuaWrnsd8kbXE2nKDAHtuLHGSYdGyNWN5JIIm5a28xdnmgdKzKHmiDgYyATetkEv7+zGow== -"@biomejs/cli-win32-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.10.tgz#db671cf67d7040a374e8cdda5aebe73c3f4ffba0" - integrity sha512-pxAbxduPO4xq/Cvgaa2lOrs9BB0hEXmmDqfMNP4ZOffGOkUrD1/QGw9UAMpFQpX2P8MqTIIRuQKcmetum4Oa6A== +"@biomejs/cli-linux-x64-musl@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.3.tgz#6c6de66af5537443c3fe1fef429e801c556a6b5a" + integrity sha512-j6eFjUcWSphmgVTbrobEMA0wdbfNHKrGi8hRsFkc17jeS1n7nHwFACxp6jWqe9sxQqyFAeyOgt2/1bOGVcbATA== -"@biomejs/cli-win32-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.10.tgz#3bdeaf5856f21aec6b6c50a7bca5464b9cda5623" - integrity sha512-M+2dgBsl3lXRiTfgPVc2p3anS4Tocojke4rzFLScZ2Y/wmF+36dRb1iHCLiyGqOzQGyTplZH1HnEYviiAqi3nA== +"@biomejs/cli-linux-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.3.tgz#d50121c6396a00cfb84ccf4cfd5e809a9cb4aa6b" + integrity sha512-CShmx0bO6BngywT3minv8K1xsQX1ig1vsBqYTLnmDY3mBiDYHlaLRIsdHoM1pEU9dV3BN5a0uiH0IGDtYh9TWA== + +"@biomejs/cli-win32-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.3.tgz#6b7564eddb8fb30c7cb0bd1f958e2ccacbf38dd7" + integrity sha512-00SthDxBPwNiT7IL/mOneif6fiEU1PXK875zhr+InpeZjovY1mF5tCuDhXo1Hh0GYyiKHKzLbItzn58SAyLhJQ== + +"@biomejs/cli-win32-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.3.tgz#57a5dbf7c25ff435a1b5162c68d6c6f8082ab6cb" + integrity sha512-GhtOPphBWlnCepqpNk/w5eGN5oGRCzL20EbAkL1XCIpyboeVC9R642noyT43l8LSenGS4qz9IL8sKPfwY37V/Q== "@isaacs/fs-minipass@^4.0.0": version "4.0.1" @@ -102,9 +106,9 @@ minipass "^7.0.4" "@noble/hashes@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.3.0.tgz#505fd39c3134a37e67c8c4e6c6049a496154879c" - integrity sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ== + version "2.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.4.0.tgz#e6a3e98edbed3c8659b28bb8f9b18ca02ec8ea4b" + integrity sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA== "@otplib/core@13.5.0": version "13.5.0" @@ -152,9 +156,9 @@ "@otplib/core" "13.5.0" "@scure/base@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.3.0.tgz#fc7acb5b7084c53e4e2537d83fa72cddac7143fd" - integrity sha512-NsG6Y03tY6R5BUis4FdVtHVkur0U6FOzskgs9ZXNl78CUc9fkZ78HmENUle1nSOkCasDmbubmWD9qwB7mm4PZA== + version "2.4.0" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.4.0.tgz#61e4ed7d4447afc247f2bff9aeb8fca53267db86" + integrity sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg== "@types/json-schema@^7.0.15": version "7.0.15" @@ -250,10 +254,10 @@ archiver@^8.0.0: tar-stream "^3.0.0" zip-stream "^7.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +argparse@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-3.0.4.tgz#ba802dd75fdc3d5bdcb4339b713d62c6c5d4565d" + integrity sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA== ast-types@^0.13.4: version "0.13.4" @@ -272,10 +276,10 @@ aws-ssl-profiles@^1.1.2: resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== -b4a@^1.6.4, b4a@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.8.1.tgz#7f16334ca80127aeb26064a28841acbf174840a4" - integrity sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw== +b4a@^1.6.4, b4a@^1.8.1, b4a@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.9.0.tgz#6336313b6e76b299c397afa68676ec6ebc788845" + integrity sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ== balanced-match@^4.0.2: version "4.0.4" @@ -283,14 +287,14 @@ balanced-match@^4.0.2: integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== bare-events@^2.5.4, bare-events@^2.7.0: - version "2.9.1" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.1.tgz#5c86616966343bcb03a1b3155feab253eadbf349" - integrity sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg== + version "2.9.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.2.tgz#01d377b64c3c7167b28b52da7add3221aecf2d7f" + integrity sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA== -bare-fs@^4.5.5: - version "4.8.0" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.8.0.tgz#8c0f916833ae11e6d9d347f9e0a7f46f7375bb8f" - integrity sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q== +bare-fs@^4.8.2: + version "4.8.4" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.8.4.tgz#f342288e7e5d9d8e79917078e1f45950507828c5" + integrity sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ== dependencies: bare-events "^2.5.4" bare-path "^3.0.0" @@ -299,23 +303,23 @@ bare-fs@^4.5.5: fast-fifo "^1.3.2" bare-path@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.1.1.tgz#d4a207c088609b4663a7556a46a97342398bf7e2" - integrity sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ== + version "3.1.2" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.1.2.tgz#67261bc74a9ba0105ba139b8638f218c09e8d82e" + integrity sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw== bare-stream@^2.6.4: - version "2.13.3" - resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.3.tgz#f6186c7cbb4bbf53a4560f35e48b16373ba51ce6" - integrity sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ== + version "2.13.4" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.4.tgz#61d448a268d1efd992103aae6bd729332f278ac0" + integrity sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA== dependencies: b4a "^1.8.1" streamx "^2.25.0" teex "^1.0.1" bare-url@^2.2.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.2.tgz#e8dafaefbdb56891a0fbc2b0e1c051296d241cd6" - integrity sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg== + version "2.5.5" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.5.tgz#2e8c18db11393aa6cae6ff246fdcda34615aacd3" + integrity sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q== dependencies: bare-path "^3.0.0" @@ -375,25 +379,25 @@ blueimp-md5@^2.16.0: resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -body-parser@^2.2.1, body-parser@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" - integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== +body-parser@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.4.0.tgz#337a76249270f83e450ad385a9606ca3b4a75c06" + integrity sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ== dependencies: bytes "^3.1.2" - content-type "^2.0.0" + content-type "^2.1.0" debug "^4.4.3" http-errors "^2.0.1" - iconv-lite "^0.7.2" + iconv-lite "^0.7.3" on-finished "^2.4.1" - qs "^6.15.2" + qs "^6.16.1" raw-body "^3.0.2" type-is "^2.1.0" brace-expansion@^5.0.8: - version "5.0.9" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf" - integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== + version "5.0.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec" + integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ== dependencies: balanced-match "^4.0.2" @@ -468,10 +472,10 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@5.6.2: - version "5.6.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== +chalk@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-6.0.1.tgz#077d4667ad6e608c9687782caed3d534d7f9abc3" + integrity sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA== chalk@^2.3.2: version "2.4.2" @@ -582,15 +586,10 @@ compression@^1.8.2: safe-buffer "5.2.1" vary "~1.1.2" -content-disposition@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" - integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== - -content-type@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== +content-disposition@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-2.0.1.tgz#88a75c7a52ef9c407c96035fe83a66939c2a4dc9" + integrity sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog== content-type@^2.0.0, content-type@^2.1.0: version "2.1.0" @@ -855,14 +854,14 @@ express-fileupload@^1.5.2: busboy "^1.6.0" express@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" - integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + version "5.3.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.3.0.tgz#ab72dace89c50c8417bd3f865dad0b4fd1ca1c73" + integrity sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g== dependencies: accepts "^2.0.0" - body-parser "^2.2.1" - content-disposition "^1.0.0" - content-type "^1.0.5" + body-parser "^2.3.0" + content-disposition "^2.0.1" + content-type "^2.0.0" cookie "^0.7.1" cookie-signature "^1.2.1" debug "^4.4.0" @@ -878,14 +877,14 @@ express@^5.2.1: on-finished "^2.4.1" once "^1.4.0" parseurl "^1.3.3" - proxy-addr "^2.0.7" - qs "^6.14.0" + proxy-addr "^2.0.8" + qs "^6.16.0" range-parser "^1.2.1" router "^2.2.0" send "^1.1.0" serve-static "^2.2.0" statuses "^2.0.1" - type-is "^2.0.1" + type-is "^2.1.0" vary "^1.1.2" fast-deep-equal@^3.1.3: @@ -893,15 +892,15 @@ fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.2.0, fast-fifo@^1.3.2: +fast-fifo@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== fast-uri@^3.0.1: - version "3.1.8" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.8.tgz#f7db8d942e20eead3cfe93b0beeb4eb0169e938b" - integrity sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg== + version "3.1.9" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.9.tgz#f1deba822b1d6ac79da2f860e872a63470cff203" + integrity sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ== fdir@^6.5.0: version "6.5.0" @@ -1076,7 +1075,7 @@ has-symbols@^1.1.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -hasown@^2.0.2, hasown@^2.0.3: +hasown@^2.0.2, hasown@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== @@ -1112,7 +1111,7 @@ https-proxy-agent@9.1.0: debug "^4.3.4" proxy-agent-negotiate "1.1.0" -iconv-lite@^0.7.2, iconv-lite@^0.7.3, iconv-lite@~0.7.0: +iconv-lite@^0.7.3, iconv-lite@~0.7.0: version "0.7.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f" integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ== @@ -1150,9 +1149,9 @@ interpret@^2.2.0: integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== ip-address@^10.1.1: - version "10.7.2" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.2.tgz#5b3b2b7d46c6293861b82dfcd49cc203aa8f2c5c" - integrity sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w== + version "10.7.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.5.tgz#4c4c423f6477d0b0b5c2ea59ac77e93eff49003e" + integrity sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA== ipaddr.js@1.9.1: version "1.9.1" @@ -1171,12 +1170,12 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.16.1: - version "2.16.2" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" - integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== +is-core-module@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.17.0.tgz#75c1c76236c9f7e38ddbfff292e127dc13b21027" + integrity sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA== dependencies: - hasown "^2.0.3" + hasown "^2.0.4" is-extglob@^2.1.1: version "2.1.1" @@ -1225,19 +1224,12 @@ isexe@^4.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-4.0.0.tgz#48f6576af8e87a18feb796b7ed5e2e5903b43dca" integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw== -js-yaml@^4.2.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb" - integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA== +js-yaml@^5.4.2: + version "5.5.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.5.0.tgz#ec843f09ffb2a54b6758a41eee109a1815d16442" + integrity sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg== dependencies: - argparse "^2.0.1" - -js-yaml@^5.4.1: - version "5.4.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.4.2.tgz#eebc7a57e0bbe1d6b6d9416a72e7463082c7da85" - integrity sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg== - dependencies: - argparse "^2.0.1" + argparse "^3.0.3" json-parse-better-errors@^1.0.1: version "1.0.2" @@ -1309,10 +1301,10 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" -liquidjs@10.29.0: - version "10.29.0" - resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.29.0.tgz#ee061890ccf0da26be377aad4f02bd32da0140d8" - integrity sha512-pCVOhs6FLAR8su3ItJ07diN26t6W5dHQRnmTMy8HPyTFuv1+oSCVJIGp5pGjfQyOZfh50KswvKtMTp6p4JEIdw== +liquidjs@10.30.0: + version "10.30.0" + resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.30.0.tgz#c84b03cf85a5d3fc76a5c7a99039d166e8970d9e" + integrity sha512-Fw4wA+8CZsJkaeLEIlPE3AoponfjEZhj7FQvYNcAEcg7jNRik7mEMoaco382FBdrYlykVf6w4vClPNctz2OYjg== dependencies: commander "^10.0.0" @@ -1392,9 +1384,9 @@ lru-cache@^7.14.1: integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== lru.min@^1.1.0, lru.min@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.4.tgz#6ea1737a8c1ba2300cc87ad46910a4bdffa0117b" - integrity sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA== + version "1.1.5" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.5.tgz#bd32483f4e342261d0eb011b06d2219e6b5ef818" + integrity sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA== math-intrinsics@^1.1.0: version "1.1.0" @@ -1478,9 +1470,9 @@ ms@^2.1.1, ms@^2.1.3: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== mysql2@^3.24.4: - version "3.24.4" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.4.tgz#7752de0cf10b1d2984103bdbc60f696c60756f31" - integrity sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA== + version "3.24.6" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.6.tgz#8e9471f0ceca917599ac021356bb24f08f168b3c" + integrity sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ== dependencies: aws-ssl-profiles "^1.1.2" generate-function "^2.3.1" @@ -1520,9 +1512,9 @@ netmask@^2.0.2: integrity sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA== node-abi@^3.3.0: - version "3.94.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.94.0.tgz#007181ed0d1b56ae9670ea6c084d2bf83538405f" - integrity sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g== + version "3.101.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.101.0.tgz#3c99775cb0a4ddf4f3f0dad2a9ad940d67ef0052" + integrity sha512-bkb/RkuWCE64eWu1PCh3Fu2pGc9mKRs+kOE+FU2bGHRYlQa86/D321DSATV54AsCQMKz3/xSt9sRgraZTpqdhQ== dependencies: semver "^7.3.5" @@ -1602,9 +1594,9 @@ objection@3.1.5: db-errors "^0.2.3" on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + version "2.4.2" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.2.tgz#6932b04b95bf6a4828201db10af75fe5c9c38b73" + integrity sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ== dependencies: ee-first "1.1.1" @@ -1733,20 +1725,20 @@ path@^0.12.7: process "^0.11.1" util "^0.10.3" -pg-cloudflare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz#4b4c20e6d8ae531d400730f4804571a8d62f1497" - integrity sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A== +pg-cloudflare@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz#0fc904c9a8b3ca3a4f71e16bcc9171224ecbb2a8" + integrity sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ== pg-connection-string@2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== -pg-connection-string@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.14.0.tgz#abc26ee4f37c56c0f3ae0fcf0b0653cc4e1c0fd9" - integrity sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg== +pg-connection-string@^2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.14.1.tgz#d9529fe2497c0cac8a9f5f6f059679c52f1bd964" + integrity sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA== pg-int8@1.0.1: version "1.0.1" @@ -1758,10 +1750,10 @@ pg-pool@^3.14.0: resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.14.0.tgz#f35ae4eb846780cad71af24099b3edfa9781ad90" integrity sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw== -pg-protocol@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.16.0.tgz#cffb008826561ee9770a8a15dc21f269d731b305" - integrity sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg== +pg-protocol@^1.16.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.16.1.tgz#87a3951b57002682d63166c029859bd834e64bf1" + integrity sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg== pg-types@2.2.0: version "2.2.0" @@ -1775,17 +1767,17 @@ pg-types@2.2.0: postgres-interval "^1.1.0" pg@^8.23.0: - version "8.23.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.0.tgz#5c2026d32bd0cb4fbd9196bac1ecf5ae66607180" - integrity sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg== + version "8.23.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.1.tgz#e4848051a04aa2f968fd5671d075f89e5e7918b9" + integrity sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ== dependencies: - pg-connection-string "^2.14.0" + pg-connection-string "^2.14.1" pg-pool "^3.14.0" - pg-protocol "^1.16.0" + pg-protocol "^1.16.1" pg-types "2.2.0" pgpass "1.0.5" optionalDependencies: - pg-cloudflare "^1.4.0" + pg-cloudflare "^1.4.1" pgpass@1.0.5: version "1.0.5" @@ -1800,9 +1792,9 @@ picomatch@^2.0.4, picomatch@^2.2.1: integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== picomatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.5.tgz#51ea57a17d86f605f81039595fbc40ed06a55fab" - integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A== + version "4.0.7" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" + integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== pify@^3.0.0: version "3.0.0" @@ -1872,7 +1864,7 @@ process@^0.11.1, process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -proxy-addr@^2.0.7: +proxy-addr@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.8.tgz#624e760fd8ab06b1b8320c2f8506d4422cf8e932" integrity sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ== @@ -1917,10 +1909,10 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -qs@^6.14.0, qs@^6.15.2: - version "6.16.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.0.tgz#c22c723a28a920f3aacdce8289fabd43eccb79fd" - integrity sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA== +qs@^6.16.0, qs@^6.16.1: + version "6.16.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.1.tgz#0643e17993e551759c85078e1f0bc4b9fe908020" + integrity sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg== dependencies: es-define-property "^1.0.1" side-channel "^1.1.1" @@ -1983,9 +1975,9 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: util-deprecate "^1.0.1" readable-stream@^4.0.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" - integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== + version "4.8.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.8.0.tgz#22af894e6d60cb0064410fc376367ec4ce470adb" + integrity sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" @@ -2035,12 +2027,12 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.12" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" - integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== + version "1.22.13" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.13.tgz#d542f97758d768f36091251a6b07540b4343f7b5" + integrity sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA== dependencies: es-errors "^1.3.0" - is-core-module "^2.16.1" + is-core-module "^2.17.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2197,9 +2189,9 @@ socks-proxy-agent@10.1.0: socks "^2.8.3" socks@^2.8.3: - version "2.8.9" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.9.tgz#aa5f130ca0f88a43fa44faf4869c50d22aa27752" - integrity sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw== + version "2.8.10" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.10.tgz#23aab4dccc1fdae110aaf4fa51cd190552432dc5" + integrity sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ== dependencies: ip-address "^10.1.1" smart-buffer "^4.2.0" @@ -2215,9 +2207,9 @@ split2@^4.1.0: integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== sql-escaper@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.5.1.tgz#fbaee511294d1f7da92fb68ca1faee2255839c7b" - integrity sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg== + version "1.5.2" + resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.5.2.tgz#5b59840dc4034bd23a9b7d00501ba947a90364b7" + integrity sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg== sqlite3@^6.0.1: version "6.0.1" @@ -2241,10 +2233,10 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -streamx@^2.12.5, streamx@^2.15.0, streamx@^2.25.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.28.0.tgz#035ab56057b7ed2211b51d532e6973f0f99fbf11" - integrity sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw== +streamx@^2.12.5, streamx@^2.25.0, streamx@^2.28.1: + version "2.28.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.28.1.tgz#376cd42a089505a69bec0efdb5189aa45586141f" + integrity sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA== dependencies: events-universal "^1.0.0" fast-fifo "^1.3.2" @@ -2324,14 +2316,14 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar-stream@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.0.tgz#0d0064d9b67ea3c9f5abde155e35faab0df37591" - integrity sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg== + version "3.2.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.2.tgz#aec7f257e712192a177a93aa0450c2e76855c8be" + integrity sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg== dependencies: - b4a "^1.6.4" - bare-fs "^4.5.5" - fast-fifo "^1.2.0" - streamx "^2.15.0" + b4a "^1.9.0" + bare-fs "^4.8.2" + fast-fifo "^1.3.2" + streamx "^2.28.1" tar@^7.5.10, tar@^7.5.4: version "7.5.22" @@ -2419,7 +2411,7 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -type-is@^2.0.1, type-is@^2.1.0: +type-is@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570" integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA== @@ -2434,14 +2426,9 @@ undefsafe@^2.0.5: integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== undici@^6.25.0: - version "6.28.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.28.0.tgz#9f0e385744fef5021d6596c5bccd783f61193c1c" - integrity sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA== - -undici@^8.10.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/undici/-/undici-8.10.2.tgz#960bcb0b43c267f86910ea7ca408ada843a44bc7" - integrity sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ== + version "6.29.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.29.1.tgz#a3317c5df0236e0633727d9a7bd4f7ed195d8575" + integrity sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA== unpipe@~1.0.0: version "1.0.0" From 5aed3a5539ef3990ab6c674bc721b217edaaee54 Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:37 -0800 Subject: [PATCH 42/76] Reject unknown keys in PUT /api/users/:id/permissions (an extra key crashes the backend) Co-Authored-By: Claude Opus 5.5 --- backend/schema/components/permission-object.json | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/schema/components/permission-object.json b/backend/schema/components/permission-object.json index cae9d26c0..9bb5bfffa 100644 --- a/backend/schema/components/permission-object.json +++ b/backend/schema/components/permission-object.json @@ -1,6 +1,7 @@ { "type": "object", "minProperties": 1, + "additionalProperties": false, "properties": { "visibility": { "type": "string", From c68456b2051965e18c7ed9a03af946569bcbca3a Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:37 -0800 Subject: [PATCH 43/76] Frontend: refresh dependencies, react-intl 12, vitest 5, happy-dom 21; fix Tabler 1.7 regressions Co-Authored-By: Claude Opus 5.5 --- frontend/package.json | 6 +- frontend/src/App.css | 27 + frontend/yarn.lock | 1089 +++++++++++++++++++---------------------- 3 files changed, 534 insertions(+), 588 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e2cc7ee64..930cb509c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,7 @@ "react": "^19.3.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.3.0", - "react-intl": "^10.2.0", + "react-intl": "^12.1.5", "react-markdown": "^10.1.0", "react-qr-code": "^2.2.0", "react-router-dom": "^7.18.4", @@ -54,7 +54,7 @@ "@types/react-dom": "^19.3.0", "@types/react-table": "^7.7.20", "@vitejs/plugin-react": "^6.1.1", - "happy-dom": "^20.14.5", + "happy-dom": "^21.0.4", "postcss": "^8.5.28", "postcss-simple-vars": "^7.0.1", "sass": "^1.104.1", @@ -62,6 +62,6 @@ "typescript": "7.0.2", "vite": "^8.3.0", "vite-plugin-checker": "^0.14.5", - "vitest": "^4.1.11" + "vitest": "^5.0.5" } } diff --git a/frontend/src/App.css b/frontend/src/App.css index ad335caea..894f70cc9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -115,4 +115,31 @@ label.row { :host, :root { margin-left: 0; } +} + +/* Keep Tabler 1.5's behaviour on Tabler 1.6/1.7. App.css loads before the + dynamically imported Tabler CSS, hence the doubled :root selectors. */ + +/* 1.6 dropped the reserved scrollbar gutter, so the layout shifted 7px between short and long pages */ +html { + scrollbar-gutter: stable; +} +@supports not (scrollbar-gutter: stable) { + html { + overflow-y: scroll; + } +} + +/* 1.6+ mixes the focus ring 50% with the background (75% dark, about 2.1:1 on white); restore full strength */ +:root:root { + --tblr-focus-ring-opacity: 1; + --tblr-focus-ring-opacity-dark: 1; +} + +/* Coloured primary buttons (btn-primary + bg-) got a blue primary border in 1.7 */ +.btn-primary[class*=" bg-"] { + --tblr-btn-border-color: transparent; + --tblr-btn-hover-border-color: transparent; + --tblr-btn-active-border-color: transparent; + --tblr-btn-box-shadow: var(--tblr-shadow-input); } \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index eb0aa21f9..ec1a44d3a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -40,27 +40,27 @@ "@babel/traverse" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/helper-string-parser@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" - integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== +"@babel/helper-string-parser@^7.29.11": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63" + integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g== "@babel/helper-validator-identifier@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/parser@^7.29.7", "@babel/parser@^7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17" - integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA== +"@babel/parser@^7.29.7", "@babel/parser@^7.29.8", "@babel/parser@^7.29.9": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.11.tgz#afa959e9d16c40d4d95012553cab363fc86cbcb7" + integrity sha512-OZ6+oaK7GMyg3IfCpj8JOmn8n+w2rJbDT0LLfVDTLb1ZFeKDh3AiNELgCMUw175IJHSkLl74hG0M0VLsvx+gag== dependencies: - "@babel/types" "^7.29.8" + "@babel/types" "^7.29.11" "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768" - integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw== + version "7.29.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.10.tgz#fe9920782f7ee34029165ac6f272984917a1641b" + integrity sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA== "@babel/template@^7.29.7": version "7.29.7" @@ -72,79 +72,91 @@ "@babel/types" "^7.29.7" "@babel/traverse@^7.29.7": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a" - integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg== + version "7.29.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.10.tgz#08ab387639b4a67304511259c7da6cf23a82e91c" + integrity sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ== dependencies: "@babel/code-frame" "^7.29.7" "@babel/generator" "^7.29.8" "@babel/helper-globals" "^7.29.7" - "@babel/parser" "^7.29.8" + "@babel/parser" "^7.29.9" "@babel/template" "^7.29.7" "@babel/types" "^7.29.8" debug "^4.3.1" -"@babel/types@^7.29.7", "@babel/types@^7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863" - integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg== +"@babel/types@^7.29.11", "@babel/types@^7.29.7", "@babel/types@^7.29.8": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc" + integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ== dependencies: - "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-string-parser" "^7.29.11" "@babel/helper-validator-identifier" "^7.29.7" "@biomejs/biome@^2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.5.10.tgz#c2a42fc6f9c7d9e12c1f77f63dfb6b5489c497b7" - integrity sha512-WRKXARA3kTuiV5sxqTpobJ/I0MVd4vk3pOL6wnp5az4LntFIhWTj1RWZq3DI9PCEN3lXcqy7p5aqUHzvq8AXyQ== + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.3.tgz#649bf3d08d702823a9ce62a7022fa16b4e7b64dc" + integrity sha512-va/0xPku7wnfzv+D8Zpdm5uKiGJWiV8neqoEPnRjvNQQwhl4tVXS4fETs4H0CCB2rGeqJ4cu9y4iOZOnOYKAUA== optionalDependencies: - "@biomejs/cli-darwin-arm64" "2.5.10" - "@biomejs/cli-darwin-x64" "2.5.10" - "@biomejs/cli-linux-arm64" "2.5.10" - "@biomejs/cli-linux-arm64-musl" "2.5.10" - "@biomejs/cli-linux-x64" "2.5.10" - "@biomejs/cli-linux-x64-musl" "2.5.10" - "@biomejs/cli-win32-arm64" "2.5.10" - "@biomejs/cli-win32-x64" "2.5.10" + "@biomejs/cli-android-arm64" "2.6.3" + "@biomejs/cli-android-x64" "2.6.3" + "@biomejs/cli-darwin-arm64" "2.6.3" + "@biomejs/cli-darwin-x64" "2.6.3" + "@biomejs/cli-linux-arm64" "2.6.3" + "@biomejs/cli-linux-arm64-musl" "2.6.3" + "@biomejs/cli-linux-x64" "2.6.3" + "@biomejs/cli-linux-x64-musl" "2.6.3" + "@biomejs/cli-win32-arm64" "2.6.3" + "@biomejs/cli-win32-x64" "2.6.3" -"@biomejs/cli-darwin-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.10.tgz#f1453a7f2e63f228a34645df1c33962f22a6c2a8" - integrity sha512-ItCrxKK6SXVT6flYs0qIuBd4AA3TTTl4d66Re6YI2FuGZnN85NmuYNzkiTJUyYw8qBLv69L5zTUB6uyWd++h3Q== +"@biomejs/cli-android-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.3.tgz#929aa987accb55957db62d1e11b76dbcc0a7f2e4" + integrity sha512-5JD7QQDEHxdeEb4sftEHG+lsuUyu8Cob/hVLqRELbgGlFl68Rldv/PPi2k4dRjERZ/EItJ3O6Xmv8lqsEtUBiw== -"@biomejs/cli-darwin-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.10.tgz#e21041e7bfab01cd453f609b940bf613713d0e24" - integrity sha512-yLsPU9pAmtChXDu8vhKAzErqe+LeeYuwuUB2FZMkRitsmdodxsYRa9KHrFispsUHzzOu+9HB3nP/TQxyia+Sjw== +"@biomejs/cli-android-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.3.tgz#f130f0ee898b437686d1cddd87e9ca5e5b7dc4de" + integrity sha512-70WOGKCgpT+Uqg6LFdTLqefV54/DypdZg+LnBQDO6AWvkgXO+9xhVClajQyDsIHYjnXMtI1pRO5sP+zMXlS/bw== -"@biomejs/cli-linux-arm64-musl@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.10.tgz#57fc8aee25c08ca6c14526ccc44e282e7ba96eb6" - integrity sha512-t1QAKZwQJRB4dvgJSgFiQ4BNfNPChg69BNonz854qLVxnjT3UvDzQg9mbkTJRu35ZqU0Rw10A73J8Urgbg2RPw== +"@biomejs/cli-darwin-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.3.tgz#b232a0fce9b30097f69d52382fc6e7074860b012" + integrity sha512-ezf+XEKeyJ2yYlBhrzyEEHLvfRYrhtNKc6maLzR/WcAcBccb4RVDeFxsymjBve2LO0bpIMRUAiS90D4bUBRT0A== -"@biomejs/cli-linux-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.10.tgz#aef789a71e5aa86f5a0e1aff6cb4c1b757ec5be1" - integrity sha512-VG8uQW/86a1roLaIFvtIbEigxIdzdJ190oGyg1tV7VYeQtOS+x10sflk7WbuXgw91EtZX5DlIIIej1YqkNLlcg== +"@biomejs/cli-darwin-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.3.tgz#8a31e6a26e26bba170a0ce36fff947038b733d72" + integrity sha512-Lg2tFdg4elJo+7i707PpLMs6N1hATVt4kqDo6MD/q/VnPs8rsKReKXsz1TA5GNdfRVAQNJ5GRiyK2EwNptK+MA== -"@biomejs/cli-linux-x64-musl@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.10.tgz#1aebe9f928dc72dc3c112e60f1211a4d646990c4" - integrity sha512-pgDDqp9JybHm2I0KRgzN6i4+lt8xu4iqxUwLzglUMmOmyRTU1AYBGKzh9sNMOtIjah7xoWvKHlLVetvyifzoiQ== +"@biomejs/cli-linux-arm64-musl@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.3.tgz#84defa2b38f3fcf04b8eee8c76cbf60b3fa5ef05" + integrity sha512-dYU70A89OFHzakQPE9VOPbE60JNcQt4mtxvUGQl+EgTkGtqCFxLD2RUn1SbiMu8Mu5xFscS1bfYDpoasaUrqNA== -"@biomejs/cli-linux-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.10.tgz#a82b414abf5d9668fb035ee50a710bf78a7c5fbf" - integrity sha512-4O6T0eq2heoHZN0a9UX+rWQoxXEBaKf+lRi2hbsGlHneUz9BWXM76nEWMK7Eeq8gzMxR1khQB6BFpAASpeXqGg== +"@biomejs/cli-linux-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.3.tgz#c7cb949571e8088b463385b15d60d4f1690c9242" + integrity sha512-8Stcvq98n1gs9VyLKuaWrnsd8kbXE2nKDAHtuLHGSYdGyNWN5JIIm5a28xdnmgdKzKHmiDgYyATetkEv7+zGow== -"@biomejs/cli-win32-arm64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.10.tgz#db671cf67d7040a374e8cdda5aebe73c3f4ffba0" - integrity sha512-pxAbxduPO4xq/Cvgaa2lOrs9BB0hEXmmDqfMNP4ZOffGOkUrD1/QGw9UAMpFQpX2P8MqTIIRuQKcmetum4Oa6A== +"@biomejs/cli-linux-x64-musl@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.3.tgz#6c6de66af5537443c3fe1fef429e801c556a6b5a" + integrity sha512-j6eFjUcWSphmgVTbrobEMA0wdbfNHKrGi8hRsFkc17jeS1n7nHwFACxp6jWqe9sxQqyFAeyOgt2/1bOGVcbATA== -"@biomejs/cli-win32-x64@2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.10.tgz#3bdeaf5856f21aec6b6c50a7bca5464b9cda5623" - integrity sha512-M+2dgBsl3lXRiTfgPVc2p3anS4Tocojke4rzFLScZ2Y/wmF+36dRb1iHCLiyGqOzQGyTplZH1HnEYviiAqi3nA== +"@biomejs/cli-linux-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.3.tgz#d50121c6396a00cfb84ccf4cfd5e809a9cb4aa6b" + integrity sha512-CShmx0bO6BngywT3minv8K1xsQX1ig1vsBqYTLnmDY3mBiDYHlaLRIsdHoM1pEU9dV3BN5a0uiH0IGDtYh9TWA== + +"@biomejs/cli-win32-arm64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.3.tgz#6b7564eddb8fb30c7cb0bd1f958e2ccacbf38dd7" + integrity sha512-00SthDxBPwNiT7IL/mOneif6fiEU1PXK875zhr+InpeZjovY1mF5tCuDhXo1Hh0GYyiKHKzLbItzn58SAyLhJQ== + +"@biomejs/cli-win32-x64@2.6.3": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.3.tgz#57a5dbf7c25ff435a1b5162c68d6c6f8082ab6cb" + integrity sha512-GhtOPphBWlnCepqpNk/w5eGN5oGRCzL20EbAkL1XCIpyboeVC9R642noyT43l8LSenGS4qz9IL8sKPfwY37V/Q== "@corvu/utils@~0.4.2": version "0.4.2" @@ -261,92 +273,85 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.12.tgz#afefe785949f16ac4cdd1e695935a321572dd56a" integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww== -"@formatjs/cli-native-darwin-arm64@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.24.tgz#fd3eacb6653b0fdd81f123defbb6752072478a97" - integrity sha512-2i/fKUcawvTe022vN1LcpCJMBHcN2pFgOzg3Hr24jV0QMpoZVpB5RuM8fhZCy9E/STnOTP+P5W9uuPhg5eCGUA== +"@formatjs/cli-native-darwin-arm64@1.1.29": + version "1.1.29" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.29.tgz#2a4fb5913318366349a5a489b302d94a292c83a4" + integrity sha512-kh+l/SZGTpDEH/aLjwbH9CqHaSW7htGiwljZ4vOobhtQbJXriQRRHzA/LAgJ+798QMflP6HjR74hK0BJPucqAw== -"@formatjs/cli-native-linux-arm64-musl@1.0.22": - version "1.0.22" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.22.tgz#bd9939c40cfc45d3e2ed73200906dd9aa1aa43aa" - integrity sha512-YUJ1ArreQGEdr2VaBIvpUVx3242YRFAwaKljlm0bOAckdk1JogzGnxIb5OJzFxlKiZkXWGFlO+erpRB6q0+kHg== +"@formatjs/cli-native-linux-arm64-musl@1.0.27": + version "1.0.27" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.27.tgz#a6a1b9c9c12bee3e7220b9d7c50c040c34ae11b9" + integrity sha512-HZtqjqJrTHJt5lvhFoz0Ak5TpsYYxgIdkyh5IJYlyCpcGzCC89bb8Bq4N8K+tqsJ+p/eN0ewsLuQA9T0gT+y/w== -"@formatjs/cli-native-linux-arm64@1.2.24": - version "1.2.24" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.24.tgz#8ff72faeaa658f69a2f2dc1491decc243ff08411" - integrity sha512-L0hFov7dgC6m9WM2cqolaL3zA48vpwe/gqd7MKILt+TrHR4ZlVv9CXvzXMDucn//FcTZw0NFBKgVXcHvlimY2g== +"@formatjs/cli-native-linux-arm64@1.2.29": + version "1.2.29" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.29.tgz#b2c8863468f594c6f003ae6100aa2a8ad1f88e7c" + integrity sha512-vSTXe5IXETlf1cMCRqUHuOREM5zpF1dCk542zOHv388F71LWIWz/Hdu6pEdI4oFxdkqEM3tcKFMWB306JOOAaQ== -"@formatjs/cli-native-linux-x64-musl@1.0.22": - version "1.0.22" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.22.tgz#5b137b928aff22b496ff5aea6a8f974d4188f149" - integrity sha512-hUwkDw47LQ0ESJvKsbZOWI50w/NP1hATfRrUy9mGRCRqKj1YTJ/HQxOjVfnrpiVEImjs4Ptws69RuIgxB5QGOA== +"@formatjs/cli-native-linux-x64-musl@1.0.27": + version "1.0.27" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.27.tgz#10773c860527ef011901e63bb6ca88e7cb570b1a" + integrity sha512-bv8thdLdTeYG0jcAMBAXb27tc4BiLvwa/v8US3kHLhkq7arDM5Dmvsf1+XSMNzXLuqdCU5p+OI/2m/qOb44Jqg== -"@formatjs/cli-native-linux-x64@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.24.tgz#23a421980b85a60266dbccd1d236d575ebdda54e" - integrity sha512-bp6FKYz8AQaHdeNI6rvMSk49ni4/uS+6AoxL/GiKXv2fL8zc459mkctTnwGR9Ol2in71Gq+nFKoZGFS50EHWCA== +"@formatjs/cli-native-linux-x64@1.1.29": + version "1.1.29" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.29.tgz#91db498b3c969380dbc2a14b2427743d80dbf764" + integrity sha512-GuF20VE1eqz6u5ndmYNbiIO7g/WJaKSFNAWlhLBMCWS/8ggyw5bqvFfZMgR1VUTa/oMVWfJp+5q+oi0DMc89Sw== -"@formatjs/cli-native-win32-x64@1.1.25": - version "1.1.25" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.25.tgz#509060f7e0cc227a52e4677f6421da6fad48b224" - integrity sha512-tYjaYxtfBoGClwnBMtGuMQV/hbXY3mrGR5QRo9Tzk9Uwpa573gojtI8sqhn5jkzqjKPG1Ob3vVIJxcJmSnqemg== +"@formatjs/cli-native-win32-x64@1.1.30": + version "1.1.30" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.30.tgz#233ccca3a1f3347ddfc1420fb6d59901cd9fefcf" + integrity sha512-iP5Qd7mTRu5Rn69bWnP1AgzdQ640TQQlkhiZGCGoUYSLEZ/bGTgDgzYvI4LRFIQYoS9niKDXAOETjwWAEPGoCw== "@formatjs/cli@^6.16.30": - version "6.16.30" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.16.30.tgz#436549695e81af050747abcfac3af366c96546fa" - integrity sha512-9pHgUriIJAsHfkoYuShydq5jHMp0IMQk7sdIkXgqD3m5UKTZIDfOyuJyuqJM5T55HOxz97y4iSSoWp7rfgdvNA== + version "6.16.35" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.16.35.tgz#3322b9763de94b2820fea70a75d9b56abd0580c3" + integrity sha512-JeTpErAlJ0br8uaUII4uINrfldnp6C6eIYNaR+Tj4YJTGAUtxjItUPsP8fY9cc/zMDXf8SuDL6NFle4VzgY1TA== optionalDependencies: - "@formatjs/cli-native-darwin-arm64" "1.1.24" - "@formatjs/cli-native-linux-arm64" "1.2.24" - "@formatjs/cli-native-linux-arm64-musl" "1.0.22" - "@formatjs/cli-native-linux-x64" "1.1.24" - "@formatjs/cli-native-linux-x64-musl" "1.0.22" - "@formatjs/cli-native-win32-x64" "1.1.25" + "@formatjs/cli-native-darwin-arm64" "1.1.29" + "@formatjs/cli-native-linux-arm64" "1.2.29" + "@formatjs/cli-native-linux-arm64-musl" "1.0.27" + "@formatjs/cli-native-linux-x64" "1.1.29" + "@formatjs/cli-native-linux-x64-musl" "1.0.27" + "@formatjs/cli-native-win32-x64" "1.1.30" "@formatjs/fast-memoize@3.1.7": version "3.1.7" resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.7.tgz#5f79b3756e4bcb5faee47b3a2f01216e1212c9e4" integrity sha512-zXfhLpvA6T7+efdt9JLbBwZ00tT7NsBMDVnDu8rpHeNNv8KfRZAMo2gkG0k9lK/Nzc//3kJ9pImsfuJxk3KhUA== -"@formatjs/icu-messageformat-parser@3.5.17": - version "3.5.17" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.17.tgz#7ac90ff6dde99468cd0b4a3f55344ccf8ae2ed87" - integrity sha512-cN9jhVqT7u0K9tix43fhjoUwL0nazyW6zsNIXs2QdPADr+nurPfYyssUiMqcSCGlPcCiqnYVxgSn7zBSuI+5Bg== +"@formatjs/icu-messageformat-parser@3.5.22": + version "3.5.22" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.22.tgz#8715bd7958ef43919bb9d7d1779518e11084e9eb" + integrity sha512-YuwN+dDKgssfuYwtOyGQD0CSn7K46g9cv1Fp0VgKpjZDDFvv8b8crzP990IcJhRPWHhKzCGsbUSJVIguyyFA5A== dependencies: - "@formatjs/icu-skeleton-parser" "2.1.11" + "@formatjs/icu-skeleton-parser" "2.1.12" -"@formatjs/icu-skeleton-parser@2.1.11": - version "2.1.11" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.11.tgz#09226edf55a18e3fea80304d2cc95aaeb0095436" - integrity sha512-j8cUmOJzVgkHuS0QiQ6ga76UIoLOFSAMWhs7aZJztH3aAdCOAE6vpC8KVvFB4cU10ON0y2/5oOVmPJ43s2lTwA== +"@formatjs/icu-skeleton-parser@2.1.12": + version "2.1.12" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.12.tgz#5471028b1f2516c63456aba92d109a0d230d2a0f" + integrity sha512-ca7tZMLpfHX3sUD7hP6gE/qr6Iq89EDd3ZLeRhbsB7r0UD55DGJq+tmFLzrGrnx5StahUevQmQQVtbDRHsfDHA== -"@formatjs/intl@4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-4.2.0.tgz#fadf90f2b1d02faaa68fa770f66a1f384e82653b" - integrity sha512-FbiaRjnQjjqo8FzayDEuuFLj+lCrSEDEmeXXGRpBUioaukr/+4djq4TYIpXnsVh2DWqYyOrMu8cLpc/rGmVHlw== +"@formatjs/intl@6.1.4": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-6.1.4.tgz#ea611cbd03f4ad63a87cc16604a8f4e1cb8a30d8" + integrity sha512-udBTb6Heg/sleurLkUdPq+T9B7FvhiZ+8EyauSpqkqKPF+S5TUplO/tZmok7JC86Zk+OKhP8rIGi8bYRF9WJaA== dependencies: "@formatjs/fast-memoize" "3.1.7" - "@formatjs/icu-messageformat-parser" "3.5.17" - intl-messageformat "11.2.14" + "@formatjs/icu-messageformat-parser" "3.5.22" + intl-messageformat "12.1.4" -"@internationalized/date@^3.12.3": - version "3.12.3" - resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.12.3.tgz#e11d14dbf12bf45057dd5996bac77f7c2f89ff9c" - integrity sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q== +"@internationalized/date@^3.12.4": + version "3.12.4" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.12.4.tgz#a391e01d54ca80b3cb4c3d5040d46a0b26c3f20f" + integrity sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ== dependencies: "@swc/helpers" "^0.5.0" -"@internationalized/number@^3.2.1": - version "3.6.8" - resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.8.tgz#aaa3e16fb9d64a8d7f130ccf848c626914dfdf9e" - integrity sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig== - dependencies: - "@swc/helpers" "^0.5.0" - -"@internationalized/number@^3.6.7": - version "3.6.7" - resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.7.tgz#5a0a8fa413b5f8679a59dcf37e2a74dc508b8371" - integrity sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg== +"@internationalized/number@^3.2.1", "@internationalized/number@^3.6.9": + version "3.6.9" + resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.9.tgz#dc237daa302bd44248bb03e73abf5ef7549f4902" + integrity sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g== dependencies: "@swc/helpers" "^0.5.0" @@ -370,12 +375,12 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d" + integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw== -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": +"@jridgewell/trace-mapping@0.3.31", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": version "0.3.31" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== @@ -390,7 +395,7 @@ dependencies: jsbi "^4.3.0" -"@kobalte/core@^0.13.4": +"@kobalte/core@^0.13.14": version "0.13.14" resolved "https://registry.yarnpkg.com/@kobalte/core/-/core-0.13.14.tgz#6d46ada9a427e07f73fb61123a03220277a793d7" integrity sha512-SBVB1lnvYbYt2x3t7fG0lo5fGLEbHQ6PII3NDPva8vD5e2v53On84ZVQiuIk0XI/55R4GQhden7Yem+R/XR8uQ== @@ -416,10 +421,10 @@ "@solid-primitives/refs" "^1.0.5" "@solid-primitives/utils" "^6.2.1" -"@oxc-project/types@=0.150.0": - version "0.150.0" - resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.150.0.tgz#e9be0840114655016b16f1c05ddaaa01df5b8853" - integrity sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw== +"@oxc-project/types@=0.155.0": + version "0.155.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.155.0.tgz#a3525bff41bc0575839e95d2064635dd73dfef81" + integrity sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q== "@parcel/watcher-android-arm64@2.6.0": version "2.6.0" @@ -517,10 +522,10 @@ "@swc/helpers" "^0.5.0" react-aria "^3.48.0" -"@react-types/shared@^3.36.1": - version "3.36.1" - resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.36.1.tgz#edb8081e3872ae68a4eb4a65e62233d0754ec186" - integrity sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag== +"@react-types/shared@^3.37.0": + version "3.37.0" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.37.0.tgz#bbbd3cddc2ba2764a6a915659a1ab0e628779ae9" + integrity sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg== "@restart/hooks@^0.4.9": version "0.4.16" @@ -551,80 +556,80 @@ uncontrollable "^8.0.4" warning "^4.0.3" -"@rolldown/binding-android-arm-eabi@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz#b38df5a6997454696207ac16b322a3ac1ff79e16" - integrity sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw== +"@rolldown/binding-android-arm-eabi@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz#3bdaa2e77aabbb78194500e22d46ebbdfc009558" + integrity sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw== -"@rolldown/binding-android-arm64@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz#bc2bf135c1e787f94b29bc321c1db0bd2884b5a7" - integrity sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg== +"@rolldown/binding-android-arm64@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz#1bf2dd523e6b94be1366f397b946d7fd0de67f1d" + integrity sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg== -"@rolldown/binding-darwin-arm64@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz#052049707ccc4cfe0b035aa8c37c4e55523eecbb" - integrity sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw== +"@rolldown/binding-darwin-arm64@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz#99fdcc6be79f156331099de799f726809af142e7" + integrity sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA== -"@rolldown/binding-darwin-x64@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz#ea67496811e4e1c2f76d5647f248073e204f5462" - integrity sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw== +"@rolldown/binding-darwin-x64@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz#752b56bcede7851580735a6cd48437eca936a182" + integrity sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A== -"@rolldown/binding-freebsd-x64@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz#4b4a605f8c24a7235afb5da9b4b8bc8974446bb5" - integrity sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g== +"@rolldown/binding-freebsd-x64@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz#f5104c1f4e50a63afae5d4d8c976a51d2facc8c6" + integrity sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ== -"@rolldown/binding-linux-arm-gnueabihf@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz#a36c6961fb24b86176463559dcd0edbe48ea74f1" - integrity sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw== +"@rolldown/binding-linux-arm-gnueabihf@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz#b0a53d7f4b5293687de6ef8727469a5e2ad68312" + integrity sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ== -"@rolldown/binding-linux-arm64-gnu@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz#7b43488618dd6f9d392e9adaaffd4d7f4bb785ce" - integrity sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw== +"@rolldown/binding-linux-arm64-gnu@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz#b9832ceb22aca59cef2d20e267550fc83cd60fce" + integrity sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw== -"@rolldown/binding-linux-arm64-musl@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz#8168a2e2ce7d9e28847f3019c98abf76c0ec5fe2" - integrity sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA== +"@rolldown/binding-linux-arm64-musl@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz#43d43aa8cd490ef529b37129755879c276ec899c" + integrity sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ== -"@rolldown/binding-linux-ppc64-gnu@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz#8f52aac40788feac33056f111176a7bf8fbc297a" - integrity sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw== +"@rolldown/binding-linux-ppc64-gnu@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz#455df0b3a2ee6cacc004b8dad6295b43c39906e7" + integrity sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw== -"@rolldown/binding-linux-s390x-gnu@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz#80cdfa507fd87bc37c47c5a16ca578f619ee3a69" - integrity sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ== +"@rolldown/binding-linux-s390x-gnu@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz#a2f243b64683c72cd1b2fcbe67505a89d9c9d89b" + integrity sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA== -"@rolldown/binding-linux-x64-gnu@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz#c2f89cdf3b223b2ba3cd4c0fdc760b360c02edf5" - integrity sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q== +"@rolldown/binding-linux-x64-gnu@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz#a514415b06af3517fcdbf228ebb7e06e8ed8385a" + integrity sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw== -"@rolldown/binding-linux-x64-musl@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz#3fbb6bce4c8a8260cb63fc2dba182c8649930c6c" - integrity sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ== +"@rolldown/binding-linux-x64-musl@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz#2b6ac8d0487af711741e1bc218cd49f113542f66" + integrity sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ== -"@rolldown/binding-openharmony-arm64@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz#c8cd18d7fd14222e2068cd0640bba88d2cd710f8" - integrity sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg== +"@rolldown/binding-openharmony-arm64@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz#70cdb40621eb478999a015019f68a97012f5e8cd" + integrity sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w== -"@rolldown/binding-win32-arm64-msvc@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz#cff9ed3be36979d7d77306bf9dca1a27ed17c1f0" - integrity sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww== +"@rolldown/binding-win32-arm64-msvc@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz#8345076b3d3b4c362e5e8411aae227f729840a39" + integrity sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg== -"@rolldown/binding-win32-x64-msvc@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz#6e6a3a7ac86eb46bed61e685a9e7cccfacc21b7a" - integrity sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g== +"@rolldown/binding-win32-x64-msvc@1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz#04ff5f1e8786fd31a68d040661a50e6dae3c7669" + integrity sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ== "@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1": version "1.0.1" @@ -638,7 +643,7 @@ dependencies: "@solid-primitives/utils" "^6.4.1" -"@solid-primitives/keyed@^1.2.0", "@solid-primitives/keyed@^1.2.2": +"@solid-primitives/keyed@^1.2.0", "@solid-primitives/keyed@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@solid-primitives/keyed/-/keyed-1.5.3.tgz#df60e7a079cc99ef80d22e4c03bb80eabc106392" integrity sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA== @@ -674,7 +679,7 @@ dependencies: "@solid-primitives/utils" "^6.4.1" -"@solid-primitives/resize-observer@^2.0.26": +"@solid-primitives/resize-observer@^2.0.26", "@solid-primitives/resize-observer@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.2.0.tgz#f6d76c0f765bb2008117c2e7504eb1707ef1268c" integrity sha512-9Fuu/EWBeGj+atGHRJp70HKhdfalmpjwxY8a32NZixdLNmfCJ45AfhLQNr6uOzETbbiMx4iCKlTrJ8KZCHC2Ww== @@ -722,11 +727,6 @@ resolved "https://registry.yarnpkg.com/@solid-primitives/utils/-/utils-6.4.1.tgz#aaa9006ac9dfa7b99b1060ab78669a8723ab2962" integrity sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg== -"@standard-schema/spec@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" - integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== - "@swc/helpers@^0.5.0": version "0.5.23" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a" @@ -735,99 +735,94 @@ tslib "^2.8.0" "@tabler/core@^1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.5.1.tgz#d083f17521acb7df4bc231bc79e653bfeae268c9" - integrity sha512-PI9rJq4H4lBh53YP/J+m5Uz6lqVQbsGPmWCMN34IP4KQQ/wy28YMO6a3Eiz1cQHdFFr+3MlU3yYhXdzvFGJfqw== + version "1.7.1" + resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.1.tgz#b055b3e3469bdb8aeca08ca45223a2c093e78053" + integrity sha512-sC42sr/uslr7kIZwyBPESWJKRm1E4hYAMMZyLDoyG0ZlEOlglRW0L5c1gf8x6i+QApT+AtHm10fDP1j2WgQJpg== dependencies: "@popperjs/core" "^2.11.8" "@tabler/icons-react@^3.46.0": - version "3.46.0" - resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.46.0.tgz#9840606d10aab071ad2c0dbdfcfba0eef6e9825d" - integrity sha512-CCm7xJWhDT2PH4ZIFkP6AgYKtVhq0gpYkjUN+GVh1AzmIQaa77OW0bQPBPQiTE0PsXMR9oSxFqA3qBglzPyrVQ== + version "3.49.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.49.0.tgz#bdfbd8fb2ede00dd17e91e9d7eff6f306d59f55f" + integrity sha512-Um59pm5RVNUaQjFUtG0r4oYJbZuBS3tGugAA2tgBg0WufMk34Eio3nsO30RUkLuL2giYPQUNdY9HvqltLLYyNA== dependencies: - "@tabler/icons" "3.46.0" + "@tabler/icons" "3.49.0" -"@tabler/icons@3.46.0": - version "3.46.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.46.0.tgz#28ba3f4895715863fdd78b49a5617764c9557fe5" - integrity sha512-f2RYFl3fzPwj5WO82x6en0dmkjefxEfOm16D1ByM6cj/McNiwOkL4VaPUoP9VVIrXAD9WnTSVFr70px703b//A== +"@tabler/icons@3.49.0": + version "3.49.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.49.0.tgz#8ad023d3847540250f318a73f80431f1b69d83ff" + integrity sha512-l+GnSkFETx0U26icxv1VUFnxxiTqXfCcVRiRAnyMwibupM2S2L/hSaZ9gmSgz01rkrsH/93NvLsxykqdanc6lw== -"@tanstack/match-sorter-utils@^8.19.4": - version "8.19.4" - resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz#dacf772b5d94f4684f10dbeb2518cf72dccab8a5" - integrity sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg== +"@tanstack/match-sorter-utils@^9.1.2": + version "9.2.6" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-9.2.6.tgz#a16c9e695111c3659805c1b278a7f205233dbe6e" + integrity sha512-DIpUSOhPfQ7u/qfUi/Wgm0wBO56MyBYXGvnlI0NcauDRPqdBUlAZZjW2Dxm92m+k1UWVhKw8YJ4f47+75Aujyw== dependencies: remove-accents "0.5.0" -"@tanstack/query-core@5.102.8": - version "5.102.8" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.102.8.tgz#c55c31b4f99124054805ce8ec8137c84108f3252" - integrity sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg== +"@tanstack/query-core@5.104.5": + version "5.104.5" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.104.5.tgz#251e241c537fb065ae188d0332e62cf413db863c" + integrity sha512-Q94gI8Opd+CbdVNOdj8ZCyV8WqLYitrDq1t96Xc8WZT6lY0r5iRnJLXYDxMex7EEf2v+mXvn4FcjaBKSxXZQ9A== -"@tanstack/query-core@5.103.1": - version "5.103.1" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.103.1.tgz#57c1db20679b98e793a1fe8e4a55a0619d2d681f" - integrity sha512-rms8HqTGp6zA00dM+cUQ2eBcgzNJefuu5CAMB37i/6MiGT1zulPOytCFu2a0qjLqVR2n1jENPj9woqFQNuCzWA== - -"@tanstack/query-devtools@5.103.1": - version "5.103.1" - resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.103.1.tgz#4691fc78c33e98304e6b3597d8ac67f3cd2b5ef8" - integrity sha512-xMOcnULRPEjww0A/0RnweNmcZ5hesLjTekqf8SHxf+BJH729qz6sUu5TsOWFo7J4fu3OR50MeuJUsM3BN78Kfw== +"@tanstack/query-devtools@5.104.5": + version "5.104.5" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.104.5.tgz#b26b26d6c500c5a820797006df7cc3bbb67f0276" + integrity sha512-BLVEw4lFF7aIRsfqGvI50SSR9oN46/EmlygC+3h5TUdI+fL8GnMQialL3H92TbYi7J1b/hYlUGnckBT2uG5h8w== dependencies: - "@kobalte/core" "^0.13.4" - "@solid-primitives/keyed" "^1.2.2" - "@solid-primitives/resize-observer" "^2.0.26" + "@kobalte/core" "^0.13.14" + "@solid-primitives/keyed" "^1.5.3" + "@solid-primitives/resize-observer" "^2.2.0" "@solid-primitives/storage" "^1.3.11" - "@tanstack/match-sorter-utils" "^8.19.4" - "@tanstack/query-core" "5.103.1" + "@tanstack/match-sorter-utils" "^9.1.2" + "@tanstack/query-core" "5.104.5" clsx "^2.1.1" - goober "^2.1.16" - solid-js "^1.9.7" + goober "^2.1.19" + solid-js "^1.9.15" solid-transition-group "^0.2.3" - superjson "^2.2.2" + superjson "^2.2.6" "@tanstack/react-query-devtools@^5.103.1": - version "5.103.1" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.103.1.tgz#b3548301405de0b1205bb259f12aba6cbf3266ef" - integrity sha512-l6OzwPbDCnuim8w9McFs3dC/rdXTMZBIBFMVrXzNuhUEzWheldubOu+nKRDtPf+1nLRuwavb3We+nNJq/0vV6w== + version "5.104.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.104.5.tgz#9c1cda1990667c005c363ec28743b511be0ec85f" + integrity sha512-2SMhSSYnOGSaPLuSTKLOj5nPoP4sqasOfkpTn5BLe6LIZuHzrpcqrJx0Rm8uUSN5Ob4lPAbh/1oWClnnGaR7Bg== dependencies: - "@tanstack/query-devtools" "5.103.1" + "@tanstack/query-devtools" "5.104.5" "@tanstack/react-query@^5.102.8": - version "5.102.8" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.102.8.tgz#bdc5c24485258e6ae2e7ecac0a4cf58c59937839" - integrity sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A== + version "5.104.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.104.5.tgz#a7c4d9317fbbdab79a1cb0c6191d3fe689a823f8" + integrity sha512-RORgFe052U+93weaNuqg7qHq7vNVNzXN/VrXqD4bbAuQCWUqOl2lV5Ui9flWk5CVvMyL/Bh27QWhzwH0aaOx9g== dependencies: - "@tanstack/query-core" "5.102.8" + "@tanstack/query-core" "5.104.5" -"@tanstack/react-store@^0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.11.1.tgz#e1e58036ffffa58f5b5d8826c2389f97502fac2c" - integrity sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ== +"@tanstack/react-store@^0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.11.2.tgz#14f81f98f43b04c34e76d348de3fe8488b3c3939" + integrity sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ== dependencies: - "@tanstack/store" "0.11.1" + "@tanstack/store" "0.11.2" use-sync-external-store "^1.6.0" "@tanstack/react-table@^9.2.4": - version "9.2.4" - resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-9.2.4.tgz#4324dd0e08d4ca53a433110d8694ad82a4606bc7" - integrity sha512-Rzp1Q4e0/nIgEjmISYR5HeEgLTNtrG+C7NFZf/AbCxPO5hg+zC3yNGwcoECigUk8SFzzvhXbd2rFdtP2ZiGrfA== + version "9.3.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-9.3.1.tgz#7030e813d412f54db6dd34af351774dd1caa929e" + integrity sha512-E6Ya5TnY6EaGyZYIUqig/29Lr/gJSAubQkLJFqiWkUl5nRCr0Xr9hvJot+NiRTtG+TzGxHTiqUW3bnLtQxlHug== dependencies: - "@tanstack/react-store" "^0.11.1" - "@tanstack/table-core" "9.2.4" + "@tanstack/react-store" "^0.11.2" + "@tanstack/table-core" "9.3.1" -"@tanstack/store@0.11.1", "@tanstack/store@^0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.11.1.tgz#063f634fe3cc925ad83704581a7be75301bec612" - integrity sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA== +"@tanstack/store@0.11.2", "@tanstack/store@^0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.11.2.tgz#cea02e554a483c7334a64a3ca2c0e4b703ba8531" + integrity sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q== -"@tanstack/table-core@9.2.4": - version "9.2.4" - resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-9.2.4.tgz#8d1b339d6118d306ded4ec8516419320ad2b9e57" - integrity sha512-GwdDyGGr6UXAtubF14yAwcXvdaogqfsQgIk89Suebjxob/Rjq2xvfmXsjDF1rQmKmhHsJm9TOY7myxv3i6geJw== +"@tanstack/table-core@9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-9.3.1.tgz#21ea78f0a6dc5fc3a8074c255d5f4841c12f8729" + integrity sha512-zQvlxSNi2qGM3L4jh85Z6hqMfdIR5qOfD9KHOU32nuKFJGN0RlJ7UvV1lsPb1fPVhpFYI0VP3jJGxLdLDpTKjQ== dependencies: - "@tanstack/store" "^0.11.1" + "@tanstack/store" "^0.11.2" "@testing-library/dom@^10.4.2": version "10.4.2" @@ -943,9 +938,9 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*", "@types/node@>=20.0.0", "@types/node@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.2.tgz#11bfb8e00bafe728d2113e474a7637becc9ab279" - integrity sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g== + version "26.6.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.5.tgz#cb35209e7767c503551b5fa42a48231c7441d979" + integrity sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg== dependencies: undici-types "~8.9.0" @@ -981,14 +976,7 @@ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -"@types/react@*", "@types/react@>=16.9.11": - version "19.2.18" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.18.tgz#eb0b6a1fb635d1a9692d5f84a3495bd8ad153707" - integrity sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w== - dependencies: - csstype "^3.2.2" - -"@types/react@^19.3.0": +"@types/react@*", "@types/react@>=16.9.11", "@types/react@^19.3.0": version "19.3.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.3.0.tgz#3b3ce8d8c1627fb5ccd4dadd57ef445c5a153b77" integrity sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg== @@ -1016,9 +1004,9 @@ integrity sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA== "@types/ws@^8.18.1": - version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" - integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== + version "8.18.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.2.tgz#25d6321b30ef0fddf6cc7052b4eef9ce4e412b6e" + integrity sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w== dependencies: "@types/node" "*" @@ -1123,85 +1111,40 @@ integrity sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g== "@uiw/react-textarea-code-editor@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.1.tgz#8ca1b706a3081a51c68bc0df91c9c3cdadd9944e" - integrity sha512-AERRbp/d85vWR+UPgsB5hEgerNXuyszdmhWl2fV2H2jN63jgOobwEnjIpb76Vwy8SaGa/AdehaoJX2XZgNXtJA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.2.tgz#bbd5962611e054849ec6c1049681ddb428ea403d" + integrity sha512-jI+ZmGlLad+d92FHNljoyqGSS1uxK0WKm0XRLvS/7j2K0s2PpBPgkfMbwrfGrFumSoMHJYkzW/flXeA9yfqR+Q== dependencies: "@babel/runtime" "^7.18.6" rehype "~13.0.0" rehype-prism-plus "2.0.0" "@ungap/structured-clone@^1.0.0": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.3.tgz#094041e1a4cb1987f038335421281ac8be390bcc" - integrity sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg== + version "1.4.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a" + integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ== "@vitejs/plugin-react@^6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz#47fa1a2f956e88eaf778ebf65ddce8e2b36597e7" - integrity sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw== + version "6.1.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz#924d47e890a78cdf2b72f3f41fe8ab98e089b81e" + integrity sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw== dependencies: "@rolldown/pluginutils" "^1.0.1" -"@vitest/expect@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.1.11.tgz#5f580d1f9cdbba314dbf23b2d911f8eb23878f5f" - integrity sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw== +"@vitest/mocker@5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-5.0.5.tgz#0d591e22de595595b549e14af1115c69868ba099" + integrity sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg== dependencies: - "@standard-schema/spec" "^1.1.0" - "@types/chai" "^5.2.2" - "@vitest/spy" "4.1.11" - "@vitest/utils" "4.1.11" - chai "^6.2.2" - tinyrainbow "^3.1.0" - -"@vitest/mocker@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.1.11.tgz#8e2906361bc5dfa271757a858ae80643118fcbb4" - integrity sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ== - dependencies: - "@vitest/spy" "4.1.11" + "@jridgewell/trace-mapping" "0.3.31" + "@vitest/spy" "5.0.5" estree-walker "^3.0.3" - magic-string "^0.30.21" + magic-string "^1.4.3" -"@vitest/pretty-format@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.1.11.tgz#8b28eb8240771d6ea970e33beaeb41384b51868e" - integrity sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw== - dependencies: - tinyrainbow "^3.1.0" - -"@vitest/runner@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.1.11.tgz#bfbad98c8d6c3f1fb4df12056ad569821ff77f21" - integrity sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw== - dependencies: - "@vitest/utils" "4.1.11" - pathe "^2.0.3" - -"@vitest/snapshot@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.1.11.tgz#df461eb165924a3155986dde68e13360f53f3d4c" - integrity sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog== - dependencies: - "@vitest/pretty-format" "4.1.11" - "@vitest/utils" "4.1.11" - magic-string "^0.30.21" - pathe "^2.0.3" - -"@vitest/spy@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.1.11.tgz#0add45cae953afed9c88f98e2f6fc9164558c32a" - integrity sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA== - -"@vitest/utils@4.1.11": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.1.11.tgz#9b27a4293b827942b223539bfab1bd9f7eada31b" - integrity sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ== - dependencies: - "@vitest/pretty-format" "4.1.11" - convert-source-map "^2.0.0" - tinyrainbow "^3.1.0" +"@vitest/spy@5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-5.0.5.tgz#d5629d07a1182899f2159f700d0cd4e73785d6b9" + integrity sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw== ansi-regex@^5.0.1: version "5.0.1" @@ -1281,10 +1224,10 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chai@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" - integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== +chai@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.3.0.tgz#91f89eb789c2bb1ae1787d78c8518e1d3cebfc07" + integrity sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg== character-entities-html4@^2.0.0: version "2.1.0" @@ -1333,20 +1276,15 @@ convert-source-map@^1.5.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - cookie@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== copy-anything@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.1.tgz#2734bcffa1f01f8e2afb2232f1ee31cd5fcc6834" - integrity sha512-AoT6Imdr98feSpFfmFwTFN73ccdr7uFPf27cBCgYvyyRyn1BzLRxMvrHNmwXO5LJMddRy4Rdhw2b1h7vSMKsEw== + version "4.1.5" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.5.tgz#11866cab9f08ef1f98daac8c468e10d2830294e3" + integrity sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ== cosmiconfig@^7.0.0: version "7.1.0" @@ -1394,9 +1332,9 @@ decode-named-character-reference@^1.0.0: character-entities "^2.0.0" decode-uri-component@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.0.tgz#4592fa1e1d640ec5e2760e2e168ad2ab5f2c9da1" - integrity sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg== + version "0.5.1" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.1.tgz#7f66b25bf435df723f9af55713dcf860a1c297d2" + integrity sha512-vWHQkXgg2cE3BB4rR0c+PYw9BCQzLoNIRMI7AunIkZst74tw/vpPp7VLoYtsR/cy1f55g3XDWUslfeCZjiZJyA== deepmerge@^2.1.1: version "2.2.1" @@ -1460,7 +1398,7 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-module-lexer@^2.0.0: +es-module-lexer@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.2.tgz#311fa4f40168c1975c505477c51b23234d41ad55" integrity sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw== @@ -1482,7 +1420,7 @@ estree-walker@^3.0.3: dependencies: "@types/estree" "^1.0.0" -expect-type@^1.3.0: +expect-type@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.4.0.tgz#24edf7f0cc69a44d008567ba4594ab96f3c3a3d6" integrity sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA== @@ -1549,7 +1487,7 @@ generate-password-browser@^1.1.0: buffer "^6.0.3" randombytes "^2.0.5" -goober@^2.1.16: +goober@^2.1.19: version "2.1.19" resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.19.tgz#a4b4dcdbba9325b8c4d7380099f9f281f27c6a6f" integrity sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg== @@ -1559,10 +1497,10 @@ graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -happy-dom@^20.14.5: - version "20.14.5" - resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.14.5.tgz#d76cfbb926ebde0575deb3bf92cb3e6bb88b8176" - integrity sha512-x/RzkpWO40bTjIoT30iQtt64FLLmH/iRcUCN2X//bLx7H3ifkdfPXyqsro/OYtqzIAhiLMMA7mmiOR9C3NOKjQ== +happy-dom@^21.0.4: + version "21.0.4" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-21.0.4.tgz#3b9e4b23a6ef5f94f6db242857a99742dd14f943" + integrity sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw== dependencies: "@types/node" ">=20.0.0" "@types/whatwg-mimetype" "^3.0.2" @@ -1572,7 +1510,7 @@ happy-dom@^20.14.5: whatwg-mimetype "^3.0.0" ws "^8.21.0" -hasown@^2.0.3: +hasown@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== @@ -1743,13 +1681,13 @@ inline-style-parser@0.2.7: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== -intl-messageformat@11.2.14: - version "11.2.14" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-11.2.14.tgz#610e4a21511d85dc06634424409d60bb1e9e441f" - integrity sha512-9f2VD1HFuxUvMw0RxsaP8WmMns6JRTnsNB/zghTFrp11ZktiXWwVDeZBPQchBKEmo+Gx/ZhxI7Qht7YglFD4PA== +intl-messageformat@12.1.4: + version "12.1.4" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-12.1.4.tgz#6d3ad7a0256dca2aae8ee76a25e44c61f6bc2d09" + integrity sha512-JUkmv3cAEyijN3oiRiV3ctel7yVMuUCgfh7Pu8pj1/z6L1DSEPRyhuMaLjVw3pGmGJ5RDH54WVFxfMHZWTwyHw== dependencies: "@formatjs/fast-memoize" "3.1.7" - "@formatjs/icu-messageformat-parser" "3.5.17" + "@formatjs/icu-messageformat-parser" "3.5.22" invariant@^2.2.4: version "2.2.4" @@ -1776,12 +1714,12 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.16.1: - version "2.16.2" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" - integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== +is-core-module@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.17.0.tgz#75c1c76236c9f7e38ddbfff292e127dc13b21027" + integrity sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA== dependencies: - hasown "^2.0.3" + hasown "^2.0.4" is-decimal@^2.0.0: version "2.0.1" @@ -1941,17 +1879,17 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== -magic-string@^0.30.21: - version "0.30.21" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" - integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== +magic-string@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.4.tgz#35c82a312db8064966cd42f0c6427b865d0482bc" + integrity sha512-jQz9sVhrZuphH4NrAIHSgkDJCluQO7Ioi4KGRLa7aTUjAODQ13hENX0YIytqYE/rIkO485gQG2LeBxJaW53kkQ== dependencies: - "@jridgewell/sourcemap-codec" "^1.5.5" + "@jridgewell/sourcemap-codec" "^1.6.0" mdast-util-from-markdown@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" - integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz#a92a9130c95e0e137e7cffaf85588a7da9e4bda6" + integrity sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -2032,17 +1970,19 @@ mdast-util-to-hast@^13.0.0: vfile "^6.0.0" mdast-util-to-markdown@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" - integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz#40e9db55768d7dd11fae4462531cf0223a2b4f35" + integrity sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" longest-streak "^3.0.0" mdast-util-phrasing "^4.0.0" mdast-util-to-string "^4.0.0" + micromark-util-character "^2.0.0" micromark-util-classify-character "^2.0.0" micromark-util-decode-string "^2.0.0" + micromark-util-html-tag-name "^2.0.0" unist-util-visit "^5.0.0" zwitch "^2.0.0" @@ -2059,26 +1999,27 @@ memoize-one@^6.0.0: integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== micromark-core-commonmark@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" - integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz#ff748f2327717b3675f0864e5aabc4f2a0028da3" + integrity sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w== dependencies: decode-named-character-reference "^1.0.0" devlop "^1.0.0" micromark-factory-destination "^2.0.0" micromark-factory-label "^2.0.0" - micromark-factory-space "^2.0.0" + micromark-factory-space "^2.1.0" micromark-factory-title "^2.0.0" micromark-factory-whitespace "^2.0.0" micromark-util-character "^2.0.0" micromark-util-chunked "^2.0.0" micromark-util-classify-character "^2.0.0" + micromark-util-edit-map "^1.0.0" micromark-util-html-tag-name "^2.0.0" micromark-util-normalize-identifier "^2.0.0" micromark-util-resolve-all "^2.0.0" micromark-util-subtokenize "^2.0.0" micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" + micromark-util-types "^2.0.3" micromark-factory-destination@^2.0.0: version "2.0.1" @@ -2099,10 +2040,10 @@ micromark-factory-label@^2.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromark-factory-space@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" - integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== +micromark-factory-space@^2.0.0, micromark-factory-space@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz#afc208b018c330f17f438731c2014fdff1cd154f" + integrity sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q== dependencies: micromark-util-character "^2.0.0" micromark-util-types "^2.0.0" @@ -2176,6 +2117,13 @@ micromark-util-decode-string@^2.0.0: micromark-util-decode-numeric-character-reference "^2.0.0" micromark-util-symbol "^2.0.0" +micromark-util-edit-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz#0b5dd9ab4295f7389472c5fee2b61ffd45774cb2" + integrity sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q== + dependencies: + micromark-util-types "^2.0.0" + micromark-util-encode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" @@ -2224,15 +2172,15 @@ micromark-util-symbol@^2.0.0: resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== -micromark-util-types@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" - integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== +micromark-util-types@^2.0.0, micromark-util-types@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.3.tgz#e7b04fa58589199c7ef58b37cd696fcb2bce44e7" + integrity sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg== micromark@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" - integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + version "4.0.3" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.3.tgz#6d29c0a5383c1dda7975a6c897e0e18d5694f10d" + integrity sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw== dependencies: "@types/debug" "^4.0.0" debug "^4.0.0" @@ -2244,6 +2192,7 @@ micromark@^4.0.0: micromark-util-chunked "^2.0.0" micromark-util-combine-extensions "^2.0.0" micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-edit-map "^1.0.0" micromark-util-encode "^2.0.0" micromark-util-normalize-identifier "^2.0.0" micromark-util-resolve-all "^2.0.0" @@ -2262,10 +2211,10 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.18: - version "3.3.19" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.19.tgz#336d4aa4bcd4fb24d2cddede7ffeae40bec03f0a" - integrity sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug== +nanoid@^3.3.20: + version "3.3.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.20.tgz#dfd4444d85aa10111ef66ba85ed72e08ba0855d8" + integrity sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ== node-addon-api@^7.0.0: version "7.1.1" @@ -2285,10 +2234,10 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -obug@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.4.tgz#9090d8a548a522517915d2aa6aae907197ac6cf8" - integrity sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA== +obug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/obug/-/obug-3.0.0.tgz#2ddfc17d1c1dd8c825eebd127099da307c919299" + integrity sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw== parent-module@^1.0.0: version "1.0.1" @@ -2347,11 +2296,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathe@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" - integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -2362,7 +2306,7 @@ picocolors@1.1.1, picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^4.0.3, picomatch@^4.0.4, picomatch@^4.0.7: +picomatch@^4.0.4, picomatch@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== @@ -2372,14 +2316,14 @@ postcss-simple-vars@^7.0.1: resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -postcss@^8.5.28: - version "8.5.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9" - integrity sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A== +postcss@^8.5.28, postcss@^8.5.30: + version "8.5.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.31.tgz#d44731fa02f8ae8371629ec7220e2ae0dbdf23ec" + integrity sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg== dependencies: - nanoid "^3.3.18" + nanoid "^3.3.20" picocolors "^1.1.1" - source-map-js "^1.2.1" + source-map-js "^1.2.2" pretty-format@^27.0.2: version "27.5.1" @@ -2455,18 +2399,18 @@ randombytes@^2.0.5: safe-buffer "^5.1.0" react-aria@^3.48.0: - version "3.51.0" - resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.51.0.tgz#b8129df769eda51a0f92d256fddcc105badf6ed7" - integrity sha512-AyWLw0XR38cFPwBu/ErgGaVrc5dupLEKmRlMXTGvFKOtbaGRQ2+yQJkjVhpdHhoRhU4+G+tJDFeHDTS8tK3bfQ== + version "3.53.1" + resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.53.1.tgz#8c78b8e1a2c680d404c1aabe75f31c98d2444b10" + integrity sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg== dependencies: - "@internationalized/date" "^3.12.3" - "@internationalized/number" "^3.6.7" + "@internationalized/date" "^3.12.4" + "@internationalized/number" "^3.6.9" "@internationalized/string" "^3.2.10" - "@react-types/shared" "^3.36.1" + "@react-types/shared" "^3.37.0" "@swc/helpers" "^0.5.0" aria-hidden "^1.2.3" clsx "^2.0.0" - react-stately "3.49.0" + react-stately "3.51.0" use-sync-external-store "^1.6.0" react-bootstrap@^2.10.10: @@ -2489,9 +2433,9 @@ react-bootstrap@^2.10.10: warning "^4.0.3" react-dom@^19.3.0: - version "19.3.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.0.tgz#a0ff19161d7f4e9be5469a07278b0c06e00f9001" - integrity sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q== + version "19.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.1.tgz#b384c53cfcbe9c2a5d09d863b98455de9d68afe2" + integrity sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ== dependencies: scheduler "^0.28.0" @@ -2500,14 +2444,14 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-intl@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-10.2.0.tgz#576783362ada9bc9a8ced594b1d8ff1225a08f2f" - integrity sha512-b7wiqmo/fWQZ5Et7f8Qsh58rD+6STV/KrX4NuVjmpGFAxk2vqgEwGKDlW0uRfWG2mg4zt7sWnxvOBfLPMDkMYA== +react-intl@^12.1.5: + version "12.1.5" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.5.tgz#a79322b86d9281a6e77430d5d12d4dca70635843" + integrity sha512-nlyc4qcORgNTUmSHM9ZBoYhuK57yzPlV1oqlo7mwhHDrUDNC+wUzGZufwMjayXo7xqmQyQywAlYllaEBbbfqew== dependencies: - "@formatjs/icu-messageformat-parser" "3.5.17" - "@formatjs/intl" "4.2.0" - intl-messageformat "11.2.14" + "@formatjs/icu-messageformat-parser" "3.5.22" + "@formatjs/intl" "6.1.4" + intl-messageformat "12.1.4" react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1" @@ -2579,15 +2523,15 @@ react-select@^5.10.2: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.2.0" -react-stately@3.49.0: - version "3.49.0" - resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.49.0.tgz#bc7fda7d0245e2a1239a8f0451950b27639d0e17" - integrity sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A== +react-stately@3.51.0: + version "3.51.0" + resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.51.0.tgz#115925ddfdd4838baedc7d1123ffcb5f937230fc" + integrity sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw== dependencies: - "@internationalized/date" "^3.12.3" - "@internationalized/number" "^3.6.7" + "@internationalized/date" "^3.12.4" + "@internationalized/number" "^3.6.9" "@internationalized/string" "^3.2.10" - "@react-types/shared" "^3.36.1" + "@react-types/shared" "^3.37.0" "@swc/helpers" "^0.5.0" use-sync-external-store "^1.6.0" @@ -2609,9 +2553,9 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5: prop-types "^15.6.2" react@^19.3.0: - version "19.3.0" - resolved "https://registry.yarnpkg.com/react/-/react-19.3.0.tgz#e20aa3a14687075ce863351b5878b0608c3fbfb2" - integrity sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog== + version "19.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-19.3.1.tgz#26e9523fc36ea06be5050ebb780249a133260ad7" + integrity sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw== readdirp@^5.0.0: version "5.1.1" @@ -2708,12 +2652,12 @@ resolve-from@^4.0.0: integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve@^1.19.0: - version "1.22.12" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" - integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== + version "1.22.13" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.13.tgz#d542f97758d768f36091251a6b07540b4343f7b5" + integrity sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA== dependencies: es-errors "^1.3.0" - is-core-module "^2.16.1" + is-core-module "^2.17.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2722,29 +2666,29 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -rolldown@~1.2.6: - version "1.2.9" - resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.9.tgz#b449d73b7aeaf0097adfc25967a76fa1bf304c56" - integrity sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg== +rolldown@~1.2.14: + version "1.2.15" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.15.tgz#d232b8201e1be3471d636375fe10af1dfc4eac35" + integrity sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g== dependencies: - "@oxc-project/types" "=0.150.0" + "@oxc-project/types" "=0.155.0" "@rolldown/pluginutils" "^1.0.0" optionalDependencies: - "@rolldown/binding-android-arm-eabi" "1.2.9" - "@rolldown/binding-android-arm64" "1.2.9" - "@rolldown/binding-darwin-arm64" "1.2.9" - "@rolldown/binding-darwin-x64" "1.2.9" - "@rolldown/binding-freebsd-x64" "1.2.9" - "@rolldown/binding-linux-arm-gnueabihf" "1.2.9" - "@rolldown/binding-linux-arm64-gnu" "1.2.9" - "@rolldown/binding-linux-arm64-musl" "1.2.9" - "@rolldown/binding-linux-ppc64-gnu" "1.2.9" - "@rolldown/binding-linux-s390x-gnu" "1.2.9" - "@rolldown/binding-linux-x64-gnu" "1.2.9" - "@rolldown/binding-linux-x64-musl" "1.2.9" - "@rolldown/binding-openharmony-arm64" "1.2.9" - "@rolldown/binding-win32-arm64-msvc" "1.2.9" - "@rolldown/binding-win32-x64-msvc" "1.2.9" + "@rolldown/binding-android-arm-eabi" "1.2.15" + "@rolldown/binding-android-arm64" "1.2.15" + "@rolldown/binding-darwin-arm64" "1.2.15" + "@rolldown/binding-darwin-x64" "1.2.15" + "@rolldown/binding-freebsd-x64" "1.2.15" + "@rolldown/binding-linux-arm-gnueabihf" "1.2.15" + "@rolldown/binding-linux-arm64-gnu" "1.2.15" + "@rolldown/binding-linux-arm64-musl" "1.2.15" + "@rolldown/binding-linux-ppc64-gnu" "1.2.15" + "@rolldown/binding-linux-s390x-gnu" "1.2.15" + "@rolldown/binding-linux-x64-gnu" "1.2.15" + "@rolldown/binding-linux-x64-musl" "1.2.15" + "@rolldown/binding-openharmony-arm64" "1.2.15" + "@rolldown/binding-win32-arm64-msvc" "1.2.15" + "@rolldown/binding-win32-x64-msvc" "1.2.15" rooks@^9.9.0: version "9.9.0" @@ -2764,9 +2708,9 @@ safe-buffer@^5.1.0: integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== sass@^1.104.1: - version "1.104.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.104.1.tgz#29a4bdb33c48a8bb656e300a33afcafbfcbc8491" - integrity sha512-yDA+1aIG3EHgN4V/BvuhCvu61FF6hEd4e+9DxikUm9U0CAGuvdIZ/UYy7qbOxjhbbWQraoyLlMuzdRGOSV5Bmw== + version "1.106.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.106.1.tgz#53b3df8ebfe0e5e0bc111afe54bd2f6fda089f9c" + integrity sha512-kD+Gi66vycZcbJt+42NC4z4PXNMcCajVrJPhqoKKXy5cOBMmJQt052CxIBcRjwYXeImHYOpu/ydWWGZQIenl7A== dependencies: chokidar "^5.0.0" immutable "^5.1.5" @@ -2779,39 +2723,34 @@ scheduler@^0.28.0: resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.28.0.tgz#03d3ca348aeaf8b7502f64468f217b27523f9695" integrity sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw== -seroval-plugins@~1.5.4: - version "1.5.6" - resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.5.6.tgz#7bca495aceb5b2428b9cb44b4b09e67c2de1c948" - integrity sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ== +seroval-plugins@~1.6.8: + version "1.6.12" + resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.6.12.tgz#a8f6013fc0fb68f797f53cde12deb25c3e9fbfee" + integrity sha512-+reiwp/lYxiyctSrdvX7OQ1YLrcbWYgBiZrhSFL+V43b0/DGRLNVSDyZINbQe+tWplPOI2fw2dPrdv6hcc09kQ== -seroval@~1.5.4: - version "1.5.6" - resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.5.6.tgz#68d4f6a05c3bde25daaaea6b7220146ff42353ca" - integrity sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA== +seroval@~1.6.8: + version "1.6.12" + resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.6.12.tgz#d6ad056e44e52e666a7ca7919a67294eb13ecd3b" + integrity sha512-CqbIVfk/7GL3ITwZI55lcPbpQbjHZ2DD5cUj4iH7E9YsCfHNJV1mx7yNp86Z84w5IpmkZZS/2s5vPOWmVWSdQg== set-cookie-parser@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" - integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== - -siginfo@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" - integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + version "2.7.3" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.3.tgz#ded8e573414ceb6f5752f610dc9751f4eda0295d" + integrity sha512-irZHtTqurLna57KQG/s+fzOwv/J6XSH4cs8M/vsrY3uCaQ3S6+QJguAH0WdN47SXlk82voMZ4naWMQf8fdEoIg== signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -solid-js@^1.9.7: - version "1.9.15" - resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.15.tgz#209cbd4e0c7108d1e9bd1537ea7f91e03277a763" - integrity sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg== +solid-js@^1.9.15: + version "1.9.17" + resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.17.tgz#9f8444b3667f756c233dce15f76e431cf9a86480" + integrity sha512-4GAmLOWPfNV8H9Jfn2LSFTpNEG6jiOKPiwd5lnLGQtehHaX6l3TOLzJqQ7mqwB/3k1Yy3zyumFVopmc/tm34ow== dependencies: csstype "^3.1.0" - seroval "~1.5.4" - seroval-plugins "~1.5.4" + seroval "~1.6.8" + seroval-plugins "~1.6.8" solid-presence@^0.2.0: version "0.2.0" @@ -2835,10 +2774,10 @@ solid-transition-group@^0.2.3: "@solid-primitives/refs" "^1.0.5" "@solid-primitives/transition-group" "^1.0.2" -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.2.tgz#79fb88d97535a21cb27162cb05d211b67acea4ab" + integrity sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw== source-map@^0.5.7: version "0.5.7" @@ -2855,15 +2794,10 @@ split-on-first@^3.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== -stackback@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" - integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== - -std-env@^4.0.0-rc.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.2.0.tgz#8ebe0ec60485668ab47227b312f4254cdf80c9d3" - integrity sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw== +std-env@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.3.0.tgz#4e7231b61fd83219aa7f76f71fa8818b07e41321" + integrity sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag== stringify-entities@^4.0.0: version "4.0.4" @@ -2899,7 +2833,7 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -superjson@^2.2.2: +superjson@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.6.tgz#a223a3a988172a5f9656e2063fe5f733af40d099" integrity sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA== @@ -2921,17 +2855,17 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinybench@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" - integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== +tinybench@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.2.2.tgz#e03ffd4d46fabebae1d5bb292bd25e39505551ee" + integrity sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA== -tinyexec@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.3.0.tgz#aacc1dbb1d4e93e6ad8dd64944e09f9ad147a474" - integrity sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ== +tinyexec@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.3.1.tgz#16a2e3c6e23fafce72640e678e651db36145b9c6" + integrity sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA== -tinyglobby@^0.2.15, tinyglobby@^0.2.17: +tinyglobby@^0.2.17: version "0.2.17" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== @@ -2939,11 +2873,6 @@ tinyglobby@^0.2.15, tinyglobby@^0.2.17: fdir "^6.5.0" picomatch "^4.0.4" -tinyrainbow@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.1.1.tgz#c0168387d3d8d70b6b3c2c0936de5fee738cea20" - integrity sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw== - tmp@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059" @@ -3058,22 +2987,22 @@ unist-util-stringify-position@^4.0.0: dependencies: "@types/unist" "^3.0.0" -unist-util-visit-parents@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" - integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== +unist-util-visit-parents@^6.0.0, unist-util-visit-parents@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.1.0.tgz#eff74226782b378957731bcf62cf5e9d8a080606" + integrity sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" unist-util-visit@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" - integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== + version "5.2.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.2.0.tgz#914256bc5e1801e280c12970fad4328352645974" + integrity sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" - unist-util-visit-parents "^6.0.0" + unist-util-visit-parents "^6.1.0" use-isomorphic-layout-effect@^1.2.0: version "1.2.1" @@ -3081,9 +3010,9 @@ use-isomorphic-layout-effect@^1.2.0: integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== use-sync-external-store@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" - integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + version "1.7.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz#6dcb66ef569e02f186af6b3d575f414ce746e18f" + integrity sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A== vfile-location@^5.0.0: version "5.0.3" @@ -3122,44 +3051,37 @@ vite-plugin-checker@^0.14.5: proper-lockfile "^4.1.2" tiny-invariant "^1.3.3" -"vite@^6.0.0 || ^7.0.0 || ^8.0.0", vite@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.0.tgz#f9565cfd4879d58d28fa64d3c7aff3b372d2ccc8" - integrity sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ== +vite@^8.3.0: + version "8.3.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.5.tgz#9614f66026cb92fe486cb9d3c90780b2a69fc85d" + integrity sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ== dependencies: lightningcss "^1.33.0" picomatch "^4.0.7" - postcss "^8.5.28" - rolldown "~1.2.6" + postcss "^8.5.30" + rolldown "~1.2.14" tinyglobby "^0.2.17" optionalDependencies: fsevents "~2.3.3" -vitest@^4.1.11: - version "4.1.11" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.1.11.tgz#1653c1521ae917f960d9b21877797c47dfd8bf21" - integrity sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw== +vitest@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-5.0.5.tgz#266e335f4177541e3e60f876e0694f966a9601f2" + integrity sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg== dependencies: - "@vitest/expect" "4.1.11" - "@vitest/mocker" "4.1.11" - "@vitest/pretty-format" "4.1.11" - "@vitest/runner" "4.1.11" - "@vitest/snapshot" "4.1.11" - "@vitest/spy" "4.1.11" - "@vitest/utils" "4.1.11" - es-module-lexer "^2.0.0" - expect-type "^1.3.0" - magic-string "^0.30.21" - obug "^2.1.1" - pathe "^2.0.3" - picomatch "^4.0.3" - std-env "^4.0.0-rc.1" - tinybench "^2.9.0" - tinyexec "^1.0.2" - tinyglobby "^0.2.15" - tinyrainbow "^3.1.0" - vite "^6.0.0 || ^7.0.0 || ^8.0.0" - why-is-node-running "^2.3.0" + "@types/chai" "^5.2.2" + "@vitest/mocker" "5.0.5" + chai "^6.3.0" + es-module-lexer "^2.3.2" + expect-type "^1.4.0" + magic-string "^1.4.3" + obug "^3.0.0" + picomatch "^4.0.7" + std-env "^4.3.0" + tinybench "^6.2.2" + tinyexec "^1.3.1" + tinyglobby "^0.2.17" + why-is-node-running "3.2.1" warning@^4.0.0, warning@^4.0.3: version "4.0.3" @@ -3178,18 +3100,15 @@ whatwg-mimetype@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -why-is-node-running@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" - integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== - dependencies: - siginfo "^2.0.0" - stackback "0.0.2" +why-is-node-running@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-3.2.1.tgz#f66f0d61f861e4d3965a7ad63645a953f550830c" + integrity sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ== ws@^8.21.0: - version "8.21.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.3.tgz#660b4faddb6a3e575c86e078126919961f4de4fc" - integrity sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw== + version "8.22.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.0.tgz#40f16e1d7588ac0575041be5c49b8eb261c28e73" + integrity sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg== yaml@^1.10.0: version "1.10.3" From d1314199079865ccb695b6dbe935e8ff36f887be Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:38 -0800 Subject: [PATCH 44/76] Show certbot's failure reason in the UI instead of "Internal Error" Co-Authored-By: Claude Opus 5.5 --- backend/internal/certificate.js | 108 ++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 9f81b764d..5a9958bc5 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -24,6 +24,104 @@ const certbotCommand = "certbot"; const certbotLogsDir = "/data/logs"; const certbotWorkDir = "/tmp/letsencrypt-lib"; +// Certbot output lines that say nothing about why a request failed +const certbotNoise = [ + /^Saving debug log/, + /^Ask for help/, + /^See the logfile/, + /^(- )+-?$/, + /^Processing \//, + /^Renewing an existing/, + /^Requesting a certificate/, + /^Plugins selected/, + /^Account registered/, + /^Waiting \d+ seconds/, + /^All renewals failed/, + /^\S+\/fullchain\.pem \(failure\)$/, + /^\d+ renew failure\(s\)/, +]; + +// Plain-language hints for failures that are common and fixable outside NPM +const certbotHints = [ + [ + /expected \S+ to be a symlink/i, + () => + "This certificate's files in /etc/letsencrypt were copied without their symlinks (live/ must link to archive/). Restore /etc/letsencrypt from a copy made with tar or rsync -a, or delete this certificate and create it again.", + ], + [ + /Renewal configuration file \S+ is broken/i, + () => + "This certificate's files in /etc/letsencrypt are damaged or missing. Restore /etc/letsencrypt from a backup, or delete this certificate and create it again.", + ], + [ + /\b9109\b.*?location: ([0-9a-f.:]+)/i, + (m) => { + const ip = m[1].replace(/\.+$/, ""); + return `Cloudflare refused the API token from this server's public IP (${ip}). The token has Client IP Address Filtering: add ${ip} to the token in Cloudflare (My Profile > API Tokens), or remove the filter, then renew again.`; + }, + ], + [ + /\b(6003|6111|9103|9109|10000)\b|Invalid request headers|Invalid access token|Authentication error/i, + () => + "Cloudflare rejected the API token. Check the token value and that it has Zone > DNS > Edit for this domain's zone.", + ], + [ + /too many (certificates|failed authorizations|new orders)|rateLimited/i, + () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).", + ], + [ + /NXDOMAIN|DNS problem/i, + () => "Let's Encrypt could not look up the domain. Check the DNS records of every domain on the certificate.", + ], + [ + /Timeout during connect|Connection refused|Invalid response from/i, + () => + "Let's Encrypt could not reach this server over HTTP (port 80) for the challenge. Check that the domain points here and port 80 is open, or use a DNS challenge.", + ], +]; + +/** + * Turns certbot's stderr into a message the UI can show: a hint for common causes plus certbot's own reason. + * Certbot never prints DNS provider credentials. + * + * @param {String} stderr + * @returns {String} + */ +const certbotFailureMessage = (stderr) => { + const reason = `${stderr || ""}` + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !certbotNoise.some((re) => re.test(line))) + .join(" ") + .slice(0, 800); + + for (const [pattern, hint] of certbotHints) { + const match = reason.match(pattern); + if (match) { + return `${hint(match)} (Certbot: ${reason})`; + } + } + return reason ? `Certbot failed: ${reason}` : "Certbot failed. See /data/logs/letsencrypt.log for details."; +}; + +/** + * Runs certbot; a failure becomes a public error with the real reason instead of "Internal Error". + * + * @param {Array} args + * @param {Object} [opts] + * @returns {Promise} + */ +const runCertbot = async (args, opts) => { + try { + return await utils.execFile(certbotCommand, args, opts); + } catch (err) { + if (err instanceof error.CommandError) { + throw new error.ValidationError(certbotFailureMessage(err.message), err); + } + throw err; + } +}; + const omissions = () => { return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials", "meta.certificate_key"]; }; @@ -813,7 +911,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - const result = await utils.execFile(certbotCommand, args, adds.opts); + const result = await runCertbot(args, adds.opts); logger.success(result); return result; }, @@ -880,7 +978,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await utils.execFile(certbotCommand, args, adds.opts); + const result = await runCertbot(args, adds.opts); logger.info(result); return result; } finally { @@ -974,7 +1072,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - const result = await utils.execFile(certbotCommand, args, adds.opts); + const result = await runCertbot(args, adds.opts); logger.info(result); return result; }, @@ -1058,7 +1156,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await utils.execFile(certbotCommand, args, adds.opts); + const result = await runCertbot(args, adds.opts); logger.info(result); return result; } finally { @@ -1101,7 +1199,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await utils.execFile(certbotCommand, args, adds.opts); + const result = await runCertbot(args, adds.opts); await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); logger.info(result); return result; From d7a79a547fb3ccbd7dff37e45b36f58f31f13ac3 Mon Sep 17 00:00:00 2001 From: mikeyhenein Date: Mon, 2 Nov 2026 20:28:39 -0800 Subject: [PATCH 45/76] Remove a certificate's certbot files on delete even when revoking fails Co-Authored-By: Claude Opus 5.5 --- backend/internal/certificate.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 9f81b764d..fad97f6a9 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -418,10 +418,40 @@ const internalCertificate = { if (row.provider === "letsencrypt") { // Revoke the cert await internalCertificate.revokeLetsEncryptSsl(row); + // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config); remove them anyway + internalCertificate.removeLetsEncryptFiles(row.id); } return true; }, + /** + * Removes a certificate's certbot files, as `certbot delete` would. + * + * @param {Number} certificateId + */ + removeLetsEncryptFiles: (certificateId) => { + const paths = [ + internalCertificate.getLiveCertPath(certificateId), + `/etc/letsencrypt/archive/npm-${certificateId}`, + `/etc/letsencrypt/renewal/npm-${certificateId}.conf`, + `/etc/letsencrypt/credentials/credentials-${certificateId}`, + ]; + const failed = []; + for (const p of paths) { + try { + fs.rmSync(p, { recursive: true, force: true }); + } catch (err) { + logger.error(`Could not remove ${p}: ${err.message}`); + failed.push(p); + } + } + if (failed.length) { + throw new error.ValidationError( + `The certificate was removed from NPM, but these files could not be deleted: ${failed.join(", ")}. Delete them by hand.`, + ); + } + }, + /** * All Certs * From 044d093ec5e52ddb5d3d50e5647946054acb127a Mon Sep 17 00:00:00 2001 From: wikrie Date: Tue, 3 Nov 2026 21:27:47 +0100 Subject: [PATCH 46/76] Update dns-multi plugin version to 5.6.0 update needed as used version 4.9 did not exists anymore --- backend/certbot/dns-plugins.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index f22da5af7..5e90c9d6c 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -173,7 +173,7 @@ "full_plugin_name": "dns-multi", "name": "DnsMulti", "package_name": "certbot-dns-multi", - "version": "~=4.9" + "version": "~=5.6.0" }, "dnspod": { "credentials": "dns_dnspod_email = \"email@example.com\"\ndns_dnspod_api_token = \"id,key\"", From 3fb2f7261fd18d045bb9629862e25b4aef164016 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 04:42:08 +0000 Subject: [PATCH 47/76] build(deps): bump decode-uri-component from 0.5.0 to 0.5.1 in /frontend Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.5.0 to 0.5.1. - [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases) - [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.5.0...v0.5.1) --- updated-dependencies: - dependency-name: decode-uri-component dependency-version: 0.5.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index eb0aa21f9..f934b8e74 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1394,9 +1394,9 @@ decode-named-character-reference@^1.0.0: character-entities "^2.0.0" decode-uri-component@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.0.tgz#4592fa1e1d640ec5e2760e2e168ad2ab5f2c9da1" - integrity sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg== + version "0.5.1" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.1.tgz#7f66b25bf435df723f9af55713dcf860a1c297d2" + integrity sha512-vWHQkXgg2cE3BB4rR0c+PYw9BCQzLoNIRMI7AunIkZst74tw/vpPp7VLoYtsR/cy1f55g3XDWUslfeCZjiZJyA== deepmerge@^2.1.1: version "2.2.1" From f915ca24625547b6a4ecf3de83601421191a361f Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 4 Nov 2026 20:54:23 +1000 Subject: [PATCH 48/76] Update all deps, and cypress --- backend/package.json | 20 +- backend/yarn.lock | 290 +++++++------- docs/yarn.lock | 870 ++++++++++++++++++++-------------------- frontend/package.json | 42 +- frontend/yarn.lock | 737 +++++++++++++++++----------------- test/cypress/Dockerfile | 2 +- test/package.json | 14 +- test/yarn.lock | 533 ++++++++++++------------ 8 files changed, 1266 insertions(+), 1242 deletions(-) diff --git a/backend/package.json b/backend/package.json index adcbe5411..722bb0174 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,37 +13,37 @@ "regenerate-config": "node scripts/regenerate-config" }, "dependencies": { - "@apidevtools/json-schema-ref-parser": "^16.0.2", + "@apidevtools/json-schema-ref-parser": "^16.0.5", "ajv": "^8.20.0", "archiver": "^8.0.0", "batchflow": "^0.4.0", "bcrypt": "^6.0.0", "better-sqlite3": "^13.0.3", - "body-parser": "^2.3.0", + "body-parser": "^2.4.0", "chalk": "6.0.1", "compression": "^1.8.2", - "express": "^5.2.1", + "express": "^5.3.0", "express-fileupload": "^1.5.2", "gravatar": "^1.8.2", "jsonwebtoken": "^9.0.3", "knex": "3.3.0", - "liquidjs": "10.30.0", + "liquidjs": "10.30.1", "lodash": "^4.18.1", "moment": "^2.31.0", - "mysql2": "^3.24.4", + "mysql2": "^3.24.6", "node-rsa": "^2.0.0", - "objection": "3.1.5", - "otplib": "^13.5.0", + "objection": "4.4.2", + "otplib": "^13.6.0", "path": "^0.12.7", - "pg": "^8.23.0", + "pg": "^8.23.1", "proxy-agent": "^8.0.2", "signale": "1.4.0", "sqlite3": "^6.0.1", "temp-write": "^6.0.1" }, "devDependencies": { - "@apidevtools/swagger-parser": "^13.0.0", - "@biomejs/biome": "^2.5.10", + "@apidevtools/swagger-parser": "^13.1.0", + "@biomejs/biome": "^2.6.4", "nodemon": "^3.1.14" }, "signale": { diff --git a/backend/yarn.lock b/backend/yarn.lock index 66013beb5..9cc6fb99b 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@apidevtools/json-schema-ref-parser@^16.0.2", "@apidevtools/json-schema-ref-parser@^16.0.3": +"@apidevtools/json-schema-ref-parser@^16.0.3", "@apidevtools/json-schema-ref-parser@^16.0.5": version "16.0.5" resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.5.tgz#4f5ab1d20fe9dcde15d4167a3d5dd0434e0109a6" integrity sha512-z3LiEMqTP2FNu6u8bdUgBYErVuwMSZReOJy1MCEIB0BDHMoNgBtkciby32HOlcrfWwrOSzRU8YIsZ0CogYRuYQ== @@ -19,7 +19,7 @@ resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== -"@apidevtools/swagger-parser@^13.0.0": +"@apidevtools/swagger-parser@^13.1.0": version "13.1.0" resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-13.1.0.tgz#efc8ffcc573838e715d0bce83d917b79636ca172" integrity sha512-dkCuqCWh5U7u0aurmmc/qvel2KeKDxHTwVtUdYonndIpD7mXV6AVaWNdvaja2HAy628z7FT/Qdz8sCgzwhgwKg== @@ -32,71 +32,71 @@ ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.2" -"@biomejs/biome@^2.5.10": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.3.tgz#649bf3d08d702823a9ce62a7022fa16b4e7b64dc" - integrity sha512-va/0xPku7wnfzv+D8Zpdm5uKiGJWiV8neqoEPnRjvNQQwhl4tVXS4fETs4H0CCB2rGeqJ4cu9y4iOZOnOYKAUA== +"@biomejs/biome@^2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.4.tgz#4f190d1cfa3003a6649db9197b34e47937420179" + integrity sha512-8sKKwaNW4ot22awZ3lBjwWLHnUFXgXnrBPYS2gNlBAXRbDC++7kqjK2H7dR/FgCDtPvO1xfKpgEhJkGxAKYkxA== optionalDependencies: - "@biomejs/cli-android-arm64" "2.6.3" - "@biomejs/cli-android-x64" "2.6.3" - "@biomejs/cli-darwin-arm64" "2.6.3" - "@biomejs/cli-darwin-x64" "2.6.3" - "@biomejs/cli-linux-arm64" "2.6.3" - "@biomejs/cli-linux-arm64-musl" "2.6.3" - "@biomejs/cli-linux-x64" "2.6.3" - "@biomejs/cli-linux-x64-musl" "2.6.3" - "@biomejs/cli-win32-arm64" "2.6.3" - "@biomejs/cli-win32-x64" "2.6.3" + "@biomejs/cli-android-arm64" "2.6.4" + "@biomejs/cli-android-x64" "2.6.4" + "@biomejs/cli-darwin-arm64" "2.6.4" + "@biomejs/cli-darwin-x64" "2.6.4" + "@biomejs/cli-linux-arm64" "2.6.4" + "@biomejs/cli-linux-arm64-musl" "2.6.4" + "@biomejs/cli-linux-x64" "2.6.4" + "@biomejs/cli-linux-x64-musl" "2.6.4" + "@biomejs/cli-win32-arm64" "2.6.4" + "@biomejs/cli-win32-x64" "2.6.4" -"@biomejs/cli-android-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.3.tgz#929aa987accb55957db62d1e11b76dbcc0a7f2e4" - integrity sha512-5JD7QQDEHxdeEb4sftEHG+lsuUyu8Cob/hVLqRELbgGlFl68Rldv/PPi2k4dRjERZ/EItJ3O6Xmv8lqsEtUBiw== +"@biomejs/cli-android-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.4.tgz#5e51c3c734b12c16d1b0168b5d1eb53847985c0a" + integrity sha512-jwrwigP4tBcHxzuV8izioX/BOUhJL/4e2rNSOE5nCjBkVC018LQAQUGiH+CNkFYvLtQ1dcZo5/xXERmaAGc7JQ== -"@biomejs/cli-android-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.3.tgz#f130f0ee898b437686d1cddd87e9ca5e5b7dc4de" - integrity sha512-70WOGKCgpT+Uqg6LFdTLqefV54/DypdZg+LnBQDO6AWvkgXO+9xhVClajQyDsIHYjnXMtI1pRO5sP+zMXlS/bw== +"@biomejs/cli-android-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.4.tgz#caee3503ae8edf048afb7055007745e9cd4c4bcf" + integrity sha512-HdGInPJ4nZFwHxzvEtcp2nmSBtUyJrurmtIK43upFckuS3+ZqVM05HtsxQWnqGsuy/pxckIAjyKWlEKRlBXUyA== -"@biomejs/cli-darwin-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.3.tgz#b232a0fce9b30097f69d52382fc6e7074860b012" - integrity sha512-ezf+XEKeyJ2yYlBhrzyEEHLvfRYrhtNKc6maLzR/WcAcBccb4RVDeFxsymjBve2LO0bpIMRUAiS90D4bUBRT0A== +"@biomejs/cli-darwin-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.4.tgz#dd8c4cbc4a531fd9a248808c624cb958c7c8184c" + integrity sha512-0VvcUz46wbg59Q4TZ59KkIjDJrxEyu4Eoe79+F4SChxoWe+W7AEyIp6F5r72C9+p6MsB6dIovnZt1D30lGM3XA== -"@biomejs/cli-darwin-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.3.tgz#8a31e6a26e26bba170a0ce36fff947038b733d72" - integrity sha512-Lg2tFdg4elJo+7i707PpLMs6N1hATVt4kqDo6MD/q/VnPs8rsKReKXsz1TA5GNdfRVAQNJ5GRiyK2EwNptK+MA== +"@biomejs/cli-darwin-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.4.tgz#181a204b13e9c264499a654e3d9fc4fdddba1541" + integrity sha512-fxmvkF81srlS3DiMyfm+aG3J0eG+6/BjaxRyjy57KbLSafnq0PO+hHfVWmtH2ty0S7nm6yDXBRwySfFeaRJKRQ== -"@biomejs/cli-linux-arm64-musl@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.3.tgz#84defa2b38f3fcf04b8eee8c76cbf60b3fa5ef05" - integrity sha512-dYU70A89OFHzakQPE9VOPbE60JNcQt4mtxvUGQl+EgTkGtqCFxLD2RUn1SbiMu8Mu5xFscS1bfYDpoasaUrqNA== +"@biomejs/cli-linux-arm64-musl@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.4.tgz#34c5110ab8298def8417ce55933e62ad5a6afcdd" + integrity sha512-31ZaH/MHqPX+O+evRwRrCHQ8W5/9adY+/eMytGGWZnRx1T41QsWIF09+/WujynLBhWZUxNGS5CtsWLwgOFTAyg== -"@biomejs/cli-linux-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.3.tgz#c7cb949571e8088b463385b15d60d4f1690c9242" - integrity sha512-8Stcvq98n1gs9VyLKuaWrnsd8kbXE2nKDAHtuLHGSYdGyNWN5JIIm5a28xdnmgdKzKHmiDgYyATetkEv7+zGow== +"@biomejs/cli-linux-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.4.tgz#a4deed06840a31b5788b37eb8ac8064eb92c5f4f" + integrity sha512-6+bK1Y/evmIUiJr7auertuMbiaYVicJhqA1bZyL47lCMWugv8l0gFQCqvR/gN1vZMHYm+HwoTKgeMxdNFCoQTg== -"@biomejs/cli-linux-x64-musl@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.3.tgz#6c6de66af5537443c3fe1fef429e801c556a6b5a" - integrity sha512-j6eFjUcWSphmgVTbrobEMA0wdbfNHKrGi8hRsFkc17jeS1n7nHwFACxp6jWqe9sxQqyFAeyOgt2/1bOGVcbATA== +"@biomejs/cli-linux-x64-musl@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.4.tgz#c7bff48f5aeede76931be6f2981db1e94c422dca" + integrity sha512-bomSa2XdQAGGj6Cwdm4M13qE+lN9+AOlW8KtI4hdyVyRRF/Z71qPAi95T4wtdZK0QpBbkTW8hgbZZCwPi4xzIw== -"@biomejs/cli-linux-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.3.tgz#d50121c6396a00cfb84ccf4cfd5e809a9cb4aa6b" - integrity sha512-CShmx0bO6BngywT3minv8K1xsQX1ig1vsBqYTLnmDY3mBiDYHlaLRIsdHoM1pEU9dV3BN5a0uiH0IGDtYh9TWA== +"@biomejs/cli-linux-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.4.tgz#70f438459a279e5ef2599deae3d3be2c0120c672" + integrity sha512-tbFZMdL6dP/ws4edXxYG9WhvaVa1ZDzL4G5DzahnWW7Psib9H+VTdyU4XXVxpJk5j/nYZroSbvSNyYlpW3yCIw== -"@biomejs/cli-win32-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.3.tgz#6b7564eddb8fb30c7cb0bd1f958e2ccacbf38dd7" - integrity sha512-00SthDxBPwNiT7IL/mOneif6fiEU1PXK875zhr+InpeZjovY1mF5tCuDhXo1Hh0GYyiKHKzLbItzn58SAyLhJQ== +"@biomejs/cli-win32-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.4.tgz#6ea7435132a4bbf1e894a6a781d56665787a0532" + integrity sha512-eCsqt8Ma3eVap+oyJCDnJZH60SIU464B66n3y38EUTJdM1T/HfZol1FJC9vb+sHToX0ix8XzqPcwJnOzyAJz0g== -"@biomejs/cli-win32-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.3.tgz#57a5dbf7c25ff435a1b5162c68d6c6f8082ab6cb" - integrity sha512-GhtOPphBWlnCepqpNk/w5eGN5oGRCzL20EbAkL1XCIpyboeVC9R642noyT43l8LSenGS4qz9IL8sKPfwY37V/Q== +"@biomejs/cli-win32-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.4.tgz#d8c80a6d93308d3759414aa2949e2371e49d5a99" + integrity sha512-pYsMy5AThiHFOZAsngg8qN4PurM6nInKBhVX7sbj3eNpRQqo1Hb0MgCwPL7VqVMrmcEEMj9Ks3qbkallR65e8g== "@isaacs/fs-minipass@^4.0.0": version "4.0.1" @@ -105,57 +105,57 @@ dependencies: minipass "^7.0.4" -"@noble/hashes@^2.2.0": +"@noble/hashes@^2.2.0", "@noble/hashes@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.4.0.tgz#e6a3e98edbed3c8659b28bb8f9b18ca02ec8ea4b" integrity sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA== -"@otplib/core@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/core/-/core-13.5.0.tgz#629a7fef5b6394f34ff55213b936223b368796a4" - integrity sha512-2rURdkYkb3BDhMs3j/oCCPTve1ybJ6ruLfLfSe1ZSPV+y6RFTbLfAuT0m0ZCnps88ogkIq9t/+Li/kg5RofQwQ== +"@otplib/core@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/core/-/core-13.6.0.tgz#f939a821604467b87be29b465cb54e2f270c3789" + integrity sha512-TL6/FKKzR55Vr7zo3YGFtPMCSV3SpYPtuvE3T1XcwjFczD9pHU4dYYQBl0AcNvGcqAt4/fPqhZEFfB2OBcnlPA== -"@otplib/hotp@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/hotp/-/hotp-13.5.0.tgz#2832cc6e52f3d037428f094177b4779babe19aa5" - integrity sha512-1EwwAti05CeWJn4xXOVMBK9N6dIJlJw/cQkQyiL9OVlkeS452wAve3ffodi+5IUZdWoF1wxVug38RisdxDqDWw== +"@otplib/hotp@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/hotp/-/hotp-13.6.0.tgz#b9c0e2ed27f63dff9bb704c881bb006f54a2d4b4" + integrity sha512-oPpaQBkAYjBxAs+FWdGNumsEwWJhIZVcsv0vEi+w4OzzLQ6UDZ2qXZNSeF9NljTj6/8d4e1Z4o6RgJa3aIxAOA== dependencies: - "@otplib/core" "13.5.0" - "@otplib/uri" "13.5.0" + "@otplib/core" "13.6.0" + "@otplib/uri" "13.6.0" -"@otplib/plugin-base32-scure@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.5.0.tgz#ba2f329aeea3ed4df3c4e21ce2e9c2e8c9aaed4f" - integrity sha512-3JEIHindUMiIeNL0jXepSAkZ/7IkWq4sPdG9eXK0lrMXZG9RdKu5oz/4tuuTodtsFUPYhKlRMqv5xUvQIkMIaA== +"@otplib/plugin-base32-scure@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.6.0.tgz#73509fbd9946642dd78f2990190f5cdb86aa0f81" + integrity sha512-s6uMbj4MBebvAPnBns1y2RhQ765/U04qHlZmFLV02dlD1S06pU46g/7tHX0vkCx5Pnty723loXto/PM5gFEEdg== dependencies: - "@otplib/core" "13.5.0" - "@scure/base" "^2.2.0" + "@otplib/core" "13.6.0" + "@scure/base" "^2.4.0" -"@otplib/plugin-crypto-noble@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.5.0.tgz#820227beb159090a7448ce7245a4e615b5579f0e" - integrity sha512-fihOAGFvc4b8XTKyIK3jifFP2nLrUNc2bOaJ3UkUKJjy1XI2FbHKG0WmOH+jFsPfa6VsOnouCnEmSznDxIe0pA== +"@otplib/plugin-crypto-noble@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.6.0.tgz#c2702906e6cc128cf8b32fafd35163683a02394e" + integrity sha512-m/8mOhtIhsr4zLtb9MlLI6+w7ZNiKSFnT8Dv7eHR9TCyf2Sxva5e+MbV0wONmF7ZLuvo9F3DtzfPjc748PhJcw== dependencies: - "@noble/hashes" "^2.2.0" - "@otplib/core" "13.5.0" + "@noble/hashes" "^2.4.0" + "@otplib/core" "13.6.0" -"@otplib/totp@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/totp/-/totp-13.5.0.tgz#48a60807457726ea34f1d1185f994120652e4468" - integrity sha512-GD9LzQnbHDwXCp79s8AaiA5cJR6luX5fEr9AN32AWx4ooRvllPbMkG80gHSA0jbGCnAIX/ChyOtTZ8tgJHMkSg== +"@otplib/totp@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/totp/-/totp-13.6.0.tgz#fa309ced2594e83b060d9b746d0218d2957153a3" + integrity sha512-bMlq0MaUaQE3lOSH352Mrzcwpau5pMf7pMLROS+llk2evpC/sE7SHl9ajBl+PiPnGGb6LGYEjDvKqX8caXkGdw== dependencies: - "@otplib/core" "13.5.0" - "@otplib/hotp" "13.5.0" - "@otplib/uri" "13.5.0" + "@otplib/core" "13.6.0" + "@otplib/hotp" "13.6.0" + "@otplib/uri" "13.6.0" -"@otplib/uri@13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@otplib/uri/-/uri-13.5.0.tgz#d2da470be24f872bb1c24717bcdd2e5c122e3ce3" - integrity sha512-LsL1hqTEgJHY40U2eb/Qp6OR1tKkNGwHQTrqqjIuJj0cawGWkLEmeUFK1MHzWBQ4bgy5IdeB5Et7gIDSAH/CRw== +"@otplib/uri@13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@otplib/uri/-/uri-13.6.0.tgz#c26e70293ccd4bc8f1d6e46c051e2b70bf1a56dc" + integrity sha512-CiLFMONTpCi3ara6yQQX3nlpYhafCALKJweA7+7Z+aDkRrW3QmD5a/9thKmQ0cGp2W70AMG/6BSTy82h74LHRw== dependencies: - "@otplib/core" "13.5.0" + "@otplib/core" "13.6.0" -"@scure/base@^2.2.0": +"@scure/base@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.4.0.tgz#61e4ed7d4447afc247f2bff9aeb8fca53267db86" integrity sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg== @@ -195,10 +195,10 @@ ajv-draft-04@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== dependencies: ajv "^8.0.0" @@ -379,7 +379,7 @@ blueimp-md5@^2.16.0: resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -body-parser@^2.3.0: +body-parser@^2.3.0, body-parser@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.4.0.tgz#337a76249270f83e450ad385a9606ca3b4a75c06" integrity sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ== @@ -629,11 +629,6 @@ data-uri-to-buffer@8.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz#c642ffbc96d8fae76cccecbad43cbddd7818c785" integrity sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ== -db-errors@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2" - integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng== - debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -728,9 +723,9 @@ emoji-regex@^8.0.0: integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== encodeurl@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + version "2.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.1.tgz#f268548822bd87b5b4fdce83c56388c6d33c9de6" + integrity sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.5" @@ -853,7 +848,7 @@ express-fileupload@^1.5.2: dependencies: busboy "^1.6.0" -express@^5.2.1: +express@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/express/-/express-5.3.0.tgz#ab72dace89c50c8417bd3f865dad0b4fd1ca1c73" integrity sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g== @@ -1149,9 +1144,9 @@ interpret@^2.2.0: integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== ip-address@^10.1.1: - version "10.7.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.5.tgz#4c4c423f6477d0b0b5c2ea59ac77e93eff49003e" - integrity sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA== + version "10.7.6" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.6.tgz#e3c06961cedf352494c06c916c696cfdb11068dd" + integrity sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg== ipaddr.js@1.9.1: version "1.9.1" @@ -1170,10 +1165,10 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.17.0.tgz#75c1c76236c9f7e38ddbfff292e127dc13b21027" - integrity sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA== +is-core-module@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.18.0.tgz#8a988af8d3729fb6bbcaf025bf820cfe46b95f4c" + integrity sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw== dependencies: hasown "^2.0.4" @@ -1301,10 +1296,10 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" -liquidjs@10.30.0: - version "10.30.0" - resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.30.0.tgz#c84b03cf85a5d3fc76a5c7a99039d166e8970d9e" - integrity sha512-Fw4wA+8CZsJkaeLEIlPE3AoponfjEZhj7FQvYNcAEcg7jNRik7mEMoaco382FBdrYlykVf6w4vClPNctz2OYjg== +liquidjs@10.30.1: + version "10.30.1" + resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.30.1.tgz#aa8c24535d638f88679a22cd8042c38b3ca61dc6" + integrity sha512-P1Iz14WUHZdfbOYwHOIhtl/iAWIOYk1ks5l9rPZlDM6vZo+6BLeYIuWHAPkIz+U8Mm8HKZKITIrjX7DQJO2fqA== dependencies: commander "^10.0.0" @@ -1469,7 +1464,7 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mysql2@^3.24.4: +mysql2@^3.24.6: version "3.24.6" resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.6.tgz#8e9471f0ceca917599ac021356bb24f08f168b3c" integrity sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ== @@ -1584,14 +1579,13 @@ object-inspect@^1.13.3, object-inspect@^1.13.4: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -objection@3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.5.tgz#53c32f6b6cba2958bc28cf723de96c2676da8286" - integrity sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ== +objection@4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/objection/-/objection-4.4.2.tgz#e87f28244c64f5eb151fc9dd3106bc7c2d4c4863" + integrity sha512-Iavk3Gdl0RJI25MjjMwB2MG87x2pcB+QQjXqxHvaAWZIm/xJ+dLnyFk+/B+H0fEJOYypv0Y6vaeWJbMp+XZTnw== dependencies: ajv "^8.17.1" - ajv-formats "^2.1.1" - db-errors "^0.2.3" + ajv-formats "^3.0.1" on-finished@^2.4.1: version "2.4.2" @@ -1612,17 +1606,17 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -otplib@^13.5.0: - version "13.5.0" - resolved "https://registry.yarnpkg.com/otplib/-/otplib-13.5.0.tgz#78b5d0d9edf83078d7fe37243bbcdd96d7a65d37" - integrity sha512-RpcC6aq4rANX6MverMuU3pqHVLgMPO7vl6qR8ga7YzoEHlPJknJ58cSVAaf3jBYnasiQVQHXXl2mj/4pYlsVEQ== +otplib@^13.6.0: + version "13.6.0" + resolved "https://registry.yarnpkg.com/otplib/-/otplib-13.6.0.tgz#aa462e7e2c0370278666a608144c3194c7b2f7fe" + integrity sha512-PCx6+14PYndCRk0G3JuJHmlExzENs43gafqMUVmSFmzMbEweVhBiYcBCL8B32ZMEDPZA4OsMxFEeAob1H+YFZg== dependencies: - "@otplib/core" "13.5.0" - "@otplib/hotp" "13.5.0" - "@otplib/plugin-base32-scure" "13.5.0" - "@otplib/plugin-crypto-noble" "13.5.0" - "@otplib/totp" "13.5.0" - "@otplib/uri" "13.5.0" + "@otplib/core" "13.6.0" + "@otplib/hotp" "13.6.0" + "@otplib/plugin-base32-scure" "13.6.0" + "@otplib/plugin-crypto-noble" "13.6.0" + "@otplib/totp" "13.6.0" + "@otplib/uri" "13.6.0" p-limit@^1.1.0: version "1.3.0" @@ -1766,7 +1760,7 @@ pg-types@2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^8.23.0: +pg@^8.23.1: version "8.23.1" resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.1.tgz#e4848051a04aa2f968fd5671d075f89e5e7918b9" integrity sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ== @@ -1910,9 +1904,9 @@ pump@^3.0.0: once "^1.3.1" qs@^6.16.0, qs@^6.16.1: - version "6.16.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.1.tgz#0643e17993e551759c85078e1f0bc4b9fe908020" - integrity sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg== + version "6.17.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.17.1.tgz#43ac71d29ca0e9d0eed16a218e35b6cde5628e81" + integrity sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA== dependencies: es-define-property "^1.0.1" side-channel "^1.1.1" @@ -2027,12 +2021,12 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.13" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.13.tgz#d542f97758d768f36091251a6b07540b4343f7b5" - integrity sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA== + version "1.22.14" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.14.tgz#302388cd2d7731a100db1dedce7b050f74ba38f7" + integrity sha512-1ZjYvR9sbkiwC5+9QHetqW5HmA2pHRMaoupD01t2HlYnV/vydC2zPwLixhsMieSWVZt0JJRg5LVIk0Ow1D/TrA== dependencies: es-errors "^1.3.0" - is-core-module "^2.17.0" + is-core-module "^2.18.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2063,9 +2057,9 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^7.3.5, semver@^7.5.3, semver@^7.5.4: - version "7.8.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" - integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + version "7.8.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.6.tgz#67c0cda133a3ceb6f70b803e67625bf969000424" + integrity sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw== send@^1.1.0, send@^1.2.0: version "1.2.1" @@ -2400,9 +2394,9 @@ touch@^3.1.0: integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== tslib@^2.0.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + version "2.9.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.9.1.tgz#8986213ba2f17f38ba39a7c43d0e565a9134ff5f" + integrity sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA== tunnel-agent@^0.6.0: version "0.6.0" diff --git a/docs/yarn.lock b/docs/yarn.lock index f8c7568b2..af8b69d46 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2,15 +2,15 @@ # yarn lockfile v1 -"@algolia/abtesting@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.12.2.tgz#1cba5e3c654d02c6d435822a0a0070a5c435daa6" - integrity sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ== +"@algolia/abtesting@1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.27.0.tgz#5f507d696271fec578269693faf1adf383c05eb8" + integrity sha512-XXm0DTCmSzHT40mn0Hs8H00pBcuaqQET0BsWfaHrQdijNfrhSZPLueGVQvYAeSeXVuS420arotSfgqY25nFNbA== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" "@algolia/autocomplete-core@1.17.7": version "1.17.7" @@ -39,146 +39,146 @@ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz#105e84ad9d1a31d3fb86ba20dc890eefe1a313a0" integrity sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg== -"@algolia/client-abtesting@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz#264a72f0e9d2fe0d0dc5c3d2d16bbb9cfe2ce9e8" - integrity sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A== +"@algolia/client-abtesting@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.61.0.tgz#94c4365c9c206d6f314d04f66ccdb48661c111cf" + integrity sha512-NSq61VNhN63nstgbEh2YzygmtybxPnBdFwmjPOa7laJp8RapLrkqwHUaI4ELCyuZMhcuRlUvOL25y+SDVkgnsA== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/client-analytics@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.46.2.tgz#3f00a237508aa0c46c9c02dea9c855e0a78e241f" - integrity sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA== +"@algolia/client-analytics@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.61.0.tgz#83324527f13047669c2de59099cd811e31e5f8dc" + integrity sha512-WBMStmOLas4bWHh0rgOOb8iTQL07fsfPwvEW2vrQL+5v2KWMcRix9cYSQp3SOGjdweNjUszbGO84VBBMHBnHbQ== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/client-common@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.46.2.tgz#7f282fd8f721b0d96958445df2170f4c7dce6aac" - integrity sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg== +"@algolia/client-common@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.61.0.tgz#053b7bded65eafc13d709f0ad23b6956561c8f5f" + integrity sha512-mzrW0KkEiZHB09cU2Sh2vyxUGSi9fVoglCeKnf3wJ3ixo+7ilHH64wLK45eSoX13+CVXA2KTjZJGcEmBS3Z57Q== -"@algolia/client-insights@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.46.2.tgz#194b7b529ee8a4ffd5d70037745082996c3b9aa0" - integrity sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA== +"@algolia/client-insights@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.61.0.tgz#fe5c960ae71c561e933e959a464e6d9a62e5a791" + integrity sha512-TseZAUL8ZUtKQY6MQbJR/Qw9OSf54s9gvcDtQ7GZX9miZD1e24E1/usa1lGrrgq5KDpgQ8+ePCQIITFcq0Y56Q== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/client-personalization@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.46.2.tgz#d604da7f0a3df1b3e2a9fe338d368e48fb781f8e" - integrity sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA== +"@algolia/client-personalization@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.61.0.tgz#c2b39d43d845181658f4f1731ad9b11280a1023b" + integrity sha512-4CBpVN2WDVLocAzMU8me5UHWSbf6oXI7KDy3K0pL/8i0eB1+nOMQBU7jmUL6kBl2XMoYAu6tnsZmxpZ59b+Y2g== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/client-query-suggestions@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz#f13bc5897bfbdc19509d430a26e9bbe2402e00c9" - integrity sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w== +"@algolia/client-query-suggestions@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.61.0.tgz#fd70402c32bb3ed4e8bba4a8b89689672f99cdfc" + integrity sha512-PU2XREmx7DdZ6SjuuaM+ZwhfMFOUPXjLeUYyvxTFA8oz06yrY/wcn5V/UOMv01rdeTuG2petMZLzTJADF9KyEw== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/client-search@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.46.2.tgz#771367916aaa3fb7a19d5944f8375504b0568ba6" - integrity sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA== +"@algolia/client-search@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.61.0.tgz#e120030ae546407015bd52e0c73185f8c562de44" + integrity sha512-npseUJ0UwaiwU4XbKyHiFcZbrS1AbLszbPRxi3W9YUrrqdpvfSeGKMlLv63haH4PZsCog5wG40lJGKXbnrDAEQ== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/ingestion@1.46.2": - version "1.46.2" - resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.46.2.tgz#2a5d8a592d9f864dfb438722506382af56f8554f" - integrity sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ== +"@algolia/ingestion@1.61.0": + version "1.61.0" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.61.0.tgz#ee7fc87d06aff23f1b6f5c30fd352d271cd0cf73" + integrity sha512-rA/W43fD2qQemeQ2/uix/KSEUdikY55RTmgbfBRoF9HjCJ/zryD9RAKJXWxBlFXRBg9q8EPPYH3GlSXcRHnpgg== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/monitoring@1.46.2": - version "1.46.2" - resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.46.2.tgz#bd199368a49cb799cf12cfe76c49de6dd3021148" - integrity sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw== +"@algolia/monitoring@1.61.0": + version "1.61.0" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.61.0.tgz#ad3e3b31ab078b852b07ee7f8e95a08be7201391" + integrity sha512-3CEPgD95sBitQVQsPXmIfMkcX0Kh12d5wmI7iOm1yrllPBu4Vx30Q2xMv9wyHm2C2bHQwz+KPBohtq6Hh1K5eg== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/recommend@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.46.2.tgz#e74bade1254046ed9be8ccd37f2a116ab9799508" - integrity sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q== +"@algolia/recommend@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.61.0.tgz#de30dfd0799aa5eb6f50c47cb745bb830ea99b7a" + integrity sha512-2pCOG8rRFW+0OeUmNUMoRH5xXktCEdUqtjuz6FCFQa1lxqPIUBr4HHIneXTIuAgUC0ckokqBCZk0v5qU7DvBuA== dependencies: - "@algolia/client-common" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/client-common" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" -"@algolia/requester-browser-xhr@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz#7662480143405e815e1eed99136b4b2acd838ee7" - integrity sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg== +"@algolia/requester-browser-xhr@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.61.0.tgz#57abc51ff771929f3a08a3c79ea1cfa7aa52e870" + integrity sha512-7Y7P6Kp+7DQkrOCzmIYjoCpLPQoA8vlxcIzyjpfMrokBNX0LIQSPyR5JY/EdSzCN6CvNlYGJ5SiO7j6lZad5Jw== dependencies: - "@algolia/client-common" "5.46.2" + "@algolia/client-common" "5.61.0" -"@algolia/requester-fetch@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz#dee07f0131b75f30d083bafd6fb878afe7402eb9" - integrity sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw== +"@algolia/requester-fetch@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.61.0.tgz#e7a2cf2555259cc6d27c47778424f7d90c6e4235" + integrity sha512-WNTNQcrWcGAgLDU3+6IFWs3QPosMCYSlwlPEdEO2ivLnlm8u5HtGQvOz/JnY9d2bhofDBL+S5wdtm9+Uob+dsA== dependencies: - "@algolia/client-common" "5.46.2" + "@algolia/client-common" "5.61.0" -"@algolia/requester-node-http@5.46.2": - version "5.46.2" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz#7869d67cb2926bbdbfbfed2b4757e547c2e227eb" - integrity sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg== +"@algolia/requester-node-http@5.61.0": + version "5.61.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.61.0.tgz#5b9c56c2d09bc12bbd29c318c08804dd394f6fc0" + integrity sha512-CA+6MlgPkjSlGQpx+rLBSnHFy8pyYpGEjs7Yp0kVzOC6RM5jq99Z4KGOuplcNZBjQlM521t0nEvKLxFOv2NSZA== dependencies: - "@algolia/client-common" "5.46.2" + "@algolia/client-common" "5.61.0" -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== +"@babel/helper-string-parser@^7.29.11": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63" + integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g== -"@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/parser@^7.28.5": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" - integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== +"@babel/parser@^7.29.9": + version "7.29.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423" + integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg== dependencies: - "@babel/types" "^7.28.6" + "@babel/types" "^7.29.11" -"@babel/types@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" - integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== +"@babel/types@^7.29.11": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc" + integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ== dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" + "@babel/helper-string-parser" "^7.29.11" + "@babel/helper-validator-identifier" "^7.29.7" "@docsearch/css@3.8.2": version "3.8.2" @@ -319,9 +319,9 @@ integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@iconify-json/simple-icons@^1.2.21": - version "1.2.66" - resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.66.tgz#d0576ca65f69616b49491129e17132316cf3f309" - integrity sha512-D1OnnXwiQXFkVMw5M+Bt8mPsXeMkQyGmMdrmN7lsQlKMUkfLOp6JWhnUJ92po51WXT046aF/zzqSmkKqg08p4Q== + version "1.2.101" + resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.101.tgz#323b81ed81d282dbf95eb39329b27e1e398243a0" + integrity sha512-MXJkMZDancJ/9I+Bgdthi9kXvn3AwWStxoTHwEPwAFQ9kMLBASO1BSstg52fNtjXeizm1Wi91R+QnWxNGkWHPQ== dependencies: "@iconify/types" "*" @@ -331,134 +331,139 @@ integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== "@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d" + integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw== -"@rollup/rollup-android-arm-eabi@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" - integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== +"@rollup/rollup-android-arm-eabi@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.67.5.tgz#b40e32abb60e3d93419f613a06e9bb9dcd1ffb89" + integrity sha512-FdWPeulkH+FH+rBzyKBD59tuVN4tRKvmVilSJm0OcnoYsQTpd9UhD78OaPcsYqo2fX6hCwAG0YIjrDLoQ3pR3g== -"@rollup/rollup-android-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" - integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== +"@rollup/rollup-android-arm64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.67.5.tgz#13a0785e083b6ace19b655670c562d5ab5123011" + integrity sha512-bmkDuBGnF+Ji8atWlXcNjxIC52ShCbdXPbW7Nz0lRoCFi1lrvsO0XobJY36kTj7duc1vOmGJeQ9vtVuGjnzihA== -"@rollup/rollup-darwin-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" - integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== +"@rollup/rollup-android-x64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-x64/-/rollup-android-x64-4.67.5.tgz#869d6c63038bfe24d08a502f61d35cda42093020" + integrity sha512-6KvVfnqPTqmbQ5YobMl9HYiSymAsCt8HlNeriDshwJQcveXwT+vIMW6wGL1RcJbXH9+r3BAJvjab5+rIgtSEqA== -"@rollup/rollup-darwin-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" - integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== +"@rollup/rollup-darwin-arm64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.67.5.tgz#cd65c5b38a4fba26f7706572712fc59fda5bb043" + integrity sha512-SNamgdrY69oPdBrQV9fKGo6PSmKcVhyn0D0jnDmcqgRJvrxQGGBhtekOHTUJ7TCgkW0nE8XMH9v2CwXmjNIzVA== -"@rollup/rollup-freebsd-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" - integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== +"@rollup/rollup-darwin-x64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.67.5.tgz#5c64e10fd00852fb1ec6df51044153d0664d5845" + integrity sha512-HNdl34thiJtHDSglogjmAlQ296L8cc8zA7ODH65nnmY7E+jPP55WEbh8J5583rWFLMjxyYywxZuwPOwRvEi5tA== -"@rollup/rollup-freebsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" - integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== +"@rollup/rollup-freebsd-arm64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.67.5.tgz#49c0ba3ab90a0f43a970f335f6cc308a7f147f53" + integrity sha512-WvZYtANNswfHzSNHaJAoG6Ipfc4SyZIDYuIjFdHv07xXxhHl+Xx8o+bNDWsxpOADFT2mKXcXVrfOG4OlWTLo8Q== -"@rollup/rollup-linux-arm-gnueabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" - integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== +"@rollup/rollup-freebsd-x64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.67.5.tgz#db50139f24c3d5768aea2c92c79495653501016f" + integrity sha512-EUU+Wgf9NyHekceC+DxAdm42f6bq2ezH/7DEb/JnCb8SAUhG2xph5BiVukxplWal2m5pP8CzJZE+FLwRopywjQ== -"@rollup/rollup-linux-arm-musleabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" - integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== +"@rollup/rollup-linux-arm-gnueabihf@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.67.5.tgz#7125f8ab7c4a14ed3caf08755133e06e8648479f" + integrity sha512-dgU1CvUuE2IkaJBAeuAbxpDdMHAdZBT5oth5u22DtuoMg4gXXYr2+Sm30RigPJG1F2VEoiGJUziGLuIh9+jPxA== -"@rollup/rollup-linux-arm64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" - integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== +"@rollup/rollup-linux-arm-musleabihf@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.67.5.tgz#fe81890f371e96cf90c97034c65392264a2eb55e" + integrity sha512-rdgJcJig2VngYlcTCRQw2BA8kLWM6QwDFe9uLFo81lya0jPC1r8wZu92sAvTUIv48GWlkRVi6/fpsa5CoFSKoQ== -"@rollup/rollup-linux-arm64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" - integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== +"@rollup/rollup-linux-arm64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.67.5.tgz#b5c1dab117caae5a14c265bc4cf00ce285606e17" + integrity sha512-+jtCiQnrEFHWAUAcUpP7wppb1p6HKOgBP3mXx75cEYpDF2Y5MEqBky2HNz+osFuVx5uX44gnMTmbe5RJauzn6A== -"@rollup/rollup-linux-loong64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" - integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== +"@rollup/rollup-linux-arm64-musl@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.67.5.tgz#650dc54bcb603133ea653e7d9eaa3e6986692d8e" + integrity sha512-heflX7ENhL8eHmLcUxDEHCSK8CKLyPprysb2JGoSTh5Ip7nHrzIRc+2USyxul3nxi98H8pGxJIEDEpURlS1X5A== -"@rollup/rollup-linux-loong64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" - integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== +"@rollup/rollup-linux-loong64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.67.5.tgz#b76a833d8599a441a3ab876c256be1e214d76af6" + integrity sha512-rRtNU0zBfRHkJ8pscHN9qRtUYLq8Rao4EX7aVWvKcFmJUANkCCXFRU7ovNBEMhpEONyUEB/3DRVDXwDDguSfHA== -"@rollup/rollup-linux-ppc64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" - integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== +"@rollup/rollup-linux-loong64-musl@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.67.5.tgz#f87bf8e2cdde82bb80c2027a46c9398d881ff8c0" + integrity sha512-/6auyi9yj8MD/Q+8P+f2ay57jQ7a0j7Yp1JK86pecH6ZnRViQZ5GOjoRSjGArFeTSHFznRbM2QPQcBgDPyNMfQ== -"@rollup/rollup-linux-ppc64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" - integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== +"@rollup/rollup-linux-ppc64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.67.5.tgz#dccd7bb3df5b4ec1ab9849c67cec0f08204c326b" + integrity sha512-Hsc0vlt+vPvqzUkHKffWKf3YE/yexrY+yeFf7IsUvDC6PV7YGCsA/gJvPa+WJdgxGJnwuoO1HmLkbahaLKabYQ== -"@rollup/rollup-linux-riscv64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" - integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== +"@rollup/rollup-linux-ppc64-musl@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.67.5.tgz#a70d486e76639d50448ad094611679d42aac0bc8" + integrity sha512-Jx5TMMSPJwHY7F/JwPdS6nuBwny2ECRK+EsAJV5HZrfiddeSS6ZrohkLkFZm3rjqBUuQ4FPX5gGMtakUuQO6fQ== -"@rollup/rollup-linux-riscv64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" - integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== +"@rollup/rollup-linux-riscv64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.67.5.tgz#26f5b3497df1142fdd993e3d5472128ff29f7d15" + integrity sha512-fGWcq2lo/XCqAkBLOokg66D/z+MUouryB8i+QdU9RR6O5knG7rucc0WFPqWYL1Wk/wuiJouDfmyBxmZKRM9bag== -"@rollup/rollup-linux-s390x-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" - integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== +"@rollup/rollup-linux-riscv64-musl@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.67.5.tgz#964491bc28d7bbe5642b4ed15de9cb99c48a86da" + integrity sha512-JwO/Ys3jybc0UW3V5XO2CMnp0RhvoczDDQTgAEE1IctT+HRY3W5parIIWLUpmAK4gHSeeYJofm2iQPFA62K6ag== -"@rollup/rollup-linux-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" - integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== +"@rollup/rollup-linux-s390x-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.67.5.tgz#b3abae3f022136691d59d37ef978cf6c1ecb91d6" + integrity sha512-vVD4iqel6J6A1xAJlb4mxEO9i1wOJ4/SkcKoDKAuK/wqEBPOA7/P5dDyZvNx46ocHwOUwzo3WjhTdKqET9kEAg== -"@rollup/rollup-linux-x64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" - integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== +"@rollup/rollup-linux-x64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.67.5.tgz#07b8f2c8c2a9d8b580c7835d49bebc89d193ce18" + integrity sha512-HmEfNh5Cdd/NHxgqwMzKnjlvicQQA+D+w+vQXDJSV0x/aHiQSjVwh1Th5RoA7QsMieIaxibt/5iJq68BraTSUg== -"@rollup/rollup-openbsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" - integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== +"@rollup/rollup-linux-x64-musl@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.67.5.tgz#ae086941d3be6de7b220fcbd93347a73ad539e4d" + integrity sha512-NfmALpbe009nYzZ2QqlSW6nzbGddXpqk3nwualrIr+4QflQCTGMzt3QKK20yARiAnSV9fJdM5WNwIYUf31x3uA== -"@rollup/rollup-openharmony-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" - integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== +"@rollup/rollup-openbsd-x64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.67.5.tgz#27b0e37c1157b5b84bb68e112f0892f73cb7d343" + integrity sha512-ngcE7Udpy/iJ3lexVhYcbpUV2OJIakRGus3snc6K52BnAAJetDBG1Ku5Eh3CECtnW3mpBYWg5VCD4tTaOXKOVQ== -"@rollup/rollup-win32-arm64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" - integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== +"@rollup/rollup-openharmony-arm64@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.67.5.tgz#a2585a511516569a744064aa66ea9a0ea474f42c" + integrity sha512-DZ01A2EdTYX8xVeGoGiWkJBRWDh+/BOflwGPVsBay43PDxcq8ez0xuovZnPnk1xjE3CdI0pdZXxsJp/Xjmf7uQ== -"@rollup/rollup-win32-ia32-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" - integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== +"@rollup/rollup-win32-arm64-msvc@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.67.5.tgz#634b8e4fe8cbdc666e92ab03098c4a77bc431fa1" + integrity sha512-nJKiMKDO140DUCEcQZl+f4HiGPMBxrW2webjuWJ2E3qPeoruHf2zByml4TP52lA1zDi6L4I92D/5stxeA/6pig== -"@rollup/rollup-win32-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" - integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== +"@rollup/rollup-win32-ia32-msvc@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.67.5.tgz#e4ddd5318e2d438fd1964a9b6e5495322ea2dae1" + integrity sha512-ykLkBCf+Kls24QmqM3IRf1T4sGMh0rXPyymtj+hXlWKPJnXbXraHqUUsKtOIxqLasjT1i7DhfQGAaZYeiHbSOw== -"@rollup/rollup-win32-x64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" - integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== +"@rollup/rollup-win32-x64-gnu@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.67.5.tgz#3592c21f7e7010288f7aaef94db5fc358d93315e" + integrity sha512-NiFPUFw8cHhkmBlL1k1eaz+5AXXm89rztj+DQdDuQ7VGPM0+ADFhezQ2KcBMF50UEFzGUr0+DVjVND743XwKWQ== + +"@rollup/rollup-win32-x64-msvc@4.67.5": + version "4.67.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.67.5.tgz#d97682fccabcddcda5d44ecace6c636140022ffa" + integrity sha512-Khj/vrKCpdbiAWYbJAeSy4D5iep+qwW/86lNoKvdleJYGxgbRE7h87+5d5T9D7gt8NcqhNFpreRgIg8T4bLzKQ== "@shikijs/core@2.5.0", "@shikijs/core@^2.1.0": version "2.5.0" @@ -520,19 +525,19 @@ "@types/hast" "^3.0.4" "@shikijs/vscode-textmate@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224" - integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== + version "10.1.0" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.1.0.tgz#de59840c1d9903b5f5732dbc7852437003d35f11" + integrity sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ== -"@types/estree@1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== +"@types/estree@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6" + integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g== "@types/hast@^3.0.0", "@types/hast@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" - integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.5.tgz#48020de4c0e63492f4ca9db42068c108f68b7f8f" + integrity sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g== dependencies: "@types/unist" "*" @@ -542,9 +547,9 @@ integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== "@types/markdown-it@^14.1.2": - version "14.1.2" - resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" - integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + version "14.2.0" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.2.0.tgz#4074873f98fbc791131e31d7446b12dfa3a31df9" + integrity sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ== dependencies: "@types/linkify-it" "^5" "@types/mdurl" "^2" @@ -572,70 +577,70 @@ integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA== "@ungap/structured-clone@^1.0.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + version "1.4.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a" + integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ== "@vitejs/plugin-vue@^5.2.1": version "5.2.4" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8" integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA== -"@vue/compiler-core@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.26.tgz#1a91ea90980528bedff7b1c292690bfb30612485" - integrity sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w== +"@vue/compiler-core@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.44.tgz#6e5b3b56ec7747390dd8d20ee31717fc58106bb4" + integrity sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ== dependencies: - "@babel/parser" "^7.28.5" - "@vue/shared" "3.5.26" - entities "^7.0.0" + "@babel/parser" "^7.29.9" + "@vue/shared" "3.5.44" + entities "^7.0.1" estree-walker "^2.0.2" - source-map-js "^1.2.1" + source-map-js "^1.2.2" -"@vue/compiler-dom@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz#66c36b6ed8bdf43236d7188ea332bc9d078eb286" - integrity sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A== +"@vue/compiler-dom@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.44.tgz#43794b48fa3acee2600709e56c75a18e6bb2fe3c" + integrity sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg== dependencies: - "@vue/compiler-core" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/compiler-core" "3.5.44" + "@vue/shared" "3.5.44" -"@vue/compiler-sfc@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz#fb1c6c4bf9a9e22bb169e039e19437cb6995917a" - integrity sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA== +"@vue/compiler-sfc@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.44.tgz#73b85b01825d429adda0ea2d5821c2e55eb381eb" + integrity sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw== dependencies: - "@babel/parser" "^7.28.5" - "@vue/compiler-core" "3.5.26" - "@vue/compiler-dom" "3.5.26" - "@vue/compiler-ssr" "3.5.26" - "@vue/shared" "3.5.26" + "@babel/parser" "^7.29.9" + "@vue/compiler-core" "3.5.44" + "@vue/compiler-dom" "3.5.44" + "@vue/compiler-ssr" "3.5.44" + "@vue/shared" "3.5.44" estree-walker "^2.0.2" magic-string "^0.30.21" - postcss "^8.5.6" - source-map-js "^1.2.1" + postcss "^8.5.29" + source-map-js "^1.2.2" -"@vue/compiler-ssr@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz#f6e94bccbb5339180779036ddfb614f998a197ea" - integrity sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw== +"@vue/compiler-ssr@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.44.tgz#8036f76c6f45eebbbc6d0d1cf2fd4d76e44a3138" + integrity sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA== dependencies: - "@vue/compiler-dom" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/compiler-dom" "3.5.44" + "@vue/shared" "3.5.44" "@vue/devtools-api@^7.7.0": - version "7.7.9" - resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz#999dbea50da6b00cf59a1336f11fdc2b43d9e063" - integrity sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g== + version "7.7.10" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz#bfa52dee8918edeea1bfe04cce9f809eeed19862" + integrity sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA== dependencies: - "@vue/devtools-kit" "^7.7.9" + "@vue/devtools-kit" "^7.7.10" -"@vue/devtools-kit@^7.7.9": - version "7.7.9" - resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz#bc218a815616e8987df7ab3e10fc1fb3b8706c58" - integrity sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA== +"@vue/devtools-kit@^7.7.10": + version "7.7.10" + resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz#2f2b6b6eaad9ba96c031fb20c4d59c80d35dfad3" + integrity sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw== dependencies: - "@vue/devtools-shared" "^7.7.9" + "@vue/devtools-shared" "^7.7.10" birpc "^2.3.0" hookable "^5.5.3" mitt "^3.0.1" @@ -643,50 +648,51 @@ speakingurl "^14.0.1" superjson "^2.2.2" -"@vue/devtools-shared@^7.7.9": - version "7.7.9" - resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz#fa4c096b744927081a7dda5fcf05f34b1ae6ca14" - integrity sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA== +"@vue/devtools-shared@^7.7.10": + version "7.7.10" + resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz#ba45001aeb5f7b99a72db2fb824fc04a280aca53" + integrity sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ== dependencies: rfdc "^1.4.1" -"@vue/reactivity@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.26.tgz#59a1edf566dc80133c1c26c93711c877e8602c48" - integrity sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ== +"@vue/reactivity@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.44.tgz#6721374ab041003d16f2a90304f720c3b35d568c" + integrity sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw== dependencies: - "@vue/shared" "3.5.26" + "@vue/shared" "3.5.44" -"@vue/runtime-core@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.26.tgz#3f2c040bcf8018c03a1ab5adb0d788c13c986f0e" - integrity sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q== +"@vue/runtime-core@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.44.tgz#e3e4a2cd13aee28c2a447df583614138b86dbabd" + integrity sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ== dependencies: - "@vue/reactivity" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/reactivity" "3.5.44" + "@vue/shared" "3.5.44" -"@vue/runtime-dom@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz#5954848614883948ecc1f631a67b32cc32f81936" - integrity sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ== +"@vue/runtime-dom@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.44.tgz#7406fd4a63580b7a532b41f7cf4fc1dfedbdeddb" + integrity sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg== dependencies: - "@vue/reactivity" "3.5.26" - "@vue/runtime-core" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/reactivity" "3.5.44" + "@vue/runtime-core" "3.5.44" + "@vue/shared" "3.5.44" csstype "^3.2.3" -"@vue/server-renderer@3.5.26": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.26.tgz#269055497fcc75b3984063f866f17c748b565ef4" - integrity sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA== +"@vue/server-renderer@3.5.44": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.44.tgz#42617f6efc6b58fc94f181a79ef23e71db4b8d51" + integrity sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ== dependencies: - "@vue/compiler-ssr" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/compiler-ssr" "3.5.44" + "@vue/runtime-dom" "3.5.44" + "@vue/shared" "3.5.44" -"@vue/shared@3.5.26", "@vue/shared@^3.5.13": - version "3.5.26" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.26.tgz#1e02ef2d64aced818cd31d81ce5175711dc90a9f" - integrity sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A== +"@vue/shared@3.5.44", "@vue/shared@^3.5.13": + version "3.5.44" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.44.tgz#eefe91aa2e17c617260c4ae4c21246be5d85642e" + integrity sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg== "@vueuse/core@12.8.2", "@vueuse/core@^12.4.0": version "12.8.2" @@ -720,24 +726,24 @@ vue "^3.5.13" algoliasearch@^5.14.2: - version "5.46.2" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.46.2.tgz#3afba0e53f3293e39cfde9b2ef27c583d44bf2a5" - integrity sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q== + version "5.61.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.61.0.tgz#b3f5d1e5e98057cc16bb8ff1ce551cde3978cb41" + integrity sha512-CFtHkGANnIAG6TY5g9s/h+yxZ0SN2/ICMjMY1EWj9gKGiS4LSGFrmMmTA/xxyxOH8AEazFD3YSfRZMh5vIEh2w== dependencies: - "@algolia/abtesting" "1.12.2" - "@algolia/client-abtesting" "5.46.2" - "@algolia/client-analytics" "5.46.2" - "@algolia/client-common" "5.46.2" - "@algolia/client-insights" "5.46.2" - "@algolia/client-personalization" "5.46.2" - "@algolia/client-query-suggestions" "5.46.2" - "@algolia/client-search" "5.46.2" - "@algolia/ingestion" "1.46.2" - "@algolia/monitoring" "1.46.2" - "@algolia/recommend" "5.46.2" - "@algolia/requester-browser-xhr" "5.46.2" - "@algolia/requester-fetch" "5.46.2" - "@algolia/requester-node-http" "5.46.2" + "@algolia/abtesting" "1.27.0" + "@algolia/client-abtesting" "5.61.0" + "@algolia/client-analytics" "5.61.0" + "@algolia/client-common" "5.61.0" + "@algolia/client-insights" "5.61.0" + "@algolia/client-personalization" "5.61.0" + "@algolia/client-query-suggestions" "5.61.0" + "@algolia/client-search" "5.61.0" + "@algolia/ingestion" "1.61.0" + "@algolia/monitoring" "1.61.0" + "@algolia/recommend" "5.61.0" + "@algolia/requester-browser-xhr" "5.61.0" + "@algolia/requester-fetch" "5.61.0" + "@algolia/requester-node-http" "5.61.0" birpc@^2.3.0: version "2.9.0" @@ -765,11 +771,9 @@ comma-separated-tokens@^2.0.0: integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== copy-anything@^4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.0.5.tgz#16cabafd1ea4bb327a540b750f2b4df522825aea" - integrity sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA== - dependencies: - is-what "^5.2.0" + version "4.1.5" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.5.tgz#11866cab9f08ef1f98daac8c468e10d2830294e3" + integrity sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ== csstype@^3.2.3: version "3.2.3" @@ -793,10 +797,10 @@ emoji-regex-xs@^1.0.0: resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== -entities@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.0.tgz#2ae4e443f3f17d152d3f5b0f79b932c1e59deb7a" - integrity sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ== +entities@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== esbuild@^0.21.3: version "0.21.5" @@ -845,9 +849,9 @@ fsevents@~2.3.2, fsevents@~2.3.3: integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== hast-util-to-html@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" - integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== + version "9.0.6" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz#249d71c9d2549b4c4953230e9327e5e6d6a22025" + integrity sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -878,11 +882,6 @@ html-void-elements@^3.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== -is-what@^5.2.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/is-what/-/is-what-5.5.0.tgz#a3031815757cfe1f03fed990bf6355a2d3f628c4" - integrity sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw== - magic-string@^0.30.21: version "0.30.21" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" @@ -911,36 +910,36 @@ mdast-util-to-hast@^13.0.0: vfile "^6.0.0" micromark-util-character@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1" - integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== dependencies: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" micromark-util-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1" - integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== micromark-util-sanitize-uri@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de" - integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== dependencies: micromark-util-character "^2.0.0" micromark-util-encode "^2.0.0" micromark-util-symbol "^2.0.0" micromark-util-symbol@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044" - integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== micromark-util-types@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e" - integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.3.tgz#e7b04fa58589199c7ef58b37cd696fcb2bce44e7" + integrity sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg== minisearch@^7.1.1: version "7.2.0" @@ -952,10 +951,10 @@ mitt@^3.0.1: resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -nanoid@^3.3.16: - version "3.3.16" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c" - integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== +nanoid@^3.3.20: + version "3.3.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.20.tgz#dfd4444d85aa10111ef66ba85ed72e08ba0855d8" + integrity sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ== oniguruma-to-es@^3.1.0: version "3.1.1" @@ -976,24 +975,24 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -postcss@^8.4.43, postcss@^8.5.6: - version "8.5.25" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.25.tgz#5012a598eaaa897f21bbe8553be3cb7bd2bd78cb" - integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw== +postcss@^8.4.43, postcss@^8.5.29: + version "8.5.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803" + integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g== dependencies: - nanoid "^3.3.16" + nanoid "^3.3.20" picocolors "^1.1.1" - source-map-js "^1.2.1" + source-map-js "^1.2.2" preact@^10.0.0: - version "10.21.0" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.21.0.tgz#5b0335c873a1724deb66e517830db4fd310c24f6" - integrity sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg== + version "10.29.8" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.29.8.tgz#fc85b82dc2474e245b1430b51781d417361f5fc3" + integrity sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q== property-information@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" - integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== + version "7.2.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a" + integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg== regex-recursion@^6.0.2: version "6.0.2" @@ -1020,37 +1019,38 @@ rfdc@^1.4.1: integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== rollup@^4.20.0: - version "4.59.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" - integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== + version "4.67.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.67.5.tgz#98968abd5a2889597c5df82b2f50367168f59f33" + integrity sha512-3Ve5VH1hacCVutdpbmu/htg4kz+JCOIWgy4Q3hjHPYGHeHeIElNHJ0LcJ3rCfIsd5G7qOl5k5mwpzGMj9kdHPA== dependencies: - "@types/estree" "1.0.8" + "@types/estree" "^1.0.10" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.59.0" - "@rollup/rollup-android-arm64" "4.59.0" - "@rollup/rollup-darwin-arm64" "4.59.0" - "@rollup/rollup-darwin-x64" "4.59.0" - "@rollup/rollup-freebsd-arm64" "4.59.0" - "@rollup/rollup-freebsd-x64" "4.59.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" - "@rollup/rollup-linux-arm-musleabihf" "4.59.0" - "@rollup/rollup-linux-arm64-gnu" "4.59.0" - "@rollup/rollup-linux-arm64-musl" "4.59.0" - "@rollup/rollup-linux-loong64-gnu" "4.59.0" - "@rollup/rollup-linux-loong64-musl" "4.59.0" - "@rollup/rollup-linux-ppc64-gnu" "4.59.0" - "@rollup/rollup-linux-ppc64-musl" "4.59.0" - "@rollup/rollup-linux-riscv64-gnu" "4.59.0" - "@rollup/rollup-linux-riscv64-musl" "4.59.0" - "@rollup/rollup-linux-s390x-gnu" "4.59.0" - "@rollup/rollup-linux-x64-gnu" "4.59.0" - "@rollup/rollup-linux-x64-musl" "4.59.0" - "@rollup/rollup-openbsd-x64" "4.59.0" - "@rollup/rollup-openharmony-arm64" "4.59.0" - "@rollup/rollup-win32-arm64-msvc" "4.59.0" - "@rollup/rollup-win32-ia32-msvc" "4.59.0" - "@rollup/rollup-win32-x64-gnu" "4.59.0" - "@rollup/rollup-win32-x64-msvc" "4.59.0" + "@rollup/rollup-android-arm-eabi" "4.67.5" + "@rollup/rollup-android-arm64" "4.67.5" + "@rollup/rollup-android-x64" "4.67.5" + "@rollup/rollup-darwin-arm64" "4.67.5" + "@rollup/rollup-darwin-x64" "4.67.5" + "@rollup/rollup-freebsd-arm64" "4.67.5" + "@rollup/rollup-freebsd-x64" "4.67.5" + "@rollup/rollup-linux-arm-gnueabihf" "4.67.5" + "@rollup/rollup-linux-arm-musleabihf" "4.67.5" + "@rollup/rollup-linux-arm64-gnu" "4.67.5" + "@rollup/rollup-linux-arm64-musl" "4.67.5" + "@rollup/rollup-linux-loong64-gnu" "4.67.5" + "@rollup/rollup-linux-loong64-musl" "4.67.5" + "@rollup/rollup-linux-ppc64-gnu" "4.67.5" + "@rollup/rollup-linux-ppc64-musl" "4.67.5" + "@rollup/rollup-linux-riscv64-gnu" "4.67.5" + "@rollup/rollup-linux-riscv64-musl" "4.67.5" + "@rollup/rollup-linux-s390x-gnu" "4.67.5" + "@rollup/rollup-linux-x64-gnu" "4.67.5" + "@rollup/rollup-linux-x64-musl" "4.67.5" + "@rollup/rollup-openbsd-x64" "4.67.5" + "@rollup/rollup-openharmony-arm64" "4.67.5" + "@rollup/rollup-win32-arm64-msvc" "4.67.5" + "@rollup/rollup-win32-ia32-msvc" "4.67.5" + "@rollup/rollup-win32-x64-gnu" "4.67.5" + "@rollup/rollup-win32-x64-msvc" "4.67.5" fsevents "~2.3.2" shiki@^2.1.0: @@ -1067,10 +1067,10 @@ shiki@^2.1.0: "@shikijs/vscode-textmate" "^10.0.2" "@types/hast" "^3.0.4" -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +source-map-js@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.2.tgz#79fb88d97535a21cb27162cb05d211b67acea4ab" + integrity sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw== space-separated-tokens@^2.0.0: version "2.0.2" @@ -1098,9 +1098,9 @@ superjson@^2.2.2: copy-anything "^4" tabbable@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581" - integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg== + version "6.5.1" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.5.1.tgz#cfca6f3f397db9edbe08ad35915e0d7807050a18" + integrity sha512-dMCuwDUY6Noz3zVKLefBC4wq+aJV20yc7vWmM7uTNGwtQSFF5kX7OUIUsxuVajz/bdt5Xqdah3/0NLjth+cM5A== trim-lines@^3.0.0: version "3.0.1" @@ -1108,9 +1108,9 @@ trim-lines@^3.0.0: integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== unist-util-is@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" - integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== dependencies: "@types/unist" "^3.0.0" @@ -1128,27 +1128,27 @@ unist-util-stringify-position@^4.0.0: dependencies: "@types/unist" "^3.0.0" -unist-util-visit-parents@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" - integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== +unist-util-visit-parents@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.1.0.tgz#eff74226782b378957731bcf62cf5e9d8a080606" + integrity sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" unist-util-visit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" - integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + version "5.2.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.2.0.tgz#914256bc5e1801e280c12970fad4328352645974" + integrity sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" - unist-util-visit-parents "^6.0.0" + unist-util-visit-parents "^6.1.0" vfile-message@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" - integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + version "4.0.3" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== dependencies: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" @@ -1197,15 +1197,15 @@ vitepress@^1.6.4: vue "^3.5.13" vue@^3.5.13: - version "3.5.26" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.26.tgz#03a0b17311e0e593d34b9358fa249b85e3a6d9fb" - integrity sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA== + version "3.5.44" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.44.tgz#de7828ab2cf53757ecb920564b7b6c36bf187aca" + integrity sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ== dependencies: - "@vue/compiler-dom" "3.5.26" - "@vue/compiler-sfc" "3.5.26" - "@vue/runtime-dom" "3.5.26" - "@vue/server-renderer" "3.5.26" - "@vue/shared" "3.5.26" + "@vue/compiler-dom" "3.5.44" + "@vue/compiler-sfc" "3.5.44" + "@vue/runtime-dom" "3.5.44" + "@vue/server-renderer" "3.5.44" + "@vue/shared" "3.5.44" zwitch@^2.0.4: version "2.0.4" diff --git a/frontend/package.json b/frontend/package.json index 930cb509c..a16716f23 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,11 +16,11 @@ "test": "vitest" }, "dependencies": { - "@tabler/core": "^1.5.1", - "@tabler/icons-react": "^3.46.0", - "@tanstack/react-query": "^5.102.8", - "@tanstack/react-table": "^9.2.4", - "@uiw/react-textarea-code-editor": "^3.1.1", + "@tabler/core": "^1.7.3", + "@tabler/icons-react": "^3.50.0", + "@tanstack/react-query": "^5.105.3", + "@tanstack/react-table": "^9.3.1", + "@uiw/react-textarea-code-editor": "^3.1.3", "classnames": "^2.5.1", "country-flag-icons": "^1.6.20", "date-fns": "^4.4.0", @@ -28,40 +28,40 @@ "formik": "^2.4.9", "generate-password-browser": "^1.1.0", "humps": "^2.0.1", - "query-string": "^9.5.1", - "react": "^19.3.0", + "query-string": "^10.0.0", + "react": "^19.3.1", "react-bootstrap": "^2.10.10", - "react-dom": "^19.3.0", - "react-intl": "^12.1.5", + "react-dom": "^19.3.1", + "react-intl": "^12.1.6", "react-markdown": "^10.1.0", "react-qr-code": "^2.2.0", - "react-router-dom": "^7.18.4", + "react-router-dom": "^7.18.5", "react-select": "^5.10.2", - "react-toastify": "^11.1.0", + "react-toastify": "^11.2.0", "rooks": "^9.9.0" }, "devDependencies": { - "@biomejs/biome": "^2.5.10", - "@formatjs/cli": "^6.16.30", - "@tanstack/react-query-devtools": "^5.103.1", + "@biomejs/biome": "^2.6.4", + "@formatjs/cli": "^6.18.0", + "@tanstack/react-query-devtools": "^5.105.3", "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", - "@types/country-flag-icons": "^1.2.2", + "@types/country-flag-icons": "^1.6.20", "@types/humps": "^2.0.6", - "@types/node": "^26.6.2", + "@types/node": "^26.6.6", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@types/react-table": "^7.7.20", - "@vitejs/plugin-react": "^6.1.1", + "@vitejs/plugin-react": "^6.2.1", "happy-dom": "^21.0.4", - "postcss": "^8.5.28", + "postcss": "^8.5.32", "postcss-simple-vars": "^7.0.1", - "sass": "^1.104.1", + "sass": "^1.107.1", "tmp": "^0.2.7", "typescript": "7.0.2", - "vite": "^8.3.0", + "vite": "^8.3.5", "vite-plugin-checker": "^0.14.5", - "vitest": "^5.0.5" + "vitest": "^5.1.0" } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ec1a44d3a..a6f9996de 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -16,13 +16,13 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/generator@^7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979" - integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg== +"@babel/generator@^7.29.12": + version "7.29.12" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.12.tgz#682e578704a8681589f434e7d0cc64296ebb6c63" + integrity sha512-2W76mhjrpQFO1lK5e8IvGYB2D0YZ7S2R1Z2BjO2ZUYuW+amcpK/z60U6ZZmzskwoa7sJ6XohP/Wg8H5KxXI/IA== dependencies: - "@babel/parser" "^7.29.8" - "@babel/types" "^7.29.8" + "@babel/parser" "^7.29.12" + "@babel/types" "^7.29.11" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" @@ -50,10 +50,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/parser@^7.29.7", "@babel/parser@^7.29.8", "@babel/parser@^7.29.9": - version "7.29.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.11.tgz#afa959e9d16c40d4d95012553cab363fc86cbcb7" - integrity sha512-OZ6+oaK7GMyg3IfCpj8JOmn8n+w2rJbDT0LLfVDTLb1ZFeKDh3AiNELgCMUw175IJHSkLl74hG0M0VLsvx+gag== +"@babel/parser@^7.29.12", "@babel/parser@^7.29.7": + version "7.29.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423" + integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg== dependencies: "@babel/types" "^7.29.11" @@ -72,19 +72,19 @@ "@babel/types" "^7.29.7" "@babel/traverse@^7.29.7": - version "7.29.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.10.tgz#08ab387639b4a67304511259c7da6cf23a82e91c" - integrity sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ== + version "7.29.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.12.tgz#60f3196a8bded1e2b2d2efb7e47a99086733865f" + integrity sha512-MscXe/NAZcOkYEsRajHZFt3ynsb22PUi6CTrp5U7+RYycR9By84mn9mFN54YgfITiqK4hFx3Ju3tZSyzfpS5ew== dependencies: "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.8" + "@babel/generator" "^7.29.12" "@babel/helper-globals" "^7.29.7" - "@babel/parser" "^7.29.9" + "@babel/parser" "^7.29.12" "@babel/template" "^7.29.7" - "@babel/types" "^7.29.8" + "@babel/types" "^7.29.11" debug "^4.3.1" -"@babel/types@^7.29.11", "@babel/types@^7.29.7", "@babel/types@^7.29.8": +"@babel/types@^7.29.11", "@babel/types@^7.29.7": version "7.29.11" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc" integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ== @@ -92,71 +92,71 @@ "@babel/helper-string-parser" "^7.29.11" "@babel/helper-validator-identifier" "^7.29.7" -"@biomejs/biome@^2.5.10": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.3.tgz#649bf3d08d702823a9ce62a7022fa16b4e7b64dc" - integrity sha512-va/0xPku7wnfzv+D8Zpdm5uKiGJWiV8neqoEPnRjvNQQwhl4tVXS4fETs4H0CCB2rGeqJ4cu9y4iOZOnOYKAUA== +"@biomejs/biome@^2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.4.tgz#4f190d1cfa3003a6649db9197b34e47937420179" + integrity sha512-8sKKwaNW4ot22awZ3lBjwWLHnUFXgXnrBPYS2gNlBAXRbDC++7kqjK2H7dR/FgCDtPvO1xfKpgEhJkGxAKYkxA== optionalDependencies: - "@biomejs/cli-android-arm64" "2.6.3" - "@biomejs/cli-android-x64" "2.6.3" - "@biomejs/cli-darwin-arm64" "2.6.3" - "@biomejs/cli-darwin-x64" "2.6.3" - "@biomejs/cli-linux-arm64" "2.6.3" - "@biomejs/cli-linux-arm64-musl" "2.6.3" - "@biomejs/cli-linux-x64" "2.6.3" - "@biomejs/cli-linux-x64-musl" "2.6.3" - "@biomejs/cli-win32-arm64" "2.6.3" - "@biomejs/cli-win32-x64" "2.6.3" + "@biomejs/cli-android-arm64" "2.6.4" + "@biomejs/cli-android-x64" "2.6.4" + "@biomejs/cli-darwin-arm64" "2.6.4" + "@biomejs/cli-darwin-x64" "2.6.4" + "@biomejs/cli-linux-arm64" "2.6.4" + "@biomejs/cli-linux-arm64-musl" "2.6.4" + "@biomejs/cli-linux-x64" "2.6.4" + "@biomejs/cli-linux-x64-musl" "2.6.4" + "@biomejs/cli-win32-arm64" "2.6.4" + "@biomejs/cli-win32-x64" "2.6.4" -"@biomejs/cli-android-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.3.tgz#929aa987accb55957db62d1e11b76dbcc0a7f2e4" - integrity sha512-5JD7QQDEHxdeEb4sftEHG+lsuUyu8Cob/hVLqRELbgGlFl68Rldv/PPi2k4dRjERZ/EItJ3O6Xmv8lqsEtUBiw== +"@biomejs/cli-android-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.4.tgz#5e51c3c734b12c16d1b0168b5d1eb53847985c0a" + integrity sha512-jwrwigP4tBcHxzuV8izioX/BOUhJL/4e2rNSOE5nCjBkVC018LQAQUGiH+CNkFYvLtQ1dcZo5/xXERmaAGc7JQ== -"@biomejs/cli-android-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.3.tgz#f130f0ee898b437686d1cddd87e9ca5e5b7dc4de" - integrity sha512-70WOGKCgpT+Uqg6LFdTLqefV54/DypdZg+LnBQDO6AWvkgXO+9xhVClajQyDsIHYjnXMtI1pRO5sP+zMXlS/bw== +"@biomejs/cli-android-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.4.tgz#caee3503ae8edf048afb7055007745e9cd4c4bcf" + integrity sha512-HdGInPJ4nZFwHxzvEtcp2nmSBtUyJrurmtIK43upFckuS3+ZqVM05HtsxQWnqGsuy/pxckIAjyKWlEKRlBXUyA== -"@biomejs/cli-darwin-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.3.tgz#b232a0fce9b30097f69d52382fc6e7074860b012" - integrity sha512-ezf+XEKeyJ2yYlBhrzyEEHLvfRYrhtNKc6maLzR/WcAcBccb4RVDeFxsymjBve2LO0bpIMRUAiS90D4bUBRT0A== +"@biomejs/cli-darwin-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.4.tgz#dd8c4cbc4a531fd9a248808c624cb958c7c8184c" + integrity sha512-0VvcUz46wbg59Q4TZ59KkIjDJrxEyu4Eoe79+F4SChxoWe+W7AEyIp6F5r72C9+p6MsB6dIovnZt1D30lGM3XA== -"@biomejs/cli-darwin-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.3.tgz#8a31e6a26e26bba170a0ce36fff947038b733d72" - integrity sha512-Lg2tFdg4elJo+7i707PpLMs6N1hATVt4kqDo6MD/q/VnPs8rsKReKXsz1TA5GNdfRVAQNJ5GRiyK2EwNptK+MA== +"@biomejs/cli-darwin-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.4.tgz#181a204b13e9c264499a654e3d9fc4fdddba1541" + integrity sha512-fxmvkF81srlS3DiMyfm+aG3J0eG+6/BjaxRyjy57KbLSafnq0PO+hHfVWmtH2ty0S7nm6yDXBRwySfFeaRJKRQ== -"@biomejs/cli-linux-arm64-musl@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.3.tgz#84defa2b38f3fcf04b8eee8c76cbf60b3fa5ef05" - integrity sha512-dYU70A89OFHzakQPE9VOPbE60JNcQt4mtxvUGQl+EgTkGtqCFxLD2RUn1SbiMu8Mu5xFscS1bfYDpoasaUrqNA== +"@biomejs/cli-linux-arm64-musl@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.4.tgz#34c5110ab8298def8417ce55933e62ad5a6afcdd" + integrity sha512-31ZaH/MHqPX+O+evRwRrCHQ8W5/9adY+/eMytGGWZnRx1T41QsWIF09+/WujynLBhWZUxNGS5CtsWLwgOFTAyg== -"@biomejs/cli-linux-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.3.tgz#c7cb949571e8088b463385b15d60d4f1690c9242" - integrity sha512-8Stcvq98n1gs9VyLKuaWrnsd8kbXE2nKDAHtuLHGSYdGyNWN5JIIm5a28xdnmgdKzKHmiDgYyATetkEv7+zGow== +"@biomejs/cli-linux-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.4.tgz#a4deed06840a31b5788b37eb8ac8064eb92c5f4f" + integrity sha512-6+bK1Y/evmIUiJr7auertuMbiaYVicJhqA1bZyL47lCMWugv8l0gFQCqvR/gN1vZMHYm+HwoTKgeMxdNFCoQTg== -"@biomejs/cli-linux-x64-musl@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.3.tgz#6c6de66af5537443c3fe1fef429e801c556a6b5a" - integrity sha512-j6eFjUcWSphmgVTbrobEMA0wdbfNHKrGi8hRsFkc17jeS1n7nHwFACxp6jWqe9sxQqyFAeyOgt2/1bOGVcbATA== +"@biomejs/cli-linux-x64-musl@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.4.tgz#c7bff48f5aeede76931be6f2981db1e94c422dca" + integrity sha512-bomSa2XdQAGGj6Cwdm4M13qE+lN9+AOlW8KtI4hdyVyRRF/Z71qPAi95T4wtdZK0QpBbkTW8hgbZZCwPi4xzIw== -"@biomejs/cli-linux-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.3.tgz#d50121c6396a00cfb84ccf4cfd5e809a9cb4aa6b" - integrity sha512-CShmx0bO6BngywT3minv8K1xsQX1ig1vsBqYTLnmDY3mBiDYHlaLRIsdHoM1pEU9dV3BN5a0uiH0IGDtYh9TWA== +"@biomejs/cli-linux-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.4.tgz#70f438459a279e5ef2599deae3d3be2c0120c672" + integrity sha512-tbFZMdL6dP/ws4edXxYG9WhvaVa1ZDzL4G5DzahnWW7Psib9H+VTdyU4XXVxpJk5j/nYZroSbvSNyYlpW3yCIw== -"@biomejs/cli-win32-arm64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.3.tgz#6b7564eddb8fb30c7cb0bd1f958e2ccacbf38dd7" - integrity sha512-00SthDxBPwNiT7IL/mOneif6fiEU1PXK875zhr+InpeZjovY1mF5tCuDhXo1Hh0GYyiKHKzLbItzn58SAyLhJQ== +"@biomejs/cli-win32-arm64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.4.tgz#6ea7435132a4bbf1e894a6a781d56665787a0532" + integrity sha512-eCsqt8Ma3eVap+oyJCDnJZH60SIU464B66n3y38EUTJdM1T/HfZol1FJC9vb+sHToX0ix8XzqPcwJnOzyAJz0g== -"@biomejs/cli-win32-x64@2.6.3": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.3.tgz#57a5dbf7c25ff435a1b5162c68d6c6f8082ab6cb" - integrity sha512-GhtOPphBWlnCepqpNk/w5eGN5oGRCzL20EbAkL1XCIpyboeVC9R642noyT43l8LSenGS4qz9IL8sKPfwY37V/Q== +"@biomejs/cli-win32-x64@2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.4.tgz#d8c80a6d93308d3759414aa2949e2371e49d5a99" + integrity sha512-pYsMy5AThiHFOZAsngg8qN4PurM6nInKBhVX7sbj3eNpRQqo1Hb0MgCwPL7VqVMrmcEEMj9Ks3qbkallR65e8g== "@corvu/utils@~0.4.2": version "0.4.2" @@ -273,47 +273,59 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.12.tgz#afefe785949f16ac4cdd1e695935a321572dd56a" integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww== -"@formatjs/cli-native-darwin-arm64@1.1.29": - version "1.1.29" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.29.tgz#2a4fb5913318366349a5a489b302d94a292c83a4" - integrity sha512-kh+l/SZGTpDEH/aLjwbH9CqHaSW7htGiwljZ4vOobhtQbJXriQRRHzA/LAgJ+798QMflP6HjR74hK0BJPucqAw== +"@formatjs/cli-native-darwin-arm64@1.1.32": + version "1.1.32" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.32.tgz#27bf0a83cacf0e92ffe39d8f4c99de9910403428" + integrity sha512-T3R9V8DMW0y2FT3wQembm2g+g9xlszI2XBnSLqzjjSaL7NyolNN1kcLO1kYHZTx9b78e8B6zXPqk2GhChA3iNg== -"@formatjs/cli-native-linux-arm64-musl@1.0.27": - version "1.0.27" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.27.tgz#a6a1b9c9c12bee3e7220b9d7c50c040c34ae11b9" - integrity sha512-HZtqjqJrTHJt5lvhFoz0Ak5TpsYYxgIdkyh5IJYlyCpcGzCC89bb8Bq4N8K+tqsJ+p/eN0ewsLuQA9T0gT+y/w== +"@formatjs/cli-native-darwin-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-x64/-/cli-native-darwin-x64-1.1.0.tgz#4c2e81d01abd6168d3cc0cace8a24b26beb0b546" + integrity sha512-TUw3bYa2E1k2KAEFBrtCB3t4r/h9na4m8IkXsDrt4YRBYSQggYxqWCVNXO+ZiMmTOQehQdD5TyF62og4Gw8UTg== -"@formatjs/cli-native-linux-arm64@1.2.29": - version "1.2.29" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.29.tgz#b2c8863468f594c6f003ae6100aa2a8ad1f88e7c" - integrity sha512-vSTXe5IXETlf1cMCRqUHuOREM5zpF1dCk542zOHv388F71LWIWz/Hdu6pEdI4oFxdkqEM3tcKFMWB306JOOAaQ== +"@formatjs/cli-native-linux-arm64-musl@1.0.30": + version "1.0.30" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.30.tgz#0881f08733ff0dc684219827097ce2b011eccd53" + integrity sha512-GOiklaXhcj3dvctTb2qWYrZk7VapJk0pbcJFUJnoONJqNJB0X985RB7t2EXyNOCNujFr6kR8XPNVIAYj1vVIBA== -"@formatjs/cli-native-linux-x64-musl@1.0.27": - version "1.0.27" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.27.tgz#10773c860527ef011901e63bb6ca88e7cb570b1a" - integrity sha512-bv8thdLdTeYG0jcAMBAXb27tc4BiLvwa/v8US3kHLhkq7arDM5Dmvsf1+XSMNzXLuqdCU5p+OI/2m/qOb44Jqg== +"@formatjs/cli-native-linux-arm64@1.2.32": + version "1.2.32" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.32.tgz#0c44f3ebf9186418347f312853a9e3d169a7e515" + integrity sha512-OBiwXNWVmyaCh+Ga4/OqTseYJiOzLZB2aQIY5+sVG63/pP+63iVxROMctPsW5lqNgFmfe+tuvz8yXac7uRyLlw== -"@formatjs/cli-native-linux-x64@1.1.29": - version "1.1.29" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.29.tgz#91db498b3c969380dbc2a14b2427743d80dbf764" - integrity sha512-GuF20VE1eqz6u5ndmYNbiIO7g/WJaKSFNAWlhLBMCWS/8ggyw5bqvFfZMgR1VUTa/oMVWfJp+5q+oi0DMc89Sw== +"@formatjs/cli-native-linux-x64-musl@1.0.30": + version "1.0.30" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.30.tgz#e2538361506b88208b3f0dfb0107480c45984107" + integrity sha512-4nQkxuqTBLXMRp+0L1T2y/MOoiWxju/peidvMZVZDCF4hHlbAmDEj43FoHA9Iq/FskpNx898sp/OROKBD2X19w== -"@formatjs/cli-native-win32-x64@1.1.30": - version "1.1.30" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.30.tgz#233ccca3a1f3347ddfc1420fb6d59901cd9fefcf" - integrity sha512-iP5Qd7mTRu5Rn69bWnP1AgzdQ640TQQlkhiZGCGoUYSLEZ/bGTgDgzYvI4LRFIQYoS9niKDXAOETjwWAEPGoCw== +"@formatjs/cli-native-linux-x64@1.1.32": + version "1.1.32" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.32.tgz#986ac4e8003ecbac5872dce49bfd9f610aa321f3" + integrity sha512-aagNUKgj8LF7bpd1zx9BzsRkneviCZbXx4IzdaGZeehY4iY4OVO+MCTvVjSfRBOw2qKUJWu5dlo5gL98jyt7uA== -"@formatjs/cli@^6.16.30": - version "6.16.35" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.16.35.tgz#3322b9763de94b2820fea70a75d9b56abd0580c3" - integrity sha512-JeTpErAlJ0br8uaUII4uINrfldnp6C6eIYNaR+Tj4YJTGAUtxjItUPsP8fY9cc/zMDXf8SuDL6NFle4VzgY1TA== +"@formatjs/cli-native-win32-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-arm64/-/cli-native-win32-arm64-1.1.0.tgz#f9ccb3348e42679d371d685b089e4095af27c3b3" + integrity sha512-1sGjugyj8428wvDmfY8JUWkktaiIPPXnoQZeHhHpH4HMjEihIinJ0OaPR282eZ+BUFKu2yg7oeg3iFkY9XXwiA== + +"@formatjs/cli-native-win32-x64@1.1.33": + version "1.1.33" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.33.tgz#305b5344f813322ff85131af0f303596baaca1de" + integrity sha512-tUd3LNJOjSf1SyoXAVfljYFS4Z9nAb16Pofnzcjt0FwN48+lPbwfBovns1wfZ7sVZ8InYLqk+kZujBfJBTvabg== + +"@formatjs/cli@^6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.18.0.tgz#d55354f397bb5900e92dadc981b45f27fada5116" + integrity sha512-fBj1lxVPq2IC5lO48l56F2yOPqVW3OOexI/PYJbu1xLHN/+j9Tk+VeqBkZUEPsxrNwxjaOrLRsi/rDcEdrVedA== optionalDependencies: - "@formatjs/cli-native-darwin-arm64" "1.1.29" - "@formatjs/cli-native-linux-arm64" "1.2.29" - "@formatjs/cli-native-linux-arm64-musl" "1.0.27" - "@formatjs/cli-native-linux-x64" "1.1.29" - "@formatjs/cli-native-linux-x64-musl" "1.0.27" - "@formatjs/cli-native-win32-x64" "1.1.30" + "@formatjs/cli-native-darwin-arm64" "1.1.32" + "@formatjs/cli-native-darwin-x64" "1.1.0" + "@formatjs/cli-native-linux-arm64" "1.2.32" + "@formatjs/cli-native-linux-arm64-musl" "1.0.30" + "@formatjs/cli-native-linux-x64" "1.1.32" + "@formatjs/cli-native-linux-x64-musl" "1.0.30" + "@formatjs/cli-native-win32-arm64" "1.1.0" + "@formatjs/cli-native-win32-x64" "1.1.33" "@formatjs/fast-memoize@3.1.7": version "3.1.7" @@ -421,10 +433,10 @@ "@solid-primitives/refs" "^1.0.5" "@solid-primitives/utils" "^6.2.1" -"@oxc-project/types@=0.155.0": - version "0.155.0" - resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.155.0.tgz#a3525bff41bc0575839e95d2064635dd73dfef81" - integrity sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q== +"@oxc-project/types@=0.156.0": + version "0.156.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.156.0.tgz#00e35b1ead490d81b4c7fe1dbfe35fc55e89c5e6" + integrity sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw== "@parcel/watcher-android-arm64@2.6.0": version "2.6.0" @@ -556,80 +568,80 @@ uncontrollable "^8.0.4" warning "^4.0.3" -"@rolldown/binding-android-arm-eabi@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz#3bdaa2e77aabbb78194500e22d46ebbdfc009558" - integrity sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw== +"@rolldown/binding-android-arm-eabi@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz#abc3e1d5aacd1983d32a490c3a8793770da3bd1a" + integrity sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA== -"@rolldown/binding-android-arm64@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz#1bf2dd523e6b94be1366f397b946d7fd0de67f1d" - integrity sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg== +"@rolldown/binding-android-arm64@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz#85c4d52aeb79c69e8abd52e3ba3174bb946bcd49" + integrity sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg== -"@rolldown/binding-darwin-arm64@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz#99fdcc6be79f156331099de799f726809af142e7" - integrity sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA== +"@rolldown/binding-darwin-arm64@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz#d188fd77cdb2b6dc140011a573203e374fdba2d8" + integrity sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ== -"@rolldown/binding-darwin-x64@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz#752b56bcede7851580735a6cd48437eca936a182" - integrity sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A== +"@rolldown/binding-darwin-x64@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz#eb84cc00765ce2f6c48d85624ebecaa2c88470ba" + integrity sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ== -"@rolldown/binding-freebsd-x64@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz#f5104c1f4e50a63afae5d4d8c976a51d2facc8c6" - integrity sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ== +"@rolldown/binding-freebsd-x64@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz#d3c51e8a2cd2977eac7fff0e6655e2715a39328c" + integrity sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q== -"@rolldown/binding-linux-arm-gnueabihf@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz#b0a53d7f4b5293687de6ef8727469a5e2ad68312" - integrity sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ== +"@rolldown/binding-linux-arm-gnueabihf@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz#216c99c72c005c375d63d31060209b78a8983fec" + integrity sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA== -"@rolldown/binding-linux-arm64-gnu@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz#b9832ceb22aca59cef2d20e267550fc83cd60fce" - integrity sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw== +"@rolldown/binding-linux-arm64-gnu@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz#4e03383b29c5ba30bbfdd06ec746c13e7dee12dd" + integrity sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w== -"@rolldown/binding-linux-arm64-musl@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz#43d43aa8cd490ef529b37129755879c276ec899c" - integrity sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ== +"@rolldown/binding-linux-arm64-musl@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz#d159c4c87ecda02962be95a1dccde1ef6934ccf2" + integrity sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg== -"@rolldown/binding-linux-ppc64-gnu@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz#455df0b3a2ee6cacc004b8dad6295b43c39906e7" - integrity sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw== +"@rolldown/binding-linux-ppc64-gnu@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz#6e4be205f9d2188b2a996c0f82242beac7d573f6" + integrity sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA== -"@rolldown/binding-linux-s390x-gnu@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz#a2f243b64683c72cd1b2fcbe67505a89d9c9d89b" - integrity sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA== +"@rolldown/binding-linux-s390x-gnu@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz#7e68a24dbc387fdba75d95992974eddf39c9ddaf" + integrity sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg== -"@rolldown/binding-linux-x64-gnu@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz#a514415b06af3517fcdbf228ebb7e06e8ed8385a" - integrity sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw== +"@rolldown/binding-linux-x64-gnu@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz#591cdec3e65bd701ba2e7b671ca9d694142dec2c" + integrity sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w== -"@rolldown/binding-linux-x64-musl@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz#2b6ac8d0487af711741e1bc218cd49f113542f66" - integrity sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ== +"@rolldown/binding-linux-x64-musl@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz#833ac527e031f51c2ac23a615a19ed111092088c" + integrity sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA== -"@rolldown/binding-openharmony-arm64@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz#70cdb40621eb478999a015019f68a97012f5e8cd" - integrity sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w== +"@rolldown/binding-openharmony-arm64@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz#2991fbf22995469b1404afbf3850713fd917b525" + integrity sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ== -"@rolldown/binding-win32-arm64-msvc@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz#8345076b3d3b4c362e5e8411aae227f729840a39" - integrity sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg== +"@rolldown/binding-win32-arm64-msvc@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz#52d854f35cccde05f6404262bd35342a372c79f2" + integrity sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw== -"@rolldown/binding-win32-x64-msvc@1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz#04ff5f1e8786fd31a68d040661a50e6dae3c7669" - integrity sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ== +"@rolldown/binding-win32-x64-msvc@1.2.16": + version "1.2.16" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz#cda4755739a62beba28a612e243a9a4155c76821" + integrity sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw== "@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1": version "1.0.1" @@ -728,30 +740,28 @@ integrity sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg== "@swc/helpers@^0.5.0": - version "0.5.23" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a" - integrity sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw== - dependencies: - tslib "^2.8.0" + version "0.5.24" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.24.tgz#b1a41a35fcfb1413564981be1bab04b46168961b" + integrity sha512-6tcXo4kA8HgGO7AVmawB57RYi/piBPYVHVcO6dXjX+SZw8GoHmi262sp4v2Ha2kNP9Cx84qei27yT+fjS1lxgg== -"@tabler/core@^1.5.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.1.tgz#b055b3e3469bdb8aeca08ca45223a2c093e78053" - integrity sha512-sC42sr/uslr7kIZwyBPESWJKRm1E4hYAMMZyLDoyG0ZlEOlglRW0L5c1gf8x6i+QApT+AtHm10fDP1j2WgQJpg== +"@tabler/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.3.tgz#79b23adb6bf8bc6c6f3fcec8949226a938bc1d5a" + integrity sha512-9PDS6U69svI987zIpd0AVLF4Gw9Uxfq1aypceVMNxye9Jp4QWYZEtzTL7z60L8qLyVQupHA+ug1AIA8gGAnYRA== dependencies: "@popperjs/core" "^2.11.8" -"@tabler/icons-react@^3.46.0": - version "3.49.0" - resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.49.0.tgz#bdfbd8fb2ede00dd17e91e9d7eff6f306d59f55f" - integrity sha512-Um59pm5RVNUaQjFUtG0r4oYJbZuBS3tGugAA2tgBg0WufMk34Eio3nsO30RUkLuL2giYPQUNdY9HvqltLLYyNA== +"@tabler/icons-react@^3.50.0": + version "3.50.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.50.0.tgz#c401128a88bf5720b48229f4317f038ad9c75e37" + integrity sha512-X9XhFbxAmSNimQ71VAd7opfd4z84x1pLrEbBT33VCIEsFs8zWVqdhh5r/Jyi/3iJjQ861kE5Wm9ixiJfS2nwBA== dependencies: - "@tabler/icons" "3.49.0" + "@tabler/icons" "3.50.0" -"@tabler/icons@3.49.0": - version "3.49.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.49.0.tgz#8ad023d3847540250f318a73f80431f1b69d83ff" - integrity sha512-l+GnSkFETx0U26icxv1VUFnxxiTqXfCcVRiRAnyMwibupM2S2L/hSaZ9gmSgz01rkrsH/93NvLsxykqdanc6lw== +"@tabler/icons@3.50.0": + version "3.50.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.50.0.tgz#fb2bdeec8896e173c34789902aeb51ffda139753" + integrity sha512-HflLxyrH1cqC7s7jzQGSjpsTAVIoaf7+/yHjbdM59rJQBMjliibp+LYhDPFxOMcwa1M8DNpOk6ENzQiWn9a1Dg== "@tanstack/match-sorter-utils@^9.1.2": version "9.2.6" @@ -760,41 +770,41 @@ dependencies: remove-accents "0.5.0" -"@tanstack/query-core@5.104.5": - version "5.104.5" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.104.5.tgz#251e241c537fb065ae188d0332e62cf413db863c" - integrity sha512-Q94gI8Opd+CbdVNOdj8ZCyV8WqLYitrDq1t96Xc8WZT6lY0r5iRnJLXYDxMex7EEf2v+mXvn4FcjaBKSxXZQ9A== +"@tanstack/query-core@5.105.3": + version "5.105.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.105.3.tgz#6574e94812f491aabdb828cf3d71bfe7ea72320e" + integrity sha512-v1rbNy7MB/4Akth7CQH+CerV13K7Ok9hAusEsfz4sCjDdu3/kOSJPoWfoB+YjdEX1eBFBWKNAGE5nqZCgdlHDw== -"@tanstack/query-devtools@5.104.5": - version "5.104.5" - resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.104.5.tgz#b26b26d6c500c5a820797006df7cc3bbb67f0276" - integrity sha512-BLVEw4lFF7aIRsfqGvI50SSR9oN46/EmlygC+3h5TUdI+fL8GnMQialL3H92TbYi7J1b/hYlUGnckBT2uG5h8w== +"@tanstack/query-devtools@5.105.3": + version "5.105.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.105.3.tgz#6aec77ce69756204b28439a8eea9bf8511d772b0" + integrity sha512-CZQ7vqAc/XTxi6omDeAkQW3RW99HKIxZDvHi1Y/hnyZFMmYhEfCmBd7wfUb1m/BEqdXTEKqZvoRKI7oKDKUchA== dependencies: "@kobalte/core" "^0.13.14" "@solid-primitives/keyed" "^1.5.3" "@solid-primitives/resize-observer" "^2.2.0" "@solid-primitives/storage" "^1.3.11" "@tanstack/match-sorter-utils" "^9.1.2" - "@tanstack/query-core" "5.104.5" + "@tanstack/query-core" "5.105.3" clsx "^2.1.1" goober "^2.1.19" solid-js "^1.9.15" solid-transition-group "^0.2.3" superjson "^2.2.6" -"@tanstack/react-query-devtools@^5.103.1": - version "5.104.5" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.104.5.tgz#9c1cda1990667c005c363ec28743b511be0ec85f" - integrity sha512-2SMhSSYnOGSaPLuSTKLOj5nPoP4sqasOfkpTn5BLe6LIZuHzrpcqrJx0Rm8uUSN5Ob4lPAbh/1oWClnnGaR7Bg== +"@tanstack/react-query-devtools@^5.105.3": + version "5.105.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.105.3.tgz#074faf923edf26f486fa54e4729474c4da946755" + integrity sha512-ggye4KEyv9QDj4bXoqt5pfbzz3XzC0pJAQhFgGAkmKLo8cchklxafKSVafJMHllpHXAJmXJgyi4NL149FdmPnQ== dependencies: - "@tanstack/query-devtools" "5.104.5" + "@tanstack/query-devtools" "5.105.3" -"@tanstack/react-query@^5.102.8": - version "5.104.5" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.104.5.tgz#a7c4d9317fbbdab79a1cb0c6191d3fe689a823f8" - integrity sha512-RORgFe052U+93weaNuqg7qHq7vNVNzXN/VrXqD4bbAuQCWUqOl2lV5Ui9flWk5CVvMyL/Bh27QWhzwH0aaOx9g== +"@tanstack/react-query@^5.105.3": + version "5.105.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.105.3.tgz#e0d3f755aab793e86db04581824e24434283e207" + integrity sha512-dj5hGWZmwoc9NG4enoXuYbiYwb5FxgJYPVv06eUgicMTfEMTRXn08Xv9D/r1V8TfjAv+BpxdvZFzJ1h3hSWZcQ== dependencies: - "@tanstack/query-core" "5.104.5" + "@tanstack/query-core" "5.105.3" "@tanstack/react-store@^0.11.2": version "0.11.2" @@ -804,7 +814,7 @@ "@tanstack/store" "0.11.2" use-sync-external-store "^1.6.0" -"@tanstack/react-table@^9.2.4": +"@tanstack/react-table@^9.3.1": version "9.3.1" resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-9.3.1.tgz#7030e813d412f54db6dd34af351774dd1caa929e" integrity sha512-E6Ya5TnY6EaGyZYIUqig/29Lr/gJSAubQkLJFqiWkUl5nRCr0Xr9hvJot+NiRTtG+TzGxHTiqUW3bnLtQxlHug== @@ -870,10 +880,12 @@ "@types/deep-eql" "*" assertion-error "^2.0.1" -"@types/country-flag-icons@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz#8f51089cab857f0f700feabd38b3960d006d64f2" - integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w== +"@types/country-flag-icons@^1.6.20": + version "1.6.20" + resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.6.20.tgz#c92fc9e5180c8e6c4139cc8c1f27ddde0c87fb6d" + integrity sha512-rpQPv1G0MlBPEGHL3IxKP1X68uzs+LBK/5IMaEVr6fC0VmX0lYyUvjvCJTCs7P7Xlqfc5k5tRDVv2X1bNPNXnA== + dependencies: + country-flag-icons "*" "@types/debug@^4.0.0": version "4.1.13" @@ -895,9 +907,9 @@ "@types/estree" "*" "@types/estree@*", "@types/estree@^1.0.0": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" - integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6" + integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g== "@types/hast@^2.0.0": version "2.3.10" @@ -937,10 +949,10 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@>=20.0.0", "@types/node@^26.6.2": - version "26.6.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.5.tgz#cb35209e7767c503551b5fa42a48231c7441d979" - integrity sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg== +"@types/node@*", "@types/node@>=20.0.0", "@types/node@^26.6.6": + version "26.6.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.6.tgz#9ed7b5817e728ed9216ef7c5b7d373fe3b777104" + integrity sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog== dependencies: undici-types "~8.9.0" @@ -1110,10 +1122,10 @@ resolved "https://registry.yarnpkg.com/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz#cf3b7b0d6ce5635daca4c8e01c189cdcde47ec3c" integrity sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g== -"@uiw/react-textarea-code-editor@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.2.tgz#bbd5962611e054849ec6c1049681ddb428ea403d" - integrity sha512-jI+ZmGlLad+d92FHNljoyqGSS1uxK0WKm0XRLvS/7j2K0s2PpBPgkfMbwrfGrFumSoMHJYkzW/flXeA9yfqR+Q== +"@uiw/react-textarea-code-editor@^3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.3.tgz#74e0ace20408af9c77930dd877a34274df46a8c2" + integrity sha512-R9u0h4IoRpFJWjIP/w7rGnEX6rhIS68WmTn2PdG+btgmyfctENC54AXqWVagkC4pPx2I01Z2+bmYJHVLLAM1IA== dependencies: "@babel/runtime" "^7.18.6" rehype "~13.0.0" @@ -1124,27 +1136,27 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a" integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ== -"@vitejs/plugin-react@^6.1.1": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz#924d47e890a78cdf2b72f3f41fe8ab98e089b81e" - integrity sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw== +"@vitejs/plugin-react@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.2.1.tgz#b0e5c19cff9cce00dc1d63b963bae273661814ec" + integrity sha512-9KyWCij3r0+qLG3Q62vBmv0VyevmYibAtxaCIVK9o0k6CXgaajZRIe8tpfchQcdZArJ5b/7OrIz4W3b2uxVRDA== dependencies: "@rolldown/pluginutils" "^1.0.1" -"@vitest/mocker@5.0.5": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-5.0.5.tgz#0d591e22de595595b549e14af1115c69868ba099" - integrity sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg== +"@vitest/mocker@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-5.1.0.tgz#fba68dd77810c2f053b2ad7ea7ffc56e8d94eb8e" + integrity sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ== dependencies: "@jridgewell/trace-mapping" "0.3.31" - "@vitest/spy" "5.0.5" + "@vitest/spy" "5.1.0" estree-walker "^3.0.3" magic-string "^1.4.3" -"@vitest/spy@5.0.5": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-5.0.5.tgz#d5629d07a1182899f2159f700d0cd4e73785d6b9" - integrity sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw== +"@vitest/spy@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-5.1.0.tgz#0a10d0ca39c0df70951715ba1081dd186a451c23" + integrity sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A== ansi-regex@^5.0.1: version "5.0.1" @@ -1297,7 +1309,7 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -country-flag-icons@^1.6.20: +country-flag-icons@*, country-flag-icons@^1.6.20: version "1.6.20" resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.6.20.tgz#aa6f36104568993f9cd43e7da283f9b7d0a802cc" integrity sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw== @@ -1331,11 +1343,6 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" -decode-uri-component@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.1.tgz#7f66b25bf435df723f9af55713dcf860a1c297d2" - integrity sha512-vWHQkXgg2cE3BB4rR0c+PYw9BCQzLoNIRMI7AunIkZst74tw/vpPp7VLoYtsR/cy1f55g3XDWUslfeCZjiZJyA== - deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" @@ -1445,10 +1452,10 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -filter-obj@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" - integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== +filter-obj@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-6.1.0.tgz#58725ceed8f0de54b432d74b6a3eb149453d7ed0" + integrity sha512-xdMtCAODmPloU9qtmPcdBV9Kd27NtMse+4ayThxqIHUES5Z2S6bGpap5PpdmNM56ub7y3i1eyr+vJJIIgWGKmA== find-root@^1.1.0: version "1.1.0" @@ -1530,9 +1537,9 @@ hast-util-from-html@^2.0.0: vfile-message "^4.0.0" hast-util-from-parse5@^8.0.0: - version "8.0.3" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" - integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + version "8.0.4" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.4.tgz#cdbd8df25b0d031e440f3fa4f76ecbd676b41209" + integrity sha512-Yq7XlXor0Cpb71sI2E0zpv8QxQ5a6rfhDEETk4bcgrFX2SvJAY2PHocWwFtL6U9qyfiyByW3wZ6sZEfP2kW81g== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -1558,9 +1565,9 @@ hast-util-parse-selector@^4.0.0: "@types/hast" "^3.0.0" hast-util-to-html@^9.0.0: - version "9.0.5" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" - integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== + version "9.0.6" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz#249d71c9d2549b4c4953230e9327e5e6d6a22025" + integrity sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -1575,9 +1582,9 @@ hast-util-to-html@^9.0.0: zwitch "^2.0.4" hast-util-to-jsx-runtime@^2.0.0: - version "2.3.6" - resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" - integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + version "2.3.7" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.7.tgz#2825e4a628e1ee64017c20ad281d5018c1aef536" + integrity sha512-F7DNV2NMcTxA4GqPVHnXzX+mpoPHAIY9rJAVH3CZ1FdBZOcBUEjUuvMkMpHVs/sicEtznh9tMGj5WUR9S4E2rg== dependencies: "@types/estree" "^1.0.0" "@types/hast" "^3.0.0" @@ -1591,7 +1598,7 @@ hast-util-to-jsx-runtime@^2.0.0: mdast-util-mdxjs-esm "^2.0.0" property-information "^7.0.0" space-separated-tokens "^2.0.0" - style-to-js "^1.0.0" + style-to-object "^2.0.0" unist-util-position "^5.0.0" vfile-message "^4.0.0" @@ -1621,9 +1628,9 @@ hastscript@^7.0.0: space-separated-tokens "^2.0.0" hastscript@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" - integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== + version "9.0.3" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.3.tgz#960b9832fda11fd15b22b8a82e9e313926db9270" + integrity sha512-1ALZXSmCGmW3h8AMz4R4+QKSKFi7sNgXLVwmwB8FYVtl1CwQ3WDkSAEY/d5tp1g/vbQgq2s1WlHgfO3jbw1TsA== dependencies: "@types/hast" "^3.0.0" comma-separated-tokens "^2.0.0" @@ -1676,10 +1683,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -inline-style-parser@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" - integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== +inline-style-parser@0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.9.tgz#148d759a4b8677a560a1d3e185e6db7f6be57652" + integrity sha512-IttN8BqKLxzUrnTqHI+/hwAJQItBAUjdoHERCDv+oOuX48VP6bzW8+QjOXSfoevzq3YrYAwX1uLHp8TqO9yIeg== intl-messageformat@12.1.4: version "12.1.4" @@ -1714,10 +1721,10 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.17.0.tgz#75c1c76236c9f7e38ddbfff292e127dc13b21027" - integrity sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA== +is-core-module@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.18.0.tgz#8a988af8d3729fb6bbcaf025bf820cfe46b95f4c" + integrity sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw== dependencies: hasown "^2.0.4" @@ -1880,9 +1887,9 @@ lz-string@^1.5.0: integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== magic-string@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.4.tgz#35c82a312db8064966cd42f0c6427b865d0482bc" - integrity sha512-jQz9sVhrZuphH4NrAIHSgkDJCluQO7Ioi4KGRLa7aTUjAODQ13hENX0YIytqYE/rIkO485gQG2LeBxJaW53kkQ== + version "1.4.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.5.tgz#201c852d51eb0664d6f66cf7ab93b20b416e48d9" + integrity sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w== dependencies: "@jridgewell/sourcemap-codec" "^1.6.0" @@ -1917,9 +1924,9 @@ mdast-util-mdx-expression@^2.0.0: mdast-util-to-markdown "^2.0.0" mdast-util-mdx-jsx@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" - integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + version "3.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.1.tgz#40e60b71a5b822b7cb278fac4be2338ae64d7337" + integrity sha512-oXp1GIa+E9IwIs3a106+tr3Bz2MlTTBvlXjOZMoIat11kk7vjqgmBnksB9WHmWv+gZh7k3IUgpAHlwHB7DH4GQ== dependencies: "@types/estree-jsx" "^1.0.0" "@types/hast" "^3.0.0" @@ -1970,9 +1977,9 @@ mdast-util-to-hast@^13.0.0: vfile "^6.0.0" mdast-util-to-markdown@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz#40e9db55768d7dd11fae4462531cf0223a2b4f35" - integrity sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA== + version "2.2.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.2.tgz#9b558c5d3202b928a38d02ebaeec8a0ebe0e342f" + integrity sha512-ELNT5jYDUdx0B/hlUSSAiAEtvqkY3Oriwkp1KEYseF1Ccu4MnKkmE5yK3oE3WGT/Pq8VTE+XwP0GLL5pafY8yA== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -2316,10 +2323,10 @@ postcss-simple-vars@^7.0.1: resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -postcss@^8.5.28, postcss@^8.5.30: - version "8.5.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.31.tgz#d44731fa02f8ae8371629ec7220e2ae0dbdf23ec" - integrity sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg== +postcss@^8.5.30, postcss@^8.5.32: + version "8.5.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803" + integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g== dependencies: nanoid "^3.3.20" picocolors "^1.1.1" @@ -2375,14 +2382,13 @@ qrcode-generator@^2.0.4: resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-2.0.4.tgz#e8b3f30922577eba52078aa9c0d5a2a74fe1fd94" integrity sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g== -query-string@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.5.1.tgz#aecdc091d3dc7ce293eed83957e220d523a3d0f7" - integrity sha512-/zO3RwuRCMTIcEgq6YMv4OrtEE1XzBG7w5N6zc6ydYnkWYOWsnLI/5894hYEzfESfMOT4cHCsRTIdxsSl1KjGg== +query-string@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-10.0.0.tgz#fbc0743e6a0e0c1a600f2d1d399ce42521e17b0d" + integrity sha512-QYodx/Ywk8lCcOmsyupXVN91yWPAdjq0xVqE2FE2BHFYnma71C8txR8JCBptjzoUpkfAK7Qjua9VjYRTru4LYg== dependencies: - decode-uri-component "^0.5.0" - filter-obj "^5.1.0" - split-on-first "^3.0.0" + filter-obj "^6.1.0" + split-on-first "^4.0.0" raf@^3.4.1: version "3.4.1" @@ -2432,7 +2438,7 @@ react-bootstrap@^2.10.10: uncontrollable "^7.2.1" warning "^4.0.3" -react-dom@^19.3.0: +react-dom@^19.3.1: version "19.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.1.tgz#b384c53cfcbe9c2a5d09d863b98455de9d68afe2" integrity sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ== @@ -2444,10 +2450,10 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-intl@^12.1.5: - version "12.1.5" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.5.tgz#a79322b86d9281a6e77430d5d12d4dca70635843" - integrity sha512-nlyc4qcORgNTUmSHM9ZBoYhuK57yzPlV1oqlo7mwhHDrUDNC+wUzGZufwMjayXo7xqmQyQywAlYllaEBbbfqew== +react-intl@^12.1.6: + version "12.1.6" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.6.tgz#5b1f29a4382b84cd34c35bf625c831ab8ad0a53a" + integrity sha512-cxiqHlRK/JdDW/hMzusEv+LAqhfjUCL1YZAlazE39gT6jT3UrrNi3H50cgsveyUaASW14kNM2izIYMPYsYDCgA== dependencies: "@formatjs/icu-messageformat-parser" "3.5.22" "@formatjs/intl" "6.1.4" @@ -2493,17 +2499,17 @@ react-qr-code@^2.2.0: prop-types "^15.8.1" qrcode-generator "^2.0.4" -react-router-dom@^7.18.4: - version "7.18.4" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.18.4.tgz#07b72b8aab2d80af847425107f966f1688c703d0" - integrity sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw== +react-router-dom@^7.18.5: + version "7.18.5" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.18.5.tgz#25e19a13396de166785c1199227a64bcd6815a8d" + integrity sha512-9HcfDMU6JJ/fp6gJZYGwNE6RknVGS4kBjH1HkNw959mCJi70TVwdipQWv2FEol9uGpi+SZTFtVpJPtEQfayeOw== dependencies: - react-router "7.18.4" + react-router "7.18.5" -react-router@7.18.4: - version "7.18.4" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.18.4.tgz#dc30cbb27e936f06d9e1d75ab648b317deb698a4" - integrity sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ== +react-router@7.18.5: + version "7.18.5" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.18.5.tgz#808611f9f8675fe4506699815168822f4d9f3ee3" + integrity sha512-dTgvcXDOrLPrlLHrcZAqWveiK1hMSqN+rGAJzegP8HtmNkfRxOoshGWBNX3ZJ0cZjOHkRdlUJtaIhZMLLyGyPw== dependencies: cookie "^1.0.1" set-cookie-parser "^2.6.0" @@ -2535,10 +2541,10 @@ react-stately@3.51.0: "@swc/helpers" "^0.5.0" use-sync-external-store "^1.6.0" -react-toastify@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.1.0.tgz#3d3c73a44d5cac868ee9a52e0f90dd706532c4a4" - integrity sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg== +react-toastify@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.2.0.tgz#9035cbf6549c68fca0c150a9591cbc9fe1a41596" + integrity sha512-lznu1N00MfEv4p9vGWNVqCfDQ3DxScDAZTFk6STMsEcLcRZKT4PCO0JxsNduGN7LJPBSkXXs/YuLydjh650T7Q== dependencies: clsx "^2.1.1" @@ -2552,7 +2558,7 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^19.3.0: +react@^19.3.1: version "19.3.1" resolved "https://registry.yarnpkg.com/react/-/react-19.3.1.tgz#26e9523fc36ea06be5050ebb780249a133260ad7" integrity sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw== @@ -2652,12 +2658,12 @@ resolve-from@^4.0.0: integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve@^1.19.0: - version "1.22.13" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.13.tgz#d542f97758d768f36091251a6b07540b4343f7b5" - integrity sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA== + version "1.22.14" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.14.tgz#302388cd2d7731a100db1dedce7b050f74ba38f7" + integrity sha512-1ZjYvR9sbkiwC5+9QHetqW5HmA2pHRMaoupD01t2HlYnV/vydC2zPwLixhsMieSWVZt0JJRg5LVIk0Ow1D/TrA== dependencies: es-errors "^1.3.0" - is-core-module "^2.17.0" + is-core-module "^2.18.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2667,28 +2673,28 @@ retry@^0.12.0: integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== rolldown@~1.2.14: - version "1.2.15" - resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.15.tgz#d232b8201e1be3471d636375fe10af1dfc4eac35" - integrity sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g== + version "1.2.16" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.16.tgz#6c99aafa32361d578fbd945aeb43e533bb8f4c28" + integrity sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw== dependencies: - "@oxc-project/types" "=0.155.0" + "@oxc-project/types" "=0.156.0" "@rolldown/pluginutils" "^1.0.0" optionalDependencies: - "@rolldown/binding-android-arm-eabi" "1.2.15" - "@rolldown/binding-android-arm64" "1.2.15" - "@rolldown/binding-darwin-arm64" "1.2.15" - "@rolldown/binding-darwin-x64" "1.2.15" - "@rolldown/binding-freebsd-x64" "1.2.15" - "@rolldown/binding-linux-arm-gnueabihf" "1.2.15" - "@rolldown/binding-linux-arm64-gnu" "1.2.15" - "@rolldown/binding-linux-arm64-musl" "1.2.15" - "@rolldown/binding-linux-ppc64-gnu" "1.2.15" - "@rolldown/binding-linux-s390x-gnu" "1.2.15" - "@rolldown/binding-linux-x64-gnu" "1.2.15" - "@rolldown/binding-linux-x64-musl" "1.2.15" - "@rolldown/binding-openharmony-arm64" "1.2.15" - "@rolldown/binding-win32-arm64-msvc" "1.2.15" - "@rolldown/binding-win32-x64-msvc" "1.2.15" + "@rolldown/binding-android-arm-eabi" "1.2.16" + "@rolldown/binding-android-arm64" "1.2.16" + "@rolldown/binding-darwin-arm64" "1.2.16" + "@rolldown/binding-darwin-x64" "1.2.16" + "@rolldown/binding-freebsd-x64" "1.2.16" + "@rolldown/binding-linux-arm-gnueabihf" "1.2.16" + "@rolldown/binding-linux-arm64-gnu" "1.2.16" + "@rolldown/binding-linux-arm64-musl" "1.2.16" + "@rolldown/binding-linux-ppc64-gnu" "1.2.16" + "@rolldown/binding-linux-s390x-gnu" "1.2.16" + "@rolldown/binding-linux-x64-gnu" "1.2.16" + "@rolldown/binding-linux-x64-musl" "1.2.16" + "@rolldown/binding-openharmony-arm64" "1.2.16" + "@rolldown/binding-win32-arm64-msvc" "1.2.16" + "@rolldown/binding-win32-x64-msvc" "1.2.16" rooks@^9.9.0: version "9.9.0" @@ -2707,10 +2713,10 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -sass@^1.104.1: - version "1.106.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.106.1.tgz#53b3df8ebfe0e5e0bc111afe54bd2f6fda089f9c" - integrity sha512-kD+Gi66vycZcbJt+42NC4z4PXNMcCajVrJPhqoKKXy5cOBMmJQt052CxIBcRjwYXeImHYOpu/ydWWGZQIenl7A== +sass@^1.107.1: + version "1.107.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.107.1.tgz#a8a461744666e31f8973041a6b9ae7fcb265befd" + integrity sha512-aJ1/LvxOOb04YhmiBjzZn3jVwdHKol+OVhoMV6fFl02fF9T3i+5Mg9l/CnzqtM+NX5Jn5o3lAbs/ZJivTCLHfQ== dependencies: chokidar "^5.0.0" immutable "^5.1.5" @@ -2728,7 +2734,7 @@ seroval-plugins@~1.6.8: resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.6.12.tgz#a8f6013fc0fb68f797f53cde12deb25c3e9fbfee" integrity sha512-+reiwp/lYxiyctSrdvX7OQ1YLrcbWYgBiZrhSFL+V43b0/DGRLNVSDyZINbQe+tWplPOI2fw2dPrdv6hcc09kQ== -seroval@~1.6.8: +seroval@~1.6.11: version "1.6.12" resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.6.12.tgz#d6ad056e44e52e666a7ca7919a67294eb13ecd3b" integrity sha512-CqbIVfk/7GL3ITwZI55lcPbpQbjHZ2DD5cUj4iH7E9YsCfHNJV1mx7yNp86Z84w5IpmkZZS/2s5vPOWmVWSdQg== @@ -2744,12 +2750,12 @@ signal-exit@^3.0.2: integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== solid-js@^1.9.15: - version "1.9.17" - resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.17.tgz#9f8444b3667f756c233dce15f76e431cf9a86480" - integrity sha512-4GAmLOWPfNV8H9Jfn2LSFTpNEG6jiOKPiwd5lnLGQtehHaX6l3TOLzJqQ7mqwB/3k1Yy3zyumFVopmc/tm34ow== + version "1.9.18" + resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.18.tgz#a26f33066c8877fabf7094dea092138eb0ae9b63" + integrity sha512-CHGO5YFk6Y9m/1YSxVtKnXfrW7JD3Tmof8HmR4z6TmCPsS8efo8LnzIR8CQh3cUvffqYm6DWyBFfk8dQw1CNQQ== dependencies: csstype "^3.1.0" - seroval "~1.6.8" + seroval "~1.6.11" seroval-plugins "~1.6.8" solid-presence@^0.2.0: @@ -2789,10 +2795,10 @@ space-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== -split-on-first@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" - integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== +split-on-first@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-4.0.0.tgz#f49436d62be0d529cef522e2fd4bd08725de2ff3" + integrity sha512-pyrgNqiEBHA9KlEtg9quYGagtrlTyukL3ryQ+RmjRn9SYmcNQmfa+sWIsI+hlcL4WrmFdrt7IfM6o8ktBAooDQ== std-env@^4.3.0: version "4.3.0" @@ -2814,19 +2820,12 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -style-to-js@^1.0.0: - version "1.1.21" - resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" - integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== +style-to-object@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-2.0.2.tgz#f14fc7b1ec0fc7d8e88208a0e09fdd20b5e362e6" + integrity sha512-QmbXPUylqU80HAgTOJeEOdbA40W9KtxmjMhQy+tq+b8F2aoh3aQLuy82hh2jm2qsC9hR9eI0+eQidTMydKcqRA== dependencies: - style-to-object "1.0.14" - -style-to-object@1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" - integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== - dependencies: - inline-style-parser "0.2.7" + inline-style-parser "0.2.9" stylis@4.2.0: version "4.2.0" @@ -2856,9 +2855,9 @@ tiny-warning@^1.0.2: integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== tinybench@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.2.2.tgz#e03ffd4d46fabebae1d5bb292bd25e39505551ee" - integrity sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA== + version "6.2.3" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.2.3.tgz#2e560dfbf17f83ddbe0be0681f8ed0553d2136d5" + integrity sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw== tinyexec@^1.3.1: version "1.3.1" @@ -2888,10 +2887,10 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -tslib@^2.0.0, tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tslib@^2.0.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.9.1.tgz#8986213ba2f17f38ba39a7c43d0e565a9134ff5f" + integrity sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA== typescript@7.0.2: version "7.0.2" @@ -3051,7 +3050,7 @@ vite-plugin-checker@^0.14.5: proper-lockfile "^4.1.2" tiny-invariant "^1.3.3" -vite@^8.3.0: +vite@^8.3.5: version "8.3.5" resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.5.tgz#9614f66026cb92fe486cb9d3c90780b2a69fc85d" integrity sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ== @@ -3064,13 +3063,13 @@ vite@^8.3.0: optionalDependencies: fsevents "~2.3.3" -vitest@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-5.0.5.tgz#266e335f4177541e3e60f876e0694f966a9601f2" - integrity sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg== +vitest@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-5.1.0.tgz#5c6d94179dcb84bb153e87c3fc4e73cf7f29f9a0" + integrity sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA== dependencies: "@types/chai" "^5.2.2" - "@vitest/mocker" "5.0.5" + "@vitest/mocker" "5.1.0" chai "^6.3.0" es-module-lexer "^2.3.2" expect-type "^1.4.0" @@ -3106,9 +3105,9 @@ why-is-node-running@3.2.1: integrity sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ== ws@^8.21.0: - version "8.22.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.0.tgz#40f16e1d7588ac0575041be5c49b8eb261c28e73" - integrity sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg== + version "8.22.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.1.tgz#60d427870e8272ddb10c7fc5cdf00a70eaec369e" + integrity sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g== yaml@^1.10.0: version "1.10.3" diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile index cf071bafd..f6692614b 100644 --- a/test/cypress/Dockerfile +++ b/test/cypress/Dockerfile @@ -1,4 +1,4 @@ -FROM cypress/included:15.15.0 +FROM cypress/included:16.2.1 # Disable Cypress CLI colors ENV FORCE_COLOR=0 diff --git a/test/package.json b/test/package.json index 84062e892..af04a520d 100644 --- a/test/package.json +++ b/test/package.json @@ -6,19 +6,19 @@ "dependencies": { "@cypress/grep": "^7.0.0", "@jc21/cypress-swagger-validation": "^0.3.2", - "@quobix/vacuum": "^0.26.4", - "axios": "^1.19.0", + "@quobix/vacuum": "^0.36.2", + "axios": "^1.21.0", "chalk": "^5.6.2", - "cypress": "^16.0.0", - "cypress-multi-reporters": "^2.0.5", + "cypress": "^16.2.1", + "cypress-multi-reporters": "^2.1.0", "cypress-wait-until": "^3.0.2", - "eslint": "^10.9.1", + "eslint": "^10.14.0", "eslint-plugin-align-assignments": "^1.1.2", "eslint-plugin-chai-friendly": "^1.2.1", - "eslint-plugin-cypress": "^7.0.1", + "eslint-plugin-cypress": "^7.0.2", "form-data": "^4.0.6", "lodash": "^4.18.1", - "mocha": "^12.0.0", + "mocha": "^12.0.3", "mocha-junit-reporter": "^2.2.1" }, "scripts": { diff --git a/test/yarn.lock b/test/yarn.lock index 5f24217e9..2482b2268 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -48,10 +48,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4" integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw== -"@babel/helper-string-parser@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" - integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== +"@babel/helper-string-parser@^7.29.11": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63" + integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g== "@babel/helper-validator-identifier@^7.29.7": version "7.29.7" @@ -59,11 +59,11 @@ integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== "@babel/parser@^7.27.2": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" - integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== + version "7.29.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423" + integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg== dependencies: - "@babel/types" "^7.29.7" + "@babel/types" "^7.29.11" "@babel/plugin-syntax-jsx@^7.27.1": version "7.29.7" @@ -72,14 +72,32 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/types@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" - integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== +"@babel/types@^7.29.11": + version "7.29.11" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc" + integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ== dependencies: - "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-string-parser" "^7.29.11" "@babel/helper-validator-identifier" "^7.29.7" +"@cacheable/memory@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cacheable/memory/-/memory-2.2.0.tgz#72aeb8b051f6d597d10ac8595b94ad8302bd2239" + integrity sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ== + dependencies: + "@cacheable/utils" "^2.5.0" + "@keyv/bigmap" "^1.3.1" + hookified "^1.15.1" + keyv "^5.6.0" + +"@cacheable/utils@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@cacheable/utils/-/utils-2.5.0.tgz#534c91113aa48fe43baedb169550b6ee070ef303" + integrity sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA== + dependencies: + hashery "^1.5.1" + keyv "^5.6.0" + "@cypress/grep@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@cypress/grep/-/grep-7.0.0.tgz#2cdc92a395017947dc18fe3623fa7334631887e1" @@ -90,9 +108,9 @@ globby "^11.0.4" "@cypress/request@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-4.0.0.tgz#2bc3a10a7d50f338dc644dc5fd14a60107c6f3e2" - integrity sha512-wGTQfwDMMMiz/muFw4YbCLwTh0uZsXKK+6zWBzftADpitSi6iM62C8GzEhNcng2srUiGPksOriQkA8zakW2R0g== + version "4.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-4.0.1.tgz#c3f0eec41834e7590d21cd740a4c6f7e0326368d" + integrity sha512-y20e+e6dFYkOUUJLVUZTsJRuTiXZaUQ32WD+R/ux/HBybbTx4ge7cNINcua0pU8+SNkKuRbOF12mBmzuzM8n5w== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -107,7 +125,7 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "~6.14.1" + qs "^6.15.2" safe-buffer "^5.1.2" tough-cookie "^5.0.0" tunnel-agent "^0.6.0" @@ -121,9 +139,9 @@ lodash.once "^4.1.1" "@eslint-community/eslint-utils@^4.8.0": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" - integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6" + integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg== dependencies: eslint-visitor-keys "^3.4.3" @@ -160,10 +178,10 @@ resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091" integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== -"@eslint/plugin-kit@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz#4b0962f3f2c7ce8bc98b3ecfe34525c09d2cb729" - integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== +"@eslint/plugin-kit@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz#cc7268cc36405b331ef92db1bc37971f21d66fe1" + integrity sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q== dependencies: "@eslint/core" "^1.2.1" levn "^0.4.1" @@ -175,7 +193,7 @@ dependencies: "@humanfs/types" "^0.15.0" -"@humanfs/node@^0.16.6": +"@humanfs/node@^0.16.8": version "0.16.8" resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed" integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== @@ -227,6 +245,19 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@keyv/bigmap@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c" + integrity sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ== + dependencies: + hashery "^1.4.0" + hookified "^1.15.0" + +"@keyv/serialize@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a" + integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -248,14 +279,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@quobix/vacuum@^0.26.4": - version "0.26.8" - resolved "https://registry.yarnpkg.com/@quobix/vacuum/-/vacuum-0.26.8.tgz#1a912c10655ab59c5af7179ff0cbe76609fb44de" - integrity sha512-iTF9MScMs+oqoE9QXQZxo6AfyZMg5YOtAL97oLuZ6/1TBRiq/+tRK2STtaVFxs5Vd05xHeO9hRdssUurzmkIpQ== +"@quobix/vacuum@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@quobix/vacuum/-/vacuum-0.36.2.tgz#229e3aaffdb3391f5a6957d47277dd59452fac6b" + integrity sha512-rvHYm44AAN+qXjARBKXRHCOFIDWCFUj0iLxuSB2XmC0sHJcUJJzEoELK3v2ntUWkZiVJFF+3wyVlgMzew36AOA== dependencies: - https-proxy-agent "^7.0.6" - node-fetch "^3.3.2" - tar "^7.5.13" + https-proxy-agent "^9.1.0" + tar "^7.5.22" "@types/esrecurse@^4.3.1": version "4.3.1" @@ -263,9 +293,9 @@ integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== "@types/estree@^1.0.6", "@types/estree@^1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6" + integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g== "@types/json-schema@^7.0.15": version "7.0.15" @@ -300,9 +330,9 @@ acorn-walk@^8.2.0: acorn "^8.11.0" acorn@^8.11.0, acorn@^8.16.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" - integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + version "8.19.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.19.0.tgz#6b26fd97ce1cc9dc50bdf7ad305e9f1384d4f28a" + integrity sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw== agent-base@6: version "6.0.2" @@ -311,10 +341,10 @@ agent-base@6: dependencies: debug "4" -agent-base@^7.1.2: - version "7.1.4" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" - integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== +agent-base@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-9.0.0.tgz#ec9efb08314e1e75b0852d74aabf9a387f99834e" + integrity sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA== ajv-draft-04@^1.0.0: version "1.0.0" @@ -329,9 +359,9 @@ ajv-formats@^3.0.1: ajv "^8.0.0" ajv@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" - integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -339,9 +369,9 @@ ajv@^6.14.0: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.17.1: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" - integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== + version "8.20.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" + integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -361,9 +391,9 @@ ansi-regex@^5.0.1: integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" - integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + version "6.5.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.5.0.tgz#83f070e17628d0031eb33eb2315a72d8bb11d2a0" + integrity sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ== ansi-styles@^4.1.0: version "4.3.0" @@ -387,6 +417,11 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +argparse@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-3.0.4.tgz#ba802dd75fdc3d5bdcb4339b713d62c6c5d4565d" + integrity sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -424,10 +459,10 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== -axios@^1.19.0, axios@^1.7.7: - version "1.20.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.20.0.tgz#515513445aa60e71d04b6521ca6210829ccb4786" - integrity sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg== +axios@^1.21.0, axios@^1.7.7: + version "1.21.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.21.0.tgz#30c363a2652d9649e8b4d2645b34c64ca541ff67" + integrity sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA== dependencies: follow-redirects "^1.16.0" form-data "^4.0.6" @@ -435,9 +470,9 @@ axios@^1.19.0, axios@^1.7.7: proxy-from-env "^2.1.0" balanced-match@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.3.tgz#6337a2f23e0604a30481423432f99eac603599f9" - integrity sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g== + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== base64-js@^1.3.1: version "1.5.1" @@ -462,9 +497,9 @@ bluebird@^3.7.2: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== brace-expansion@^5.0.8: - version "5.0.9" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf" - integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== + version "5.0.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec" + integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ== dependencies: balanced-match "^4.0.2" @@ -480,11 +515,6 @@ browser-stdout@^1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - buffer@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -493,6 +523,17 @@ buffer@^5.7.1: base64-js "^1.3.1" ieee754 "^1.1.13" +cacheable@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-2.5.0.tgz#d142d41043e5a865f6053cc70ef4e3ad068bd5ce" + integrity sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g== + dependencies: + "@cacheable/memory" "^2.2.0" + "@cacheable/utils" "^2.5.0" + hookified "^1.15.0" + keyv "^5.6.0" + qified "^0.10.1" + cachedir@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" @@ -654,10 +695,10 @@ crypt@0.0.2: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -cypress-multi-reporters@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-2.0.5.tgz#2ef058ac7ea1f03adf368eee47c61d7d4f2644db" - integrity sha512-5ReXlNE7C/9/rpDI3z0tAJbPXsTHK7P3ogvUtBntQlmctRQ+sSMts7dIQY5MTb0XfBSge3CuwvNvaoqtw90KSQ== +cypress-multi-reporters@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-2.1.0.tgz#c664fe4ab18840d44e8398710481f053eeb997cc" + integrity sha512-QCxLVZPkIRjAykSY8EfWWlVhLVeGh0iAsZpXJhsHbP7mmHN6GGVUPUEhZfLy3FO5kd5bGhmjGsW/YY9h3qADbw== dependencies: debug "^4.4.0" lodash "^4.17.21" @@ -668,10 +709,10 @@ cypress-wait-until@^3.0.2: resolved "https://registry.yarnpkg.com/cypress-wait-until/-/cypress-wait-until-3.0.2.tgz#c90dddfa4c46a2c422f5b91d486531c560bae46e" integrity sha512-iemies796dD5CgjG5kV0MnpEmKSH+s7O83ZoJLVzuVbZmm4lheMsZqAVT73hlMx4QlkwhxbyUzhOBUOZwoOe0w== -cypress@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-16.0.0.tgz#22afcb0dbc9dda87188b05b0d7ac058215b8c35e" - integrity sha512-/H3zsayGwIGboFIiLWaXxy6ADmCdWy5QjjDBZgslCvjIgYAf1DkZerwAM7wXMPSpfpQ4w+lGjFlmwneRBpg27g== +cypress@^16.2.1: + version "16.2.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-16.2.1.tgz#98993dd2e6669e303f4ec3391c380b2c59e3b005" + integrity sha512-3Vqf1Krr92hKww/Owob9JrDO7TgBCR2ct+OQrvBOFGSnoGsikL4YF5arqDrJb+SM8Fzi7xXIeVmCeaOqJKPhRA== dependencies: "@cypress/request" "^4.0.0" "@cypress/xvfb" "^1.2.4" @@ -720,11 +761,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - dayjs@^1.11.23: version "1.11.23" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.23.tgz#b0a363506dde5f36cf5075e42ebe8115165a8c79" @@ -816,9 +852,9 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== dependencies: es-errors "^1.3.0" @@ -858,12 +894,12 @@ eslint-plugin-chai-friendly@^1.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.2.1.tgz#141392762aa8626ac3d25780a502e625ce74922b" integrity sha512-mV3EOJLDr8+L+LS8uCkP711fnNHz+4PsmPyz18xwkvjJwfLRlnx0Eu6CFnb5B+dW5ahoav2jVer2KFYsmIuv3A== -eslint-plugin-cypress@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-7.0.1.tgz#eb80500478533204ad5ebd2e49944224b0e47941" - integrity sha512-Jn2sV9rt8iiUYZY+iGY8fvV8iseJ6Tl1jngnLeSRyuHvYgxEbDUUFwNhwj1cr9gI4xCzTDh4mgXJ3YY7+dEMVg== +eslint-plugin-cypress@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-7.0.2.tgz#9260295baa503b13eb1b82b6b2daa9c98dee4f44" + integrity sha512-bJ46P9NYdXpqiO5Tn7rB5qQJ4r0TwFq7r9ZsOhUTK7wqF8MwinrNj6gB9Tt0xkJdKQE5FI1w3KY8VmPShje3MA== dependencies: - globals "^17.11.0" + globals "^17.12.0" eslint-scope@^9.1.2: version "9.1.2" @@ -885,18 +921,18 @@ eslint-visitor-keys@^5.0.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^10.9.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.9.1.tgz#409da5c41a5536d5a849f8555a18ca7ef1eb963b" - integrity sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A== +eslint@^10.14.0: + version "10.14.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.14.0.tgz#b7574df08d11d9eaa04222f09606674a0ea51db9" + integrity sha512-T0pNsxkb9IKmtQjhpK3d7nWOpFv+YHuMbTJnSVMoQblF0onvXfErrtp4V1jHtsQPan7dKaGVD0q1WCXnzMF31A== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.2" "@eslint/config-array" "^0.23.5" "@eslint/config-helpers" "^0.7.0" "@eslint/core" "^1.2.1" - "@eslint/plugin-kit" "^0.7.2" - "@humanfs/node" "^0.16.6" + "@eslint/plugin-kit" "^0.7.3" + "@humanfs/node" "^0.16.8" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" @@ -910,14 +946,14 @@ eslint@^10.9.1: esquery "^1.7.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" + file-entry-cache "11.1.5 || >11.1.6 <12" find-up "^5.0.0" glob-parent "^6.0.2" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" json-stable-stringify-without-jsonify "^1.0.1" - minimatch "^10.2.5" + minimatch "^10.2.6" natural-compare "^1.4.0" optionator "^0.9.3" @@ -1038,14 +1074,14 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-uri@^3.0.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.6.tgz#bfdd308ef763f835fed059f3c6c925f708e33e3a" - integrity sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q== + version "3.1.9" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.9.tgz#f1deba822b1d6ac79da2f860e872a63470cff203" + integrity sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ== fastq@^1.6.0: - version "1.20.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" - integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + version "1.20.3" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.3.tgz#7ab8731e647b56abcfeee997dae333ca3ee1d04d" + integrity sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw== dependencies: reusify "^1.0.4" @@ -1054,20 +1090,12 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== +"file-entry-cache@11.1.5 || >11.1.6 <12": + version "11.1.5" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.5.tgz#c8210eb055de63e68685ccfb6a017e386d4577d0" + integrity sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q== dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" + flat-cache "^6.1.23" fill-range@^7.1.1: version "7.1.1" @@ -1088,6 +1116,11 @@ find-test-names@^1.28.18: simple-bin-help "^1.8.0" tinyglobby "^0.2.13" +find-up-simple@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.1.tgz#18fb90ad49e45252c4d7fca56baade04fa3fca1e" + integrity sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ== + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -1096,23 +1129,24 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== +flat-cache@^6.1.23: + version "6.1.23" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.23.tgz#735dc888c271868d7301b3bbb8edba5028afb61d" + integrity sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA== dependencies: - flatted "^3.2.9" - keyv "^4.5.4" + cacheable "^2.5.0" + flatted "^3.4.2" + hookified "^1.15.0" -flatted@^3.2.9: - version "3.4.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" - integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== +flatted@^3.4.2: + version "3.4.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6" + integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q== follow-redirects@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" - integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== + version "1.16.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.2.tgz#a233160dd1bc9b16cbbab43709e6adc7cc941c8c" + integrity sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow== forever-agent@~0.6.1: version "0.6.1" @@ -1130,13 +1164,6 @@ form-data@^4.0.6, form-data@~4.0.4: hasown "^2.0.4" mime-types "^2.1.35" -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -1153,9 +1180,9 @@ function-bind@^1.1.2: integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== get-east-asian-width@^1.0.0, get-east-asian-width@^1.3.1, get-east-asian-width@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz#216900f91df11a8b2c198c3e1d93d6c035a776b9" - integrity sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA== + version "1.7.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz#7ee99f0bd5ab7c622002deeabba232923726d1d4" + integrity sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q== get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" @@ -1225,10 +1252,10 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -globals@^17.11.0: - version "17.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87" - integrity sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw== +globals@^17.12.0: + version "17.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.14.0.tgz#5bbbe515622ba24229bd94d4a7c441740e1b50dd" + integrity sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg== globby@^11.0.4: version "11.1.0" @@ -1277,20 +1304,30 @@ hasha@5.2.2: is-stream "^2.0.0" type-fest "^0.8.0" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== +hashery@^1.4.0, hashery@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/hashery/-/hashery-1.5.1.tgz#4ba82ad54911ac617467870845d57a9fe508a400" + integrity sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ== dependencies: - function-bind "^1.1.2" + hookified "^1.15.0" -hasown@^2.0.4: +hasown@^2.0.2, hasown@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== dependencies: function-bind "^1.1.2" +hookified@^1.15.0, hookified@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.15.1.tgz#b1fafeaa5489cdc29cb85546a8f837ed4ffbbcb6" + integrity sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg== + +hookified@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hookified/-/hookified-2.2.0.tgz#1d024ac1668973dd5bcc4a96ab9ccdb7639ef8d4" + integrity sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA== + http-signature@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f" @@ -1308,13 +1345,14 @@ https-proxy-agent@^5.0.1: agent-base "6" debug "4" -https-proxy-agent@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" - integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== +https-proxy-agent@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz#a9f60f79bc1578c37b166dd7f58b6bcb5c57e44b" + integrity sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA== dependencies: - agent-base "^7.1.2" - debug "4" + agent-base "9.0.0" + debug "^4.3.4" + proxy-agent-negotiate "1.1.0" human-signals@^1.1.1: version "1.1.1" @@ -1383,11 +1421,16 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.2, is-path-inside@^3.0.3: +is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-path-inside@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" + integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -1414,29 +1457,24 @@ isstream@~0.1.2: integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== js-yaml@^4.1.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.1.tgz#01216c001d67f48e2cd560d708c7af21090a3848" - integrity sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ== + version "4.3.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb" + integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA== dependencies: argparse "^2.0.1" js-yaml@^5.0.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.4.1.tgz#4629d91d4b4551f300435f0e4127899710f816fb" - integrity sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ== + version "5.5.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.5.0.tgz#ec843f09ffb2a54b6758a41eee109a1815d16442" + integrity sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg== dependencies: - argparse "^2.0.1" + argparse "^3.0.3" jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -1463,18 +1501,18 @@ json-stringify-safe@~5.0.1: integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + version "6.2.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6" + integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" jsonpath@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.2.1.tgz#2b74a4bcc78948e43e33ac971138ce0c68bce701" - integrity sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.3.0.tgz#623197970fb433845c68024bf9e2b864f5376ab2" + integrity sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w== dependencies: esprima "1.2.5" static-eval "2.1.1" @@ -1490,12 +1528,12 @@ jsprim@^2.0.2: json-schema "0.4.0" verror "1.10.0" -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== +keyv@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.6.0.tgz#03044074c6b4d072d0a62c7b9fa649537baf0105" + integrity sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw== dependencies: - json-buffer "3.0.1" + "@keyv/serialize" "^1.1.1" levn@^0.4.1: version "0.4.1" @@ -1554,9 +1592,9 @@ log-update@^6.1.0: wrap-ansi "^9.0.0" lru-cache@^11.0.0: - version "11.5.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.2.tgz#00e16665c90c620fba14a3c368732a976493f760" - integrity sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g== + version "11.5.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.3.tgz#a7767b4034e35269562d2b9da2b185b5df70c421" + integrity sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg== math-intrinsics@^1.1.0: version "1.1.0" @@ -1612,7 +1650,7 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== -minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5: +minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.6: version "10.2.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== @@ -1652,24 +1690,24 @@ mocha-junit-reporter@^2.2.1: strip-ansi "^6.0.1" xml "^1.0.1" -mocha@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-12.0.0.tgz#6284d68e66fac4597e8411b0fd62ab4c6717023c" - integrity sha512-NYNh5IFt6WYqm9bi4601m7vix8MZdXC0DwS4gY6WhXO2RgJWhivhISVmq1oklCif18OSI9l+vx5Mdm5oh1XGiQ== +mocha@^12.0.3: + version "12.0.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-12.0.3.tgz#f3022f5684f5a8306323dd5ab4d0f881a90cd031" + integrity sha512-beammieDdiWdkjANWeWPpkxUROYGi5FN88fPOkLiticz7HTg3Y+Pho3EYE6SUdcuWnZ3h2WD8EXCA55JF4o6wg== dependencies: browser-stdout "^1.3.1" chokidar "^5.0.0" debug "^4.3.5" diff "^9.0.0" - find-up "^5.0.0" + find-up-simple "^1.0.1" glob "^13.0.0" - is-path-inside "^3.0.3" + is-path-inside "^4.0.0" is-unicode-supported "^0.1.0" js-yaml "^5.0.0" minimatch "^10.2.2" ms "^2.1.3" picocolors "^1.1.1" - serialize-javascript "^7.0.2" + serialize-javascript "^7.1.1" strip-json-comments "^5.0.3" supports-color "^8.1.1" workerpool "^10.0.0" @@ -1684,20 +1722,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" - integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - npm-run-path@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -1705,7 +1729,7 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== @@ -1811,9 +1835,9 @@ picomatch@^2.3.1: integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== picomatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" - integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + version "4.0.7" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" + integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== pify@^2.2.0: version "2.3.0" @@ -1835,6 +1859,11 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +proxy-agent-negotiate@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz#de7d37aede9d71e74346125d6f484fac8092b615" + integrity sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ== + proxy-from-env@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" @@ -1846,9 +1875,9 @@ proxy-from-env@^2.1.0: integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== pump@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" - integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.4.tgz#1f313430527fa8b905622ebd22fe1444e757ab3c" + integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -1858,12 +1887,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@~6.14.1: - version "6.14.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" - integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== +qified@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/qified/-/qified-0.10.1.tgz#0640bf21bbe6ca540db290ad8f5fde4d870b8bda" + integrity sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA== dependencies: - side-channel "^1.1.0" + hookified "^2.1.1" + +qs@^6.15.2: + version "6.17.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.17.1.tgz#43ac71d29ca0e9d0eed16a218e35b6cde5628e81" + integrity sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA== + dependencies: + es-define-property "^1.0.1" + side-channel "^1.1.1" queue-microtask@^1.2.2: version "1.2.3" @@ -1923,14 +1960,14 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^7.6.3: - version "7.7.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" - integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + version "7.8.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.6.tgz#67c0cda133a3ceb6f70b803e67625bf969000424" + integrity sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw== -serialize-javascript@^7.0.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.2.tgz#0161010a0815dd7b7e011cbc5a3644850b8c2289" - integrity sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ== +serialize-javascript@^7.1.1: + version "7.1.4" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.4.tgz#477af55bdec0d831fa3b209245f673217fd27380" + integrity sha512-FqlEuoTq/zRmA4Lg6aOqyH3lCHbUtCB0bmHCeFniX2GBdDqMJM3FwdvEhlE5Pim1dlt2FfBdVPmUNNiGhhKqpw== shebang-command@^2.0.0: version "2.0.0" @@ -1944,13 +1981,13 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== dependencies: es-errors "^1.3.0" - object-inspect "^1.13.3" + object-inspect "^1.13.4" side-channel-map@^1.0.1: version "1.0.1" @@ -1973,14 +2010,14 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== +side-channel@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== dependencies: es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" @@ -2066,9 +2103,9 @@ string-width@^7.0.0: strip-ansi "^7.1.0" string-width@^8.2.0: - version "8.2.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-8.2.1.tgz#165089cfa527cc88fbc23dd73313f5e334af1ea1" - integrity sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA== + version "8.3.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-8.3.0.tgz#f1e85ca6d276ed9226c979c3651fb7abf548fbbe" + integrity sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ== dependencies: get-east-asian-width "^1.5.0" strip-ansi "^7.1.2" @@ -2112,11 +2149,11 @@ supports-color@^8.1.1: has-flag "^4.0.0" systeminformation@^5.31.1: - version "5.31.17" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.31.17.tgz#60bd1cdaeb34212b5ad2410608ee47d41d2d8560" - integrity sha512-TvFA9iwDWlMjqZVlKIJ0Cy+Zgm9ttlMx0SMRwJDMNKyhlEKWBMb3+WRwDi/3dvHdWbexpos4Osp4U49p5WjB5g== + version "5.33.19" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.33.19.tgz#b956e75ff6d7bdc6019ca4fc13c78b01f47cc415" + integrity sha512-Zm7wGeCG7PqfvzegMJIzrxICopWqYCeLATVmvxOOci1isLcNVKgY4Fr0hhYQzvc7cgtK3377jQVChTFAnznOiA== -tar@^7.5.13: +tar@^7.5.22: version "7.5.22" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.22.tgz#a696f998136e71487dc3f869a85bba2c67971ba9" integrity sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA== @@ -2133,9 +2170,9 @@ throttleit@^1.0.0: integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== tinyglobby@^0.2.13: - version "0.2.16" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" - integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + version "0.2.17" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== dependencies: fdir "^6.5.0" picomatch "^4.0.4" @@ -2231,11 +2268,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -web-streams-polyfill@^3.0.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" - integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -2283,11 +2315,10 @@ yallist@^5.0.0: integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yauzl@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.3.1.tgz#b0de68ae3a862715e130849679a4236f54dba45f" - integrity sha512-RNPCUkiE/ZgO4w8i9U5yDQVHaFDdnzaFANElRvpJteCspvmv2VqrRb9lvS6odVD+jqI/zDsxAHJVsafpcheVQQ== + version "3.4.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.4.0.tgz#88b2a21455f37ca7dccf2eeb33bacb4392322719" + integrity sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw== dependencies: - buffer-crc32 "~0.2.3" pend "~1.2.0" yocto-queue@^0.1.0: From 1c76fb76ea263b22221efd4e84e32fcf35f7b693 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 4 Nov 2026 22:08:48 +1000 Subject: [PATCH 49/76] install yarn in cypress image --- test/cypress/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile index f6692614b..10fda2b1f 100644 --- a/test/cypress/Dockerfile +++ b/test/cypress/Dockerfile @@ -18,6 +18,7 @@ RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz COPY --chown=1000 ./test /test WORKDIR /test -RUN yarn install && yarn cache clean +RUN npm i -g yarn && yarn install && yarn cache clean ENTRYPOINT [] CMD ["cypress", "run"] + From ec0f30106ddef7de6a8072e2985b21c47cfbb6c8 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 5 Nov 2026 07:32:24 +1000 Subject: [PATCH 50/76] Migrate cypress to v16 --- .../e2e/api/AccessListHostToggle.cy.js | 2 +- .../cypress/e2e/api/AccessListLocations.cy.js | 2 +- test/cypress/e2e/api/Streams.cy.js | 10 ++++--- test/cypress/plugins/index.mjs | 27 +++++++++++++++++++ test/cypress/support/commands.mjs | 6 ++--- 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/test/cypress/e2e/api/AccessListHostToggle.cy.js b/test/cypress/e2e/api/AccessListHostToggle.cy.js index ee1bee0dc..46e01fb3a 100644 --- a/test/cypress/e2e/api/AccessListHostToggle.cy.js +++ b/test/cypress/e2e/api/AccessListHostToggle.cy.js @@ -25,7 +25,7 @@ describe('Access Lists across Proxy Host disable/enable', () => { */ const request = (auth) => { const authArg = auth ? `-u '${auth.username}:${auth.password}'` : ''; - return cy.exec(`curl --noproxy '*' -s -o /dev/null -w '%{http_code}' ${authArg} http://${domain}/`) + return cy.task('exec', { command: `curl --noproxy '*' -s -o /dev/null -w '%{http_code}' ${authArg} http://${domain}/` }, { log: false }) .then((result) => { expect(result.exitCode).to.eq(0); return result.stdout.trim(); diff --git a/test/cypress/e2e/api/AccessListLocations.cy.js b/test/cypress/e2e/api/AccessListLocations.cy.js index 2ea2742ef..a30366a0f 100644 --- a/test/cypress/e2e/api/AccessListLocations.cy.js +++ b/test/cypress/e2e/api/AccessListLocations.cy.js @@ -31,7 +31,7 @@ describe('Per-path Access Lists', () => { */ const request = (path, creds) => { const auth = creds ? `-u '${creds.username}:${creds.password}'` : ''; - return cy.exec(`curl --noproxy '*' -s -w '\n%{http_code}' ${auth} http://${domain}${path}`) + return cy.task('exec', { command: `curl --noproxy '*' -s -w '\n%{http_code}' ${auth} http://${domain}${path}` }, { log: false }) .then((result) => { expect(result.exitCode).to.eq(0); const lines = result.stdout.trim().split('\n'); diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js index 95c6dba13..0587629fa 100644 --- a/test/cypress/e2e/api/Streams.cy.js +++ b/test/cypress/e2e/api/Streams.cy.js @@ -31,7 +31,7 @@ describe('Streams', () => { }); }); - cy.exec('rm -f /test/results/testssl.json'); + cy.task('exec', { command: 'rm -f /test/results/testssl.json' }); }); it('Should be able to create TCP Stream', () => { @@ -55,7 +55,7 @@ describe('Streams', () => { expect(data).to.have.property('tcp_forwarding', true); expect(data).to.have.property('udp_forwarding', false); - cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => { + cy.task('exec', { command: 'curl --noproxy -- http://website1.example.com:1500' }).then((result) => { expect(result.exitCode).to.eq(0); expect(result.stdout).to.contain('yay it works'); }); @@ -106,7 +106,7 @@ describe('Streams', () => { expect(data).to.have.property('tcp_forwarding', true); expect(data).to.have.property('udp_forwarding', true); - cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => { + cy.task('exec', { command: 'curl --noproxy -- http://website1.example.com:1502' }).then((result) => { expect(result.exitCode).to.eq(0); expect(result.stdout).to.contain('yay it works'); }); @@ -166,7 +166,9 @@ describe('Streams', () => { // Check the ssl termination cy.task('log', '[testssl.sh] Running ...'); - cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', { + cy.task('exec', { + command: '/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', + }, { timeout: 120000, // 2 minutes }).then((result) => { cy.task('log', `[testssl.sh] ${result.stdout}`); diff --git a/test/cypress/plugins/index.mjs b/test/cypress/plugins/index.mjs index 6a2ff9ffb..ba0706886 100644 --- a/test/cypress/plugins/index.mjs +++ b/test/cypress/plugins/index.mjs @@ -1,5 +1,6 @@ import { plugin as cypressGrepPlugin } from "@cypress/grep/plugin"; import { SwaggerValidation } from "@jc21/cypress-swagger-validation"; +import { exec } from "node:child_process"; import chalk from "chalk"; import backendTask from "./backendApi/task.mjs"; @@ -25,6 +26,32 @@ export default (on, config) => { return null; }, }); + on("task", { + /** + * Replacement for cy.exec(), removed in Cypress 16 + * + * @param {object} options + * @param {string} options.command + * @param {bool} [options.failOnNonZeroExit] Defaults to true + * @returns {object} { exitCode, stdout, stderr } + */ + exec({ command, failOnNonZeroExit = true }) { + return new Promise((resolve, reject) => { + exec(command, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => { + const result = { + exitCode: error ? (error.code ?? 1) : 0, + stdout: stdout.trimEnd(), + stderr: stderr.trimEnd(), + }; + if (error && failOnNonZeroExit) { + reject(new Error(`Command failed with exit code ${result.exitCode}: ${command}\n${result.stderr}`)); + return; + } + resolve(result); + }); + }); + }, + }); on("task", { getFixturesFolder() { return config.fixturesFolder; diff --git a/test/cypress/support/commands.mjs b/test/cypress/support/commands.mjs index 03c5bf224..0526cf553 100644 --- a/test/cypress/support/commands.mjs +++ b/test/cypress/support/commands.mjs @@ -33,7 +33,7 @@ Cypress.Commands.add("validateSwaggerFile", (url, savePath) => { return cy .request(url) .then((response) => cy.writeFile(savePath, response.body, { log: false })) - .then(() => cy.exec(`yarn swagger-lint '${savePath}'`, { failOnNonZeroExit: false })) + .then(() => cy.task('exec', { command: `yarn swagger-lint '${savePath}'`, failOnNonZeroExit: false })) .then((result) => cy.task('log', `Swagger Vacuum Results:\n${result.stdout || ''}`) .then(() => expect(result.exitCode).to.eq(0))); }); @@ -167,10 +167,10 @@ Cypress.Commands.add('createCustomCerts', ({domain, certFile, keyFile}) => { const fullKeyFile = `${fixturesFolder}/${keyFile}`; const cmd = `mkcert -cert-file="${fullCertFile}" -key-file="${fullKeyFile}" "${domain}"`; cy.log(`Creating custom certs with command: ${cmd}`); - return cy.exec(cmd).then((result) => { + return cy.task('exec', { command: cmd }).then((result) => { cy.log(`mkcert output:\n${JSON.stringify(result)}`); expect(result.exitCode).to.eq(0); - return cy.exec('mkcert -install').then((result2) => { + return cy.task('exec', { command: 'mkcert -install' }).then((result2) => { cy.log(`mkcert install output:\n${JSON.stringify(result2)}`); expect(result2.exitCode).to.eq(0); }).then(() => { From db292e947df18f8ae156adadeb8424ce1a3eaa46 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 5 Nov 2026 08:00:40 +1000 Subject: [PATCH 51/76] Bump version --- .version | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.version b/.version index 752490696..0e7079b69 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.16.0 +2.16.1 diff --git a/README.md b/README.md index 0ee51c350..84f58d94f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + From 9754c4c6394e7d0abfb988e1db51b232db815af1 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 5 Nov 2026 08:38:55 +1000 Subject: [PATCH 52/76] Bump mid version, as Tabler has changed the dark UI it's worth more than a revision change --- .version | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.version b/.version index 0e7079b69..d76bd2ba3 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.16.1 +2.17.0 diff --git a/README.md b/README.md index 84f58d94f..76a24316b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + From b5ee4f7b1b8e656ce37aa373edf613ec706b53f2 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 5 Nov 2026 10:34:33 +1000 Subject: [PATCH 53/76] Update pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f3fddbc0c..39e5c0ec6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,16 @@ - + +WARNINGS: + +1. An AI bot will comment on this PR! If you don't get a perfect score or don't +address any issues raised, your PR won't get merged! + +2. Any PR without a description will be closed. The title is not enough! + +3. Don't go creating a duplicate PR! Check that someone hasn't already created +something that tackles your fix and if so, help them first + +--> ## Why @@ -28,4 +38,3 @@ you change will break API for existing users --> - [ ] AI was used to write this - [ ] AI was used to review this - From cc46ce5ab66a8cdd9048bf956743903009043dfc Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 5 Nov 2026 16:43:28 +1000 Subject: [PATCH 54/76] Biome schema match to package --- backend/biome.json | 2 +- frontend/biome.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/biome.json b/backend/biome.json index 5d1794b5a..0e8f7062c 100644 --- a/backend/biome.json +++ b/backend/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.4/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/frontend/biome.json b/frontend/biome.json index e84969d2b..4ed66e61a 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.4/schema.json", "vcs": { "enabled": true, "clientKind": "git", From bc8bbf3562ca6d48b9d663edf4a99db4df86f7a1 Mon Sep 17 00:00:00 2001 From: Abdulhafiz Davlatov Date: Tue, 29 Sep 2026 00:43:33 +0500 Subject: [PATCH 55/76] feat: rate limit auth endpoints and scope user proxy host permissions - Add in-memory sliding window rate limiter for auth token endpoints with CI/test bypass and successful request exclusion - Add user_permission meta column migration and Objection model configuration - Support scoping allowed proxy hosts per user when in 'Created Items Only' visibility mode - Restrict advanced nginx configuration editing on hosts to admin users only - Fix user permissions route ID parsing and omit id from permission update payloads - Add allowed proxy hosts selector in permissions modal and display permissions summary badges in users table --- backend/internal/dead-host.js | 14 +++ backend/internal/proxy-host.js | 70 +++++++++++++-- backend/internal/redirection-host.js | 18 +++- backend/internal/user.js | 7 +- backend/lib/access.js | 16 +++- backend/lib/express/rate-limit.js | 89 ++++++++++++++++++ .../20260906220000_user_permission_meta.js | 41 +++++++++ backend/models/user_permission.js | 4 + backend/routes/tokens.js | 12 ++- backend/routes/users.js | 2 +- .../schema/components/permission-object.json | 19 ++++ .../paths/users/userID/permissions/put.json | 5 +- frontend/src/api/backend/models.ts | 5 ++ .../src/components/Form/LocationsFields.tsx | 43 ++++----- frontend/src/locale/src/en.json | 9 ++ frontend/src/locale/src/et.json | 9 ++ frontend/src/locale/src/vi.json | 12 +++ frontend/src/modals/DeadHostModal.tsx | 46 ++++++---- frontend/src/modals/PermissionsModal.tsx | 90 +++++++++++++++++-- frontend/src/modals/ProxyHostModal.tsx | 74 ++++++++++----- frontend/src/modals/RedirectionHostModal.tsx | 46 ++++++---- frontend/src/pages/Users/Table.tsx | 43 +++++++++ test/cypress/config/ci.mjs | 1 + 23 files changed, 571 insertions(+), 104 deletions(-) create mode 100644 backend/lib/express/rate-limit.js create mode 100644 backend/migrations/20260906220000_user_permission_meta.js diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 6523e6289..c8e0e17cd 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -54,6 +54,11 @@ const internalDeadHost = { thisData.advanced_config = ""; } + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + const row = await deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); // Add to audit log @@ -151,6 +156,15 @@ const internalDeadHost = { thisData = internalHost.cleanSslHstsData(thisData, row); + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if ( + !isAdmin && + typeof data.advanced_config !== "undefined" && + data.advanced_config !== row.advanced_config + ) { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + // do the row update await deadHostModel.query().where({ id: data.id }).patch(data); diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index f079dab02..176604563 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -3,6 +3,7 @@ import errs from "../lib/error.js"; import { castJsonIfNeed } from "../lib/helpers.js"; import utils from "../lib/utils.js"; import proxyHostModel from "../models/proxy_host.js"; +import userPermissionModel from "../models/user_permission.js"; import internalAuditLog from "./audit-log.js"; import internalCertificate from "./certificate.js"; import internalHost from "./host.js"; @@ -57,6 +58,18 @@ const internalProxyHost = { thisData.advanced_config = ""; } + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + if ( + !isAdmin && + Array.isArray(thisData.locations) && + thisData.locations.some((loc) => loc?.advanced_config && loc.advanced_config.trim() !== "") + ) { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { @@ -183,6 +196,29 @@ const internalProxyHost = { thisData = internalHost.cleanSslHstsData(thisData, row); + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if ( + !isAdmin && + typeof thisData.advanced_config !== "undefined" && + thisData.advanced_config !== row.advanced_config + ) { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + if (!isAdmin && Array.isArray(thisData.locations)) { + const existingLocations = Array.isArray(row.locations) ? row.locations : []; + for (const loc of thisData.locations) { + const existingLoc = existingLocations.find((l) => l.path === loc.path); + const existingAdv = existingLoc?.advanced_config || ""; + if (typeof loc.advanced_config === "undefined") { + loc.advanced_config = existingAdv; + } else if ((loc.advanced_config || "") !== existingAdv) { + throw new errs.PermissionError( + "You do not have permission to modify advanced configuration", + ); + } + } + } + return proxyHostModel .query() .where({ id: thisData.id }) @@ -243,7 +279,14 @@ const internalProxyHost = { .first(); if (access_data.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); + const permissions = access.getPermissions ? access.getPermissions() : {}; + const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; + query.andWhere((builder) => { + builder.where("owner_user_id", access.token.getUserId(1)); + if (allowedIds.length > 0) { + builder.orWhereIn("id", allowedIds); + } + }); } if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { @@ -427,7 +470,14 @@ const internalProxyHost = { .orderBy(castJsonIfNeed("domain_names"), "ASC"); if (accessData.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); + const permissions = access.getPermissions ? access.getPermissions() : {}; + const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; + query.andWhere((builder) => { + builder.where("owner_user_id", access.token.getUserId(1)); + if (allowedIds.length > 0) { + builder.orWhereIn("id", allowedIds); + } + }); } // Query is used for searching @@ -455,16 +505,22 @@ const internalProxyHost = { * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { + getCount: async (user_id, visibility) => { const query = proxyHostModel.query().count("id as count").where("is_deleted", 0); if (visibility !== "all") { - query.andWhere("owner_user_id", user_id); + const perm = await userPermissionModel.query().where("user_id", user_id).first(); + const allowedIds = perm?.meta?.proxy_host_ids || perm?.meta?.proxyHostIds || []; + query.andWhere((builder) => { + builder.where("owner_user_id", user_id); + if (allowedIds.length > 0) { + builder.orWhereIn("id", allowedIds); + } + }); } - return query.first().then((row) => { - return Number.parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, }; diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index 542439fd3..5e0f3a260 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -53,8 +53,13 @@ const internalRedirectionHost = { // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === "undefined") { - data.advanced_config = ""; + if (typeof thisData.advanced_config === "undefined") { + thisData.advanced_config = ""; + } + + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); } return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); @@ -183,6 +188,15 @@ const internalRedirectionHost = { thisData = internalHost.cleanSslHstsData(thisData, row); + const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); + if ( + !isAdmin && + typeof thisData.advanced_config !== "undefined" && + thisData.advanced_config !== row.advanced_config + ) { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + } + return redirectionHostModel .query() .where({ id: thisData.id }) diff --git a/backend/internal/user.js b/backend/internal/user.js index 8dd86f461..e27ac2111 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -444,7 +444,7 @@ const internalUser = { return internalUser.get(access, { id: data.id }); }) .then((user) => { - if (user.id !== data.id) { + if (Number(user.id) !== Number(data.id)) { // Sanity check that something crazy hasn't happened throw new errs.InternalValidationError( `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, @@ -454,6 +454,7 @@ const internalUser = { return user; }) .then((user) => { + const permData = _.omit(data, ["id"]); // Get perms row, patch if it exists return userPermissionModel .query() @@ -465,10 +466,10 @@ const internalUser = { return userPermissionModel .query() .where("user_id", user.id) - .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data)); + .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, permData)); } // insert - return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data)); + return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, permData)); }) .then((permissions) => { // Add to Audit Log diff --git a/backend/lib/access.js b/backend/lib/access.js index d119a4f92..3e27ff388 100644 --- a/backend/lib/access.js +++ b/backend/lib/access.js @@ -139,7 +139,13 @@ export default function (tokenString) { const query = proxyHostModel.query().select("id").andWhere("is_deleted", 0); if (permissions.visibility === "user") { - query.andWhere("owner_user_id", tokenUserId); + const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; + query.andWhere((builder) => { + builder.where("owner_user_id", tokenUserId); + if (allowedIds.length > 0) { + builder.orWhereIn("id", allowedIds); + } + }); } const rows = await query; @@ -213,6 +219,10 @@ export default function (tokenString) { return { token: Token, + getPermissions: () => permissions, + getRoles: () => userRoles, + hasRole: (role) => userRoles.includes(role), + /** * * @param {Boolean} [allowInternal] @@ -220,7 +230,8 @@ export default function (tokenString) { */ load: async (allowInternal) => { if (tokenString) { - return await Token.load(tokenString); + await this.init(); + return tokenData; } allowInternalAccess = allowInternal; return allowInternal || null; @@ -255,6 +266,7 @@ export default function (tokenString) { permission_streams: permissions.streams, permission_access_lists: permissions.access_lists, permission_certificates: permissions.certificates, + permission_meta: permissions.meta, }, }; diff --git a/backend/lib/express/rate-limit.js b/backend/lib/express/rate-limit.js new file mode 100644 index 000000000..baa34493d --- /dev/null +++ b/backend/lib/express/rate-limit.js @@ -0,0 +1,89 @@ +import { isCI } from "../config.js"; + +/** + * In-memory sliding window rate limiter for authentication endpoints + */ + +const ipRequests = new Map(); + +// Clean up old entries every 2 minutes +const cleanupTimer = setInterval(() => { + const now = Date.now(); + for (const [ip, record] of ipRequests.entries()) { + if (now - record.startTime > record.windowMs * 2) { + ipRequests.delete(ip); + } + } +}, 120000); +cleanupTimer.unref(); + +export default function createRateLimiter(options = {}) { + const windowMs = options.windowMs || 60 * 1000; // 1 minute default + const max = options.max || 10; // 10 requests default + const message = options.message || "Too many attempts from this IP, please try again later."; + const skipSuccessfulRequests = options.skipSuccessfulRequests !== false; + + return (req, res, next) => { + // Bypass rate limiting in CI, test environments, or when explicitly disabled + if ( + isCI() || + process.env.CI === "true" || + process.env.NODE_ENV === "test" || + process.env.DISABLE_RATE_LIMIT === "true" + ) { + return next(); + } + + const socketAddr = req.socket?.remoteAddress || "unknown"; + const isTrustedLocalProxy = + socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; + const forwardedFor = isTrustedLocalProxy ? req.headers["x-forwarded-for"]?.split(",")[0].trim() : null; + const clientIp = forwardedFor || socketAddr; + + const now = Date.now(); + let record = ipRequests.get(clientIp); + + if (!record || now - record.startTime > windowMs) { + record = { + count: 1, + startTime: now, + windowMs, + }; + ipRequests.set(clientIp, record); + + if (skipSuccessfulRequests) { + const currentRecord = record; + res.on("finish", () => { + if (res.statusCode < 400 && currentRecord.count > 0) { + currentRecord.count--; + } + }); + } + + return next(); + } + + record.count++; + + if (skipSuccessfulRequests) { + const currentRecord = record; + res.on("finish", () => { + if (res.statusCode < 400 && currentRecord.count > 0) { + currentRecord.count--; + } + }); + } + + if (record.count > max) { + res.setHeader("Retry-After", Math.ceil((record.startTime + windowMs - now) / 1000)); + return res.status(429).json({ + error: { + code: 429, + message, + }, + }); + } + + next(); + }; +} diff --git a/backend/migrations/20260906220000_user_permission_meta.js b/backend/migrations/20260906220000_user_permission_meta.js new file mode 100644 index 000000000..1573c77d8 --- /dev/null +++ b/backend/migrations/20260906220000_user_permission_meta.js @@ -0,0 +1,41 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "user_permission_meta"; + +/** + * Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .alterTable("user_permission", (table) => { + table.json("meta").nullable(); + }) + .then(() => { + logger.info(`[${migrateName}] user_permission Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); + + return knex.schema + .alterTable("user_permission", (table) => { + table.dropColumn("meta"); + }) + .then(() => { + logger.info(`[${migrateName}] user_permission Table altered`); + }); +}; + +export { up, down }; diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js index cc6eac2cc..0ad444c9c 100644 --- a/backend/models/user_permission.js +++ b/backend/models/user_permission.js @@ -24,6 +24,10 @@ class UserPermission extends Model { static get tableName() { return "user_permission"; } + + static get jsonAttributes() { + return ["meta"]; + } } export default UserPermission; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 8a6a1bc0f..b5f25890c 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -1,10 +1,18 @@ import express from "express"; import internalToken from "../internal/token.js"; import jwtdecode from "../lib/express/jwt-decode.js"; +import createRateLimiter from "../lib/express/rate-limit.js"; import apiValidator from "../lib/validator/api.js"; import { debug, express as logger } from "../logger.js"; import { getValidationSchema } from "../schema/index.js"; +const tokenRateLimiter = createRateLimiter({ + windowMs: 60 * 1000, + max: 10, + message: "Too many login attempts. Please try again in 1 minute.", + skipSuccessfulRequests: true, +}); + const router = express.Router({ caseSensitive: true, strict: true, @@ -42,7 +50,7 @@ router * * Create a new Token */ - .post(async (req, res, next) => { + .post(tokenRateLimiter, async (req, res, next) => { try { const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body); const result = await internalToken.getTokenFromEmail(data); @@ -64,7 +72,7 @@ router * * Verify 2FA code and get full token */ - .post(async (req, res, next) => { + .post(tokenRateLimiter, async (req, res, next) => { try { const { challenge_token, code } = await apiValidator(getValidationSchema("/tokens/2fa", "post"), req.body); const result = await internalToken.verify2FA(challenge_token, code); diff --git a/backend/routes/users.js b/backend/routes/users.js index 3f972223b..2cdb4101c 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -260,7 +260,7 @@ router .put(async (req, res, next) => { try { const payload = await apiValidator(getValidationSchema("/users/{userID}/permissions", "put"), req.body); - payload.id = req.params.user_id; + payload.id = Number.parseInt(req.params.user_id, 10); const result = await internalUser.setPermissions(res.locals.access, payload); res.status(200).send(result); } catch (err) { diff --git a/backend/schema/components/permission-object.json b/backend/schema/components/permission-object.json index 9bb5bfffa..c49e6cba9 100644 --- a/backend/schema/components/permission-object.json +++ b/backend/schema/components/permission-object.json @@ -44,6 +44,25 @@ "description": "Certificates Permissions", "enum": ["hidden", "view", "manage"], "example": "hidden" + }, + "meta": { + "type": "object", + "description": "Permission metadata", + "example": { + "proxy_host_ids": [1] + }, + "properties": { + "proxy_host_ids": { + "type": "array", + "description": "Allowed Proxy Host IDs", + "example": [1], + "items": { + "type": "integer", + "description": "Proxy Host ID", + "example": 1 + } + } + } } } } diff --git a/backend/schema/paths/users/userID/permissions/put.json b/backend/schema/paths/users/userID/permissions/put.json index 764e7466c..0bbef6dfb 100644 --- a/backend/schema/paths/users/userID/permissions/put.json +++ b/backend/schema/paths/users/userID/permissions/put.json @@ -35,7 +35,10 @@ "dead_hosts": "hidden", "proxy_hosts": "manage", "redirection_hosts": "hidden", - "streams": "hidden" + "streams": "hidden", + "meta": { + "proxy_host_ids": [1] + } } } } diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 96c1b8a48..347c62744 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -16,6 +16,11 @@ export interface UserPermissions { streams: string; accessLists: string; certificates: string; + meta?: { + proxyHostIds?: number[]; + proxy_host_ids?: number[]; + [key: string]: any; + }; } export interface User { diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx index 0f8d83033..8cd826702 100644 --- a/frontend/src/components/Form/LocationsFields.tsx +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -30,8 +30,9 @@ interface Row { interface Props { initialValues: ProxyLocation[]; name?: string; + allowAdvanced?: boolean; } -export function LocationsFields({ initialValues, name = "locations" }: Props) { +export function LocationsFields({ initialValues, name = "locations", allowAdvanced = true }: Props) { const [rows, setRows] = useState(() => (initialValues || []).map((value, id) => ({ id, value }))); const { setFieldValue } = useFormikContext(); const [expanded, setExpanded] = useState([]); @@ -217,7 +218,7 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { {isOpen && (

-
+
Location
-
- -
+ {allowAdvanced && ( +
+ +
+ )}
-
+
-
+
-
+
diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx index 4f6bc0fab..37a00dfaf 100644 --- a/frontend/src/modals/PermissionsModal.tsx +++ b/frontend/src/modals/PermissionsModal.tsx @@ -8,7 +8,7 @@ import Modal from "react-bootstrap/Modal"; import { setPermissions } from "src/api/backend"; import { Button, Loading } from "src/components"; import { FormModal } from "src/components/Form"; -import { useUser } from "src/hooks"; +import { useProxyHosts, useUser } from "src/hooks"; import { T } from "src/locale"; import styles from "./PermissionsModal.module.css"; @@ -23,6 +23,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { const queryClient = useQueryClient(); const [errorMsg, setErrorMsg] = useState(null); const { data, isLoading, error } = useUser(id); + const { data: proxyHosts } = useProxyHosts(); const [isSubmitting, setIsSubmitting] = useState(false); const onSubmit = async (values: any, { setSubmitting }: any) => { @@ -152,18 +153,24 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { preventHide={isSubmitting} initialValues={ { - visibility: data.permissions?.visibility, - accessLists: data.permissions?.accessLists, - certificates: data.permissions?.certificates, - deadHosts: data.permissions?.deadHosts, - proxyHosts: data.permissions?.proxyHosts, - redirectionHosts: data.permissions?.redirectionHosts, - streams: data.permissions?.streams, + visibility: data.permissions?.visibility || "user", + accessLists: data.permissions?.accessLists || "hidden", + certificates: data.permissions?.certificates || "hidden", + deadHosts: data.permissions?.deadHosts || "hidden", + proxyHosts: data.permissions?.proxyHosts || "hidden", + redirectionHosts: data.permissions?.redirectionHosts || "hidden", + streams: data.permissions?.streams || "hidden", + meta: { + proxy_host_ids: + data.permissions?.meta?.proxyHostIds || + data.permissions?.meta?.proxy_host_ids || + [], + }, } as any } onSubmit={onSubmit} > - {({ onHide }) => ( + {({ values, setFieldValue, onHide }: any) => (
@@ -227,6 +234,71 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { {({ field, form }: any) => getPermissionButtons(field, form)}
+ {values.visibility === "user" && values.proxyHosts !== "hidden" && ( +
+ +
+ +
+
+ {proxyHosts && proxyHosts.length > 0 ? ( + proxyHosts.map((host) => { + const selectedIds: number[] = + values.meta?.proxy_host_ids || []; + const isChecked = selectedIds.includes(host.id); + return ( +
+ { + const newIds = e.target.checked + ? [...selectedIds, host.id] + : selectedIds.filter( + (hId) => hId !== host.id, + ); + setFieldValue( + "meta.proxy_host_ids", + newIds, + ); + }} + /> + +
+ ); + }) + ) : ( +
+ +
+ )} +
+
+ )}
@@ -174,12 +191,12 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
-
+
{({ field, form }: any) => (
-
+
{({ field, form }: any) => (
-
-
+
{({ field, form }: any) => (
-
- +
{ />
-
- -
+ {isAdmin && ( +
+ +
+ )}
diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx index f9af6ccf9..78134c454 100644 --- a/frontend/src/modals/RedirectionHostModal.tsx +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -14,7 +14,7 @@ import { SSLOptionsFields, } from "src/components"; import { FormModal } from "src/components/Form"; -import { useRedirectionHost, useSetRedirectionHost } from "src/hooks"; +import { useRedirectionHost, useSetRedirectionHost, useUser } from "src/hooks"; import { T } from "src/locale"; import { validateString } from "src/modules/Validations"; import { showObjectSuccess } from "src/notifications"; @@ -27,11 +27,14 @@ interface Props extends InnerModalProps { id: number | "new"; } const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data: currentUser } = useUser("me"); const { data, isLoading, error } = useRedirectionHost(id); const { mutate: setRedirectionHost } = useSetRedirectionHost(); const [errorMsg, setErrorMsg] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); + const isAdmin = currentUser?.roles?.includes("admin"); + const onSubmit = async (values: any, { setSubmitting }: any) => { if (isSubmitting) return; setIsSubmitting(true); @@ -42,6 +45,11 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = ...values, }; + if (!isAdmin) { + delete payload.advanced_config; + delete payload.advancedConfig; + } + setRedirectionHost(payload, { onError: (err: any) => setErrorMsg(), onSuccess: () => { @@ -134,19 +142,21 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = -
  • - - - -
  • + {isAdmin && ( +
  • + + + +
  • + )}
    @@ -326,9 +336,11 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = />
    -
    - -
    + {isAdmin && ( +
    + +
    + )}
    diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx index a5a61057b..49c64e6b8 100644 --- a/frontend/src/pages/Users/Table.tsx +++ b/frontend/src/pages/Users/Table.tsx @@ -90,6 +90,49 @@ export default function Table({ return ; }, }), + columnHelper.accessor((row: any) => row.permissions, { + id: "permissions", + header: intl.formatMessage({ id: "action.permissions" }), + cell: (info: any) => { + const permissions = info.getValue(); + const isAdminUser = info.row.original.roles?.includes("admin"); + if (isAdminUser) { + return ( + + + + ); + } + if (!permissions) { + return -; + } + const items = [ + { key: "proxyHosts", label: "proxy-hosts", val: permissions.proxyHosts }, + { key: "redirectionHosts", label: "redirection-hosts", val: permissions.redirectionHosts }, + { key: "deadHosts", label: "dead-hosts", val: permissions.deadHosts }, + { key: "streams", label: "streams", val: permissions.streams }, + { key: "accessLists", label: "access-lists", val: permissions.accessLists }, + { key: "certificates", label: "certificates", val: permissions.certificates }, + ]; + return ( +
    + {items.map((it) => { + if (it.val === "hidden") return null; + const isManage = it.val === "manage"; + return ( + + : {it.val} + + ); + })} +
    + ); + }, + }), columnHelper.accessor((row: any) => row.isDisabled, { id: "isDisabled", header: intl.formatMessage({ id: "column.status" }), diff --git a/test/cypress/config/ci.mjs b/test/cypress/config/ci.mjs index 86c0a80df..b1e208190 100644 --- a/test/cypress/config/ci.mjs +++ b/test/cypress/config/ci.mjs @@ -7,6 +7,7 @@ export default defineConfig({ allowCypressEnv: false, requestTimeout: 30000, defaultCommandTimeout: 20000, + taskTimeout: 90000, reporter: "cypress-multi-reporters", reporterOptions: { configFile: "multi-reporter.json" From 0d9054211985ee4f331349f9815d9c15adde06e0 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 25 Oct 2026 18:01:59 +0100 Subject: [PATCH 56/76] Give the same error for every failed login An unknown or passwordless account was rejected without the i18n key that a wrong password gets, so the response told which emails have an account. --- backend/internal/token.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/internal/token.js b/backend/internal/token.js index 67255b213..f7a8457a6 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -35,13 +35,13 @@ export default { .first(); if (!user) { - throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N); } const auth = await authModel.query().where("user_id", "=", user.id).where("type", "=", "password").first(); if (!auth) { - throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N); } const valid = await auth.verifyPassword(data.secret); From c08f2e65a8189b5a73b331f8f1af46cc21b42c17 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 25 Oct 2026 18:01:59 +0100 Subject: [PATCH 57/76] Add test for the same error on every failed login --- test/cypress/e2e/api/Tokens.cy.js | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/cypress/e2e/api/Tokens.cy.js diff --git a/test/cypress/e2e/api/Tokens.cy.js b/test/cypress/e2e/api/Tokens.cy.js new file mode 100644 index 000000000..c5bd55c41 --- /dev/null +++ b/test/cypress/e2e/api/Tokens.cy.js @@ -0,0 +1,61 @@ +/// + +describe('Tokens endpoints', () => { + const user = { + email: 'cypress@example.com', + secret: 'changeme', + }; + const disabledUser = { + email: 'disabled@example.com', + secret: 'changeme', + }; + const unknownEmail = 'nobody@example.com'; + + const login = (identity, secret) => { + return cy.request({ + method: 'POST', + url: '/api/tokens', + body: {identity, secret}, + failOnStatusCode: false, + }); + }; + + before(() => { + cy.resetUsers(); + cy.getToken().then((token) => { + cy.task('backendApiPost', { + token: token, + path: '/api/users', + data: { + name: 'Disabled User', + nickname: 'Disabled', + email: disabledUser.email, + is_disabled: true, + auth: { + type: 'password', + secret: disabledUser.secret, + }, + }, + }); + }); + }); + + it('Should give the same answer for an unknown email, a disabled account and a wrong password', () => { + // Only the error is compared: CI runs the backend in debug mode, which adds a stack trace that a real + // client never gets. + login(user.email, 'not-the-password').then((wrongPassword) => { + expect(wrongPassword.status).to.equal(400); + expect(wrongPassword.body.error.message_i18n).to.equal('error.invalid-auth'); + + login(unknownEmail, user.secret).then((unknown) => { + expect(unknown.status).to.equal(wrongPassword.status); + expect(unknown.body.error).to.deep.equal(wrongPassword.body.error); + }); + + login(disabledUser.email, disabledUser.secret).then((disabled) => { + expect(disabled.status).to.equal(wrongPassword.status); + expect(disabled.body.error).to.deep.equal(wrongPassword.body.error); + }); + }); + }); +}); From e93b771ff5d5f8abd5e4f9949b1383db462d21e0 Mon Sep 17 00:00:00 2001 From: Alexis Loskoutoff Date: Sun, 25 Oct 2026 18:02:06 +0100 Subject: [PATCH 58/76] Check a dummy hash when a login names no account Without a password to check, a login for an unknown account skipped bcrypt and answered far sooner than one with a wrong password, which still told which emails have an account. --- backend/internal/token.js | 24 +++++++++++++----------- backend/models/auth.js | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/backend/internal/token.js b/backend/internal/token.js index f7a8457a6..d3a178d4f 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -34,18 +34,20 @@ export default { .andWhere("is_disabled", 0) .first(); - if (!user) { - throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N); - } + // An unknown, disabled or passwordless account goes through the same queries and the same password check + // as a real one, and fails with the same error: answering any sooner or any differently would tell anyone + // trying addresses which ones have an account. There is no user 0, so its lookup finds nothing. An empty + // secret, which creating a user through the API can store, is no hash: bcrypt would reject it at once. + const auth = await authModel + .query() + .where("user_id", "=", user?.id ?? 0) + .where("type", "=", "password") + .first(); - const auth = await authModel.query().where("user_id", "=", user.id).where("type", "=", "password").first(); - - if (!auth) { - throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N); - } - - const valid = await auth.verifyPassword(data.secret); - if (!valid) { + const valid = auth?.secret + ? await auth.verifyPassword(data.secret) + : await authModel.verifyDummyPassword(data.secret); + if (!user || !valid) { throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N); } diff --git a/backend/models/auth.js b/backend/models/auth.js index e8af582d5..c6228feeb 100644 --- a/backend/models/auth.js +++ b/backend/models/auth.js @@ -1,6 +1,7 @@ // Objection Docs: // http://vincit.github.io/objection.js/ +import crypto from "node:crypto"; import bcrypt from "bcrypt"; import { Model } from "objection"; import db from "../db.js"; @@ -11,10 +12,15 @@ import User from "./user.js"; Model.knex(db()); const boolFields = ["is_deleted"]; +const saltRounds = 13; + +// A hash of a random value, for a login that names an account with no password to check it against. Hashed +// with the same cost as real passwords, and made at startup so the first such login takes no longer either. +const dummyHash = bcrypt.hash(crypto.randomBytes(32).toString("hex"), saltRounds); function encryptPassword() { if (this.type === "password" && this.secret) { - return bcrypt.hash(this.secret, 13).then((hash) => { + return bcrypt.hash(this.secret, saltRounds).then((hash) => { this.secret = hash; }); } @@ -60,6 +66,18 @@ class Auth extends Model { return bcrypt.compare(password, this.secret); } + /** + * Take as long as verifyPassword does, then fail. bcrypt is nearly all of the time a login takes, so a login + * that skipped it for an unknown account would answer far sooner, and say which accounts exist. + * + * @param {String} password + * @returns {Promise} + */ + static async verifyDummyPassword(password) { + await bcrypt.compare(password, await dummyHash); + return false; + } + static get name() { return "Auth"; } From 3f2c1867712ff68b96091d568d9b0f7c33a7e296 Mon Sep 17 00:00:00 2001 From: Stephan Paternotte Date: Sat, 7 Nov 2026 15:47:16 +0100 Subject: [PATCH 59/76] Update nl.json Lang: Dutch translation file updated + some minor corrections --- frontend/src/locale/src/nl.json | 91 ++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json index 86d49d95e..28fe956b2 100644 --- a/frontend/src/locale/src/nl.json +++ b/frontend/src/locale/src/nl.json @@ -71,6 +71,12 @@ "access-list.help.rules-order": { "defaultMessage": "Houd er rekening mee dat de regels worden toegepast in de volgorde waarin ze zijn gedefinieerd." }, + "access-list.inherit": { + "defaultMessage": "Overnemen van Proxy Host" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "Gebruikt de toegangslijst van de Proxy Host" + }, "access-list.pass-auth": { "defaultMessage": "Authenticatie naar upstream doorgeven" }, @@ -101,6 +107,9 @@ "action.allow": { "defaultMessage": "Toestaan" }, + "action.clear": { + "defaultMessage": "Wissen" + }, "action.close": { "defaultMessage": "Sluiten" }, @@ -122,6 +131,9 @@ "action.enable": { "defaultMessage": "Inschakelen" }, + "action.logs": { + "defaultMessage": "Logboeken" + }, "action.permissions": { "defaultMessage": "Machtigingen" }, @@ -266,6 +278,9 @@ "column.email": { "defaultMessage": "E-mail" }, + "column.error": { + "defaultMessage": "Fout" + }, "column.event": { "defaultMessage": "Gebeurtenis" }, @@ -422,6 +437,9 @@ "footer.github-fork": { "defaultMessage": "Maak een Fork op Github" }, + "form.unsaved-changes": { + "defaultMessage": "Je hebt niet-opgeslagen wijzigingen. AWeet je zeker dat je ze wilt weggooien?" + }, "host.flags.block-exploits": { "defaultMessage": "Veelvoorkomende kwetsbaarheden blokkeren" }, @@ -461,6 +479,12 @@ "loading": { "defaultMessage": "Laden…" }, + "location.advanced-config": { + "defaultMessage": "Heeft aangepaste Nginx-configuratie" + }, + "location.filter": { + "defaultMessage": "Filteren op pad of bestemming" + }, "login.2fa-code": { "defaultMessage": "Verificatiecode" }, @@ -479,6 +503,51 @@ "login.title": { "defaultMessage": "Log in op je account" }, + "logs": { + "defaultMessage": "Logboeken" + }, + "logs.channel.access": { + "defaultMessage": "Toegang" + }, + "logs.channel.error": { + "defaultMessage": "Fout" + }, + "logs.download": { + "defaultMessage": "Downloaden" + }, + "logs.empty": { + "defaultMessage": "Nog geen logboekregistraties." + }, + "logs.level": { + "defaultMessage": "Niveau" + }, + "logs.level.all": { + "defaultMessage": "Alle niveaus" + }, + "logs.lines": { + "defaultMessage": "Regels" + }, + "logs.live": { + "defaultMessage": "Live" + }, + "logs.paused": { + "defaultMessage": "Gepauzeerd" + }, + "logs.refresh": { + "defaultMessage": "Verversen" + }, + "logs.search-placeholder": { + "defaultMessage": "Logboek doorzoeken…" + }, + "logs.source": { + "defaultMessage": "Bron" + }, + "logs.source.system": { + "defaultMessage": "Systeem" + }, + "logs.truncated-warning": { + "defaultMessage": "Alleen het meest recente deel van dit bestand wordt getoond." + }, "nginx-config.label": { "defaultMessage": "Aangepaste Nginx-configuratie" }, @@ -575,12 +644,21 @@ "password.show": { "defaultMessage": "Wachtwoord weergeven" }, + "permissions.allowed-proxy-hosts": { + "defaultMessage": "Toegestane Proxy Hosts" + }, + "permissions.allowed-proxy-hosts-help": { + "defaultMessage": "Verleent extra toegang tot geselecteerde Proxy Hosts die door andere gebruikers zijn aangemaakt (naast de hosts die door de gebruiker zelf zijn aangemaakt)." + }, "permissions.hidden": { "defaultMessage": "Verborgen" }, "permissions.manage": { "defaultMessage": "Beheren" }, + "permissions.no-proxy-hosts": { + "defaultMessage": "Geen Proxy Hosts gevonden" + }, "permissions.view": { "defaultMessage": "Alleen weergeven" }, @@ -594,13 +672,13 @@ "defaultMessage": "Alleen aangemaakte items" }, "proxy-host": { - "defaultMessage": "Proxyhost" + "defaultMessage": "Proxy Host" }, "proxy-host.forward-host": { "defaultMessage": "Hostname / IP Doorsturen" }, "proxy-hosts": { - "defaultMessage": "Proxyhosts" + "defaultMessage": "Proxy Hosts" }, "proxy-hosts.count": { "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}" @@ -609,7 +687,7 @@ "defaultMessage": "Openbaar" }, "redirection-host": { - "defaultMessage": "Omleidingshost" + "defaultMessage": "Omgeleide Host" }, "redirection-host.forward-domain": { "defaultMessage": "Doorgestuurd Domein" @@ -624,7 +702,7 @@ "defaultMessage": "{count} {count, plural, one {Omgeleide Host} other {Omgeleide Hosts}}" }, "redirection-hosts.http-code.300": { - "defaultMessage": "300 Meerkeuze" + "defaultMessage": "300 Meerdere keuzes" }, "redirection-hosts.http-code.301": { "defaultMessage": "301 Definitief verplaatst" @@ -636,10 +714,10 @@ "defaultMessage": "303 Zie andere" }, "redirection-hosts.http-code.307": { - "defaultMessage": "307 Tijdelijke omleiding" + "defaultMessage": "307 Tijdelijk omgeleid" }, "redirection-hosts.http-code.308": { - "defaultMessage": "308 Definitieve omleiding" + "defaultMessage": "308 Definitief omgeleid" }, "role.admin": { "defaultMessage": "Beheerder" @@ -774,3 +852,4 @@ "defaultMessage": "Gebruikers" } } + From e824d353f0719d9f9758248a7df040f17171dae0 Mon Sep 17 00:00:00 2001 From: Stephan Paternotte Date: Sat, 7 Nov 2026 15:55:09 +0100 Subject: [PATCH 60/76] Update frontend/src/locale/src/nl.json Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- frontend/src/locale/src/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json index 28fe956b2..140255d7a 100644 --- a/frontend/src/locale/src/nl.json +++ b/frontend/src/locale/src/nl.json @@ -438,7 +438,7 @@ "defaultMessage": "Maak een Fork op Github" }, "form.unsaved-changes": { - "defaultMessage": "Je hebt niet-opgeslagen wijzigingen. AWeet je zeker dat je ze wilt weggooien?" + "defaultMessage": "Je hebt niet-opgeslagen wijzigingen. Weet je zeker dat je ze wilt weggooien?" }, "host.flags.block-exploits": { "defaultMessage": "Veelvoorkomende kwetsbaarheden blokkeren" From 15beff767d589b4a676acc14a36d8432cf6b0384 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 9 Nov 2026 02:16:59 +0800 Subject: [PATCH 61/76] Add Traditional Chinese (Taiwan) translation --- frontend/check-locales.cjs | 1 + frontend/src/locale/IntlProvider.tsx | 22 +- frontend/src/locale/Utils.test.tsx | 44 +- frontend/src/locale/src/HelpDoc/index.ts | 40 +- .../locale/src/HelpDoc/zh-tw/AccessLists.md | 7 + .../locale/src/HelpDoc/zh-tw/Certificates.md | 21 + .../src/locale/src/HelpDoc/zh-tw/DeadHosts.md | 7 + .../locale/src/HelpDoc/zh-tw/ProxyHosts.md | 7 + .../src/HelpDoc/zh-tw/RedirectionHosts.md | 5 + .../src/locale/src/HelpDoc/zh-tw/Streams.md | 5 + .../src/locale/src/HelpDoc/zh-tw/index.ts | 6 + frontend/src/locale/src/lang-list.json | 5 +- frontend/src/locale/src/zh-tw.json | 854 ++++++++++++++++++ frontend/src/modals/HelpModal.tsx | 2 +- 14 files changed, 1014 insertions(+), 12 deletions(-) create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/Streams.md create mode 100644 frontend/src/locale/src/HelpDoc/zh-tw/index.ts create mode 100644 frontend/src/locale/src/zh-tw.json diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs index 251b19c4b..1b02ce8ec 100644 --- a/frontend/check-locales.cjs +++ b/frontend/check-locales.cjs @@ -23,6 +23,7 @@ const allLocales = [ ["cs", "cs-CZ"], ["vi", "vi-VN"], ["zh", "zh-CN"], + ["zh-tw", "zh-TW"], ["ko", "ko-KR"], ["bg", "bg-BG"], ["id", "id-ID"], diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index 671ef8ed5..108f75f78 100644 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -18,6 +18,7 @@ import langSk from "./lang/sk.json"; import langCs from "./lang/cs.json"; import langVi from "./lang/vi.json"; import langZh from "./lang/zh.json"; +import langZhTw from "./lang/zh-tw.json"; import langTr from "./lang/tr.json"; import langHu from "./lang/hu.json"; import langNo from "./lang/no.json"; @@ -27,7 +28,7 @@ import langFa from "./lang/fa.json"; import langList from "./lang/lang-list.json"; // first item of each array should be the language code, -// not the country code +// or the full locale when multiple variants share a language // Remember when adding to this list, also update check-locales.js script const localeOptions = [ ["en", "en-US", langEn], @@ -46,6 +47,7 @@ const localeOptions = [ ["cs", "cs-CZ", langCs], ["vi", "vi-VN", langVi], ["zh", "zh-CN", langZh], + ["zh-TW", "zh-TW", langZhTw], ["ko", "ko-KR", langKo], ["bg", "bg-BG", langBg], ["id", "id-ID", langId], @@ -58,18 +60,26 @@ const localeOptions = [ ]; const loadMessages = (locale?: string): typeof langList & typeof langEn => { - const thisLocale = (locale || "en").slice(0, 2); + const requestedLocale = (locale || "en").toLowerCase(); + const shortLocale = requestedLocale.slice(0, 2); + const localeOption = + localeOptions.find((option) => String(option[1]).toLowerCase() === requestedLocale) ?? + localeOptions.find((option) => String(option[0]).toLowerCase() === shortLocale); // ensure this lang exists in localeOptions above, otherwise fallback to en - if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { + if (!localeOption || localeOption[0] === "en") { return Object.assign({}, langList, langEn); } - return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]); + return Object.assign({}, langList, langEn, localeOption[2]); }; const getFlagCodeForLocale = (locale?: string) => { - const thisLocale = (locale || "en").slice(0, 2); + const requestedLocale = (locale || "en").toLowerCase(); + if (requestedLocale === "zh-tw") { + return "TW"; + } + const thisLocale = requestedLocale.slice(0, 2); // only add to this if your flag is different from the locale code const specialCases: Record = { @@ -160,4 +170,4 @@ const T = ({ //console.log("L:", localeOptions); -export { localeOptions, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T }; +export { localeOptions, loadMessages, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T }; diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx index 77db2b6b6..0111c242d 100644 --- a/frontend/src/locale/Utils.test.tsx +++ b/frontend/src/locale/Utils.test.tsx @@ -1,4 +1,6 @@ -import { formatDateTime, getFlagCodeForLocale } from "src/locale"; +import { changeLocale, formatDateTime, getFlagCodeForLocale, getLocale, loadMessages } from "src/locale"; +import { getHelpFile } from "src/locale/src/HelpDoc"; +import * as zhTwHelp from "src/locale/src/HelpDoc/zh-tw"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; describe("DateFormatter", () => { @@ -83,6 +85,9 @@ describe("getFlagCodeForLocale", () => { it("returns correct flag code for special-case locales", () => { expect(getFlagCodeForLocale("ja-JP")).toBe("JP"); expect(getFlagCodeForLocale("zh-CN")).toBe("CN"); + expect(getFlagCodeForLocale("zh")).toBe("CN"); + expect(getFlagCodeForLocale("zh-TW")).toBe("TW"); + expect(getFlagCodeForLocale("ZH-tw")).toBe("TW"); expect(getFlagCodeForLocale("vi-VN")).toBe("VN"); expect(getFlagCodeForLocale("ko-KR")).toBe("KR"); expect(getFlagCodeForLocale("cs-CZ")).toBe("CZ"); @@ -123,3 +128,40 @@ describe("formatDateTime Vietnamese locale", () => { } }); }); + +describe("Chinese locale variants", () => { + it("loads Traditional Chinese by full locale before falling back to Simplified Chinese", () => { + expect(loadMessages("ZH-tw")).toEqual(loadMessages("zh-TW")); + expect(loadMessages("zh-TW").save).toBe("儲存"); + expect(loadMessages("zh").save).toBe("保存"); + expect(loadMessages("zh-CN")).toEqual(loadMessages("zh")); + expect(loadMessages("zh-HK")).toEqual(loadMessages("zh")); + }); + + it("preserves the full locale and resets RTL layout when switching to Traditional Chinese", () => { + const originalLocale = getLocale(); + const savedLocale = window.localStorage.getItem("locale"); + try { + changeLocale("fa-IR"); + expect(document.documentElement.dir).toBe("rtl"); + changeLocale("zh-TW"); + expect(getLocale()).toBe("zh-TW"); + expect(window.localStorage.getItem("locale")).toBe("zh-TW"); + expect(document.documentElement.lang).toBe("zh-TW"); + expect(document.documentElement.dir).toBe("ltr"); + } finally { + changeLocale(originalLocale); + if (savedLocale === null) { + window.localStorage.removeItem("locale"); + } else { + window.localStorage.setItem("locale", savedLocale); + } + } + }); + + it("selects HelpDocs by full locale before falling back to the language code", () => { + expect(getHelpFile("ZH-tw", "AccessLists")).toBe(getHelpFile("zh-TW", "AccessLists")); + expect(getHelpFile("zh-TW", "AccessLists")).toBe(zhTwHelp.AccessLists.default); + expect(getHelpFile("zh-CN", "AccessLists")).toBe(getHelpFile("zh", "AccessLists")); + }); +}); diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts index ceddf4c78..08f7618d8 100644 --- a/frontend/src/locale/src/HelpDoc/index.ts +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -17,19 +17,53 @@ import * as sk from "./sk/index"; import * as cs from "./cs/index"; import * as vi from "./vi/index"; import * as zh from "./zh/index"; +import * as zhTw from "./zh-tw/index"; import * as tr from "./tr/index"; import * as hu from "./hu/index"; import * as uk from "./uk/index"; import * as az from "./az/index"; import * as fa from "./fa/index"; -const items: any = { en, de, pt, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu, uk, az, fa }; +const items: any = { + en, + de, + pt, + es, + et, + ja, + sk, + cs, + zh, + "zh-TW": zhTw, + pl, + ru, + it, + vi, + nl, + bg, + ko, + ga, + id, + fr, + tr, + hu, + uk, + az, + fa, +}; const fallbackLang = "en"; export const getHelpFile = (lang: string, section: string): string => { - if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") { - return items[lang][section].default; + const requestedLang = lang.toLowerCase(); + const exactLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang); + const shortLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang.slice(0, 2)); + + if (exactLang && typeof items[exactLang][section] !== "undefined") { + return items[exactLang][section].default; + } + if (shortLang && typeof items[shortLang][section] !== "undefined") { + return items[shortLang][section].default; } // Fallback to English if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") { diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md new file mode 100644 index 000000000..d8ba7ba2a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md @@ -0,0 +1,7 @@ +## 什麼是存取清單? + +存取清單可針對特定用戶端 IP 位址設定黑名單或白名單,並可為代理主機設定 HTTP 基本驗證。 + +您可以在單一存取清單中設定多項用戶端規則、使用者名稱與密碼,再將其套用至一個或多個 _代理主機_。 + +對於透過代理轉送、但本身未內建驗證機制的網路服務,或需要防止未知用戶端存取時,這項功能格外實用。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md new file mode 100644 index 000000000..4199c2867 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md @@ -0,0 +1,21 @@ +## 憑證說明 + +### HTTP 憑證 + +使用 HTTP 驗證方式申請憑證時,Let's Encrypt 伺服器會嘗試透過 HTTP(不是 HTTPS!)連線至您的網域;驗證成功後,便會核發憑證。 + +使用此方式前,您必須為網域建立一個可透過 HTTP 存取、且指向此 Nginx 安裝環境的 _代理主機_。憑證核發後,您可以修改該 _代理主機_,讓 HTTPS 連線也使用此憑證。不過,為了讓憑證能夠續期,_代理主機_ 仍須保留 HTTP 存取設定。 + +此流程不支援萬用字元網域。 + +### DNS 憑證 + +使用 DNS 驗證方式申請憑證時,您需要使用 DNS 服務供應商外掛程式。系統會透過該 DNS 服務供應商在您的網域中建立暫時性的 DNS 紀錄,接著 Let's Encrypt 會查詢這些紀錄,以確認您是網域所有者;驗證成功後,便會核發憑證。 + +申請此類憑證前,您不必先建立 _代理主機_,也不必將 _代理主機_ 設定為可透過 HTTP 存取。 + +此流程支援萬用字元網域。 + +### 自訂憑證 + +使用此選項,上傳由您自己的憑證授權單位提供的 SSL 憑證。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md new file mode 100644 index 000000000..1aa6b6457 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md @@ -0,0 +1,7 @@ +## 什麼是 404 主機? + +404 主機就是用來顯示 404 頁面的主機設定。 + +當您的網域已被搜尋引擎收錄,而您想提供更友善的錯誤頁面,或明確告知搜尋引擎的索引程式該網域的頁面已不存在時,這項功能會很實用。 + +使用 404 主機的另一項好處,是可以從記錄檔追蹤對該主機的存取,並查看來源網址。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md new file mode 100644 index 000000000..a6d27c361 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md @@ -0,0 +1,7 @@ +## 什麼是代理主機? + +代理主機是接收連線的端點,會將連線轉送至您指定的網路服務。 + +它可選擇代為處理 SSL 連線,適用於未內建 SSL 支援的服務。 + +代理主機是 Nginx Proxy Manager 最常見的用途。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md new file mode 100644 index 000000000..3876a4cec --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md @@ -0,0 +1,5 @@ +## 什麼是重新導向主機? + +重新導向主機會將送往指定網域的請求重新導向,讓訪客前往另一個網域。 + +這類主機最常用於網站更換網域後,搜尋引擎或其他來源的連結仍指向舊網域的情況。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md new file mode 100644 index 000000000..b068f9b26 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md @@ -0,0 +1,5 @@ +## 什麼是連接埠轉送? + +連接埠轉送是 Nginx 相對較新的功能,可將 TCP/UDP 流量直接轉送至網路中的另一台電腦。 + +若您正在架設遊戲伺服器、FTP 伺服器或 SSH 伺服器,這項功能會很方便。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/index.ts b/frontend/src/locale/src/HelpDoc/zh-tw/index.ts new file mode 100644 index 000000000..a9bb46ba7 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/index.ts @@ -0,0 +1,6 @@ +export * as AccessLists from "./AccessLists.md"; +export * as Certificates from "./Certificates.md"; +export * as DeadHosts from "./DeadHosts.md"; +export * as ProxyHosts from "./ProxyHosts.md"; +export * as RedirectionHosts from "./RedirectionHosts.md"; +export * as Streams from "./Streams.md"; diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 9ba12915a..e71e61c79 100644 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -36,7 +36,10 @@ "defaultMessage": "Čeština" }, "locale-zh-CN": { - "defaultMessage": "中文" + "defaultMessage": "简体中文" + }, + "locale-zh-TW": { + "defaultMessage": "繁體中文(台灣)" }, "locale-pl-PL": { "defaultMessage": "Polski" diff --git a/frontend/src/locale/src/zh-tw.json b/frontend/src/locale/src/zh-tw.json new file mode 100644 index 000000000..fb4c79525 --- /dev/null +++ b/frontend/src/locale/src/zh-tw.json @@ -0,0 +1,854 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "剩餘備用碼:{count}" + }, + "2fa.backup-warning": { + "defaultMessage": "請將這些備用碼存放在安全的地方。每個備用碼都只能使用一次。" + }, + "2fa.disable": { + "defaultMessage": "停用雙因素驗證" + }, + "2fa.disable-confirm": { + "defaultMessage": "停用 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "停用雙因素驗證會降低帳號的安全性。" + }, + "2fa.disabled": { + "defaultMessage": "已停用" + }, + "2fa.done": { + "defaultMessage": "我已儲存備用碼" + }, + "2fa.enable": { + "defaultMessage": "啟用雙因素驗證" + }, + "2fa.enabled": { + "defaultMessage": "已啟用" + }, + "2fa.enter-code": { + "defaultMessage": "輸入驗證碼" + }, + "2fa.enter-code-disable": { + "defaultMessage": "輸入驗證碼以停用" + }, + "2fa.regenerate": { + "defaultMessage": "重新產生" + }, + "2fa.regenerate-backup": { + "defaultMessage": "重新產生備用碼" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "請輸入驗證碼以產生新的備用碼。舊的備用碼將會失效。" + }, + "2fa.secret-key": { + "defaultMessage": "祕密金鑰" + }, + "2fa.setup-instructions": { + "defaultMessage": "請用驗證器 App 掃描此 QR Code,或手動輸入祕密金鑰。" + }, + "2fa.status": { + "defaultMessage": "狀態" + }, + "2fa.title": { + "defaultMessage": "雙因素驗證" + }, + "2fa.verify-enable": { + "defaultMessage": "驗證並啟用" + }, + "access-list": { + "defaultMessage": "存取清單" + }, + "access-list.access-count": { + "defaultMessage": "{count} 條規則" + }, + "access-list.auth-count": { + "defaultMessage": "{count} 位使用者" + }, + "access-list.help-rules-last": { + "defaultMessage": "當至少有 1 條規則時,系統會在最後加入一條「拒絕全部」規則" + }, + "access-list.help.rules-order": { + "defaultMessage": "允許與拒絕規則會依照設定順序套用。" + }, + "access-list.inherit": { + "defaultMessage": "沿用代理主機的設定" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "使用代理主機的存取清單" + }, + "access-list.pass-auth": { + "defaultMessage": "將驗證資訊傳送至上游伺服器" + }, + "access-list.public": { + "defaultMessage": "可公開存取" + }, + "access-list.public.subtitle": { + "defaultMessage": "不需要基本驗證" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 或 192.168.1.0/24 或 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "符合任一條件即可" + }, + "access-list.subtitle": { + "defaultMessage": "{users} 位使用者,{rules} 條規則,建立於 {date}" + }, + "access-lists": { + "defaultMessage": "存取清單" + }, + "action.add": { + "defaultMessage": "新增" + }, + "action.add-location": { + "defaultMessage": "新增路徑" + }, + "action.allow": { + "defaultMessage": "允許" + }, + "action.clear": { + "defaultMessage": "清除" + }, + "action.close": { + "defaultMessage": "關閉" + }, + "action.delete": { + "defaultMessage": "刪除" + }, + "action.deny": { + "defaultMessage": "拒絕" + }, + "action.disable": { + "defaultMessage": "停用" + }, + "action.download": { + "defaultMessage": "下載" + }, + "action.edit": { + "defaultMessage": "編輯" + }, + "action.enable": { + "defaultMessage": "啟用" + }, + "action.logs": { + "defaultMessage": "紀錄" + }, + "action.permissions": { + "defaultMessage": "權限" + }, + "action.renew": { + "defaultMessage": "續期" + }, + "action.view-details": { + "defaultMessage": "查看詳細資訊" + }, + "auditlogs": { + "defaultMessage": "稽核紀錄" + }, + "auto": { + "defaultMessage": "自動" + }, + "cancel": { + "defaultMessage": "取消" + }, + "certificate": { + "defaultMessage": "憑證" + }, + "certificate.custom-certificate": { + "defaultMessage": "憑證" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "憑證私鑰" + }, + "certificate.custom-intermediate": { + "defaultMessage": "中繼憑證" + }, + "certificate.in-use": { + "defaultMessage": "使用中" + }, + "certificate.none.subtitle": { + "defaultMessage": "未指派憑證" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "此主機不會使用 HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "無" + }, + "certificate.not-in-use": { + "defaultMessage": "未使用" + }, + "certificate.renew": { + "defaultMessage": "續期憑證" + }, + "certificates": { + "defaultMessage": "憑證" + }, + "certificates.custom": { + "defaultMessage": "自訂憑證" + }, + "certificates.custom.warning": { + "defaultMessage": "不支援受密碼片語保護的私鑰檔案。" + }, + "certificates.dns.credentials": { + "defaultMessage": "驗證資料檔案內容" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "這個外掛需要一份設定檔,其中包含 API 權杖或服務供應商要求的其他驗證資料。" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "這些資料會以純文字儲存在資料庫與檔案中!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "DNS 傳播時間(秒)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "留空即可使用外掛的預設值。此欄位代表等待 DNS 傳播的秒數。" + }, + "certificates.dns.provider": { + "defaultMessage": "DNS 服務供應商" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "選擇服務供應商…" + }, + "certificates.dns.warning": { + "defaultMessage": "這部分需要具備一些 Certbot 與 DNS 外掛的相關知識。請參閱對應外掛的官方文件。" + }, + "certificates.http.reachability-404": { + "defaultMessage": "此網域有伺服器回應,但似乎不是 Nginx Proxy Manager。請確認網域已指向執行 NPM 的 IP 位址。" + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "由於與 site24x7.com 通訊時發生錯誤,無法檢查是否可連線。" + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "此網域沒有伺服器回應。請確認網域存在,並指向執行 NPM 的 IP 位址;如有需要,請在路由器上轉送連接埠 80。" + }, + "certificates.http.reachability-ok": { + "defaultMessage": "可以連線到您的伺服器,應可正常申請憑證。" + }, + "certificates.http.reachability-other": { + "defaultMessage": "此網域有伺服器回應,但傳回了未預期的狀態碼 {code}。該伺服器是 NPM 嗎?請確認網域已指向執行 NPM 的 IP 位址。" + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "此網域有伺服器回應,但傳回了未預期的資料。該伺服器是 NPM 嗎?請確認網域已指向執行 NPM 的 IP 位址。" + }, + "certificates.http.test-results": { + "defaultMessage": "測試結果" + }, + "certificates.http.warning": { + "defaultMessage": "這些網域必須已設定為指向目前這套 Nginx Proxy Manager。" + }, + "certificates.key-type": { + "defaultMessage": "金鑰類型" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA 相容性較廣;ECDSA 速度較快且更安全,但較舊的系統可能不支援。" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "使用 Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "申請新憑證" + }, + "column.access": { + "defaultMessage": "存取規則" + }, + "column.authorization": { + "defaultMessage": "驗證帳號" + }, + "column.authorizations": { + "defaultMessage": "驗證帳號" + }, + "column.custom-locations": { + "defaultMessage": "自訂路徑" + }, + "column.destination": { + "defaultMessage": "目的地" + }, + "column.details": { + "defaultMessage": "詳細資訊" + }, + "column.email": { + "defaultMessage": "電子郵件" + }, + "column.error": { + "defaultMessage": "錯誤" + }, + "column.event": { + "defaultMessage": "事件" + }, + "column.expires": { + "defaultMessage": "到期時間" + }, + "column.http-code": { + "defaultMessage": "HTTP 狀態碼" + }, + "column.incoming-port": { + "defaultMessage": "連入連接埠" + }, + "column.name": { + "defaultMessage": "名稱" + }, + "column.protocol": { + "defaultMessage": "通訊協定" + }, + "column.provider": { + "defaultMessage": "服務供應商" + }, + "column.roles": { + "defaultMessage": "角色" + }, + "column.rules": { + "defaultMessage": "規則" + }, + "column.satisfy": { + "defaultMessage": "條件" + }, + "column.satisfy-all": { + "defaultMessage": "全部" + }, + "column.satisfy-any": { + "defaultMessage": "任一" + }, + "column.scheme": { + "defaultMessage": "通訊協定" + }, + "column.source": { + "defaultMessage": "來源" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "狀態" + }, + "created-on": { + "defaultMessage": "建立時間:{date}" + }, + "dashboard": { + "defaultMessage": "儀表板" + }, + "dead-host": { + "defaultMessage": "404 主機" + }, + "dead-hosts": { + "defaultMessage": "404 主機" + }, + "dead-hosts.count": { + "defaultMessage": "{count} 個 404 主機" + }, + "disabled": { + "defaultMessage": "已停用" + }, + "domain-names": { + "defaultMessage": "網域名稱" + }, + "domain-names.max": { + "defaultMessage": "最多可使用 {count} 個網域名稱" + }, + "domain-names.placeholder": { + "defaultMessage": "輸入網域名稱以新增…" + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "此類型不允許使用萬用字元" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "此憑證授權單位不支援萬用字元" + }, + "domains.advanced": { + "defaultMessage": "進階" + }, + "domains.force-ssl": { + "defaultMessage": "強制使用 SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "啟用 HSTS" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS 子網域" + }, + "domains.http2-support": { + "defaultMessage": "支援 HTTP/2" + }, + "domains.trust-forwarded-proto": { + "defaultMessage": "信任上游代理傳來的 X-Forwarded-Proto/X-Forwarded-Scheme 標頭" + }, + "domains.use-dns": { + "defaultMessage": "使用 DNS 驗證" + }, + "email-address": { + "defaultMessage": "電子郵件地址" + }, + "empty-search": { + "defaultMessage": "找不到結果" + }, + "empty-subtitle": { + "defaultMessage": "現在就建立一個吧。" + }, + "enabled": { + "defaultMessage": "已啟用" + }, + "error.access.at-least-one": { + "defaultMessage": "至少需要一個驗證帳號或一條存取規則" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "驗證帳號的使用者名稱不可重複" + }, + "error.invalid-auth": { + "defaultMessage": "電子郵件地址或密碼無效" + }, + "error.invalid-domain": { + "defaultMessage": "網域無效:{domain}" + }, + "error.invalid-email": { + "defaultMessage": "電子郵件地址無效" + }, + "error.max-character-length": { + "defaultMessage": "長度不可超過 {max} 個字元" + }, + "error.max-domains": { + "defaultMessage": "網域數量過多,最多為 {max} 個" + }, + "error.maximum": { + "defaultMessage": "不可大於 {max}" + }, + "error.min-character-length": { + "defaultMessage": "長度不得少於 {min} 個字元" + }, + "error.minimum": { + "defaultMessage": "不可小於 {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "兩次輸入的密碼必須相同" + }, + "error.required": { + "defaultMessage": "此欄位為必填" + }, + "expires.on": { + "defaultMessage": "到期時間:{date}" + }, + "footer.github-fork": { + "defaultMessage": "在 GitHub 上 Fork 本專案" + }, + "form.unsaved-changes": { + "defaultMessage": "您有尚未儲存的變更,確定要捨棄嗎?" + }, + "host.flags.block-exploits": { + "defaultMessage": "阻擋常見攻擊" + }, + "host.flags.cache-assets": { + "defaultMessage": "快取靜態資源" + }, + "host.flags.preserve-path": { + "defaultMessage": "保留路徑" + }, + "host.flags.protocols": { + "defaultMessage": "通訊協定" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "WebSocket 支援" + }, + "host.forward-port": { + "defaultMessage": "轉送連接埠" + }, + "host.forward-scheme": { + "defaultMessage": "通訊協定" + }, + "hosts": { + "defaultMessage": "主機" + }, + "http-only": { + "defaultMessage": "僅限 HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt(DNS 驗證)" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt(HTTP 驗證)" + }, + "loading": { + "defaultMessage": "載入中…" + }, + "location.advanced-config": { + "defaultMessage": "含有自訂 Nginx 設定" + }, + "location.filter": { + "defaultMessage": "依路徑或目的地篩選" + }, + "login.2fa-code": { + "defaultMessage": "驗證碼" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "輸入驗證碼" + }, + "login.2fa-description": { + "defaultMessage": "請輸入驗證器 App 顯示的驗證碼" + }, + "login.2fa-title": { + "defaultMessage": "雙因素驗證" + }, + "login.2fa-verify": { + "defaultMessage": "驗證" + }, + "login.title": { + "defaultMessage": "登入您的帳號" + }, + "logs": { + "defaultMessage": "紀錄" + }, + "logs.channel.access": { + "defaultMessage": "存取" + }, + "logs.channel.error": { + "defaultMessage": "錯誤" + }, + "logs.download": { + "defaultMessage": "下載" + }, + "logs.empty": { + "defaultMessage": "尚無紀錄。" + }, + "logs.level": { + "defaultMessage": "等級" + }, + "logs.level.all": { + "defaultMessage": "所有等級" + }, + "logs.lines": { + "defaultMessage": "行數" + }, + "logs.live": { + "defaultMessage": "即時更新" + }, + "logs.paused": { + "defaultMessage": "已暫停" + }, + "logs.refresh": { + "defaultMessage": "重新整理" + }, + "logs.search-placeholder": { + "defaultMessage": "搜尋紀錄…" + }, + "logs.source": { + "defaultMessage": "來源" + }, + "logs.source.system": { + "defaultMessage": "系統" + }, + "logs.truncated-warning": { + "defaultMessage": "僅顯示此檔案最新的一部分內容。" + }, + "nginx-config.label": { + "defaultMessage": "自訂 Nginx 設定" + }, + "nginx-config.placeholder": { + "defaultMessage": "# 在此輸入自訂 Nginx 設定,風險自負!" + }, + "no-permission-error": { + "defaultMessage": "您沒有權限查看此內容。" + }, + "notfound.action": { + "defaultMessage": "回到首頁" + }, + "notfound.content": { + "defaultMessage": "很抱歉,找不到您要查看的頁面。" + }, + "notfound.title": { + "defaultMessage": "糟糕…您找到了一個錯誤頁面" + }, + "notification.error": { + "defaultMessage": "錯誤" + }, + "notification.object-deleted": { + "defaultMessage": "{object} 已刪除" + }, + "notification.object-disabled": { + "defaultMessage": "{object} 已停用" + }, + "notification.object-enabled": { + "defaultMessage": "{object} 已啟用" + }, + "notification.object-renewed": { + "defaultMessage": "{object} 已續期" + }, + "notification.object-saved": { + "defaultMessage": "{object} 已儲存" + }, + "notification.success": { + "defaultMessage": "成功" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "新增 {object}" + }, + "object.delete": { + "defaultMessage": "刪除 {object}" + }, + "object.delete.content": { + "defaultMessage": "確定要刪除 {object} 嗎?" + }, + "object.edit": { + "defaultMessage": "編輯 {object}" + }, + "object.empty": { + "defaultMessage": "目前沒有 {objects}" + }, + "object.event.created": { + "defaultMessage": "已建立 {object}" + }, + "object.event.deleted": { + "defaultMessage": "已刪除 {object}" + }, + "object.event.disabled": { + "defaultMessage": "已停用 {object}" + }, + "object.event.enabled": { + "defaultMessage": "已啟用 {object}" + }, + "object.event.renewed": { + "defaultMessage": "已續期 {object}" + }, + "object.event.updated": { + "defaultMessage": "已更新 {object}" + }, + "offline": { + "defaultMessage": "離線" + }, + "online": { + "defaultMessage": "線上" + }, + "options": { + "defaultMessage": "選項" + }, + "password": { + "defaultMessage": "密碼" + }, + "password.generate": { + "defaultMessage": "產生隨機密碼" + }, + "password.hide": { + "defaultMessage": "隱藏密碼" + }, + "password.show": { + "defaultMessage": "顯示密碼" + }, + "permissions.allowed-proxy-hosts": { + "defaultMessage": "允許存取的代理主機" + }, + "permissions.allowed-proxy-hosts-help": { + "defaultMessage": "另外開放存取其他使用者建立的指定代理主機(使用者自己建立的主機原本就能存取)。" + }, + "permissions.hidden": { + "defaultMessage": "隱藏" + }, + "permissions.manage": { + "defaultMessage": "管理" + }, + "permissions.no-proxy-hosts": { + "defaultMessage": "找不到代理主機" + }, + "permissions.view": { + "defaultMessage": "僅供查看" + }, + "permissions.visibility.all": { + "defaultMessage": "所有項目" + }, + "permissions.visibility.title": { + "defaultMessage": "項目可見範圍" + }, + "permissions.visibility.user": { + "defaultMessage": "僅限自己建立的項目" + }, + "proxy-host": { + "defaultMessage": "代理主機" + }, + "proxy-host.forward-host": { + "defaultMessage": "轉送主機名稱 / IP" + }, + "proxy-hosts": { + "defaultMessage": "代理主機" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} 個代理主機" + }, + "public": { + "defaultMessage": "公開" + }, + "redirection-host": { + "defaultMessage": "重新導向主機" + }, + "redirection-host.forward-domain": { + "defaultMessage": "轉送網域" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP 狀態碼" + }, + "redirection-hosts": { + "defaultMessage": "重新導向主機" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} 個重新導向主機" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 多重選擇" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 永久移動" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 暫時移動" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 查看其他位置" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 暫時重新導向" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 永久重新導向" + }, + "role.admin": { + "defaultMessage": "管理員" + }, + "role.standard-user": { + "defaultMessage": "一般使用者" + }, + "save": { + "defaultMessage": "儲存" + }, + "setting": { + "defaultMessage": "設定" + }, + "settings": { + "defaultMessage": "設定" + }, + "settings.default-site": { + "defaultMessage": "預設網站" + }, + "settings.default-site.404": { + "defaultMessage": "404 頁面" + }, + "settings.default-site.444": { + "defaultMessage": "無回應(444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "歡迎頁面" + }, + "settings.default-site.description": { + "defaultMessage": "Nginx 收到未知主機的請求時顯示的內容" + }, + "settings.default-site.html": { + "defaultMessage": "自訂 HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "重新導向" + }, + "setup.preamble": { + "defaultMessage": "建立管理員帳號即可開始使用。" + }, + "setup.title": { + "defaultMessage": "歡迎!" + }, + "sign-in": { + "defaultMessage": "登入" + }, + "ssl-certificate": { + "defaultMessage": "SSL 憑證" + }, + "stream": { + "defaultMessage": "連接埠轉送" + }, + "stream.forward-host": { + "defaultMessage": "轉送主機" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com 或 10.0.0.1 或 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "連入連接埠" + }, + "streams": { + "defaultMessage": "連接埠轉送" + }, + "streams.count": { + "defaultMessage": "{count} 組連接埠轉送" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "測試" + }, + "update-available": { + "defaultMessage": "有新版本可用:{latestVersion}" + }, + "user": { + "defaultMessage": "使用者" + }, + "user.change-password": { + "defaultMessage": "變更密碼" + }, + "user.confirm-password": { + "defaultMessage": "確認密碼" + }, + "user.current-password": { + "defaultMessage": "目前密碼" + }, + "user.edit-profile": { + "defaultMessage": "編輯個人資料" + }, + "user.full-name": { + "defaultMessage": "姓名" + }, + "user.login-as": { + "defaultMessage": "以 {name} 身分登入" + }, + "user.logout": { + "defaultMessage": "登出" + }, + "user.new-password": { + "defaultMessage": "新密碼" + }, + "user.nickname": { + "defaultMessage": "暱稱" + }, + "user.set-password": { + "defaultMessage": "設定密碼" + }, + "user.set-permissions": { + "defaultMessage": "設定 {name} 的權限" + }, + "user.switch-dark": { + "defaultMessage": "切換至深色模式" + }, + "user.switch-light": { + "defaultMessage": "切換至淺色模式" + }, + "user.two-factor": { + "defaultMessage": "雙因素驗證" + }, + "username": { + "defaultMessage": "使用者名稱" + }, + "users": { + "defaultMessage": "使用者" + } +} diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx index f05816142..1d395fc11 100644 --- a/frontend/src/modals/HelpModal.tsx +++ b/frontend/src/modals/HelpModal.tsx @@ -18,7 +18,7 @@ const showHelpModal = (section: string, color?: string) => { const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => { const [markdownText, setMarkdownText] = useState(""); - const lang = getLocale(true); + const lang = getLocale(); useEffect(() => { try { From 47c9bfba4faa6d1ea9a4f69c081bb1fe46ebf66f Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 11:55:20 +1000 Subject: [PATCH 62/76] Update dependencies --- backend/biome.json | 2 +- backend/package.json | 8 +- backend/yarn.lock | 144 +++++------ docs/yarn.lock | 418 +++++++++++++++---------------- frontend/biome.json | 2 +- frontend/package.json | 20 +- frontend/yarn.lock | 556 +++++++++++++++++++++--------------------- 7 files changed, 575 insertions(+), 575 deletions(-) diff --git a/backend/biome.json b/backend/biome.json index 0e8f7062c..0f1b5b62c 100644 --- a/backend/biome.json +++ b/backend/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.6.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.6/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/backend/package.json b/backend/package.json index 722bb0174..1e1646333 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,7 +13,7 @@ "regenerate-config": "node scripts/regenerate-config" }, "dependencies": { - "@apidevtools/json-schema-ref-parser": "^16.0.5", + "@apidevtools/json-schema-ref-parser": "^16.0.6", "ajv": "^8.20.0", "archiver": "^8.0.0", "batchflow": "^0.4.0", @@ -23,7 +23,7 @@ "chalk": "6.0.1", "compression": "^1.8.2", "express": "^5.3.0", - "express-fileupload": "^1.5.2", + "express-fileupload": "^1.6.0", "gravatar": "^1.8.2", "jsonwebtoken": "^9.0.3", "knex": "3.3.0", @@ -32,7 +32,7 @@ "moment": "^2.31.0", "mysql2": "^3.24.6", "node-rsa": "^2.0.0", - "objection": "4.4.2", + "objection": "4.4.3", "otplib": "^13.6.0", "path": "^0.12.7", "pg": "^8.23.1", @@ -43,7 +43,7 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "^13.1.0", - "@biomejs/biome": "^2.6.4", + "@biomejs/biome": "^2.6.6", "nodemon": "^3.1.14" }, "signale": { diff --git a/backend/yarn.lock b/backend/yarn.lock index 9cc6fb99b..30c1fcc72 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@apidevtools/json-schema-ref-parser@^16.0.3", "@apidevtools/json-schema-ref-parser@^16.0.5": - version "16.0.5" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.5.tgz#4f5ab1d20fe9dcde15d4167a3d5dd0434e0109a6" - integrity sha512-z3LiEMqTP2FNu6u8bdUgBYErVuwMSZReOJy1MCEIB0BDHMoNgBtkciby32HOlcrfWwrOSzRU8YIsZ0CogYRuYQ== +"@apidevtools/json-schema-ref-parser@^16.0.3", "@apidevtools/json-schema-ref-parser@^16.0.6": + version "16.0.6" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.6.tgz#30b21ddbaa6ddd1161d1534829002dab0d720980" + integrity sha512-cJXhmvqCn4Ud7X7d4L/BtAiJRIrLNiuIZ48ZTuDfN0mYejVXxdzMGR07teW6eKzLwcN0FwMi7gykE2Kr09LS1g== dependencies: js-yaml "^5.4.2" @@ -32,71 +32,71 @@ ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.2" -"@biomejs/biome@^2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.4.tgz#4f190d1cfa3003a6649db9197b34e47937420179" - integrity sha512-8sKKwaNW4ot22awZ3lBjwWLHnUFXgXnrBPYS2gNlBAXRbDC++7kqjK2H7dR/FgCDtPvO1xfKpgEhJkGxAKYkxA== +"@biomejs/biome@^2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.6.tgz#b40e03ff3dbe070963dd6332171ffffeb3cf7619" + integrity sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ== optionalDependencies: - "@biomejs/cli-android-arm64" "2.6.4" - "@biomejs/cli-android-x64" "2.6.4" - "@biomejs/cli-darwin-arm64" "2.6.4" - "@biomejs/cli-darwin-x64" "2.6.4" - "@biomejs/cli-linux-arm64" "2.6.4" - "@biomejs/cli-linux-arm64-musl" "2.6.4" - "@biomejs/cli-linux-x64" "2.6.4" - "@biomejs/cli-linux-x64-musl" "2.6.4" - "@biomejs/cli-win32-arm64" "2.6.4" - "@biomejs/cli-win32-x64" "2.6.4" + "@biomejs/cli-android-arm64" "2.6.6" + "@biomejs/cli-android-x64" "2.6.6" + "@biomejs/cli-darwin-arm64" "2.6.6" + "@biomejs/cli-darwin-x64" "2.6.6" + "@biomejs/cli-linux-arm64" "2.6.6" + "@biomejs/cli-linux-arm64-musl" "2.6.6" + "@biomejs/cli-linux-x64" "2.6.6" + "@biomejs/cli-linux-x64-musl" "2.6.6" + "@biomejs/cli-win32-arm64" "2.6.6" + "@biomejs/cli-win32-x64" "2.6.6" -"@biomejs/cli-android-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.4.tgz#5e51c3c734b12c16d1b0168b5d1eb53847985c0a" - integrity sha512-jwrwigP4tBcHxzuV8izioX/BOUhJL/4e2rNSOE5nCjBkVC018LQAQUGiH+CNkFYvLtQ1dcZo5/xXERmaAGc7JQ== +"@biomejs/cli-android-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.6.tgz#dd4a994b35837a521459fea4f4f27a194c47258d" + integrity sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA== -"@biomejs/cli-android-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.4.tgz#caee3503ae8edf048afb7055007745e9cd4c4bcf" - integrity sha512-HdGInPJ4nZFwHxzvEtcp2nmSBtUyJrurmtIK43upFckuS3+ZqVM05HtsxQWnqGsuy/pxckIAjyKWlEKRlBXUyA== +"@biomejs/cli-android-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.6.tgz#1804e55a64e9e0b11f56492863c98fd80f1a4028" + integrity sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw== -"@biomejs/cli-darwin-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.4.tgz#dd8c4cbc4a531fd9a248808c624cb958c7c8184c" - integrity sha512-0VvcUz46wbg59Q4TZ59KkIjDJrxEyu4Eoe79+F4SChxoWe+W7AEyIp6F5r72C9+p6MsB6dIovnZt1D30lGM3XA== +"@biomejs/cli-darwin-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.6.tgz#dd6be6a1d8457a465c555311c909e6aa81f1c9ea" + integrity sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw== -"@biomejs/cli-darwin-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.4.tgz#181a204b13e9c264499a654e3d9fc4fdddba1541" - integrity sha512-fxmvkF81srlS3DiMyfm+aG3J0eG+6/BjaxRyjy57KbLSafnq0PO+hHfVWmtH2ty0S7nm6yDXBRwySfFeaRJKRQ== +"@biomejs/cli-darwin-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.6.tgz#fb2609d97f4ead53dbff30507a42be1fe56a1e9c" + integrity sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w== -"@biomejs/cli-linux-arm64-musl@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.4.tgz#34c5110ab8298def8417ce55933e62ad5a6afcdd" - integrity sha512-31ZaH/MHqPX+O+evRwRrCHQ8W5/9adY+/eMytGGWZnRx1T41QsWIF09+/WujynLBhWZUxNGS5CtsWLwgOFTAyg== +"@biomejs/cli-linux-arm64-musl@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.6.tgz#28412c5039466b87c9d3a59176994a45422d60a4" + integrity sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA== -"@biomejs/cli-linux-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.4.tgz#a4deed06840a31b5788b37eb8ac8064eb92c5f4f" - integrity sha512-6+bK1Y/evmIUiJr7auertuMbiaYVicJhqA1bZyL47lCMWugv8l0gFQCqvR/gN1vZMHYm+HwoTKgeMxdNFCoQTg== +"@biomejs/cli-linux-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.6.tgz#04ced06069af3c7a4d20da44c53c21e70ce42176" + integrity sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ== -"@biomejs/cli-linux-x64-musl@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.4.tgz#c7bff48f5aeede76931be6f2981db1e94c422dca" - integrity sha512-bomSa2XdQAGGj6Cwdm4M13qE+lN9+AOlW8KtI4hdyVyRRF/Z71qPAi95T4wtdZK0QpBbkTW8hgbZZCwPi4xzIw== +"@biomejs/cli-linux-x64-musl@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.6.tgz#78109fb485dcbce9eaf5977e2a70658696dc6405" + integrity sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw== -"@biomejs/cli-linux-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.4.tgz#70f438459a279e5ef2599deae3d3be2c0120c672" - integrity sha512-tbFZMdL6dP/ws4edXxYG9WhvaVa1ZDzL4G5DzahnWW7Psib9H+VTdyU4XXVxpJk5j/nYZroSbvSNyYlpW3yCIw== +"@biomejs/cli-linux-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.6.tgz#b5315ccaa511a2bfb103db50013a266887d793bb" + integrity sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g== -"@biomejs/cli-win32-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.4.tgz#6ea7435132a4bbf1e894a6a781d56665787a0532" - integrity sha512-eCsqt8Ma3eVap+oyJCDnJZH60SIU464B66n3y38EUTJdM1T/HfZol1FJC9vb+sHToX0ix8XzqPcwJnOzyAJz0g== +"@biomejs/cli-win32-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.6.tgz#41c0a80a9b98e9b879fb2413a5faf0775a526f07" + integrity sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow== -"@biomejs/cli-win32-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.4.tgz#d8c80a6d93308d3759414aa2949e2371e49d5a99" - integrity sha512-pYsMy5AThiHFOZAsngg8qN4PurM6nInKBhVX7sbj3eNpRQqo1Hb0MgCwPL7VqVMrmcEEMj9Ks3qbkallR65e8g== +"@biomejs/cli-win32-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.6.tgz#f3e5230d59a48ac1da6ab951ad5be364591fb64c" + integrity sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw== "@isaacs/fs-minipass@^4.0.0": version "4.0.1" @@ -286,6 +286,11 @@ balanced-match@^4.0.2: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== +bare-buffer@^3.0.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/bare-buffer/-/bare-buffer-3.7.1.tgz#dcd2fb7907cfe7df0cf18cb752a04ae2a41140bc" + integrity sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg== + bare-events@^2.5.4, bare-events@^2.7.0: version "2.9.2" resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.2.tgz#01d377b64c3c7167b28b52da7add3221aecf2d7f" @@ -317,10 +322,11 @@ bare-stream@^2.6.4: teex "^1.0.1" bare-url@^2.2.2: - version "2.5.5" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.5.tgz#2e8c18db11393aa6cae6ff246fdcda34615aacd3" - integrity sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q== + version "2.5.6" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.6.tgz#7f7a96c459bd432bec43f797c0afca1325cb9b5f" + integrity sha512-iqkXHkW65De+BT14nHLiBFbNuJ/DdMrZaVNJ1IXPctpK6b1olUJ5Qmy8QBvwROajTiGztso2n82kFEvCsNG7Rg== dependencies: + bare-buffer "^3.0.0" bare-path "^3.0.0" base64-js@^1.3.1: @@ -841,10 +847,10 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6" integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== -express-fileupload@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa" - integrity sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg== +express-fileupload@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.6.0.tgz#fbc7035f090a554ae54005b1d8a7bb46c8440a62" + integrity sha512-jIVP7PB+uNLrsglQUKzlK8PjxGkzPy3h0kH0XKpoxQnoT5sPNnHvfWnMMkLoP36LJS5Y8NkkdvkAYQnGCB6tdQ== dependencies: busboy "^1.6.0" @@ -1579,10 +1585,10 @@ object-inspect@^1.13.3, object-inspect@^1.13.4: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -objection@4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/objection/-/objection-4.4.2.tgz#e87f28244c64f5eb151fc9dd3106bc7c2d4c4863" - integrity sha512-Iavk3Gdl0RJI25MjjMwB2MG87x2pcB+QQjXqxHvaAWZIm/xJ+dLnyFk+/B+H0fEJOYypv0Y6vaeWJbMp+XZTnw== +objection@4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/objection/-/objection-4.4.3.tgz#aa965ccb29cfd97b3717539ac73f9095c94a5cbf" + integrity sha512-Ie1jBkoQvzCT7SBuh1nNl/c2gC6lACOy6vY7dygWyZr70bP1Um660XbxCLhXAQ9Uqwok4LWLjzsnKgNd7JpV6w== dependencies: ajv "^8.17.1" ajv-formats "^3.0.1" diff --git a/docs/yarn.lock b/docs/yarn.lock index af8b69d46..39af0fc23 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -156,19 +156,19 @@ "@algolia/client-common" "5.61.0" "@babel/helper-string-parser@^7.29.11": - version "7.29.11" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63" - integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g== + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz#00a43cc19b8f84e9e3f17f32813e69ae9bfceba8" + integrity sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ== "@babel/helper-validator-identifier@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/parser@^7.29.9": - version "7.29.12" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423" - integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg== +"@babel/parser@^7.29.12": + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.13.tgz#dbba85be72b94635123d594035fd087fe7e42bb3" + integrity sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ== dependencies: "@babel/types" "^7.29.11" @@ -335,135 +335,135 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d" integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw== -"@rollup/rollup-android-arm-eabi@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.67.5.tgz#b40e32abb60e3d93419f613a06e9bb9dcd1ffb89" - integrity sha512-FdWPeulkH+FH+rBzyKBD59tuVN4tRKvmVilSJm0OcnoYsQTpd9UhD78OaPcsYqo2fX6hCwAG0YIjrDLoQ3pR3g== +"@rollup/rollup-android-arm-eabi@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.68.0.tgz#c5a058efb08e5119d3ff95b443d1b4661afba767" + integrity sha512-Ta2n0ntLfzP0LqwwcXOIh2z0mnHjlxMS+h4Y9lo+w33Quyf9KFKEi9QZ2IKqeEBKr0qxNpXc/DQjz5KVIs8AEA== -"@rollup/rollup-android-arm64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.67.5.tgz#13a0785e083b6ace19b655670c562d5ab5123011" - integrity sha512-bmkDuBGnF+Ji8atWlXcNjxIC52ShCbdXPbW7Nz0lRoCFi1lrvsO0XobJY36kTj7duc1vOmGJeQ9vtVuGjnzihA== +"@rollup/rollup-android-arm64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.68.0.tgz#3e77dc3d6f7b9fbab6c76ef8a3f1cf69bb2a185b" + integrity sha512-iFDtS0oMaBvKbtrRKXmRcBSsI0UaWL2bWpd62iHzb0A8RSPrVz47mUGE0CfrrD0c/7/7dUhNtoN3cc6aEUn1Ow== -"@rollup/rollup-android-x64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-x64/-/rollup-android-x64-4.67.5.tgz#869d6c63038bfe24d08a502f61d35cda42093020" - integrity sha512-6KvVfnqPTqmbQ5YobMl9HYiSymAsCt8HlNeriDshwJQcveXwT+vIMW6wGL1RcJbXH9+r3BAJvjab5+rIgtSEqA== +"@rollup/rollup-android-x64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-x64/-/rollup-android-x64-4.68.0.tgz#fd2a20f0dd028df151003f1542a3a0504c33d297" + integrity sha512-OnYETr+cZYSbesBD1tCvUjrBUTkjSaOcewRUoIHqD0XI3yn5i9WpdFaE3LZwrMlq0yeJNU0N5sxfHK/fNVMqlQ== -"@rollup/rollup-darwin-arm64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.67.5.tgz#cd65c5b38a4fba26f7706572712fc59fda5bb043" - integrity sha512-SNamgdrY69oPdBrQV9fKGo6PSmKcVhyn0D0jnDmcqgRJvrxQGGBhtekOHTUJ7TCgkW0nE8XMH9v2CwXmjNIzVA== +"@rollup/rollup-darwin-arm64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.68.0.tgz#6dd9ce3f0b148344b87ffb0b0fe3168c87fcacd1" + integrity sha512-2qavWZtFeon/6oMkjsBF3OH7ENKgRTW4xRTwmz9VJeNUYhmPitpR9Jd4sAAc2OZJryHs2neoovMTCYnNvhvANQ== -"@rollup/rollup-darwin-x64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.67.5.tgz#5c64e10fd00852fb1ec6df51044153d0664d5845" - integrity sha512-HNdl34thiJtHDSglogjmAlQ296L8cc8zA7ODH65nnmY7E+jPP55WEbh8J5583rWFLMjxyYywxZuwPOwRvEi5tA== +"@rollup/rollup-darwin-x64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.68.0.tgz#78092241c8f6860b2275cd0bc7c5bdbbce4d572f" + integrity sha512-QpgJO+GwNQDPt6uYcFSjZTFVyHNIeumN2am7RISDR9Nf3e/h8sur9cIfplPGihZPUn+sTw9j3JErZIUkHg8+Pw== -"@rollup/rollup-freebsd-arm64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.67.5.tgz#49c0ba3ab90a0f43a970f335f6cc308a7f147f53" - integrity sha512-WvZYtANNswfHzSNHaJAoG6Ipfc4SyZIDYuIjFdHv07xXxhHl+Xx8o+bNDWsxpOADFT2mKXcXVrfOG4OlWTLo8Q== +"@rollup/rollup-freebsd-arm64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.68.0.tgz#fefaf85dad7595379f417b24a200c3da7fc7f802" + integrity sha512-TERqEn6P9bp4lsTyoFjT3nLKV6G++76HhGb4k6dRGcs+jvhVcTGaS2zkeRdLLRjwKxpBtdCZe11kUQ2hNGgFSA== -"@rollup/rollup-freebsd-x64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.67.5.tgz#db50139f24c3d5768aea2c92c79495653501016f" - integrity sha512-EUU+Wgf9NyHekceC+DxAdm42f6bq2ezH/7DEb/JnCb8SAUhG2xph5BiVukxplWal2m5pP8CzJZE+FLwRopywjQ== +"@rollup/rollup-freebsd-x64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.68.0.tgz#c58f4619f49ed299e99bd72df0c8e9bb9bad1cda" + integrity sha512-RBjDrqb7h5l6Xgdgz5eKYvbpnUkii/z5ejWuZmHJsXORi0X0tTtFzEEgJj2DX995kW18c6T9LGFn/deEAMXDcQ== -"@rollup/rollup-linux-arm-gnueabihf@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.67.5.tgz#7125f8ab7c4a14ed3caf08755133e06e8648479f" - integrity sha512-dgU1CvUuE2IkaJBAeuAbxpDdMHAdZBT5oth5u22DtuoMg4gXXYr2+Sm30RigPJG1F2VEoiGJUziGLuIh9+jPxA== +"@rollup/rollup-linux-arm-gnueabihf@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.68.0.tgz#630fcb12dcdeae9e7a4d26046e8de8d07cc6501c" + integrity sha512-HtLgXBqjF1Ampt9eJjzSmoGDvNb4LP8Y2aPXQZeQ5qhyExXLmoXJB9Jfm/nPi/8fJ3UFn4sZ7pQKyr8V+oTN8g== -"@rollup/rollup-linux-arm-musleabihf@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.67.5.tgz#fe81890f371e96cf90c97034c65392264a2eb55e" - integrity sha512-rdgJcJig2VngYlcTCRQw2BA8kLWM6QwDFe9uLFo81lya0jPC1r8wZu92sAvTUIv48GWlkRVi6/fpsa5CoFSKoQ== +"@rollup/rollup-linux-arm-musleabihf@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.68.0.tgz#da75938ef398e7907af4e8723af970a04387bd56" + integrity sha512-q51xJyqjaqCNtDjYD7xNVwsPa2TcVfAIBeP6E20wicKZ6XYp8Taz9SUPYhIENojAQIAoV7cO3mUdMUgWYEAuhQ== -"@rollup/rollup-linux-arm64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.67.5.tgz#b5c1dab117caae5a14c265bc4cf00ce285606e17" - integrity sha512-+jtCiQnrEFHWAUAcUpP7wppb1p6HKOgBP3mXx75cEYpDF2Y5MEqBky2HNz+osFuVx5uX44gnMTmbe5RJauzn6A== +"@rollup/rollup-linux-arm64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.68.0.tgz#1aae89d808ca937a4fad7b28e4008e910dee4923" + integrity sha512-wejnVabl92oFC/o4sr/EEJuJZMHtMq4C+LBIPphN4EUiVHe9qvZNw+biqhVHpX2ggs1+Z3ugXGwJxT+qhyaaYQ== -"@rollup/rollup-linux-arm64-musl@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.67.5.tgz#650dc54bcb603133ea653e7d9eaa3e6986692d8e" - integrity sha512-heflX7ENhL8eHmLcUxDEHCSK8CKLyPprysb2JGoSTh5Ip7nHrzIRc+2USyxul3nxi98H8pGxJIEDEpURlS1X5A== +"@rollup/rollup-linux-arm64-musl@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.68.0.tgz#f70fce082bd03ed7729dd87c62f4eb014443f5f6" + integrity sha512-tyu+hrsCWHHqV6+2tligV/recJH+HVvNV2Sp4nS1C+MKQPkNXRovKIzyHy0V0xXjm6igCLgsT2vTky5CvWP37g== -"@rollup/rollup-linux-loong64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.67.5.tgz#b76a833d8599a441a3ab876c256be1e214d76af6" - integrity sha512-rRtNU0zBfRHkJ8pscHN9qRtUYLq8Rao4EX7aVWvKcFmJUANkCCXFRU7ovNBEMhpEONyUEB/3DRVDXwDDguSfHA== +"@rollup/rollup-linux-loong64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.68.0.tgz#70eaf986a324ee721ca3a647f5aeda6b29196533" + integrity sha512-5gjJNglKDfgs3OmnGi/RVZQPhEfRoCIHxwSAP9ngmdnxreu0Vl9lI4gxmFBXOhKdftpS82xRdc/7Dp5/A1F0qg== -"@rollup/rollup-linux-loong64-musl@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.67.5.tgz#f87bf8e2cdde82bb80c2027a46c9398d881ff8c0" - integrity sha512-/6auyi9yj8MD/Q+8P+f2ay57jQ7a0j7Yp1JK86pecH6ZnRViQZ5GOjoRSjGArFeTSHFznRbM2QPQcBgDPyNMfQ== +"@rollup/rollup-linux-loong64-musl@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.68.0.tgz#3667cf43e4bbd9254e6c12004eb02e110e67ad69" + integrity sha512-2IBxTUds33rFYwOIEyf77wfW8WQThwiQQ1ofXSraZQG7PiIq7BnQBf/ge0qEI/Ny/2cSc7SKkGO/wUDqmYXqTg== -"@rollup/rollup-linux-ppc64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.67.5.tgz#dccd7bb3df5b4ec1ab9849c67cec0f08204c326b" - integrity sha512-Hsc0vlt+vPvqzUkHKffWKf3YE/yexrY+yeFf7IsUvDC6PV7YGCsA/gJvPa+WJdgxGJnwuoO1HmLkbahaLKabYQ== +"@rollup/rollup-linux-ppc64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.68.0.tgz#c04f13264b418b39374d255ccffb6cb1f4fc9039" + integrity sha512-mE/Pj2PPTZIVHkNNuHq+eYoU3td8LCm73zjrXoBWkNlnuULJS2l/fxwgGPpN4WUCNZgKZ1IXwrnnETPyOcWPmg== -"@rollup/rollup-linux-ppc64-musl@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.67.5.tgz#a70d486e76639d50448ad094611679d42aac0bc8" - integrity sha512-Jx5TMMSPJwHY7F/JwPdS6nuBwny2ECRK+EsAJV5HZrfiddeSS6ZrohkLkFZm3rjqBUuQ4FPX5gGMtakUuQO6fQ== +"@rollup/rollup-linux-ppc64-musl@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.68.0.tgz#d54bcd8beddff5cf778c4021be317b48813b4327" + integrity sha512-dYuykUkAiTv+RQdP3Ib47XcFiImkBWiRpTGSQWAMfZPQyoQYGe8Je4IUeZYNUQWIojmRCUnt5xsH4BbUb2uqSw== -"@rollup/rollup-linux-riscv64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.67.5.tgz#26f5b3497df1142fdd993e3d5472128ff29f7d15" - integrity sha512-fGWcq2lo/XCqAkBLOokg66D/z+MUouryB8i+QdU9RR6O5knG7rucc0WFPqWYL1Wk/wuiJouDfmyBxmZKRM9bag== +"@rollup/rollup-linux-riscv64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.68.0.tgz#6d2c020ef52e437115bd3eb9802bebded8b7245e" + integrity sha512-OnPDDFXLqJewY56T6ow8sA4wuzbqlliD7QJgkrx2KX+pWOpjI3usYXgB0pwK3nsgTF2odCw9IuJs0Uis/jy2qA== -"@rollup/rollup-linux-riscv64-musl@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.67.5.tgz#964491bc28d7bbe5642b4ed15de9cb99c48a86da" - integrity sha512-JwO/Ys3jybc0UW3V5XO2CMnp0RhvoczDDQTgAEE1IctT+HRY3W5parIIWLUpmAK4gHSeeYJofm2iQPFA62K6ag== +"@rollup/rollup-linux-riscv64-musl@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.68.0.tgz#2013db95988d9a8bbf533f752621406d3b6335f0" + integrity sha512-CqoQf06cK0brrRfDqH7Mt8G4MdteFGbjBdC61zEMaSVna6xo6QMUixa9bAkAD4a92g3ElPbQAkLMUzdf+Lk8dg== -"@rollup/rollup-linux-s390x-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.67.5.tgz#b3abae3f022136691d59d37ef978cf6c1ecb91d6" - integrity sha512-vVD4iqel6J6A1xAJlb4mxEO9i1wOJ4/SkcKoDKAuK/wqEBPOA7/P5dDyZvNx46ocHwOUwzo3WjhTdKqET9kEAg== +"@rollup/rollup-linux-s390x-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.68.0.tgz#59333d3c2104c649abbf6d9c98382aca4b7aff65" + integrity sha512-LPHNmE8GwiyELGLUTkqj4zsP1YEHzGdtwnKxAcdP/swsNxXsfxPtpe5nrD/KGxr136xPB+eWv2r/SGj9md0pnQ== -"@rollup/rollup-linux-x64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.67.5.tgz#07b8f2c8c2a9d8b580c7835d49bebc89d193ce18" - integrity sha512-HmEfNh5Cdd/NHxgqwMzKnjlvicQQA+D+w+vQXDJSV0x/aHiQSjVwh1Th5RoA7QsMieIaxibt/5iJq68BraTSUg== +"@rollup/rollup-linux-x64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.68.0.tgz#def6dd537d19c05834f42d2cc14ce6e4462a6d52" + integrity sha512-GDOmvgephpRCNyNGRQOZh0KD2gTyRVM1Bb1iOo759xeCUpCAoRCmJioOOSu2MwyWfNOocGd0V5I9vYLfteC6/A== -"@rollup/rollup-linux-x64-musl@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.67.5.tgz#ae086941d3be6de7b220fcbd93347a73ad539e4d" - integrity sha512-NfmALpbe009nYzZ2QqlSW6nzbGddXpqk3nwualrIr+4QflQCTGMzt3QKK20yARiAnSV9fJdM5WNwIYUf31x3uA== +"@rollup/rollup-linux-x64-musl@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.68.0.tgz#4c4e4673d23b301d9d26076983456183966b52e5" + integrity sha512-HADRxkvl5eg3hbemCmv81UwTfdQy/CiSJDOabLE+p5tpm3OTjuRtzpLXy5zPfGY9FQD4Qv7qHptXmG/YjmNmyw== -"@rollup/rollup-openbsd-x64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.67.5.tgz#27b0e37c1157b5b84bb68e112f0892f73cb7d343" - integrity sha512-ngcE7Udpy/iJ3lexVhYcbpUV2OJIakRGus3snc6K52BnAAJetDBG1Ku5Eh3CECtnW3mpBYWg5VCD4tTaOXKOVQ== +"@rollup/rollup-openbsd-x64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.68.0.tgz#7ca2e43427d49333cb211c4d1980f07e6b576d10" + integrity sha512-iERrpqu1VEfyX736HLKiXRbwGDkCV/iybARY/mn14KcI6oKhJGBx1e+bU+g+umTKghtiGSPaHkwwpII4/N3RjA== -"@rollup/rollup-openharmony-arm64@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.67.5.tgz#a2585a511516569a744064aa66ea9a0ea474f42c" - integrity sha512-DZ01A2EdTYX8xVeGoGiWkJBRWDh+/BOflwGPVsBay43PDxcq8ez0xuovZnPnk1xjE3CdI0pdZXxsJp/Xjmf7uQ== +"@rollup/rollup-openharmony-arm64@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.68.0.tgz#899bf838d15a8c69174a36886ce8fc55402b8d31" + integrity sha512-hBn57IqAbpXpyktckonwePTaFOQS6THph8ZnpRO3eZHY4LncuZGjSsseigBnKuSMKKpLnJJ7PemDvXEzniXC+A== -"@rollup/rollup-win32-arm64-msvc@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.67.5.tgz#634b8e4fe8cbdc666e92ab03098c4a77bc431fa1" - integrity sha512-nJKiMKDO140DUCEcQZl+f4HiGPMBxrW2webjuWJ2E3qPeoruHf2zByml4TP52lA1zDi6L4I92D/5stxeA/6pig== +"@rollup/rollup-win32-arm64-msvc@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.68.0.tgz#216ca59394b95ccbba5e1ab5b563112198335b0f" + integrity sha512-pJWb7mIw9Z25Hl3Q6nHFjl7AnczfOpm2gbOFYguSRv6TiH7DzsoAk/thymb/URSQu9OUEK7uaDMC5Xfm+7TAww== -"@rollup/rollup-win32-ia32-msvc@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.67.5.tgz#e4ddd5318e2d438fd1964a9b6e5495322ea2dae1" - integrity sha512-ykLkBCf+Kls24QmqM3IRf1T4sGMh0rXPyymtj+hXlWKPJnXbXraHqUUsKtOIxqLasjT1i7DhfQGAaZYeiHbSOw== +"@rollup/rollup-win32-ia32-msvc@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.68.0.tgz#d96453b50d0f212facf6d8cd3e9fa390f1ce5665" + integrity sha512-pnrhmoyF2otH2Mezol14/ELUFGuRddOCz6+I+J0pq2Itnp4UxkjABQcXawCNMaKNsCRiuL90BtNzbEd0fHjpIg== -"@rollup/rollup-win32-x64-gnu@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.67.5.tgz#3592c21f7e7010288f7aaef94db5fc358d93315e" - integrity sha512-NiFPUFw8cHhkmBlL1k1eaz+5AXXm89rztj+DQdDuQ7VGPM0+ADFhezQ2KcBMF50UEFzGUr0+DVjVND743XwKWQ== +"@rollup/rollup-win32-x64-gnu@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.68.0.tgz#2ee1a6f01e587e7dafa08fdd586dc710341a63e4" + integrity sha512-1dJ3U+ktdtaIdPaOZKwIX57+bKQk4iAjs5M1wQWLKRB6Wglp98qf8LUeuN4u6bGlIYouZKaySZJ5Qd4OalUMnA== -"@rollup/rollup-win32-x64-msvc@4.67.5": - version "4.67.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.67.5.tgz#d97682fccabcddcda5d44ecace6c636140022ffa" - integrity sha512-Khj/vrKCpdbiAWYbJAeSy4D5iep+qwW/86lNoKvdleJYGxgbRE7h87+5d5T9D7gt8NcqhNFpreRgIg8T4bLzKQ== +"@rollup/rollup-win32-x64-msvc@4.68.0": + version "4.68.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.68.0.tgz#3d82b1bb57754a15cd05a5223189cdd3e6e96bcd" + integrity sha512-e/NRJFl48wL5DFNSzQrvm4kaB5dzNNmqWjIJ6And2Sjuw4klO3w3TXMvCAEF1tHTrQQgpOD4QVkBE/92/yBGrQ== "@shikijs/core@2.5.0", "@shikijs/core@^2.1.0": version "2.5.0" @@ -586,47 +586,47 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8" integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA== -"@vue/compiler-core@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.44.tgz#6e5b3b56ec7747390dd8d20ee31717fc58106bb4" - integrity sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ== +"@vue/compiler-core@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.45.tgz#90c17a54f36dcc44f03ffdcacaca08110a89f255" + integrity sha512-XGHJrM6ufZARAc/w+tyOswStw9tdKiqCKBScI4gGaNxD+aaGu/HQWgKJ+JItjRLPqenQ7iE7G8AJC7T5ew1p+g== dependencies: - "@babel/parser" "^7.29.9" - "@vue/shared" "3.5.44" + "@babel/parser" "^7.29.12" + "@vue/shared" "3.5.45" entities "^7.0.1" estree-walker "^2.0.2" source-map-js "^1.2.2" -"@vue/compiler-dom@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.44.tgz#43794b48fa3acee2600709e56c75a18e6bb2fe3c" - integrity sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg== +"@vue/compiler-dom@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.45.tgz#d2a9d7655c53a93081c88220c385fce676859a6a" + integrity sha512-LbP2d19GSOYGPmvMBBCVJVlv7TKtjM1Cfeyu3cYia720pknZP+1cLX7H5MVatvruT6MuJhiPuNbQBW/MDn/EwA== dependencies: - "@vue/compiler-core" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/compiler-core" "3.5.45" + "@vue/shared" "3.5.45" -"@vue/compiler-sfc@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.44.tgz#73b85b01825d429adda0ea2d5821c2e55eb381eb" - integrity sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw== +"@vue/compiler-sfc@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.45.tgz#82cc47d45f60358c0b1b75a8d5f1ec837ed13e26" + integrity sha512-MKFjb2UX0M/l/jhfzqMLrI8VM4pfvFKG3pqFZkSZFHRLtNld/O4RtUPZFWSwhro/14qP88UQEkWzHxAZAQ0tsw== dependencies: - "@babel/parser" "^7.29.9" - "@vue/compiler-core" "3.5.44" - "@vue/compiler-dom" "3.5.44" - "@vue/compiler-ssr" "3.5.44" - "@vue/shared" "3.5.44" + "@babel/parser" "^7.29.12" + "@vue/compiler-core" "3.5.45" + "@vue/compiler-dom" "3.5.45" + "@vue/compiler-ssr" "3.5.45" + "@vue/shared" "3.5.45" estree-walker "^2.0.2" magic-string "^0.30.21" - postcss "^8.5.29" + postcss "^8.5.32" source-map-js "^1.2.2" -"@vue/compiler-ssr@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.44.tgz#8036f76c6f45eebbbc6d0d1cf2fd4d76e44a3138" - integrity sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA== +"@vue/compiler-ssr@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.45.tgz#a760c005b20a62fc2babb16bd5c45ceab2d6595d" + integrity sha512-RF5sbZOfYz5AK00tOJUn2PwFXR2fS1u2HVzI8D9VF9rGAFxuGBqg2bB1kJ3ZHMprqSltXnT9MZE1IsqjSM8oew== dependencies: - "@vue/compiler-dom" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/compiler-dom" "3.5.45" + "@vue/shared" "3.5.45" "@vue/devtools-api@^7.7.0": version "7.7.10" @@ -655,44 +655,44 @@ dependencies: rfdc "^1.4.1" -"@vue/reactivity@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.44.tgz#6721374ab041003d16f2a90304f720c3b35d568c" - integrity sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw== +"@vue/reactivity@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.45.tgz#f1abf52d03d352db1608d788551541fd3c680332" + integrity sha512-WqkyubNWpI4OYBOipSrjIssMdJO8K1i9Zts/Wm8Er10/0msOEwCyWWWvZnAI7WW3kQsTong6DJp8Cbb+vd4cPA== dependencies: - "@vue/shared" "3.5.44" + "@vue/shared" "3.5.45" -"@vue/runtime-core@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.44.tgz#e3e4a2cd13aee28c2a447df583614138b86dbabd" - integrity sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ== +"@vue/runtime-core@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.45.tgz#170664cec5211b4270de3b747db2c8bc5ab39c45" + integrity sha512-bzIO3+FABjpj85uIKSc+Qnj36/sxDNI3tcH9i6w6xKA1k6e0ejv0G0jMCfeJBvK3yGL5XbJ3DMZj+ClqyCHFYw== dependencies: - "@vue/reactivity" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/reactivity" "3.5.45" + "@vue/shared" "3.5.45" -"@vue/runtime-dom@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.44.tgz#7406fd4a63580b7a532b41f7cf4fc1dfedbdeddb" - integrity sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg== +"@vue/runtime-dom@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.45.tgz#9a4652ce58c3de3d5f791a63a134abcddbe39a28" + integrity sha512-ZvRTFMDgfqTDYWvJfoVpQTJx1RN5M6l7Pyd6wfdY2NaXzbPQVh9ezeQYFbNPP6bIcRVn2SqSSR25N/jSW69BFw== dependencies: - "@vue/reactivity" "3.5.44" - "@vue/runtime-core" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/reactivity" "3.5.45" + "@vue/runtime-core" "3.5.45" + "@vue/shared" "3.5.45" csstype "^3.2.3" -"@vue/server-renderer@3.5.44": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.44.tgz#42617f6efc6b58fc94f181a79ef23e71db4b8d51" - integrity sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ== +"@vue/server-renderer@3.5.45": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.45.tgz#eacee9a2e40166eaaf7e33d3b6709c73d82bd2dc" + integrity sha512-1fNXUHhhsWf4EVL4XixbGBU5mKdiIXJAB82feHRTyPPo6ZUEYWIJuEeUl3mVV6QDBp50HJOX4obLhxYXlrjrzA== dependencies: - "@vue/compiler-ssr" "3.5.44" - "@vue/runtime-dom" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/compiler-ssr" "3.5.45" + "@vue/runtime-dom" "3.5.45" + "@vue/shared" "3.5.45" -"@vue/shared@3.5.44", "@vue/shared@^3.5.13": - version "3.5.44" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.44.tgz#eefe91aa2e17c617260c4ae4c21246be5d85642e" - integrity sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg== +"@vue/shared@3.5.45", "@vue/shared@^3.5.13": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.45.tgz#7cf2a22b13b2f9d0fd01cd45fb6e7c8a41fa22b8" + integrity sha512-ue9fgHmVNPlP7iW3+cxoBX/Cnlw53dHlVSw0JKGY6z1lObg8pHoj928M2nzG4/yVsMZ3NaRZ2s2z61uC16NDwQ== "@vueuse/core@12.8.2", "@vueuse/core@^12.4.0": version "12.8.2" @@ -975,7 +975,7 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -postcss@^8.4.43, postcss@^8.5.29: +postcss@^8.4.43, postcss@^8.5.32: version "8.5.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803" integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g== @@ -1019,38 +1019,38 @@ rfdc@^1.4.1: integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== rollup@^4.20.0: - version "4.67.5" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.67.5.tgz#98968abd5a2889597c5df82b2f50367168f59f33" - integrity sha512-3Ve5VH1hacCVutdpbmu/htg4kz+JCOIWgy4Q3hjHPYGHeHeIElNHJ0LcJ3rCfIsd5G7qOl5k5mwpzGMj9kdHPA== + version "4.68.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.68.0.tgz#3dad30d9b18a9bced13222183e089e2e63ee05c0" + integrity sha512-BIHWnc1Qjdy+WLfOdcSd67sxO4iW6pTnhdZosg+7ywSaiMWxxzKldkkNpUJqeXYAiJzyB14P2cIjqC6tlb6xzw== dependencies: "@types/estree" "^1.0.10" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.67.5" - "@rollup/rollup-android-arm64" "4.67.5" - "@rollup/rollup-android-x64" "4.67.5" - "@rollup/rollup-darwin-arm64" "4.67.5" - "@rollup/rollup-darwin-x64" "4.67.5" - "@rollup/rollup-freebsd-arm64" "4.67.5" - "@rollup/rollup-freebsd-x64" "4.67.5" - "@rollup/rollup-linux-arm-gnueabihf" "4.67.5" - "@rollup/rollup-linux-arm-musleabihf" "4.67.5" - "@rollup/rollup-linux-arm64-gnu" "4.67.5" - "@rollup/rollup-linux-arm64-musl" "4.67.5" - "@rollup/rollup-linux-loong64-gnu" "4.67.5" - "@rollup/rollup-linux-loong64-musl" "4.67.5" - "@rollup/rollup-linux-ppc64-gnu" "4.67.5" - "@rollup/rollup-linux-ppc64-musl" "4.67.5" - "@rollup/rollup-linux-riscv64-gnu" "4.67.5" - "@rollup/rollup-linux-riscv64-musl" "4.67.5" - "@rollup/rollup-linux-s390x-gnu" "4.67.5" - "@rollup/rollup-linux-x64-gnu" "4.67.5" - "@rollup/rollup-linux-x64-musl" "4.67.5" - "@rollup/rollup-openbsd-x64" "4.67.5" - "@rollup/rollup-openharmony-arm64" "4.67.5" - "@rollup/rollup-win32-arm64-msvc" "4.67.5" - "@rollup/rollup-win32-ia32-msvc" "4.67.5" - "@rollup/rollup-win32-x64-gnu" "4.67.5" - "@rollup/rollup-win32-x64-msvc" "4.67.5" + "@rollup/rollup-android-arm-eabi" "4.68.0" + "@rollup/rollup-android-arm64" "4.68.0" + "@rollup/rollup-android-x64" "4.68.0" + "@rollup/rollup-darwin-arm64" "4.68.0" + "@rollup/rollup-darwin-x64" "4.68.0" + "@rollup/rollup-freebsd-arm64" "4.68.0" + "@rollup/rollup-freebsd-x64" "4.68.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.68.0" + "@rollup/rollup-linux-arm-musleabihf" "4.68.0" + "@rollup/rollup-linux-arm64-gnu" "4.68.0" + "@rollup/rollup-linux-arm64-musl" "4.68.0" + "@rollup/rollup-linux-loong64-gnu" "4.68.0" + "@rollup/rollup-linux-loong64-musl" "4.68.0" + "@rollup/rollup-linux-ppc64-gnu" "4.68.0" + "@rollup/rollup-linux-ppc64-musl" "4.68.0" + "@rollup/rollup-linux-riscv64-gnu" "4.68.0" + "@rollup/rollup-linux-riscv64-musl" "4.68.0" + "@rollup/rollup-linux-s390x-gnu" "4.68.0" + "@rollup/rollup-linux-x64-gnu" "4.68.0" + "@rollup/rollup-linux-x64-musl" "4.68.0" + "@rollup/rollup-openbsd-x64" "4.68.0" + "@rollup/rollup-openharmony-arm64" "4.68.0" + "@rollup/rollup-win32-arm64-msvc" "4.68.0" + "@rollup/rollup-win32-ia32-msvc" "4.68.0" + "@rollup/rollup-win32-x64-gnu" "4.68.0" + "@rollup/rollup-win32-x64-msvc" "4.68.0" fsevents "~2.3.2" shiki@^2.1.0: @@ -1146,9 +1146,9 @@ unist-util-visit@^5.0.0: unist-util-visit-parents "^6.1.0" vfile-message@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" - integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + version "4.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.4.tgz#24dd2080867c336e121a2b7446c247d935022565" + integrity sha512-66ycRswIkNmHH9PHku7ADtv5RVwgIrOcuv3J8Svz6h4qgGRrKVa+9/neuKyJZUXyOpYwLTvS+whZ4R2urHHgQg== dependencies: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" @@ -1197,15 +1197,15 @@ vitepress@^1.6.4: vue "^3.5.13" vue@^3.5.13: - version "3.5.44" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.44.tgz#de7828ab2cf53757ecb920564b7b6c36bf187aca" - integrity sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ== + version "3.5.45" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.45.tgz#a86c2ac53d76c72f919043d4c70ccaa5d8f06a80" + integrity sha512-3cvsib8m9c0vB24+MDKsgcW4+Da4xaBl9Z/5Tbm2pmSIsRPxcil4cdXjXlVsmJ2g64XJo5h+IRd7rsdGT9pZaw== dependencies: - "@vue/compiler-dom" "3.5.44" - "@vue/compiler-sfc" "3.5.44" - "@vue/runtime-dom" "3.5.44" - "@vue/server-renderer" "3.5.44" - "@vue/shared" "3.5.44" + "@vue/compiler-dom" "3.5.45" + "@vue/compiler-sfc" "3.5.45" + "@vue/runtime-dom" "3.5.45" + "@vue/server-renderer" "3.5.45" + "@vue/shared" "3.5.45" zwitch@^2.0.4: version "2.0.4" diff --git a/frontend/biome.json b/frontend/biome.json index 4ed66e61a..ff3bbd38a 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.6.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.6/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/frontend/package.json b/frontend/package.json index a16716f23..22e0dd6df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,9 +16,9 @@ "test": "vitest" }, "dependencies": { - "@tabler/core": "^1.7.3", - "@tabler/icons-react": "^3.50.0", - "@tanstack/react-query": "^5.105.3", + "@tabler/core": "^1.7.4", + "@tabler/icons-react": "^3.51.0", + "@tanstack/react-query": "^5.107.1", "@tanstack/react-table": "^9.3.1", "@uiw/react-textarea-code-editor": "^3.1.3", "classnames": "^2.5.1", @@ -32,7 +32,7 @@ "react": "^19.3.1", "react-bootstrap": "^2.10.10", "react-dom": "^19.3.1", - "react-intl": "^12.1.6", + "react-intl": "^12.1.7", "react-markdown": "^10.1.0", "react-qr-code": "^2.2.0", "react-router-dom": "^7.18.5", @@ -41,9 +41,9 @@ "rooks": "^9.9.0" }, "devDependencies": { - "@biomejs/biome": "^2.6.4", - "@formatjs/cli": "^6.18.0", - "@tanstack/react-query-devtools": "^5.105.3", + "@biomejs/biome": "^2.6.6", + "@formatjs/cli": "^6.18.1", + "@tanstack/react-query-devtools": "^5.107.1", "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", @@ -57,11 +57,11 @@ "happy-dom": "^21.0.4", "postcss": "^8.5.32", "postcss-simple-vars": "^7.0.1", - "sass": "^1.107.1", + "sass": "^1.108.1", "tmp": "^0.2.7", "typescript": "7.0.2", - "vite": "^8.3.5", - "vite-plugin-checker": "^0.14.5", + "vite": "^8.3.6", + "vite-plugin-checker": "^1.0.0", "vitest": "^5.1.0" } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a6f9996de..ace3c7c36 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.5.0.tgz#b5b71a25a4d16afa2482592ddfa62fccc60bc7d1" integrity sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== @@ -16,12 +16,20 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/generator@^7.29.12": - version "7.29.12" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.12.tgz#682e578704a8681589f434e7d0cc64296ebb6c63" - integrity sha512-2W76mhjrpQFO1lK5e8IvGYB2D0YZ7S2R1Z2BjO2ZUYuW+amcpK/z60U6ZZmzskwoa7sJ6XohP/Wg8H5KxXI/IA== +"@babel/code-frame@^8.0.0": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-8.0.6.tgz#5247d36cef9fd187dd4d84a0ba659fa29341616c" + integrity sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw== dependencies: - "@babel/parser" "^7.29.12" + "@babel/helper-validator-identifier" "^8.0.6" + js-tokens "^10.0.0" + +"@babel/generator@^7.29.13": + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.13.tgz#89bd5d368cde7437920ab3f2e67ef24f0ecfe620" + integrity sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw== + dependencies: + "@babel/parser" "^7.29.13" "@babel/types" "^7.29.11" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" @@ -41,26 +49,31 @@ "@babel/types" "^7.29.7" "@babel/helper-string-parser@^7.29.11": - version "7.29.11" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63" - integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g== + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz#00a43cc19b8f84e9e3f17f32813e69ae9bfceba8" + integrity sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ== "@babel/helper-validator-identifier@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/parser@^7.29.12", "@babel/parser@^7.29.7": - version "7.29.12" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423" - integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg== +"@babel/helper-validator-identifier@^8.0.6": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz#fda51b4365505595779b51ea1ae0899ad9d76866" + integrity sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ== + +"@babel/parser@^7.29.13", "@babel/parser@^7.29.7": + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.13.tgz#dbba85be72b94635123d594035fd087fe7e42bb3" + integrity sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ== dependencies: "@babel/types" "^7.29.11" "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": - version "7.29.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.10.tgz#fe9920782f7ee34029165ac6f272984917a1641b" - integrity sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA== + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.13.tgz#6b33e456181ff15b5585d932f3aebfc0669b32dd" + integrity sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ== "@babel/template@^7.29.7": version "7.29.7" @@ -72,14 +85,14 @@ "@babel/types" "^7.29.7" "@babel/traverse@^7.29.7": - version "7.29.12" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.12.tgz#60f3196a8bded1e2b2d2efb7e47a99086733865f" - integrity sha512-MscXe/NAZcOkYEsRajHZFt3ynsb22PUi6CTrp5U7+RYycR9By84mn9mFN54YgfITiqK4hFx3Ju3tZSyzfpS5ew== + version "7.29.13" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.13.tgz#2c536b67d36f8d3cb86393fe0f0567bd01b748a5" + integrity sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q== dependencies: "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.12" + "@babel/generator" "^7.29.13" "@babel/helper-globals" "^7.29.7" - "@babel/parser" "^7.29.12" + "@babel/parser" "^7.29.13" "@babel/template" "^7.29.7" "@babel/types" "^7.29.11" debug "^4.3.1" @@ -92,71 +105,71 @@ "@babel/helper-string-parser" "^7.29.11" "@babel/helper-validator-identifier" "^7.29.7" -"@biomejs/biome@^2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.4.tgz#4f190d1cfa3003a6649db9197b34e47937420179" - integrity sha512-8sKKwaNW4ot22awZ3lBjwWLHnUFXgXnrBPYS2gNlBAXRbDC++7kqjK2H7dR/FgCDtPvO1xfKpgEhJkGxAKYkxA== +"@biomejs/biome@^2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.6.tgz#b40e03ff3dbe070963dd6332171ffffeb3cf7619" + integrity sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ== optionalDependencies: - "@biomejs/cli-android-arm64" "2.6.4" - "@biomejs/cli-android-x64" "2.6.4" - "@biomejs/cli-darwin-arm64" "2.6.4" - "@biomejs/cli-darwin-x64" "2.6.4" - "@biomejs/cli-linux-arm64" "2.6.4" - "@biomejs/cli-linux-arm64-musl" "2.6.4" - "@biomejs/cli-linux-x64" "2.6.4" - "@biomejs/cli-linux-x64-musl" "2.6.4" - "@biomejs/cli-win32-arm64" "2.6.4" - "@biomejs/cli-win32-x64" "2.6.4" + "@biomejs/cli-android-arm64" "2.6.6" + "@biomejs/cli-android-x64" "2.6.6" + "@biomejs/cli-darwin-arm64" "2.6.6" + "@biomejs/cli-darwin-x64" "2.6.6" + "@biomejs/cli-linux-arm64" "2.6.6" + "@biomejs/cli-linux-arm64-musl" "2.6.6" + "@biomejs/cli-linux-x64" "2.6.6" + "@biomejs/cli-linux-x64-musl" "2.6.6" + "@biomejs/cli-win32-arm64" "2.6.6" + "@biomejs/cli-win32-x64" "2.6.6" -"@biomejs/cli-android-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.4.tgz#5e51c3c734b12c16d1b0168b5d1eb53847985c0a" - integrity sha512-jwrwigP4tBcHxzuV8izioX/BOUhJL/4e2rNSOE5nCjBkVC018LQAQUGiH+CNkFYvLtQ1dcZo5/xXERmaAGc7JQ== +"@biomejs/cli-android-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.6.tgz#dd4a994b35837a521459fea4f4f27a194c47258d" + integrity sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA== -"@biomejs/cli-android-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.4.tgz#caee3503ae8edf048afb7055007745e9cd4c4bcf" - integrity sha512-HdGInPJ4nZFwHxzvEtcp2nmSBtUyJrurmtIK43upFckuS3+ZqVM05HtsxQWnqGsuy/pxckIAjyKWlEKRlBXUyA== +"@biomejs/cli-android-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.6.tgz#1804e55a64e9e0b11f56492863c98fd80f1a4028" + integrity sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw== -"@biomejs/cli-darwin-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.4.tgz#dd8c4cbc4a531fd9a248808c624cb958c7c8184c" - integrity sha512-0VvcUz46wbg59Q4TZ59KkIjDJrxEyu4Eoe79+F4SChxoWe+W7AEyIp6F5r72C9+p6MsB6dIovnZt1D30lGM3XA== +"@biomejs/cli-darwin-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.6.tgz#dd6be6a1d8457a465c555311c909e6aa81f1c9ea" + integrity sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw== -"@biomejs/cli-darwin-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.4.tgz#181a204b13e9c264499a654e3d9fc4fdddba1541" - integrity sha512-fxmvkF81srlS3DiMyfm+aG3J0eG+6/BjaxRyjy57KbLSafnq0PO+hHfVWmtH2ty0S7nm6yDXBRwySfFeaRJKRQ== +"@biomejs/cli-darwin-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.6.tgz#fb2609d97f4ead53dbff30507a42be1fe56a1e9c" + integrity sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w== -"@biomejs/cli-linux-arm64-musl@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.4.tgz#34c5110ab8298def8417ce55933e62ad5a6afcdd" - integrity sha512-31ZaH/MHqPX+O+evRwRrCHQ8W5/9adY+/eMytGGWZnRx1T41QsWIF09+/WujynLBhWZUxNGS5CtsWLwgOFTAyg== +"@biomejs/cli-linux-arm64-musl@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.6.tgz#28412c5039466b87c9d3a59176994a45422d60a4" + integrity sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA== -"@biomejs/cli-linux-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.4.tgz#a4deed06840a31b5788b37eb8ac8064eb92c5f4f" - integrity sha512-6+bK1Y/evmIUiJr7auertuMbiaYVicJhqA1bZyL47lCMWugv8l0gFQCqvR/gN1vZMHYm+HwoTKgeMxdNFCoQTg== +"@biomejs/cli-linux-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.6.tgz#04ced06069af3c7a4d20da44c53c21e70ce42176" + integrity sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ== -"@biomejs/cli-linux-x64-musl@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.4.tgz#c7bff48f5aeede76931be6f2981db1e94c422dca" - integrity sha512-bomSa2XdQAGGj6Cwdm4M13qE+lN9+AOlW8KtI4hdyVyRRF/Z71qPAi95T4wtdZK0QpBbkTW8hgbZZCwPi4xzIw== +"@biomejs/cli-linux-x64-musl@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.6.tgz#78109fb485dcbce9eaf5977e2a70658696dc6405" + integrity sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw== -"@biomejs/cli-linux-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.4.tgz#70f438459a279e5ef2599deae3d3be2c0120c672" - integrity sha512-tbFZMdL6dP/ws4edXxYG9WhvaVa1ZDzL4G5DzahnWW7Psib9H+VTdyU4XXVxpJk5j/nYZroSbvSNyYlpW3yCIw== +"@biomejs/cli-linux-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.6.tgz#b5315ccaa511a2bfb103db50013a266887d793bb" + integrity sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g== -"@biomejs/cli-win32-arm64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.4.tgz#6ea7435132a4bbf1e894a6a781d56665787a0532" - integrity sha512-eCsqt8Ma3eVap+oyJCDnJZH60SIU464B66n3y38EUTJdM1T/HfZol1FJC9vb+sHToX0ix8XzqPcwJnOzyAJz0g== +"@biomejs/cli-win32-arm64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.6.tgz#41c0a80a9b98e9b879fb2413a5faf0775a526f07" + integrity sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow== -"@biomejs/cli-win32-x64@2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.4.tgz#d8c80a6d93308d3759414aa2949e2371e49d5a99" - integrity sha512-pYsMy5AThiHFOZAsngg8qN4PurM6nInKBhVX7sbj3eNpRQqo1Hb0MgCwPL7VqVMrmcEEMj9Ks3qbkallR65e8g== +"@biomejs/cli-win32-x64@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.6.tgz#f3e5230d59a48ac1da6ab951ad5be364591fb64c" + integrity sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw== "@corvu/utils@~0.4.2": version "0.4.2" @@ -273,85 +286,85 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.12.tgz#afefe785949f16ac4cdd1e695935a321572dd56a" integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww== -"@formatjs/cli-native-darwin-arm64@1.1.32": - version "1.1.32" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.32.tgz#27bf0a83cacf0e92ffe39d8f4c99de9910403428" - integrity sha512-T3R9V8DMW0y2FT3wQembm2g+g9xlszI2XBnSLqzjjSaL7NyolNN1kcLO1kYHZTx9b78e8B6zXPqk2GhChA3iNg== - -"@formatjs/cli-native-darwin-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-x64/-/cli-native-darwin-x64-1.1.0.tgz#4c2e81d01abd6168d3cc0cace8a24b26beb0b546" - integrity sha512-TUw3bYa2E1k2KAEFBrtCB3t4r/h9na4m8IkXsDrt4YRBYSQggYxqWCVNXO+ZiMmTOQehQdD5TyF62og4Gw8UTg== - -"@formatjs/cli-native-linux-arm64-musl@1.0.30": - version "1.0.30" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.30.tgz#0881f08733ff0dc684219827097ce2b011eccd53" - integrity sha512-GOiklaXhcj3dvctTb2qWYrZk7VapJk0pbcJFUJnoONJqNJB0X985RB7t2EXyNOCNujFr6kR8XPNVIAYj1vVIBA== - -"@formatjs/cli-native-linux-arm64@1.2.32": - version "1.2.32" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.32.tgz#0c44f3ebf9186418347f312853a9e3d169a7e515" - integrity sha512-OBiwXNWVmyaCh+Ga4/OqTseYJiOzLZB2aQIY5+sVG63/pP+63iVxROMctPsW5lqNgFmfe+tuvz8yXac7uRyLlw== - -"@formatjs/cli-native-linux-x64-musl@1.0.30": - version "1.0.30" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.30.tgz#e2538361506b88208b3f0dfb0107480c45984107" - integrity sha512-4nQkxuqTBLXMRp+0L1T2y/MOoiWxju/peidvMZVZDCF4hHlbAmDEj43FoHA9Iq/FskpNx898sp/OROKBD2X19w== - -"@formatjs/cli-native-linux-x64@1.1.32": - version "1.1.32" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.32.tgz#986ac4e8003ecbac5872dce49bfd9f610aa321f3" - integrity sha512-aagNUKgj8LF7bpd1zx9BzsRkneviCZbXx4IzdaGZeehY4iY4OVO+MCTvVjSfRBOw2qKUJWu5dlo5gL98jyt7uA== - -"@formatjs/cli-native-win32-arm64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-arm64/-/cli-native-win32-arm64-1.1.0.tgz#f9ccb3348e42679d371d685b089e4095af27c3b3" - integrity sha512-1sGjugyj8428wvDmfY8JUWkktaiIPPXnoQZeHhHpH4HMjEihIinJ0OaPR282eZ+BUFKu2yg7oeg3iFkY9XXwiA== - -"@formatjs/cli-native-win32-x64@1.1.33": +"@formatjs/cli-native-darwin-arm64@1.1.33": version "1.1.33" - resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.33.tgz#305b5344f813322ff85131af0f303596baaca1de" - integrity sha512-tUd3LNJOjSf1SyoXAVfljYFS4Z9nAb16Pofnzcjt0FwN48+lPbwfBovns1wfZ7sVZ8InYLqk+kZujBfJBTvabg== + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.33.tgz#50d6a4fd4ff5b842e16aed2c258940e3b5301692" + integrity sha512-bsY7KHNytQQMwCjQ7bJeUBPlRloYACKzd+GCrYPaWrqNiOyhDa664p3NcoyY/HZo3Bg5l/5oPo8+Wjp7Kkud+Q== -"@formatjs/cli@^6.18.0": - version "6.18.0" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.18.0.tgz#d55354f397bb5900e92dadc981b45f27fada5116" - integrity sha512-fBj1lxVPq2IC5lO48l56F2yOPqVW3OOexI/PYJbu1xLHN/+j9Tk+VeqBkZUEPsxrNwxjaOrLRsi/rDcEdrVedA== +"@formatjs/cli-native-darwin-x64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-x64/-/cli-native-darwin-x64-1.1.1.tgz#53157d342a6e92f3f6eccf40f8f295528d37c35f" + integrity sha512-RA4/HfHBGjLXLW18HCeVeS5f0OCfM0Anr/6qQYnXDJP78Fl6QeXb/0HkkhleS7Vc5Zpa5dhGMFWqDeQDPWt9gw== + +"@formatjs/cli-native-linux-arm64-musl@1.0.31": + version "1.0.31" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.31.tgz#6c520ab5f304f62df80c151e990f112d6f8cdbd5" + integrity sha512-dckGwmRYv9rdwu4NDYDlW9ZcyWlMqMYRVSwdAKlU+QAE9iZSnQgWseZ7HXNFbMjvloWlID5OsoP+Vd3S+58tJg== + +"@formatjs/cli-native-linux-arm64@1.2.33": + version "1.2.33" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.33.tgz#e9403a2e274e3a18a9dd15f74080a52483eb8d93" + integrity sha512-LWt4HAuipwTMPsMaMEC6qJ6hybTCsImWLr7QUfywoRU0fHsOlqoAtHyP5uLlHsv3ormEBGOLpKn/Ug2JRhrvYw== + +"@formatjs/cli-native-linux-x64-musl@1.0.31": + version "1.0.31" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.31.tgz#5816ef13fbcedc5e0ae15de5fa6f5ab0073f2c5b" + integrity sha512-Tf8/G37cU3nDPLjavm8c6zX0nfUBLSzX2N8Xs5dD7qfYu1/z/mB5E1z5niO4E5PDPqna7f0OumksxuCJzk7EFA== + +"@formatjs/cli-native-linux-x64@1.1.33": + version "1.1.33" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.33.tgz#82154949db14141217115b28f67ddcc118c8a0e4" + integrity sha512-SrtBFI+V5UejF05ZKUi19uWxBQOTPGI3erKMnbJB41aawfl9OryjD8BE/4C3F/g8oigp72sB7/4AAvl5E4uGwA== + +"@formatjs/cli-native-win32-arm64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-arm64/-/cli-native-win32-arm64-1.1.1.tgz#de41478bed44ffcc74b62914fb400277807164ca" + integrity sha512-e+E6qBML8sMedsSH+tPKR9nZH9Rac865NFfpsJlRsZfPSPamBMOAt3djqa2/cgojNjIsfxt73TSURLOsS3bTGA== + +"@formatjs/cli-native-win32-x64@1.1.34": + version "1.1.34" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.34.tgz#55f72c5954c047b4a92c93f0c0742ba634fdbae4" + integrity sha512-DQ0o7chfXTcmucPBJpEHBlVpXcrP11Vz1xOdJeL75x9ErZNeJSA0dX2n1aVvCSzqAuEvh0aJeROpMrkm2tnJQg== + +"@formatjs/cli@^6.18.1": + version "6.18.1" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.18.1.tgz#e5077c99ee6212370f9a655ed838af462312e0ac" + integrity sha512-HIN3BKc3lCUFTjf+e732bqsUYM7ECBnACGnomKPaquAL6nFiVXD0/MJ/xh0a8odigQNxWrzKRsnP8gcxmIx4uA== optionalDependencies: - "@formatjs/cli-native-darwin-arm64" "1.1.32" - "@formatjs/cli-native-darwin-x64" "1.1.0" - "@formatjs/cli-native-linux-arm64" "1.2.32" - "@formatjs/cli-native-linux-arm64-musl" "1.0.30" - "@formatjs/cli-native-linux-x64" "1.1.32" - "@formatjs/cli-native-linux-x64-musl" "1.0.30" - "@formatjs/cli-native-win32-arm64" "1.1.0" - "@formatjs/cli-native-win32-x64" "1.1.33" + "@formatjs/cli-native-darwin-arm64" "1.1.33" + "@formatjs/cli-native-darwin-x64" "1.1.1" + "@formatjs/cli-native-linux-arm64" "1.2.33" + "@formatjs/cli-native-linux-arm64-musl" "1.0.31" + "@formatjs/cli-native-linux-x64" "1.1.33" + "@formatjs/cli-native-linux-x64-musl" "1.0.31" + "@formatjs/cli-native-win32-arm64" "1.1.1" + "@formatjs/cli-native-win32-x64" "1.1.34" "@formatjs/fast-memoize@3.1.7": version "3.1.7" resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.7.tgz#5f79b3756e4bcb5faee47b3a2f01216e1212c9e4" integrity sha512-zXfhLpvA6T7+efdt9JLbBwZ00tT7NsBMDVnDu8rpHeNNv8KfRZAMo2gkG0k9lK/Nzc//3kJ9pImsfuJxk3KhUA== -"@formatjs/icu-messageformat-parser@3.5.22": - version "3.5.22" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.22.tgz#8715bd7958ef43919bb9d7d1779518e11084e9eb" - integrity sha512-YuwN+dDKgssfuYwtOyGQD0CSn7K46g9cv1Fp0VgKpjZDDFvv8b8crzP990IcJhRPWHhKzCGsbUSJVIguyyFA5A== +"@formatjs/icu-messageformat-parser@3.5.23": + version "3.5.23" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.23.tgz#4812fd6d3adf844036108697f0486bf0464b2d5f" + integrity sha512-iB1fYfhtSGd8uH9A8b1m01Ujcv6QtQnJasefP3jPApmz72L/zNipzf3BdoinTMd1xuviVChjUJWL60iTwjUQ+w== dependencies: - "@formatjs/icu-skeleton-parser" "2.1.12" + "@formatjs/icu-skeleton-parser" "2.1.13" -"@formatjs/icu-skeleton-parser@2.1.12": - version "2.1.12" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.12.tgz#5471028b1f2516c63456aba92d109a0d230d2a0f" - integrity sha512-ca7tZMLpfHX3sUD7hP6gE/qr6Iq89EDd3ZLeRhbsB7r0UD55DGJq+tmFLzrGrnx5StahUevQmQQVtbDRHsfDHA== +"@formatjs/icu-skeleton-parser@2.1.13": + version "2.1.13" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.13.tgz#b51ec5c826b93c34d4e45390a4d32d05c222cf07" + integrity sha512-IdAJ9NrHn7AF0zqncD9UpxQozTYyJtbwwrZnJMEKVxTcOOXtVkUb2XYTpNOGTAv58NCsk0rjRB9G+i2SvBmGAQ== -"@formatjs/intl@6.1.4": - version "6.1.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-6.1.4.tgz#ea611cbd03f4ad63a87cc16604a8f4e1cb8a30d8" - integrity sha512-udBTb6Heg/sleurLkUdPq+T9B7FvhiZ+8EyauSpqkqKPF+S5TUplO/tZmok7JC86Zk+OKhP8rIGi8bYRF9WJaA== +"@formatjs/intl@6.1.5": + version "6.1.5" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-6.1.5.tgz#ca9beb65b240b697760af90fd7d91f97b81e0bc2" + integrity sha512-WicSXzsz4wO1ksJb6Pp9a+vdOpIkidcEm8ldVtXkqFoMB/o/ETEikyPeCFgTVuSTLBb7ZNJ3ZLKXSQ/X/0+BAg== dependencies: "@formatjs/fast-memoize" "3.1.7" - "@formatjs/icu-messageformat-parser" "3.5.22" - intl-messageformat "12.1.4" + "@formatjs/icu-messageformat-parser" "3.5.23" + intl-messageformat "12.1.5" "@internationalized/date@^3.12.4": version "3.12.4" @@ -744,24 +757,24 @@ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.24.tgz#b1a41a35fcfb1413564981be1bab04b46168961b" integrity sha512-6tcXo4kA8HgGO7AVmawB57RYi/piBPYVHVcO6dXjX+SZw8GoHmi262sp4v2Ha2kNP9Cx84qei27yT+fjS1lxgg== -"@tabler/core@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.3.tgz#79b23adb6bf8bc6c6f3fcec8949226a938bc1d5a" - integrity sha512-9PDS6U69svI987zIpd0AVLF4Gw9Uxfq1aypceVMNxye9Jp4QWYZEtzTL7z60L8qLyVQupHA+ug1AIA8gGAnYRA== +"@tabler/core@^1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.4.tgz#2e1c25053d79d491319966e5b2864d68f39bcb2c" + integrity sha512-fHAPnsKl9JnJJkLxE5UbPkLUBlgn/G1b0urLh5LMGt2DnebpK2tZNEKD/8YiwGz67EoNahGretSsHovDOvJGWA== dependencies: "@popperjs/core" "^2.11.8" -"@tabler/icons-react@^3.50.0": - version "3.50.0" - resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.50.0.tgz#c401128a88bf5720b48229f4317f038ad9c75e37" - integrity sha512-X9XhFbxAmSNimQ71VAd7opfd4z84x1pLrEbBT33VCIEsFs8zWVqdhh5r/Jyi/3iJjQ861kE5Wm9ixiJfS2nwBA== +"@tabler/icons-react@^3.51.0": + version "3.51.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.51.0.tgz#d6fafa049a74d3b4ad951e3a9e610e6ee36f890a" + integrity sha512-F3GGChIUspsCq+HhXtJezc1W9QRcRlEG07Jb4dKUR1TV+mq1uvY7+G46n1fZE02jhQUOVuOoi445Bk1O9mFrDg== dependencies: - "@tabler/icons" "3.50.0" + "@tabler/icons" "3.51.0" -"@tabler/icons@3.50.0": - version "3.50.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.50.0.tgz#fb2bdeec8896e173c34789902aeb51ffda139753" - integrity sha512-HflLxyrH1cqC7s7jzQGSjpsTAVIoaf7+/yHjbdM59rJQBMjliibp+LYhDPFxOMcwa1M8DNpOk6ENzQiWn9a1Dg== +"@tabler/icons@3.51.0": + version "3.51.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.51.0.tgz#41133efa23cc0d13d4e9b0b5148e0e64746dad48" + integrity sha512-0pinvPZz7YeX+OSfxuEAbnEPWbocVXW5iU8h5lh41pYG8sZuQfpWECC980lxOT0clGIcNGn8xsBr+zKaRGdA/g== "@tanstack/match-sorter-utils@^9.1.2": version "9.2.6" @@ -770,41 +783,42 @@ dependencies: remove-accents "0.5.0" -"@tanstack/query-core@5.105.3": - version "5.105.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.105.3.tgz#6574e94812f491aabdb828cf3d71bfe7ea72320e" - integrity sha512-v1rbNy7MB/4Akth7CQH+CerV13K7Ok9hAusEsfz4sCjDdu3/kOSJPoWfoB+YjdEX1eBFBWKNAGE5nqZCgdlHDw== +"@tanstack/query-core@5.107.1": + version "5.107.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.107.1.tgz#cd1f9952b4d623e66349931df631b287d9c4a3fe" + integrity sha512-PSyAik11oQBEVOiVy5hjZWHmoUqOXqK1WYy4v55mcxozEfKaQgpUMsgnXhWjuESzSht8JfzqMS1m6M/9CpNgvg== -"@tanstack/query-devtools@5.105.3": - version "5.105.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.105.3.tgz#6aec77ce69756204b28439a8eea9bf8511d772b0" - integrity sha512-CZQ7vqAc/XTxi6omDeAkQW3RW99HKIxZDvHi1Y/hnyZFMmYhEfCmBd7wfUb1m/BEqdXTEKqZvoRKI7oKDKUchA== +"@tanstack/query-devtools@5.107.1": + version "5.107.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.107.1.tgz#d89f6a73dd7194a07805d5c8e6d9e0f84098c825" + integrity sha512-cZvgIPNvsh57mM3yTTwuL2GUMmMp5XR+3k5r2UJDyStmPYCqSwZf8z7OulgA4X8vISziAG5PoykbQ1q+2zemdA== dependencies: "@kobalte/core" "^0.13.14" "@solid-primitives/keyed" "^1.5.3" "@solid-primitives/resize-observer" "^2.2.0" "@solid-primitives/storage" "^1.3.11" "@tanstack/match-sorter-utils" "^9.1.2" - "@tanstack/query-core" "5.105.3" + "@tanstack/query-core" "5.107.1" + "@tanstack/solid-virtual" "^3.13.52" clsx "^2.1.1" goober "^2.1.19" solid-js "^1.9.15" solid-transition-group "^0.2.3" superjson "^2.2.6" -"@tanstack/react-query-devtools@^5.105.3": - version "5.105.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.105.3.tgz#074faf923edf26f486fa54e4729474c4da946755" - integrity sha512-ggye4KEyv9QDj4bXoqt5pfbzz3XzC0pJAQhFgGAkmKLo8cchklxafKSVafJMHllpHXAJmXJgyi4NL149FdmPnQ== +"@tanstack/react-query-devtools@^5.107.1": + version "5.107.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.107.1.tgz#3c57174c77929fb0ec975ef1307b11530690cec4" + integrity sha512-A3q9X3iOxtlz1jU/ou7t9CFvNHT84rTMofS1YEkFcBalvwotZ/RJKPDsX1NsQVl10J8XwN0OYMSfdjgIElt0sg== dependencies: - "@tanstack/query-devtools" "5.105.3" + "@tanstack/query-devtools" "5.107.1" -"@tanstack/react-query@^5.105.3": - version "5.105.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.105.3.tgz#e0d3f755aab793e86db04581824e24434283e207" - integrity sha512-dj5hGWZmwoc9NG4enoXuYbiYwb5FxgJYPVv06eUgicMTfEMTRXn08Xv9D/r1V8TfjAv+BpxdvZFzJ1h3hSWZcQ== +"@tanstack/react-query@^5.107.1": + version "5.107.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.107.1.tgz#277e4b8c4d6d0496047ef15afacbc8c091ce0532" + integrity sha512-udCOOLp3ZAheUrIHLCe+ZZge6nr2ZUrGMSWcCuXsjY1Bf2JeeE09oxst97PILiCY6N8xo/7bIo3Hvz13zh06FA== dependencies: - "@tanstack/query-core" "5.105.3" + "@tanstack/query-core" "5.107.1" "@tanstack/react-store@^0.11.2": version "0.11.2" @@ -822,6 +836,13 @@ "@tanstack/react-store" "^0.11.2" "@tanstack/table-core" "9.3.1" +"@tanstack/solid-virtual@^3.13.52": + version "3.13.53" + resolved "https://registry.yarnpkg.com/@tanstack/solid-virtual/-/solid-virtual-3.13.53.tgz#236ce82d068594ebb9cc68c98bb4a3629b7dc13d" + integrity sha512-LUdGSmkTkwtpUea4w7bJp6N6ErnNkWT5iXpvn0mI9ijjtVrOa0JkSX8ZhQfNZxbhm4cnlGYbz/U1SQn/LqSNlw== + dependencies: + "@tanstack/virtual-core" "3.19.11" + "@tanstack/store@0.11.2", "@tanstack/store@^0.11.2": version "0.11.2" resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.11.2.tgz#cea02e554a483c7334a64a3ca2c0e4b703ba8531" @@ -834,6 +855,11 @@ dependencies: "@tanstack/store" "^0.11.2" +"@tanstack/virtual-core@3.19.11": + version "3.19.11" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.19.11.tgz#089fd1a9a92503f8f2329c7e48ceb84178726fc1" + integrity sha512-/5iHb0RHBvj8Di59FDs4ESNm45jls33aZAi4zccVXZjc/6ZvwQP6nzEYeT6weXjTf9E6/vrp2JhYWRyO+ZP13A== + "@testing-library/dom@^10.4.2": version "10.4.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.2.tgz#e996827c4e5e1f13589527293b4b3958de2f709f" @@ -1268,11 +1294,23 @@ chokidar@^5.0.0: dependencies: readdirp "^5.0.0" +chokidar@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-6.0.0.tgz#66bb84678ab40b8d25679d71f3955bd206ab618b" + integrity sha512-Ec2Z7wpcKBiMpR7DxteoqS/sq5G5d2A4fpxzPv6+fD7xNNdrTghn7L11YwV5EN1B3i72Erc3XVT5FkWIDx90+A== + dependencies: + readdirp "^5.1.1" + classnames@^2.3.2, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== +clickable-path@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/clickable-path/-/clickable-path-0.1.1.tgz#c11c5fddbf0781f6f7382b529c5e1d7f9124839a" + integrity sha512-ROn/mdV2pR8WFdpJjFDCSouLRg9myf95xacO1iy8G/AVO/x5y124pjF3EaoWrTBRWC3vh0DPqa/MhKyx3mMeKg== + clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" @@ -1383,16 +1421,16 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -entities@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" - integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== - entities@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== +entities@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-9.0.0.tgz#c45df3cdbc5ce805474759f4ec4ea7907d9d5477" + integrity sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg== + error-ex@^1.3.1: version "1.3.4" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" @@ -1499,11 +1537,6 @@ goober@^2.1.19: resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.19.tgz#a4b4dcdbba9325b8c4d7380099f9f281f27c6a6f" integrity sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg== -graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - happy-dom@^21.0.4: version "21.0.4" resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-21.0.4.tgz#3b9e4b23a6ef5f94f6db242857a99742dd14f943" @@ -1525,14 +1558,14 @@ hasown@^2.0.4: function-bind "^1.1.2" hast-util-from-html@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" - integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.1.0.tgz#d80f4949e450ca473e4a4e703313d883daeab604" + integrity sha512-873p9AuH93gBnATbH8rU3YvjTKxyrIeWd/M9tiLMzn3G82Os0xhQurctfnbYUSrDXoyjbJGpJ2qmTpefMw7ZaQ== dependencies: "@types/hast" "^3.0.0" devlop "^1.1.0" hast-util-from-parse5 "^8.0.0" - parse5 "^7.0.0" + parse5 "^8.0.0" vfile "^6.0.0" vfile-message "^4.0.0" @@ -1688,13 +1721,13 @@ inline-style-parser@0.2.9: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.9.tgz#148d759a4b8677a560a1d3e185e6db7f6be57652" integrity sha512-IttN8BqKLxzUrnTqHI+/hwAJQItBAUjdoHERCDv+oOuX48VP6bzW8+QjOXSfoevzq3YrYAwX1uLHp8TqO9yIeg== -intl-messageformat@12.1.4: - version "12.1.4" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-12.1.4.tgz#6d3ad7a0256dca2aae8ee76a25e44c61f6bc2d09" - integrity sha512-JUkmv3cAEyijN3oiRiV3ctel7yVMuUCgfh7Pu8pj1/z6L1DSEPRyhuMaLjVw3pGmGJ5RDH54WVFxfMHZWTwyHw== +intl-messageformat@12.1.5: + version "12.1.5" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-12.1.5.tgz#1b902dd7f68764c69318a7afe363cbc34b426486" + integrity sha512-quhcDFAxy4wr/GrgWgmMjuegoedNUIxGNLORfXgDgdjuv8cBM88ClLTLXmfYC1roMSR1PPc55yUEkpJ5m67W2g== dependencies: "@formatjs/fast-memoize" "3.1.7" - "@formatjs/icu-messageformat-parser" "3.5.22" + "@formatjs/icu-messageformat-parser" "3.5.23" invariant@^2.2.4: version "2.2.4" @@ -1755,6 +1788,11 @@ is-plain-obj@^4.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== +js-tokens@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831" + integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1977,9 +2015,9 @@ mdast-util-to-hast@^13.0.0: vfile "^6.0.0" mdast-util-to-markdown@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.2.tgz#9b558c5d3202b928a38d02ebaeec8a0ebe0e342f" - integrity sha512-ELNT5jYDUdx0B/hlUSSAiAEtvqkY3Oriwkp1KEYseF1Ccu4MnKkmE5yK3oE3WGT/Pq8VTE+XwP0GLL5pafY8yA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.3.tgz#1131a68290ff68e8718e75749a6e07e7654edef3" + integrity sha512-dImdB6VZRMRac1SwAW6o1c0ESk/7pFFAMCMuKM+9v7LpTSthMM5nzXpqKw4Mjo66aXVKhNkyrP4bMB9MurPyWg== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -2228,14 +2266,6 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -npm-run-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-6.0.0.tgz#25cfdc4eae04976f3349c0b1afc089052c362537" - integrity sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== - dependencies: - path-key "^4.0.0" - unicorn-magic "^0.3.0" - object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2281,17 +2311,12 @@ parse-numeric-range@^1.3.0: resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== -parse5@^7.0.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" - integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== +parse5@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-8.1.1.tgz#d73eb52282024c921e9c5a19b870f53492e631b2" + integrity sha512-EcR/6WDcU7b2N3vQSxKALCk9lXGtUMQxXio008OEMBE0kavu/7JLfBEeFPzi43nMqVDSbEn3QCK+BMCc9zO//w== dependencies: - entities "^6.0.0" - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + entities "^9.0.0" path-parse@^1.0.7: version "1.0.7" @@ -2323,7 +2348,7 @@ postcss-simple-vars@^7.0.1: resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -postcss@^8.5.30, postcss@^8.5.32: +postcss@^8.5.32: version "8.5.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803" integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g== @@ -2358,15 +2383,6 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proper-lockfile@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - property-information@^6.0.0: version "6.5.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" @@ -2450,14 +2466,14 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-intl@^12.1.6: - version "12.1.6" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.6.tgz#5b1f29a4382b84cd34c35bf625c831ab8ad0a53a" - integrity sha512-cxiqHlRK/JdDW/hMzusEv+LAqhfjUCL1YZAlazE39gT6jT3UrrNi3H50cgsveyUaASW14kNM2izIYMPYsYDCgA== +react-intl@^12.1.7: + version "12.1.7" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.7.tgz#fb24e2fe107acb070903a44a6dbb68cb7e749974" + integrity sha512-DKPrDYGQfQpyW8g8dbEPd6abvinwHPw85LcAT6nmd8+kzexea5egLQu6KZTEGDgP+3ap8dn+sX3JTuS7v0xD4g== dependencies: - "@formatjs/icu-messageformat-parser" "3.5.22" - "@formatjs/intl" "6.1.4" - intl-messageformat "12.1.4" + "@formatjs/icu-messageformat-parser" "3.5.23" + "@formatjs/intl" "6.1.5" + intl-messageformat "12.1.5" react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1" @@ -2563,7 +2579,7 @@ react@^19.3.1: resolved "https://registry.yarnpkg.com/react/-/react-19.3.1.tgz#26e9523fc36ea06be5050ebb780249a133260ad7" integrity sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw== -readdirp@^5.0.0: +readdirp@^5.0.0, readdirp@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-5.1.1.tgz#520bca06f9d1ae1b96cc0800dbe84b983d19422c" integrity sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA== @@ -2667,12 +2683,7 @@ resolve@^1.19.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -rolldown@~1.2.14: +rolldown@~1.2.16: version "1.2.16" resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.16.tgz#6c99aafa32361d578fbd945aeb43e533bb8f4c28" integrity sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw== @@ -2713,10 +2724,10 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -sass@^1.107.1: - version "1.107.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.107.1.tgz#a8a461744666e31f8973041a6b9ae7fcb265befd" - integrity sha512-aJ1/LvxOOb04YhmiBjzZn3jVwdHKol+OVhoMV6fFl02fF9T3i+5Mg9l/CnzqtM+NX5Jn5o3lAbs/ZJivTCLHfQ== +sass@^1.108.1: + version "1.108.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.108.1.tgz#169e456df9ad50bac4593435a207106a59b645a1" + integrity sha512-sIYgSvOPeD9wABwDS/ofNjHASSck+7h3jLkk7wG1NPFL3XeS6vOQjBlAFSmRV09kZr1yt/Tuayt6kKtBbLjHNQ== dependencies: chokidar "^5.0.0" immutable "^5.1.5" @@ -2744,11 +2755,6 @@ set-cookie-parser@^2.6.0: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.3.tgz#ded8e573414ceb6f5752f610dc9751f4eda0295d" integrity sha512-irZHtTqurLna57KQG/s+fzOwv/J6XSH4cs8M/vsrY3uCaQ3S6+QJguAH0WdN47SXlk82voMZ4naWMQf8fdEoIg== -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - solid-js@^1.9.15: version "1.9.18" resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.18.tgz#a26f33066c8877fabf7094dea092138eb0ae9b63" @@ -2844,11 +2850,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tiny-invariant@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" - integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== - tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -2859,7 +2860,7 @@ tinybench@^6.2.2: resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.2.3.tgz#2e560dfbf17f83ddbe0be0681f8ed0553d2136d5" integrity sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw== -tinyexec@^1.3.1: +tinyexec@^1.2.4, tinyexec@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.3.1.tgz#16a2e3c6e23fafce72640e678e651db36145b9c6" integrity sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA== @@ -2938,11 +2939,6 @@ undici-types@~8.9.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.9.0.tgz#e240d97c8b5d85e5347ce73d25865c7906c1ec9f" integrity sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg== -unicorn-magic@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104" - integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== - unified@^11.0.0: version "11.0.5" resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" @@ -3014,17 +3010,17 @@ use-sync-external-store@^1.6.0: integrity sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A== vfile-location@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" - integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + version "5.0.4" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.4.tgz#7e101a62d77ed528deb33c88c9839fb3be25b895" + integrity sha512-vEvxatYZCIpNvzGk2UD+u4I/sZkSKoOsdRCbkOIsN73QpJbriJ1qvvBAL1PeI0y55Leoqf7pORbaSPpG/smmOg== dependencies: "@types/unist" "^3.0.0" vfile "^6.0.0" vfile-message@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" - integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + version "4.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.4.tgz#24dd2080867c336e121a2b7446c247d935022565" + integrity sha512-66ycRswIkNmHH9PHku7ADtv5RVwgIrOcuv3J8Svz6h4qgGRrKVa+9/neuKyJZUXyOpYwLTvS+whZ4R2urHHgQg== dependencies: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" @@ -3037,28 +3033,26 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" -vite-plugin-checker@^0.14.5: - version "0.14.5" - resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.14.5.tgz#b809a65bbec0195d932bfee4bb3a49cdd2907640" - integrity sha512-c9lQ92eisUO+F7Fd93aelojmiOS+NQpPgQ1XR2LTQHox1/laZf4yAoQj+L3RA9Vgh10e2nFd9b8r2LLyYZsbpA== +vite-plugin-checker@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-1.0.0.tgz#8027a347e19cdaa0e7d841cd5ae5eed9f59b5b9e" + integrity sha512-3Ef78GTs13BUSTCqUCnl5kh8/Ev00G3cTt4z2mQacCODCdx6oq4B/TgXiCvGJ1D7/Hhbd84QKz691+8gbn1uog== dependencies: - "@babel/code-frame" "^7.29.0" - chokidar "^5.0.0" - npm-run-path "^6.0.0" - picocolors "^1.1.1" + "@babel/code-frame" "^8.0.0" + chokidar "^6.0.0" + clickable-path "^0.1.1" picomatch "^4.0.4" - proper-lockfile "^4.1.2" - tiny-invariant "^1.3.3" + tinyexec "^1.2.4" -vite@^8.3.5: - version "8.3.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.5.tgz#9614f66026cb92fe486cb9d3c90780b2a69fc85d" - integrity sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ== +vite@^8.3.6: + version "8.3.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.6.tgz#ff2eb6b0b18e910c08a79c59a80faebecd422808" + integrity sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA== dependencies: lightningcss "^1.33.0" picomatch "^4.0.7" - postcss "^8.5.30" - rolldown "~1.2.14" + postcss "^8.5.32" + rolldown "~1.2.16" tinyglobby "^0.2.17" optionalDependencies: fsevents "~2.3.3" @@ -3105,9 +3099,9 @@ why-is-node-running@3.2.1: integrity sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ== ws@^8.21.0: - version "8.22.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.1.tgz#60d427870e8272ddb10c7fc5cdf00a70eaec369e" - integrity sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g== + version "8.22.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.2.tgz#b1762f4e4ac87ab44aa358f680cdf5d08b166d37" + integrity sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA== yaml@^1.10.0: version "1.10.3" From 36b3434b9c8126200a31c1da746eff715ecff077 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 21:56:56 +1000 Subject: [PATCH 63/76] Fix for adding meta.certificate_key to omissions() means upload() now reads a row with no private key, so an upload that leaves out certificate_key wipes the stored key and writeCustomCert writes undefined to privkey.pem. --- backend/internal/certificate.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 273cf7593..bee277e2a 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -735,22 +735,37 @@ const internalCertificate = { throw new error.ValidationError("Certificate file was not provided"); } + // row came through get(), which omits meta.certificate_key so it never goes out over + // the API. update() replaces meta wholesale, so build on the stored meta (read from the + // model directly) or an upload without a key would erase the stored one. + const stored = await certificateModel.query().where("id", row.id).first(); + const meta = { ...(stored?.meta || {}) }; + const uploaded = []; + _.map(data.files, (file, name) => { if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - row.meta[name] = file.data.toString(); + meta[name] = file.data.toString(); + uploaded.push(name); } }); + // Fail before anything is written: without a key, writeCustomCert would overwrite + // fullchain.pem and then fail on privkey.pem, leaving nginx with a mismatched pair. + if (!meta.certificate_key) { + throw new error.ValidationError("Certificate Key file was not provided"); + } + const certificate = await internalCertificate.update(access, { id: data.id, expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), domain_names: validations.certificate.cn ? [validations.certificate.cn] : [], - meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later + meta: _.clone(meta), // Prevent the update method from changing this value that we'll use later }); - certificate.meta = row.meta; + certificate.meta = meta; await internalCertificate.writeCustomCert(certificate); - return _.pick(row.meta, internalCertificate.allowedSslFiles); + // Only echo back what was uploaded in this request, never the stored key + return _.pick(meta, uploaded); }, /** From 0cde59a8ad24a24e2e0b98b714146d59a34a7af4 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:00:26 +1000 Subject: [PATCH 64/76] Fix for rate limiter only trusts X-Forwarded-For when the socket is loopback, and the admin nginx sets that header to its own $remote_addr --- backend/lib/express/rate-limit.js | 51 +++++++++++++++++++++++------- backend/routes/tokens.js | 31 +++++++++++++++++- docker/rootfs/etc/nginx/nginx.conf | 4 +++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/backend/lib/express/rate-limit.js b/backend/lib/express/rate-limit.js index baa34493d..a8754a826 100644 --- a/backend/lib/express/rate-limit.js +++ b/backend/lib/express/rate-limit.js @@ -4,24 +4,57 @@ import { isCI } from "../config.js"; * In-memory sliding window rate limiter for authentication endpoints */ -const ipRequests = new Map(); +// One store per limiter, so separate endpoints never share buckets +const stores = new Set(); // Clean up old entries every 2 minutes const cleanupTimer = setInterval(() => { const now = Date.now(); - for (const [ip, record] of ipRequests.entries()) { - if (now - record.startTime > record.windowMs * 2) { - ipRequests.delete(ip); + for (const store of stores) { + for (const [key, record] of store.entries()) { + if (now - record.startTime > record.windowMs * 2) { + store.delete(key); + } } } }, 120000); cleanupTimer.unref(); +/** + * The client IP as seen by the backend. X-Forwarded-For is only honoured when the socket is + * loopback, which is the admin nginx in this container; it sets the header to its own + * $remote_addr after realip processing, so the first entry is the resolved client. + * + * @param {Object} req + * @returns {String} + */ +export const getClientIp = (req) => { + const socketAddr = req.socket?.remoteAddress || "unknown"; + const isTrustedLocalProxy = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; + const forwardedFor = isTrustedLocalProxy ? req.headers["x-forwarded-for"]?.split(",")[0].trim() : null; + return forwardedFor || socketAddr; +}; + +/** + * @param {Object} [options] + * @param {Number} [options.windowMs] + * @param {Number} [options.max] + * @param {String} [options.message] + * @param {Boolean} [options.skipSuccessfulRequests] + * @param {Function} [options.keyGenerator] (req, clientIp) => String. Defaults to the client IP. + * Including the target account in the key stops every + * user being locked out when many clients share an IP + * (e.g. the admin UI behind another reverse proxy). + */ export default function createRateLimiter(options = {}) { const windowMs = options.windowMs || 60 * 1000; // 1 minute default const max = options.max || 10; // 10 requests default const message = options.message || "Too many attempts from this IP, please try again later."; const skipSuccessfulRequests = options.skipSuccessfulRequests !== false; + const keyGenerator = options.keyGenerator || ((_req, clientIp) => clientIp); + + const store = new Map(); + stores.add(store); return (req, res, next) => { // Bypass rate limiting in CI, test environments, or when explicitly disabled @@ -34,14 +67,10 @@ export default function createRateLimiter(options = {}) { return next(); } - const socketAddr = req.socket?.remoteAddress || "unknown"; - const isTrustedLocalProxy = - socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; - const forwardedFor = isTrustedLocalProxy ? req.headers["x-forwarded-for"]?.split(",")[0].trim() : null; - const clientIp = forwardedFor || socketAddr; + const key = keyGenerator(req, getClientIp(req)); const now = Date.now(); - let record = ipRequests.get(clientIp); + let record = store.get(key); if (!record || now - record.startTime > windowMs) { record = { @@ -49,7 +78,7 @@ export default function createRateLimiter(options = {}) { startTime: now, windowMs, }; - ipRequests.set(clientIp, record); + store.set(key, record); if (skipSuccessfulRequests) { const currentRecord = record; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index b5f25890c..012bb95ca 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -6,11 +6,40 @@ import apiValidator from "../lib/validator/api.js"; import { debug, express as logger } from "../logger.js"; import { getValidationSchema } from "../schema/index.js"; +// Keyed on IP + target account rather than IP alone: when the admin UI sits behind another +// proxy every client can resolve to the same IP, and an IP-only bucket would let anyone lock +// out every user with a handful of bad logins. Brute force against one account is still +// capped per IP. const tokenRateLimiter = createRateLimiter({ windowMs: 60 * 1000, max: 10, message: "Too many login attempts. Please try again in 1 minute.", skipSuccessfulRequests: true, + keyGenerator: (req, clientIp) => { + const identity = typeof req.body?.identity === "string" ? req.body.identity.trim().toLowerCase() : ""; + return `${clientIp}|${identity}`; + }, +}); + +// Keyed on the user the challenge belongs to, not the challenge token itself, so fetching +// fresh challenge tokens doesn't reset the code-guessing budget. The payload is read without +// verifying the signature; verify2FA does that. A forged token only affects buckets for the +// sender's own IP. +const challengeUserId = (token) => { + try { + const payload = JSON.parse(Buffer.from(String(token).split(".")[1], "base64url").toString()); + return String(payload?.attrs?.id ?? ""); + } catch { + return ""; + } +}; + +const twoFactorRateLimiter = createRateLimiter({ + windowMs: 60 * 1000, + max: 10, + message: "Too many login attempts. Please try again in 1 minute.", + skipSuccessfulRequests: true, + keyGenerator: (req, clientIp) => `${clientIp}|${challengeUserId(req.body?.challenge_token)}`, }); const router = express.Router({ @@ -72,7 +101,7 @@ router * * Verify 2FA code and get full token */ - .post(tokenRateLimiter, async (req, res, next) => { + .post(twoFactorRateLimiter, async (req, res, next) => { try { const { challenge_token, code } = await apiValidator(getValidationSchema("/tokens/2fa", "post"), req.body); const result = await internalToken.verify2FA(challenge_token, code); diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 471d22536..2184049c4 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -77,6 +77,10 @@ http { # Real IP Determination + # Loopback: lets an NPM proxy host forwarding to 127.0.0.1 (e.g. the admin UI on :81) + # pass the real client IP through instead of every request appearing as 127.0.0.1 + set_real_ip_from 127.0.0.1; + set_real_ip_from ::1; # Local subnets: set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; # Includes Docker subnet From 99678969b3fc51804e4e020373b36b1502cbeacb Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:04:32 +1000 Subject: [PATCH 65/76] Fix a proxy host whose access list can't be loaded is now denied to everyone instead of being served with no restrictions --- backend/internal/nginx.js | 15 +++++++++++++-- backend/templates/_access.conf | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 378b53591..0ba90b0e8 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -183,6 +183,16 @@ const internalNginx = { } if (locationAccessList) { locationCopy.access_list = locationAccessList; + } else if ( + locationCopy.access_list_id > 0 && + locationCopy.access_list_id !== host.access_list?.id + ) { + // The location names its own list but it's gone. Don't fall back to the + // host's (possibly less restrictive) list; _access.conf denies all instead. + logger.error( + `Proxy Host #${host.id} location "${locationCopy.path}" references Access List #${locationCopy.access_list_id} which does not exist, all access will be denied`, + ); + locationCopy.access_list = null; } else { locationCopy.access_list_id = host.access_list_id; locationCopy.access_list = host.access_list; @@ -338,8 +348,9 @@ const internalNginx = { } if (!host.access_list) { - logger.warn( - `Proxy Host #${host.id} references Access List #${host.access_list_id} which does not exist`, + // _access.conf renders "deny all" when the list is missing, so fail closed + logger.error( + `Proxy Host #${host.id} references Access List #${host.access_list_id} which does not exist, all access will be denied`, ); } else if (!host.access_list.clients.length && !host.access_list.items.length) { logger.warn( diff --git a/backend/templates/_access.conf b/backend/templates/_access.conf index 81983f3a3..7ae905472 100644 --- a/backend/templates/_access.conf +++ b/backend/templates/_access.conf @@ -1,4 +1,5 @@ {% if access_list_id > 0 %} + {% if access_list %} {% if access_list.items.length > 0 %} # Authorization auth_basic "Authorization required"; @@ -24,4 +25,8 @@ {% else %} satisfy all; {% endif %} + {% else %} + # Access List #{{ access_list_id }} could not be loaded: deny rather than serve unrestricted + deny all; + {% endif %} {% endif %} From fff2b0d0974a55e28d9fbbbbfe5999e65db603ac Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:09:34 +1000 Subject: [PATCH 66/76] Fix for changing the upstream Host from $host to $forward_host ($http_host) forwards the raw client Host header, including the port and any absolute-form URI mismatch, to every upstream and into the asset cache key --- .gitignore | 1 + docker/rootfs/etc/nginx/nginx.conf | 14 ++++++++++---- test/host-header-smoke.py | 7 +++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index a83a2110e..667601ec7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ test/node_modules docker/dev/dnsrouter-config.json.tmp docker/dev/resolv.conf .claude +test/__pycache__ diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 2184049c4..608c1c1d8 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -52,10 +52,16 @@ http { # Dynamically generated resolvers file include /etc/nginx/conf.d/include/resolvers[.]conf; - # Preserve the client authority, falling back when a request has no Host header. - map $http_host $forward_host { - default $http_host; - "" $host; + # Forwarded Host: nginx's normalised $host (lowercased, taken from the request line for + # absolute-form requests) plus the client's port, so upstreams that compare Host with + # Origin/Referer on non-standard ports keep working. The port is only carried over when + # the Host header names the same host nginx routed on; otherwise (no Host, absolute-form + # URI naming a different host) it falls back to bare $host. Using $host rather than the + # raw header keeps case variants and mismatched Host values out of upstream requests and + # the asset cache key. + map "$host|$http_host" $forward_host { + "~*^(?[^|]+)\|\k(?:[0-9]+)$" "$host$fwd_port"; + default $host; } # Default upstream scheme diff --git a/test/host-header-smoke.py b/test/host-header-smoke.py index a1397d878..9e6b51b0b 100644 --- a/test/host-header-smoke.py +++ b/test/host-header-smoke.py @@ -178,7 +178,10 @@ def check_image(image): for authority in authorities: for prefix in ("/", "/custom/"): for method in ("GET", "POST"): - origin = f"{scheme}://{authority}" + # nginx forwards its normalised (lowercased) $host, as browsers send + # in Host and Origin anyway + forwarded = authority.lower() + origin = f"{scheme}://{forwarded}" referer = origin + "/settings" path = prefix + "apply?one=1&two=2" payload = "rule=example" if method == "POST" else None @@ -190,7 +193,7 @@ def check_image(image): raise AssertionError((authority, path, method, str(error))) from error assert status == 200 and headers.get("Content-Type") == "application/json", ( authority, path, method, status, body) - assert json.loads(body) == {"host": authority, "origin": origin, + assert json.loads(body) == {"host": forwarded, "origin": origin, "referer": referer, "method": method, "path": path, "body": payload or ""}, body checked += 1 From 9b1566aa6b00ed35cf1725850318d3beab9b1107 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:14:07 +1000 Subject: [PATCH 67/76] Fix for the broad Cloudflare hint regex (bare numbers 6003/6111/9103/9109/10000, plus 'Authentication error' and 'Invalid access token') is checked before the rate-limit, DNS and HTTP hints, so non-Cloudflare failures get a misleading Cloudflare message. --- backend/internal/certificate.js | 37 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index bee277e2a..008a5d12a 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -41,7 +41,10 @@ const certbotNoise = [ /^\d+ renew failure\(s\)/, ]; -// Plain-language hints for failures that are common and fixable outside NPM +// Plain-language hints for failures that are common and fixable outside NPM. +// [pattern, hint, dnsProvider?]: a hint with a dnsProvider only applies to certificates using that +// provider, so a generic message (e.g. "Authentication error" from another DNS plugin) isn't +// reported as a Cloudflare problem. const certbotHints = [ [ /expected \S+ to be a symlink/i, @@ -53,21 +56,26 @@ const certbotHints = [ () => "This certificate's files in /etc/letsencrypt are damaged or missing. Restore /etc/letsencrypt from a backup, or delete this certificate and create it again.", ], + // Checked first: a rate limit is unambiguous and the most useful thing to report + [ + /too many (certificates|failed authorizations|new orders)|rateLimited/i, + () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).", + ], [ /\b9109\b.*?location: ([0-9a-f.:]+)/i, (m) => { const ip = m[1].replace(/\.+$/, ""); return `Cloudflare refused the API token from this server's public IP (${ip}). The token has Client IP Address Filtering: add ${ip} to the token in Cloudflare (My Profile > API Tokens), or remove the filter, then renew again.`; }, + "cloudflare", ], [ - /\b(6003|6111|9103|9109|10000)\b|Invalid request headers|Invalid access token|Authentication error/i, + // certbot-dns-cloudflare errors read ": ", e.g. + // "Error determining zone_id: 9109 Invalid access token." + /: (6003|6111|9103|9109|10000) [A-Za-z]|Invalid request headers|Invalid access token|Authentication error/i, () => "Cloudflare rejected the API token. Check the token value and that it has Zone > DNS > Edit for this domain's zone.", - ], - [ - /too many (certificates|failed authorizations|new orders)|rateLimited/i, - () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).", + "cloudflare", ], [ /NXDOMAIN|DNS problem/i, @@ -85,9 +93,10 @@ const certbotHints = [ * Certbot never prints DNS provider credentials. * * @param {String} stderr + * @param {String} [dnsProvider] The certificate's DNS provider, if it uses a DNS challenge * @returns {String} */ -const certbotFailureMessage = (stderr) => { +const certbotFailureMessage = (stderr, dnsProvider) => { const reason = `${stderr || ""}` .split("\n") .map((line) => line.trim()) @@ -95,7 +104,10 @@ const certbotFailureMessage = (stderr) => { .join(" ") .slice(0, 800); - for (const [pattern, hint] of certbotHints) { + for (const [pattern, hint, provider] of certbotHints) { + if (provider && provider !== dnsProvider) { + continue; + } const match = reason.match(pattern); if (match) { return `${hint(match)} (Certbot: ${reason})`; @@ -109,14 +121,15 @@ const certbotFailureMessage = (stderr) => { * * @param {Array} args * @param {Object} [opts] + * @param {String} [dnsProvider] Passed through so provider-specific hints only show for that provider * @returns {Promise} */ -const runCertbot = async (args, opts) => { +const runCertbot = async (args, opts, dnsProvider) => { try { return await utils.execFile(certbotCommand, args, opts); } catch (err) { if (err instanceof error.CommandError) { - throw new error.ValidationError(certbotFailureMessage(err.message), err); + throw new error.ValidationError(certbotFailureMessage(err.message, dnsProvider), err); } throw err; } @@ -1024,7 +1037,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await runCertbot(args, adds.opts); + const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider); logger.info(result); return result; } finally { @@ -1204,7 +1217,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await runCertbot(args, adds.opts); + const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider); logger.info(result); return result; } finally { From af54d2e346e071498674ae92f737ab23a3333fe8 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:15:17 +1000 Subject: [PATCH 68/76] Fix nginx reloading too much when cert renewal for multiple certs --- backend/internal/certificate.js | 36 ++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 008a5d12a..327fe9919 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -186,6 +186,7 @@ const internalCertificate = { * instance of Certbot is already running.' */ let sequence = Promise.resolve(); + let renewed = 0; certificates.forEach((certificate) => { sequence = sequence.then(() => @@ -199,7 +200,11 @@ const internalCertificate = { token: tokenModel(), }, { id: certificate.id }, + { reload: false }, // one reload for the whole batch, below ) + .then(() => { + renewed++; + }) .catch((err) => { // Don't want to stop the train here, just log the error logger.error(err.message); @@ -207,7 +212,11 @@ const internalCertificate = { ); }); - return sequence; + return sequence.then(() => { + if (renewed) { + return internalCertificate.reloadAfterRenew(); + } + }); }) .then(() => { logger.info("Completed SSL cert renew process"); @@ -1063,7 +1072,8 @@ const internalCertificate = { * @param {Number} data.id * @returns {Promise} */ - renew: async (access, data) => { + renew: async (access, data, options = {}) => { + const reload = options.reload !== false; await access.can("certificates:update", data); const certificate = await internalCertificate.get(access, data); @@ -1089,7 +1099,12 @@ const internalCertificate = { meta: updatedCertificate, }); - await internalNginx.reload(); + // The renewal has succeeded and been recorded at this point. A reload failure + // (e.g. an unrelated host's config is broken) must not turn it into an error; + // nginx picks the new files up on the next successful reload. + if (reload) { + await internalCertificate.reloadAfterRenew(); + } return updatedCertificate; } @@ -1097,6 +1112,21 @@ const internalCertificate = { throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); }, + /** + * Reloads nginx so it serves renewed certificate files. Errors are logged, not thrown. + * + * @returns {Promise} whether the reload succeeded + */ + reloadAfterRenew: async () => { + try { + await internalNginx.reload(); + return true; + } catch (err) { + logger.error(`Certificate renewed but nginx could not be reloaded: ${err.message}`); + return false; + } + }, + /** * @param {Object} certificate the certificate row * @returns {Promise} From 4175cf66c944f50697b0fe2df1c378e059f347ca Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:17:07 +1000 Subject: [PATCH 69/76] Fix: deleting a certificate now returns success even when some of its files can't be removed --- backend/internal/certificate.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 327fe9919..b353c4838 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -538,16 +538,24 @@ const internalCertificate = { if (row.provider === "letsencrypt") { // Revoke the cert await internalCertificate.revokeLetsEncryptSsl(row); - // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config); remove them anyway - internalCertificate.removeLetsEncryptFiles(row.id); + // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config); remove them anyway. + // The row is already deleted and audited, so leftover files are logged, not thrown: + // an error here would report a completed delete as failed, and a retry would 404. + const leftover = internalCertificate.removeLetsEncryptFiles(row.id); + if (leftover.length) { + logger.warn( + `Certificate #${row.id} was deleted, but these files could not be removed and should be deleted by hand: ${leftover.join(", ")}`, + ); + } } return true; }, /** - * Removes a certificate's certbot files, as `certbot delete` would. + * Removes a certificate's certbot files, as `certbot delete` would. Never throws. * - * @param {Number} certificateId + * @param {Number} certificateId + * @returns {String[]} paths that could not be removed */ removeLetsEncryptFiles: (certificateId) => { const paths = [ @@ -565,11 +573,7 @@ const internalCertificate = { failed.push(p); } } - if (failed.length) { - throw new error.ValidationError( - `The certificate was removed from NPM, but these files could not be deleted: ${failed.join(", ")}. Delete them by hand.`, - ); - } + return failed; }, /** From 8342713d45eb338e76bc9b9901b5576aaeb0f287 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:21:40 +1000 Subject: [PATCH 70/76] Fix a non-admin can now rename a location that has admin-written advanced config, and still can't change or add advanced config --- backend/internal/proxy-host.js | 62 +++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 176604563..587535c2a 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -14,6 +14,55 @@ const omissions = () => { }; const internalProxyHost = { + /** + * For non-admins: every location's advanced_config must be one that already exists on this + * host. Matching is by value, not path, so renaming or reordering a location that carries + * admin-written config is allowed, but new or edited config is not. Each existing config can + * be used once (no duplicating it onto other paths), and same-path matches are taken first. + * A location that omits advanced_config keeps the one at its path. Mutates `locations`. + * + * @param {Array} locations incoming locations + * @param {Array} [existingLocations] the host's current locations + * @throws {PermissionError} + */ + assertLocationAdvancedConfigUnchanged: (locations, existingLocations) => { + const existing = Array.isArray(existingLocations) ? existingLocations : []; + const advAtPath = (path) => existing.find((l) => l.path === path)?.advanced_config || ""; + const pool = existing.map((l) => l.advanced_config || "").filter(Boolean); + const take = (value) => { + const i = pool.indexOf(value); + if (i === -1) { + return false; + } + pool.splice(i, 1); + return true; + }; + const deny = () => { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + }; + + // Pass 1: locations that keep (or omit) the config at their own path + const unresolved = []; + for (const loc of locations) { + const atPath = advAtPath(loc.path); + if (typeof loc.advanced_config === "undefined") { + loc.advanced_config = atPath && take(atPath) ? atPath : ""; + } else if ((loc.advanced_config || "") === atPath && (!atPath || take(atPath))) { + // unchanged + } else { + unresolved.push(loc); + } + } + + // Pass 2: renamed/moved locations may carry an existing config not already claimed. + // Clearing a config is not allowed, as before. + for (const loc of unresolved) { + if (!loc.advanced_config || !take(loc.advanced_config)) { + deny(); + } + } + }, + /** * @param {Access} access * @param {Object} data @@ -205,18 +254,7 @@ const internalProxyHost = { throw new errs.PermissionError("You do not have permission to modify advanced configuration"); } if (!isAdmin && Array.isArray(thisData.locations)) { - const existingLocations = Array.isArray(row.locations) ? row.locations : []; - for (const loc of thisData.locations) { - const existingLoc = existingLocations.find((l) => l.path === loc.path); - const existingAdv = existingLoc?.advanced_config || ""; - if (typeof loc.advanced_config === "undefined") { - loc.advanced_config = existingAdv; - } else if ((loc.advanced_config || "") !== existingAdv) { - throw new errs.PermissionError( - "You do not have permission to modify advanced configuration", - ); - } - } + internalProxyHost.assertLocationAdvancedConfigUnchanged(thisData.locations, row.locations); } return proxyHostModel From 87b77d9461ee9865167d641d3ec6165aea81b9c8 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:25:07 +1000 Subject: [PATCH 71/76] Optimise isAdmin lookups --- backend/internal/dead-host.js | 14 +--- backend/internal/host.js | 88 +++++++++++++++++++++++ backend/internal/proxy-host.js | 101 ++------------------------- backend/internal/redirection-host.js | 14 +--- backend/lib/access.js | 11 +-- backend/models/proxy_host.js | 19 +++++ 6 files changed, 120 insertions(+), 127 deletions(-) diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index c8e0e17cd..41291ed79 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -54,10 +54,7 @@ const internalDeadHost = { thisData.advanced_config = ""; } - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } + internalHost.assertAdvancedConfigAllowed(access, thisData); const row = await deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); @@ -156,14 +153,7 @@ const internalDeadHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if ( - !isAdmin && - typeof data.advanced_config !== "undefined" && - data.advanced_config !== row.advanced_config - ) { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } + internalHost.assertAdvancedConfigAllowed(access, data, row); // do the row update await deadHostModel.query().where({ id: data.id }).patch(data); diff --git a/backend/internal/host.js b/backend/internal/host.js index 81efe160e..eb871a192 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -1,10 +1,98 @@ import _ from "lodash"; +import errs from "../lib/error.js"; import { castJsonIfNeed } from "../lib/helpers.js"; import deadHostModel from "../models/dead_host.js"; import proxyHostModel from "../models/proxy_host.js"; import redirectionHostModel from "../models/redirection_host.js"; const internalHost = { + /** + * Only admins may set or change advanced_config (raw nginx config). Throws for anyone else. + * On create (no `row`), any non-empty advanced_config is refused, including on locations. + * On update, the host's advanced_config must be unchanged (or omitted), and locations must + * only carry advanced configs the host already has (see assertLocationAdvancedConfigUnchanged). + * + * @param {Access} access + * @param {Object} data incoming host data; data.locations may be filled in on update + * @param {Object} [row] the existing host, when updating + * @throws {PermissionError} + */ + assertAdvancedConfigAllowed: (access, data, row) => { + if (access.isAdmin?.()) { + return; + } + const deny = () => { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + }; + + if (!row) { + const hasConfig = (v) => typeof v === "string" && v.trim() !== ""; + if (hasConfig(data.advanced_config)) { + deny(); + } + if (Array.isArray(data.locations) && data.locations.some((loc) => hasConfig(loc?.advanced_config))) { + deny(); + } + return; + } + + if (typeof data.advanced_config !== "undefined" && data.advanced_config !== row.advanced_config) { + deny(); + } + if (Array.isArray(data.locations)) { + internalHost.assertLocationAdvancedConfigUnchanged(data.locations, row.locations); + } + }, + + /** + * For non-admins: every location's advanced_config must be one that already exists on this + * host. Matching is by value, not path, so renaming or reordering a location that carries + * admin-written config is allowed, but new or edited config is not. Each existing config can + * be used once (no duplicating it onto other paths), and same-path matches are taken first. + * A location that omits advanced_config keeps the one at its path. Mutates `locations`. + * + * @param {Array} locations incoming locations + * @param {Array} [existingLocations] the host's current locations + * @throws {PermissionError} + */ + assertLocationAdvancedConfigUnchanged: (locations, existingLocations) => { + const existing = Array.isArray(existingLocations) ? existingLocations : []; + const advAtPath = (path) => existing.find((l) => l.path === path)?.advanced_config || ""; + const pool = existing.map((l) => l.advanced_config || "").filter(Boolean); + const take = (value) => { + const i = pool.indexOf(value); + if (i === -1) { + return false; + } + pool.splice(i, 1); + return true; + }; + const deny = () => { + throw new errs.PermissionError("You do not have permission to modify advanced configuration"); + }; + + // Pass 1: locations that keep (or omit) the config at their own path + const unresolved = []; + for (const loc of locations) { + const atPath = advAtPath(loc.path); + if (typeof loc.advanced_config === "undefined") { + loc.advanced_config = atPath && take(atPath) ? atPath : ""; + } else if ((loc.advanced_config || "") === atPath && (!atPath || take(atPath))) { + // unchanged + } else { + unresolved.push(loc); + } + } + + // Pass 2: renamed/moved locations may carry an existing config not already claimed. + // Clearing a config is not allowed, as before. + for (const loc of unresolved) { + if (!loc.advanced_config || !take(loc.advanced_config)) { + deny(); + } + } + }, + /** * Makes sure that the ssl_* and hsts_* fields play nicely together. * ie: if there is no cert, then force_ssl is off. diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 587535c2a..b148b327a 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -2,7 +2,7 @@ import _ from "lodash"; import errs from "../lib/error.js"; import { castJsonIfNeed } from "../lib/helpers.js"; import utils from "../lib/utils.js"; -import proxyHostModel from "../models/proxy_host.js"; +import proxyHostModel, { scopeToOwnerOrAllowed } from "../models/proxy_host.js"; import userPermissionModel from "../models/user_permission.js"; import internalAuditLog from "./audit-log.js"; import internalCertificate from "./certificate.js"; @@ -14,55 +14,6 @@ const omissions = () => { }; const internalProxyHost = { - /** - * For non-admins: every location's advanced_config must be one that already exists on this - * host. Matching is by value, not path, so renaming or reordering a location that carries - * admin-written config is allowed, but new or edited config is not. Each existing config can - * be used once (no duplicating it onto other paths), and same-path matches are taken first. - * A location that omits advanced_config keeps the one at its path. Mutates `locations`. - * - * @param {Array} locations incoming locations - * @param {Array} [existingLocations] the host's current locations - * @throws {PermissionError} - */ - assertLocationAdvancedConfigUnchanged: (locations, existingLocations) => { - const existing = Array.isArray(existingLocations) ? existingLocations : []; - const advAtPath = (path) => existing.find((l) => l.path === path)?.advanced_config || ""; - const pool = existing.map((l) => l.advanced_config || "").filter(Boolean); - const take = (value) => { - const i = pool.indexOf(value); - if (i === -1) { - return false; - } - pool.splice(i, 1); - return true; - }; - const deny = () => { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - }; - - // Pass 1: locations that keep (or omit) the config at their own path - const unresolved = []; - for (const loc of locations) { - const atPath = advAtPath(loc.path); - if (typeof loc.advanced_config === "undefined") { - loc.advanced_config = atPath && take(atPath) ? atPath : ""; - } else if ((loc.advanced_config || "") === atPath && (!atPath || take(atPath))) { - // unchanged - } else { - unresolved.push(loc); - } - } - - // Pass 2: renamed/moved locations may carry an existing config not already claimed. - // Clearing a config is not allowed, as before. - for (const loc of unresolved) { - if (!loc.advanced_config || !take(loc.advanced_config)) { - deny(); - } - } - }, - /** * @param {Access} access * @param {Object} data @@ -107,17 +58,7 @@ const internalProxyHost = { thisData.advanced_config = ""; } - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } - if ( - !isAdmin && - Array.isArray(thisData.locations) && - thisData.locations.some((loc) => loc?.advanced_config && loc.advanced_config.trim() !== "") - ) { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } + internalHost.assertAdvancedConfigAllowed(access, thisData); return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) @@ -245,17 +186,7 @@ const internalProxyHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if ( - !isAdmin && - typeof thisData.advanced_config !== "undefined" && - thisData.advanced_config !== row.advanced_config - ) { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } - if (!isAdmin && Array.isArray(thisData.locations)) { - internalProxyHost.assertLocationAdvancedConfigUnchanged(thisData.locations, row.locations); - } + internalHost.assertAdvancedConfigAllowed(access, thisData, row); return proxyHostModel .query() @@ -317,14 +248,7 @@ const internalProxyHost = { .first(); if (access_data.permission_visibility !== "all") { - const permissions = access.getPermissions ? access.getPermissions() : {}; - const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; - query.andWhere((builder) => { - builder.where("owner_user_id", access.token.getUserId(1)); - if (allowedIds.length > 0) { - builder.orWhereIn("id", allowedIds); - } - }); + scopeToOwnerOrAllowed(query, access.token.getUserId(1), access.getPermissions?.()?.meta); } if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { @@ -508,14 +432,7 @@ const internalProxyHost = { .orderBy(castJsonIfNeed("domain_names"), "ASC"); if (accessData.permission_visibility !== "all") { - const permissions = access.getPermissions ? access.getPermissions() : {}; - const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; - query.andWhere((builder) => { - builder.where("owner_user_id", access.token.getUserId(1)); - if (allowedIds.length > 0) { - builder.orWhereIn("id", allowedIds); - } - }); + scopeToOwnerOrAllowed(query, access.token.getUserId(1), access.getPermissions?.()?.meta); } // Query is used for searching @@ -548,13 +465,7 @@ const internalProxyHost = { if (visibility !== "all") { const perm = await userPermissionModel.query().where("user_id", user_id).first(); - const allowedIds = perm?.meta?.proxy_host_ids || perm?.meta?.proxyHostIds || []; - query.andWhere((builder) => { - builder.where("owner_user_id", user_id); - if (allowedIds.length > 0) { - builder.orWhereIn("id", allowedIds); - } - }); + scopeToOwnerOrAllowed(query, user_id, perm?.meta); } const row = await query.first(); diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index 5e0f3a260..f7ad6be33 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -57,10 +57,7 @@ const internalRedirectionHost = { thisData.advanced_config = ""; } - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if (!isAdmin && thisData.advanced_config && thisData.advanced_config.trim() !== "") { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } + internalHost.assertAdvancedConfigAllowed(access, thisData); return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) @@ -188,14 +185,7 @@ const internalRedirectionHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - const isAdmin = access.hasRole?.("admin") || access.token.hasScope("admin"); - if ( - !isAdmin && - typeof thisData.advanced_config !== "undefined" && - thisData.advanced_config !== row.advanced_config - ) { - throw new errs.PermissionError("You do not have permission to modify advanced configuration"); - } + internalHost.assertAdvancedConfigAllowed(access, thisData, row); return redirectionHostModel .query() diff --git a/backend/lib/access.js b/backend/lib/access.js index 3e27ff388..c38213b33 100644 --- a/backend/lib/access.js +++ b/backend/lib/access.js @@ -13,7 +13,7 @@ import Ajv from "ajv/dist/2020.js"; import _ from "lodash"; import { access as logger } from "../logger.js"; import authModel from "../models/auth.js"; -import proxyHostModel from "../models/proxy_host.js"; +import proxyHostModel, { scopeToOwnerOrAllowed } from "../models/proxy_host.js"; import TokenModel from "../models/token.js"; import userModel from "../models/user.js"; import permsSchema from "./access/permissions.json" with { type: "json" }; @@ -139,13 +139,7 @@ export default function (tokenString) { const query = proxyHostModel.query().select("id").andWhere("is_deleted", 0); if (permissions.visibility === "user") { - const allowedIds = permissions.meta?.proxy_host_ids || permissions.meta?.proxyHostIds || []; - query.andWhere((builder) => { - builder.where("owner_user_id", tokenUserId); - if (allowedIds.length > 0) { - builder.orWhereIn("id", allowedIds); - } - }); + scopeToOwnerOrAllowed(query, tokenUserId, permissions.meta); } const rows = await query; @@ -222,6 +216,7 @@ export default function (tokenString) { getPermissions: () => permissions, getRoles: () => userRoles, hasRole: (role) => userRoles.includes(role), + isAdmin: () => userRoles.includes("admin") || Token.hasScope("admin"), /** * diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index acb8da935..d16ddc912 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -124,4 +124,23 @@ class ProxyHost extends Model { } } +/** + * Restricts a proxy host query to hosts the user owns or has been granted by id + * (user permission meta.proxy_host_ids). + * + * @param {Object} query objection query on proxy_host + * @param {Number} userId + * @param {Object} [permissionMeta] the user's permission meta + * @returns {Object} the query + */ +export const scopeToOwnerOrAllowed = (query, userId, permissionMeta) => { + const allowedIds = permissionMeta?.proxy_host_ids || []; + return query.andWhere((builder) => { + builder.where("owner_user_id", userId); + if (allowedIds.length > 0) { + builder.orWhereIn("id", allowedIds); + } + }); +}; + export default ProxyHost; From 779927c995616bd59c9a6a8f2e3c1b28757c2656 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:28:50 +1000 Subject: [PATCH 72/76] Fix getHostsWithDomains eager loading unnecessarily --- backend/internal/host.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/internal/host.js b/backend/internal/host.js index eb871a192..8df7cbe53 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -167,18 +167,18 @@ const internalHost = { redirection_hosts: [], }; - const proxyRes = await proxyHostModel - .query() - .where("is_deleted", 0) - .withGraphFetched("[certificate, access_list.[clients,items]]"); + // No relations are eager-loaded here: this scans every host just to match domains, and + // the few that match are rendered through internalNginx.generateConfig(), which loads + // their certificate and access list itself (loadRenderRelations). + const proxyRes = await proxyHostModel.query().where("is_deleted", 0); responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames); responseObject.total_count += responseObject.proxy_hosts.length; - const redirRes = await redirectionHostModel.query().where("is_deleted", 0).withGraphFetched("[certificate]"); + const redirRes = await redirectionHostModel.query().where("is_deleted", 0); responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames); responseObject.total_count += responseObject.redirection_hosts.length; - const deadRes = await deadHostModel.query().where("is_deleted", 0).withGraphFetched("[certificate]"); + const deadRes = await deadHostModel.query().where("is_deleted", 0); responseObject.dead_hosts = internalHost._getHostsWithDomains(deadRes, domainNames); responseObject.total_count += responseObject.dead_hosts.length; From 0792a5e24f1b6c2831972c163763d21b0664d2ca Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:33:10 +1000 Subject: [PATCH 73/76] Linting fixes --- backend/internal/access-list.js | 35 +++++----- backend/internal/nginx.js | 38 ++++++----- backend/internal/proxy-host.js | 117 ++++++++++++++++---------------- backend/routes/logs.js | 10 ++- 4 files changed, 103 insertions(+), 97 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 1fdd65b6a..42f610e08 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -27,20 +27,18 @@ const getProxyHostsUsingAccessListInLocations = async (accessListId) => { let result; if (isMysql()) { const searchObj = JSON.stringify([{ access_list_id: accessListId }]); - result = await db().raw( - "SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)", - [searchObj, "$"], - ); + result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)", [ + searchObj, + "$", + ]); } else if (isPostgres()) { - result = await db().raw( - "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb", - [JSON.stringify([{ access_list_id: accessListId }])], - ); + result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb", [ + JSON.stringify([{ access_list_id: accessListId }]), + ]); } else { - result = await db().raw( - "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?", - [`%"access_list_id":${accessListId}%`], - ); + result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?", [ + `%"access_list_id":${accessListId}%`, + ]); } // knex raw() returns [rows, metadata] for MySQL, { rows } for Postgres and rows for SQLite if (!Array.isArray(result)) { @@ -225,12 +223,15 @@ const internalAccessList = { // Also regenerate configs for proxy hosts that reference this access list in their locations const locationHostRows = await getProxyHostsUsingAccessListInLocations(data.id); if (locationHostRows?.length) { - const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => { - // Exclude hosts already regenerated above - return !freshRow.proxy_hosts?.find((h) => h.id === id); - }); + const locationHostIds = locationHostRows + .map((r) => r.id) + .filter((id) => { + // Exclude hosts already regenerated above + return !freshRow.proxy_hosts?.find((h) => h.id === id); + }); if (locationHostIds.length) { - const locationHosts = await proxyHostModel.query() + const locationHosts = await proxyHostModel + .query() .where("is_deleted", 0) .whereIn("id", locationHostIds) .allowGraph(proxyHostModel.defaultAllowGraph) diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 0ba90b0e8..4efd67f6f 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -289,26 +289,28 @@ const internalNginx = { // Set the IPv6 setting for the host host.ipv6 = internalNginx.ipv6Enabled(); - locationsPromise.then(() => { - renderEngine - .parseAndRender(template, host) - .then((config_text) => { - fs.writeFileSync(filename, config_text, { encoding: "utf8" }); - debug(logger, "Wrote config:", filename, config_text); + locationsPromise + .then(() => { + renderEngine + .parseAndRender(template, host) + .then((config_text) => { + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); + debug(logger, "Wrote config:", filename, config_text); - // Restore locations array - host.locations = origLocations; + // Restore locations array + host.locations = origLocations; - resolve(true); - }) - .catch((err) => { - debug(logger, `Could not write ${filename}:`, err.message); - reject(new errs.ConfigurationError(err.message)); - }); - }).catch((err) => { - debug(logger, `Could not render locations for ${filename}:`, err.message); - reject(new errs.ConfigurationError(err.message)); - }); + resolve(true); + }) + .catch((err) => { + debug(logger, `Could not write ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); + }); + }) + .catch((err) => { + debug(logger, `Could not render locations for ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); + }); }); }, diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index b148b327a..9a3cf7e66 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -86,28 +86,28 @@ const internalProxyHost = { expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) - .then(async (row) => { - // Configure nginx - return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { - return row; - }); - }) - .then((row) => { - // Audit log - thisData.meta = _.assign({}, thisData.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: "created", - object_type: "proxy-host", - object_id: row.id, - meta: thisData, - }) - .then(() => { + .then(async (row) => { + // Configure nginx + return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { return row; }); - }); + }) + .then((row) => { + // Audit log + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); + + // Add to audit log + return internalAuditLog + .add(access, { + action: "created", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) + .then(() => { + return row; + }); + }); }, /** @@ -207,24 +207,24 @@ const internalProxyHost = { }); }); }) - .then(() => { - return internalProxyHost - .get(access, { - id: thisData.id, - expand: ["owner", "certificate", "access_list.[clients,items]"], - }) - .then(async (row) => { - if (!row.enabled) { - // No need to add nginx config if host is disabled - return row; - } - // Configure nginx - return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { - row.meta = new_meta; - return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); + .then(() => { + return internalProxyHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate", "access_list.[clients,items]"], + }) + .then(async (row) => { + if (!row.enabled) { + // No need to add nginx config if host is disabled + return row; + } + // Configure nginx + return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); + }); }); - }); - }); + }); }, /** @@ -331,36 +331,33 @@ const internalProxyHost = { expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) - .then(async (row) => { - if (!row?.id) { - throw new errs.ItemNotFoundError(data.id); - } - if (row.enabled) { - throw new errs.ValidationError("Host is already enabled"); - } + .then(async (row) => { + if (!row?.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); + } - row.enabled = 1; + row.enabled = 1; - await proxyHostModel - .query() - .where("id", row.id) - .patch({ + await proxyHostModel.query().where("id", row.id).patch({ enabled: 1, }); - // Configure nginx - await internalNginx.configure(proxyHostModel, "proxy_host", row); + // Configure nginx + await internalNginx.configure(proxyHostModel, "proxy_host", row); - // Add to audit log - await internalAuditLog.add(access, { - action: "enabled", - object_type: "proxy-host", - object_id: row.id, - meta: _.omit(row, omissions()), + // Add to audit log + await internalAuditLog.add(access, { + action: "enabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + + return true; }); - - return true; - }); }, /** diff --git a/backend/routes/logs.js b/backend/routes/logs.js index 9088507b3..09841b3bd 100644 --- a/backend/routes/logs.js +++ b/backend/routes/logs.js @@ -62,7 +62,10 @@ router enum: ["system", "letsencrypt", "host"], }, host_type: { - anyOf: [{ type: "null" }, { type: "string", enum: ["proxy", "redirection", "dead", "stream"] }], + anyOf: [ + { type: "null" }, + { type: "string", enum: ["proxy", "redirection", "dead", "stream"] }, + ], }, host_id: { anyOf: [{ type: "null" }, { type: "integer", minimum: 1 }], @@ -76,7 +79,10 @@ router level: { anyOf: [ { type: "null" }, - { type: "string", enum: ["INFO", "WARN", "ERROR", "DEBUG", "SUCCESS", "FATAL", "COMPLETE"] }, + { + type: "string", + enum: ["INFO", "WARN", "ERROR", "DEBUG", "SUCCESS", "FATAL", "COMPLETE"], + }, ], }, search: { From 04c1f5ae7690c3c421c42696840e778370ef0aa6 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 22:53:53 +1000 Subject: [PATCH 74/76] Fix review findings from PR #6029 Security - Redact certbot output before it reaches the API: strip the certificate's DNS credential values, URL query strings and userinfo, secret-looking key=value pairs and Bearer/Basic tokens. DNS plugins can echo request URLs carrying API keys. The raw CommandError is no longer attached as `previous`, since the error handler returns `previous` to the client on certificate routes. - Rate limit login/2FA on the admin nginx's real TCP peer, passed as X-NPM-Peer-Addr ($realip_remote_addr), instead of X-Forwarded-For. nginx trusts X-Real-IP from private ranges, so a client on the LAN or Docker network could get a fresh bucket on every request. Behind another proxy, limits now apply per account through that proxy. - Bound rate limiter memory: hash keys over 128 chars and cap each store at 50k entries, removing expired entries first and then the oldest. Correctness - Proxy host modal: send location advanced configs unchanged for non-admins instead of looking them up by path, which dropped the config from a renamed location. - Location advanced config: locations that omit advanced_config now get a config only after explicit ones are matched, so a new location at an old path can't take the config from the renamed location. - Certificate upload: check that the certificate matches the private key (uploaded or stored) before writing, so a mismatched pair can't break nginx -t. - Certificate delete: keep the Let's Encrypt files when revoke fails and hosts still use the certificate, so their configs still load. Cleanup - Make the rate-limit certbot hint first in the list, as its comment says. - Proxy host getCount uses the permissions access has already loaded instead of querying user_permission again. - Rate limiter: register the success refund listener in one place and call it a fixed window, not a sliding one. --- backend/internal/certificate.js | 135 +++++++++++++++--- backend/internal/host.js | 16 ++- backend/internal/proxy-host.js | 7 +- backend/internal/report.js | 6 +- backend/lib/express/rate-limit.js | 59 +++++--- backend/routes/tokens.js | 2 +- docker/rootfs/etc/nginx/conf.d/dev.conf | 3 + .../etc/nginx/conf.d/production.conf.template | 3 + frontend/src/modals/ProxyHostModal.tsx | 12 +- 9 files changed, 189 insertions(+), 54 deletions(-) diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index b353c4838..56be24e47 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -46,6 +46,11 @@ const certbotNoise = [ // provider, so a generic message (e.g. "Authentication error" from another DNS plugin) isn't // reported as a Cloudflare problem. const certbotHints = [ + // Checked first: a rate limit is unambiguous and the most useful thing to report + [ + /too many (certificates|failed authorizations|new orders)|rateLimited/i, + () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).", + ], [ /expected \S+ to be a symlink/i, () => @@ -56,11 +61,6 @@ const certbotHints = [ () => "This certificate's files in /etc/letsencrypt are damaged or missing. Restore /etc/letsencrypt from a backup, or delete this certificate and create it again.", ], - // Checked first: a rate limit is unambiguous and the most useful thing to report - [ - /too many (certificates|failed authorizations|new orders)|rateLimited/i, - () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).", - ], [ /\b9109\b.*?location: ([0-9a-f.:]+)/i, (m) => { @@ -89,15 +89,54 @@ const certbotHints = [ ]; /** - * Turns certbot's stderr into a message the UI can show: a hint for common causes plus certbot's own reason. - * Certbot never prints DNS provider credentials. + * The values in a DNS credentials file ("key = value" lines), for redacting from certbot output. * - * @param {String} stderr - * @param {String} [dnsProvider] The certificate's DNS provider, if it uses a DNS challenge + * @param {String} [credentials] + * @returns {String[]} + */ +const credentialValues = (credentials) => + `${credentials || ""}` + .split("\n") + .map((line) => line.replace(/^[^=:]*[=:]/, "").trim()) + .filter((value) => value.length >= 4); + +/** + * Removes anything that could be a secret from certbot output before it leaves the server. + * DNS plugins can echo provider errors verbatim, including request URLs that carry API keys in + * the query string (e.g. "401 Client Error ... for url: https://...?token=..."), so this strips + * the given credential values, URL query strings and userinfo, and key=value pairs whose name + * looks secret. + * + * @param {String} text + * @param {String[]} [secrets] * @returns {String} */ -const certbotFailureMessage = (stderr, dnsProvider) => { - const reason = `${stderr || ""}` +const redactCertbotOutput = (text, secrets = []) => { + let out = `${text || ""}`; + for (const secret of [...secrets].sort((a, b) => b.length - a.length)) { + out = out.split(secret).join("[REDACTED]"); + } + return out + .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [REDACTED]") + .replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/\s@]*@/gi, "$1[REDACTED]@") + .replace(/(\b[a-z][a-z0-9+.-]*:\/\/[^\s?#]*)\?[^\s#]*/gi, "$1?[REDACTED]") + .replace( + /\b([\w-]*(?:token|key|secret|password|passwd|pass|auth|credential|signature|sig)[\w-]*)(["']?\s*[=:]\s*["']?)[^\s"'&,;]+/gi, + "$1$2[REDACTED]", + ); +}; + +/** + * Turns certbot's stderr into a message the UI can show: a hint for common causes plus certbot's own reason. + * The output is redacted first (see redactCertbotOutput), since DNS plugins can echo credentials. + * + * @param {String} stderr + * @param {String} [dnsProvider] The certificate's DNS provider, if it uses a DNS challenge + * @param {String[]} [secrets] Values that must never appear in the message + * @returns {String} + */ +const certbotFailureMessage = (stderr, dnsProvider, secrets) => { + const reason = redactCertbotOutput(stderr, secrets) .split("\n") .map((line) => line.trim()) .filter((line) => line && !certbotNoise.some((re) => re.test(line))) @@ -122,14 +161,19 @@ const certbotFailureMessage = (stderr, dnsProvider) => { * @param {Array} args * @param {Object} [opts] * @param {String} [dnsProvider] Passed through so provider-specific hints only show for that provider + * @param {String} [credentials] The DNS credentials file contents, so its values are redacted from errors * @returns {Promise} */ -const runCertbot = async (args, opts, dnsProvider) => { +const runCertbot = async (args, opts, dnsProvider, credentials) => { try { return await utils.execFile(certbotCommand, args, opts); } catch (err) { if (err instanceof error.CommandError) { - throw new error.ValidationError(certbotFailureMessage(err.message, dnsProvider), err); + const secrets = credentialValues(credentials); + logger.error(redactCertbotOutput(err.message, secrets)); + // The raw error is not attached as `previous`: the API error handler returns + // `previous` to the client for certificate routes, and it holds unredacted output. + throw new error.ValidationError(certbotFailureMessage(err.message, dnsProvider, secrets)); } throw err; } @@ -515,12 +559,18 @@ const internalCertificate = { */ delete: async (access, data) => { await access.can("certificates:delete", data.id); - const row = await internalCertificate.get(access, { id: data.id }); + const hostRelations = ["proxy_hosts", "redirection_hosts", "dead_hosts", "streams"]; + const row = await internalCertificate.get(access, { id: data.id, expand: hostRelations }); if (!row?.id) { throw new error.ItemNotFoundError(data.id); } + const inUse = hostRelations.some((rel) => row[rel]?.length); + for (const rel of hostRelations) { + delete row[rel]; + } + await certificateModel.query().where("id", row.id).patch({ is_deleted: 1, }); @@ -537,8 +587,19 @@ const internalCertificate = { if (row.provider === "letsencrypt") { // Revoke the cert - await internalCertificate.revokeLetsEncryptSsl(row); - // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config); remove them anyway. + const revoked = await internalCertificate.revokeLetsEncryptSsl(row, true).then( + () => true, + () => false, + ); + // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config). + // Remove them anyway, unless a host still uses this certificate: its rendered config + // includes these files, and removing them would fail nginx -t on the next reload. + if (!revoked && inUse) { + logger.warn( + `Certificate #${row.id} was deleted but could not be revoked, and hosts still use it; its files in /etc/letsencrypt were kept`, + ); + return true; + } // The row is already deleted and audited, so leftover files are logged, not thrown: // an error here would report a completed delete as failed, and a retry would 404. const leftover = internalCertificate.removeLetsEncryptFiles(row.id); @@ -780,6 +841,9 @@ const internalCertificate = { if (!meta.certificate_key) { throw new error.ValidationError("Certificate Key file was not provided"); } + // The key may be the stored one (a certificate-only upload), so check the pair belongs + // together; a mismatch would make nginx -t fail for every host on the next reload. + await internalCertificate.checkKeyMatchesCertificate(meta.certificate, meta.certificate_key); const certificate = await internalCertificate.update(access, { id: data.id, @@ -794,6 +858,36 @@ const internalCertificate = { return _.pick(meta, uploaded); }, + /** + * Throws unless the private key is the one the certificate was issued for, by comparing the + * public key derived from each. + * + * @param {String} certificate PEM certificate (the leaf comes first) + * @param {String} privateKey PEM private key + * @returns {Promise} + */ + checkKeyMatchesCertificate: async (certificate, privateKey) => { + const certPath = await tempWrite(certificate); + const keyPath = await tempWrite(privateKey); + try { + const [certPub, keyPub] = await Promise.all([ + utils.execFile("openssl", ["x509", "-in", certPath, "-pubkey", "-noout"]), + utils.execFile("openssl", ["pkey", "-in", keyPath, "-pubout"]), + ]); + if (!certPub.trim() || certPub.trim() !== keyPub.trim()) { + throw new error.ValidationError("Certificate does not match the Certificate Key"); + } + } catch (err) { + if (err instanceof error.ValidationError) { + throw err; + } + throw new error.ValidationError("Could not check that the Certificate matches the Certificate Key"); + } finally { + fs.rmSync(certPath, { force: true }); + fs.rmSync(keyPath, { force: true }); + } + }, + /** * Uses the openssl command to validate the private key. * It will save the file to disk first, then run commands on it, then delete the file. @@ -1050,7 +1144,12 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider); + const result = await runCertbot( + args, + adds.opts, + certificate.meta.dns_provider, + certificate.meta.dns_provider_credentials, + ); logger.info(result); return result; } finally { @@ -1251,7 +1350,7 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider); + const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider, credentials); logger.info(result); return result; } finally { diff --git a/backend/internal/host.js b/backend/internal/host.js index 8df7cbe53..76329b43c 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -49,7 +49,8 @@ const internalHost = { * host. Matching is by value, not path, so renaming or reordering a location that carries * admin-written config is allowed, but new or edited config is not. Each existing config can * be used once (no duplicating it onto other paths), and same-path matches are taken first. - * A location that omits advanced_config keeps the one at its path. Mutates `locations`. + * A location that omits advanced_config keeps the one at its path, unless another location + * explicitly carries it. Mutates `locations`. * * @param {Array} locations incoming locations * @param {Array} [existingLocations] the host's current locations @@ -71,12 +72,13 @@ const internalHost = { throw new errs.PermissionError("You do not have permission to modify advanced configuration"); }; - // Pass 1: locations that keep (or omit) the config at their own path + // Pass 1: locations that keep the config at their own path const unresolved = []; + const omitted = []; for (const loc of locations) { const atPath = advAtPath(loc.path); if (typeof loc.advanced_config === "undefined") { - loc.advanced_config = atPath && take(atPath) ? atPath : ""; + omitted.push(loc); } else if ((loc.advanced_config || "") === atPath && (!atPath || take(atPath))) { // unchanged } else { @@ -91,6 +93,14 @@ const internalHost = { deny(); } } + + // Pass 3: locations that omit advanced_config keep the one at their path, if no location + // that names it explicitly has claimed it. Running this last stops a new location at an + // old path taking the config away from the location it was renamed to. + for (const loc of omitted) { + const atPath = advAtPath(loc.path); + loc.advanced_config = atPath && take(atPath) ? atPath : ""; + } }, /** diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 9a3cf7e66..5065e082e 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -3,7 +3,6 @@ import errs from "../lib/error.js"; import { castJsonIfNeed } from "../lib/helpers.js"; import utils from "../lib/utils.js"; import proxyHostModel, { scopeToOwnerOrAllowed } from "../models/proxy_host.js"; -import userPermissionModel from "../models/user_permission.js"; import internalAuditLog from "./audit-log.js"; import internalCertificate from "./certificate.js"; import internalHost from "./host.js"; @@ -455,14 +454,14 @@ const internalProxyHost = { * * @param {Number} user_id * @param {String} visibility + * @param {Object} [permissionsMeta] the user's permissions meta, as already loaded by access * @returns {Promise} */ - getCount: async (user_id, visibility) => { + getCount: async (user_id, visibility, permissionsMeta) => { const query = proxyHostModel.query().count("id as count").where("is_deleted", 0); if (visibility !== "all") { - const perm = await userPermissionModel.query().where("user_id", user_id).first(); - scopeToOwnerOrAllowed(query, user_id, perm?.meta); + scopeToOwnerOrAllowed(query, user_id, permissionsMeta); } const row = await query.first(); diff --git a/backend/internal/report.js b/backend/internal/report.js index 59f13fe69..547af3ef3 100644 --- a/backend/internal/report.js +++ b/backend/internal/report.js @@ -15,7 +15,11 @@ const internalReport = { const userId = access.token.getUserId(1); const promises = [ - internalProxyHost.getCount(userId, access_data.permission_visibility), + internalProxyHost.getCount( + userId, + access_data.permission_visibility, + access.getPermissions?.()?.meta, + ), internalRedirectionHost.getCount(userId, access_data.permission_visibility), internalStream.getCount(userId, access_data.permission_visibility), internalDeadHost.getCount(userId, access_data.permission_visibility), diff --git a/backend/lib/express/rate-limit.js b/backend/lib/express/rate-limit.js index a8754a826..512519df9 100644 --- a/backend/lib/express/rate-limit.js +++ b/backend/lib/express/rate-limit.js @@ -1,9 +1,17 @@ +import crypto from "node:crypto"; import { isCI } from "../config.js"; /** - * In-memory sliding window rate limiter for authentication endpoints + * In-memory fixed window rate limiter for authentication endpoints */ +// Keys longer than this are hashed, so a client can't make the store hold arbitrarily large +// strings (keys can include request body values such as the login identity). +const MAX_KEY_LENGTH = 128; +// Entries per limiter. A flood of unique keys sweeps expired entries, then evicts the oldest, +// instead of growing the store until the periodic cleanup runs. +const MAX_STORE_SIZE = 50000; + // One store per limiter, so separate endpoints never share buckets const stores = new Set(); @@ -21,9 +29,12 @@ const cleanupTimer = setInterval(() => { cleanupTimer.unref(); /** - * The client IP as seen by the backend. X-Forwarded-For is only honoured when the socket is - * loopback, which is the admin nginx in this container; it sets the header to its own - * $remote_addr after realip processing, so the first entry is the resolved client. + * The address to rate limit on: the TCP peer of the admin nginx, which it passes as + * X-NPM-Peer-Addr ($realip_remote_addr), only honoured when the socket is loopback (that nginx, + * in this container). The realip-resolved client address is deliberately not used: nginx trusts + * X-Real-IP from private ranges, so a client on the LAN or Docker network could pick a new + * address, and a fresh bucket, on every request. Behind another proxy this resolves to that + * proxy's address, which is why limiters also key on the target account. * * @param {Object} req * @returns {String} @@ -31,10 +42,28 @@ cleanupTimer.unref(); export const getClientIp = (req) => { const socketAddr = req.socket?.remoteAddress || "unknown"; const isTrustedLocalProxy = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; - const forwardedFor = isTrustedLocalProxy ? req.headers["x-forwarded-for"]?.split(",")[0].trim() : null; - return forwardedFor || socketAddr; + const peerAddr = isTrustedLocalProxy ? req.headers["x-npm-peer-addr"]?.trim() : null; + return peerAddr || socketAddr; }; +const makeRoom = (store, now) => { + for (const [k, r] of store.entries()) { + if (now - r.startTime > r.windowMs) { + store.delete(k); + } + } + // Maps iterate in insertion order, so this drops the oldest windows first + for (const k of store.keys()) { + if (store.size < MAX_STORE_SIZE) { + break; + } + store.delete(k); + } +}; + +const boundedKey = (key) => + key.length > MAX_KEY_LENGTH ? `sha256:${crypto.createHash("sha256").update(key).digest("hex")}` : key; + /** * @param {Object} [options] * @param {Number} [options.windowMs] @@ -67,29 +96,21 @@ export default function createRateLimiter(options = {}) { return next(); } - const key = keyGenerator(req, getClientIp(req)); + const key = boundedKey(String(keyGenerator(req, getClientIp(req)))); const now = Date.now(); let record = store.get(key); if (!record || now - record.startTime > windowMs) { record = { - count: 1, + count: 0, startTime: now, windowMs, }; - store.set(key, record); - - if (skipSuccessfulRequests) { - const currentRecord = record; - res.on("finish", () => { - if (res.statusCode < 400 && currentRecord.count > 0) { - currentRecord.count--; - } - }); + if (!store.has(key) && store.size >= MAX_STORE_SIZE) { + makeRoom(store, now); } - - return next(); + store.set(key, record); } record.count++; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 012bb95ca..7879cb9c1 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -9,7 +9,7 @@ import { getValidationSchema } from "../schema/index.js"; // Keyed on IP + target account rather than IP alone: when the admin UI sits behind another // proxy every client can resolve to the same IP, and an IP-only bucket would let anyone lock // out every user with a handful of bad logins. Brute force against one account is still -// capped per IP. +// capped per connecting address (see getClientIp). const tokenRateLimiter = createRateLimiter({ windowMs: 60 * 1000, max: 10, diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index 67efc0f8a..c54f2a46e 100644 --- a/docker/rootfs/etc/nginx/conf.d/dev.conf +++ b/docker/rootfs/etc/nginx/conf.d/dev.conf @@ -17,6 +17,9 @@ server { proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; + # The TCP peer before realip processing; the backend rate limits on this because + # X-Real-IP can be set by any client in the trusted private ranges + proxy_set_header X-NPM-Peer-Addr $realip_remote_addr; proxy_pass http://127.0.0.1:3000/; proxy_read_timeout 15m; diff --git a/docker/rootfs/etc/nginx/conf.d/production.conf.template b/docker/rootfs/etc/nginx/conf.d/production.conf.template index 452609bcb..83293ebd3 100644 --- a/docker/rootfs/etc/nginx/conf.d/production.conf.template +++ b/docker/rootfs/etc/nginx/conf.d/production.conf.template @@ -17,6 +17,9 @@ server { proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; + # The TCP peer before realip processing; the backend rate limits on this because + # X-Real-IP can be set by any client in the trusted private ranges + proxy_set_header X-NPM-Peer-Addr $realip_remote_addr; proxy_pass http://127.0.0.1:3000/; proxy_read_timeout 15m; diff --git a/frontend/src/modals/ProxyHostModal.tsx b/frontend/src/modals/ProxyHostModal.tsx index d137a459a..38cdb75be 100644 --- a/frontend/src/modals/ProxyHostModal.tsx +++ b/frontend/src/modals/ProxyHostModal.tsx @@ -53,14 +53,10 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => { if (!isAdmin) { delete payload.advanced_config; delete payload.advancedConfig; - const existingLocations = data?.locations || []; - payload.locations = payload.locations.map((loc: any) => { - const existingLoc = existingLocations.find((l: any) => l.path === loc.path); - return { - ...loc, - advancedConfig: existingLoc?.advancedConfig || "", - }; - }); + // Location advanced configs are sent as-is. Non-admins can't edit them (the editor is + // hidden), so each one is still the admin-written value and moves with its location + // when the path is renamed; the backend matches them by value. Looking them up by + // path here would silently drop the config from a renamed location. } setProxyHost(payload, { From 5b7e34eb67d35aa91dd3fe3818525caa42cfeca5 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 9 Nov 2026 23:39:26 +1000 Subject: [PATCH 75/76] Fix second round of review findings from PR #6029 Security - Check advanced_config permissions before creating a certificate on host update (proxy, redirection and 404 hosts). A refused request previously requested a Let's Encrypt certificate first, leaving an orphan certificate and using up rate limit. - Only trust X-NPM-Peer-Addr when it comes with a secret generated at container start. The admin nginx sends both headers. Any proxy host or stream forwarding to 127.0.0.1:3000 was also a loopback peer, so it could forge the header and get a fresh rate-limit bucket on every request. Without the secret (dev, or a mounted production.conf), the header is ignored. - Add a login limit per IP across all accounts (30 failed attempts a minute) alongside the per-account one. Before this, one address could spray passwords over any number of accounts. Behind a shared proxy, everyone using that proxy shares this limit. - Stop trusting X-Real-IP from loopback. It was only there for an older version of the rate limiter, and it let a stream host forwarding to a local proxy host forge the client IP and get past IP access lists. Loopback now behaves as it does on master. Correctness - Refuse to delete a certificate that hosts still use (400, with counts per host type). Their configs include its files, and a successful revoke deleted them anyway, which broke nginx -t. Otherwise the certificate is revoked and its files are removed even if the revoke fails. - Let non-admins clear a location's advanced config. Deleting the location was already allowed and has the same effect, and a location deleted and re-added at the same path arrives with an empty config, which was refused. - Clear proxy host grants (meta.proxy_host_ids) when visibility isn't "user" or proxy hosts are hidden, and drop ids of deleted hosts. Before, hidden grants came back when proxy hosts were shown again. The backend enforces this; the permissions modal also stops sending them. - 404 host and redirection host modals now wait for the current user to load, so an admin's advanced config change isn't dropped if they submit before it loads. Tests - NonAdminHosts.cy.js: refused change doesn't create a certificate, location rename and clear rules, grant clearing. - Certificates.cy.js: deleting a certificate that is in use. --- backend/internal/certificate.js | 37 ++-- backend/internal/dead-host.js | 5 +- backend/internal/host.js | 16 +- backend/internal/proxy-host.js | 5 +- backend/internal/redirection-host.js | 5 +- backend/internal/user.js | 34 +++- backend/lib/express/rate-limit.js | 30 ++- backend/routes/tokens.js | 19 +- .../nginx/certificates/certID/delete.json | 20 ++ docker/rootfs/etc/nginx/conf.d/dev.conf | 4 +- .../etc/nginx/conf.d/production.conf.template | 4 +- docker/rootfs/etc/nginx/nginx.conf | 4 - .../s6-rc.d/prepare/45-admin-port.sh | 13 +- frontend/src/modals/DeadHostModal.tsx | 12 +- frontend/src/modals/PermissionsModal.tsx | 8 +- frontend/src/modals/RedirectionHostModal.tsx | 12 +- test/cypress/e2e/api/Certificates.cy.js | 73 +++++++ test/cypress/e2e/api/NonAdminHosts.cy.js | 184 ++++++++++++++++++ 18 files changed, 423 insertions(+), 62 deletions(-) create mode 100644 test/cypress/e2e/api/NonAdminHosts.cy.js diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 56be24e47..0c3166a60 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -566,7 +566,24 @@ const internalCertificate = { throw new error.ItemNotFoundError(data.id); } - const inUse = hostRelations.some((rel) => row[rel]?.length); + // A certificate that hosts still use can't be deleted: their rendered configs include its + // files, and revoking it would leave them serving a revoked certificate. Hosts may belong + // to other users, so only counts are reported. + const labels = { + proxy_hosts: "proxy host", + redirection_hosts: "redirection host", + dead_hosts: "404 host", + streams: "stream", + }; + const usage = hostRelations + .map((rel) => ({ rel, count: row[rel]?.length || 0 })) + .filter((u) => u.count > 0) + .map((u) => `${u.count} ${labels[u.rel]}${u.count === 1 ? "" : "s"}`); + if (usage.length) { + throw new error.ValidationError( + `Certificate is in use by ${usage.join(", ")}. Remove it from them before deleting it.`, + ); + } for (const rel of hostRelations) { delete row[rel]; } @@ -586,20 +603,10 @@ const internalCertificate = { }); if (row.provider === "letsencrypt") { - // Revoke the cert - const revoked = await internalCertificate.revokeLetsEncryptSsl(row, true).then( - () => true, - () => false, - ); - // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config). - // Remove them anyway, unless a host still uses this certificate: its rendered config - // includes these files, and removing them would fail nginx -t on the next reload. - if (!revoked && inUse) { - logger.warn( - `Certificate #${row.id} was deleted but could not be revoked, and hosts still use it; its files in /etc/letsencrypt were kept`, - ); - return true; - } + // Revoke the cert. Errors are logged by revokeLetsEncryptSsl. + await internalCertificate.revokeLetsEncryptSsl(row); + // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config), + // so remove them either way. No host uses them; that was checked above. // The row is already deleted and audited, so leftover files are logged, not thrown: // an error here would report a completed delete as failed, and a retry would 404. const leftover = internalCertificate.removeLetsEncryptFiles(row.id); diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 41291ed79..5e249de59 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -132,6 +132,9 @@ const internalDeadHost = { ); } + // Before the certificate is created, so a refused request doesn't leave one behind + internalHost.assertAdvancedConfigAllowed(access, data, row); + if (createCertificate) { const cert = await internalCertificate.createQuickCertificate(access, { domain_names: data.domain_names || row.domain_names, @@ -153,8 +156,6 @@ const internalDeadHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - internalHost.assertAdvancedConfigAllowed(access, data, row); - // do the row update await deadHostModel.query().where({ id: data.id }).patch(data); diff --git a/backend/internal/host.js b/backend/internal/host.js index 76329b43c..7bdaa7837 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -45,10 +45,12 @@ const internalHost = { }, /** - * For non-admins: every location's advanced_config must be one that already exists on this - * host. Matching is by value, not path, so renaming or reordering a location that carries - * admin-written config is allowed, but new or edited config is not. Each existing config can - * be used once (no duplicating it onto other paths), and same-path matches are taken first. + * For non-admins: every location's advanced_config must be empty or one that already exists + * on this host. Matching is by value, not path, so renaming or reordering a location that + * carries admin-written config is allowed, but new or edited config is not. Each existing + * config can be used once (no duplicating it onto other paths), and same-path matches are + * taken first. Clearing a config is allowed: deleting the location is, and has the same + * effect, and a location deleted and re-added at the same path arrives with an empty config. * A location that omits advanced_config keeps the one at its path, unless another location * explicitly carries it. Mutates `locations`. * @@ -86,10 +88,10 @@ const internalHost = { } } - // Pass 2: renamed/moved locations may carry an existing config not already claimed. - // Clearing a config is not allowed, as before. + // Pass 2: renamed/moved locations may carry an existing config not already claimed, or + // none at all for (const loc of unresolved) { - if (!loc.advanced_config || !take(loc.advanced_config)) { + if (loc.advanced_config && !take(loc.advanced_config)) { deny(); } } diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 5065e082e..4682a94fa 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -157,6 +157,9 @@ const internalProxyHost = { ); } + // Before the certificate is created, so a refused request doesn't leave one behind + internalHost.assertAdvancedConfigAllowed(access, thisData, row); + if (createCertificate) { return internalCertificate .createQuickCertificate(access, { @@ -185,8 +188,6 @@ const internalProxyHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - internalHost.assertAdvancedConfigAllowed(access, thisData, row); - return proxyHostModel .query() .where({ id: thisData.id }) diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index f7ad6be33..f5945a6b8 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -157,6 +157,9 @@ const internalRedirectionHost = { ); } + // Before the certificate is created, so a refused request doesn't leave one behind + internalHost.assertAdvancedConfigAllowed(access, thisData, row); + if (createCertificate) { return internalCertificate .createQuickCertificate(access, { @@ -185,8 +188,6 @@ const internalRedirectionHost = { thisData = internalHost.cleanSslHstsData(thisData, row); - internalHost.assertAdvancedConfigAllowed(access, thisData, row); - return redirectionHostModel .query() .where({ id: thisData.id }) diff --git a/backend/internal/user.js b/backend/internal/user.js index e27ac2111..0c0313b44 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -3,6 +3,7 @@ import _ from "lodash"; import errs from "../lib/error.js"; import utils from "../lib/utils.js"; import authModel from "../models/auth.js"; +import proxyHostModel from "../models/proxy_host.js"; import userModel from "../models/user.js"; import userPermissionModel from "../models/user_permission.js"; import internalAuditLog from "./audit-log.js"; @@ -460,7 +461,8 @@ const internalUser = { .query() .where("user_id", user.id) .first() - .then((existing_auth) => { + .then(async (existing_auth) => { + permData.meta = await internalUser.normalisePermissionMeta(existing_auth, permData); if (existing_auth) { // patch return userPermissionModel @@ -489,6 +491,36 @@ const internalUser = { }); }, + /** + * The permission meta to store. Proxy host grants (meta.proxy_host_ids) only apply while + * visibility is "user" and proxy hosts are not hidden, so they're cleared otherwise; kept, + * they would silently come back when proxy hosts are shown again. Ids of hosts that no + * longer exist are dropped. `permData` may be partial, so the stored values fill the gaps. + * + * @param {Object} [existing] the stored user_permission row + * @param {Object} permData incoming permission fields + * @returns {Promise} + */ + normalisePermissionMeta: async (existing, permData) => { + const effective = _.assign( + { visibility: "user", proxy_hosts: "hidden" }, + existing || {}, + _.omit(permData, ["meta"]), + ); + const meta = _.assign({}, existing?.meta || {}, permData.meta || {}); + const requested = Array.isArray(meta.proxy_host_ids) ? _.uniq(meta.proxy_host_ids) : []; + + if (effective.visibility !== "user" || effective.proxy_hosts === "hidden" || !requested.length) { + meta.proxy_host_ids = []; + return meta; + } + + const rows = await proxyHostModel.query().select("id").where("is_deleted", 0).whereIn("id", requested); + const found = new Set(rows.map((r) => r.id)); + meta.proxy_host_ids = requested.filter((id) => found.has(id)); + return meta; + }, + /** * @param {Access} access * @param {Object} data diff --git a/backend/lib/express/rate-limit.js b/backend/lib/express/rate-limit.js index 512519df9..7512f7485 100644 --- a/backend/lib/express/rate-limit.js +++ b/backend/lib/express/rate-limit.js @@ -28,21 +28,37 @@ const cleanupTimer = setInterval(() => { }, 120000); cleanupTimer.unref(); +// Generated at container start (prepare/45-admin-port.sh) and sent by the admin nginx as +// X-NPM-Peer-Secret alongside X-NPM-Peer-Addr. Unset (development, or a mounted production.conf) +// means the header is never trusted. +const peerSecret = Buffer.from(process.env.NPM_PEER_SECRET || ""); + +const hasPeerSecret = (value) => { + if (!peerSecret.length || typeof value !== "string") { + return false; + } + const given = Buffer.from(value); + return given.length === peerSecret.length && crypto.timingSafeEqual(given, peerSecret); +}; + /** * The address to rate limit on: the TCP peer of the admin nginx, which it passes as - * X-NPM-Peer-Addr ($realip_remote_addr), only honoured when the socket is loopback (that nginx, - * in this container). The realip-resolved client address is deliberately not used: nginx trusts - * X-Real-IP from private ranges, so a client on the LAN or Docker network could pick a new - * address, and a fresh bucket, on every request. Behind another proxy this resolves to that - * proxy's address, which is why limiters also key on the target account. + * X-NPM-Peer-Addr ($realip_remote_addr). That header is only honoured from a loopback socket + * carrying the startup secret, so it can only have come from the admin nginx: a proxy host or + * stream forwarding to the backend port is also a loopback peer, but can't know the secret. + * The realip-resolved client address is deliberately not used: nginx trusts X-Real-IP from + * private ranges, so a client on the LAN or Docker network could pick a new address, and a fresh + * bucket, on every request. Behind another proxy (including an NPM proxy host in front of the + * admin UI) this resolves to that proxy's address. * * @param {Object} req * @returns {String} */ export const getClientIp = (req) => { const socketAddr = req.socket?.remoteAddress || "unknown"; - const isTrustedLocalProxy = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; - const peerAddr = isTrustedLocalProxy ? req.headers["x-npm-peer-addr"]?.trim() : null; + const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1"; + const trusted = isLoopback && hasPeerSecret(req.headers["x-npm-peer-secret"]); + const peerAddr = trusted ? req.headers["x-npm-peer-addr"]?.trim() : null; return peerAddr || socketAddr; }; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 7879cb9c1..f56daa058 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -6,10 +6,9 @@ import apiValidator from "../lib/validator/api.js"; import { debug, express as logger } from "../logger.js"; import { getValidationSchema } from "../schema/index.js"; -// Keyed on IP + target account rather than IP alone: when the admin UI sits behind another -// proxy every client can resolve to the same IP, and an IP-only bucket would let anyone lock -// out every user with a handful of bad logins. Brute force against one account is still -// capped per connecting address (see getClientIp). +// Two limits apply to logins. Per IP + target account: when the admin UI sits behind another +// proxy every client can resolve to the same IP, and an IP-only bucket at this size would let +// anyone lock out every user with a handful of bad logins. const tokenRateLimiter = createRateLimiter({ windowMs: 60 * 1000, max: 10, @@ -21,6 +20,16 @@ const tokenRateLimiter = createRateLimiter({ }, }); +// And per IP across all accounts, so one address can't spray passwords over many accounts at +// 10 a minute each. Only failures count. Behind a shared proxy this cap is shared by everyone +// using it, which is why it's higher than the per-account one. +const tokenIpRateLimiter = createRateLimiter({ + windowMs: 60 * 1000, + max: 30, + message: "Too many login attempts. Please try again in 1 minute.", + skipSuccessfulRequests: true, +}); + // Keyed on the user the challenge belongs to, not the challenge token itself, so fetching // fresh challenge tokens doesn't reset the code-guessing budget. The payload is read without // verifying the signature; verify2FA does that. A forged token only affects buckets for the @@ -79,7 +88,7 @@ router * * Create a new Token */ - .post(tokenRateLimiter, async (req, res, next) => { + .post(tokenIpRateLimiter, tokenRateLimiter, async (req, res, next) => { try { const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body); const result = await internalToken.getTokenFromEmail(data); diff --git a/backend/schema/paths/nginx/certificates/certID/delete.json b/backend/schema/paths/nginx/certificates/certID/delete.json index a99f619d6..2c38c388c 100644 --- a/backend/schema/paths/nginx/certificates/certID/delete.json +++ b/backend/schema/paths/nginx/certificates/certID/delete.json @@ -35,6 +35,26 @@ } } } + }, + "400": { + "description": "400 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "error": { + "code": 400, + "message": "Certificate is in use by 1 proxy host. Remove it from them before deleting it." + } + } + } + }, + "schema": { + "$ref": "../../../../components/error.json" + } + } + } } } } diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index c54f2a46e..57008f9a0 100644 --- a/docker/rootfs/etc/nginx/conf.d/dev.conf +++ b/docker/rootfs/etc/nginx/conf.d/dev.conf @@ -17,8 +17,8 @@ server { proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; - # The TCP peer before realip processing; the backend rate limits on this because - # X-Real-IP can be set by any client in the trusted private ranges + # The TCP peer before realip processing. In development there is no peer secret + # (see production.conf.template), so the backend ignores this and limits on the socket proxy_set_header X-NPM-Peer-Addr $realip_remote_addr; proxy_pass http://127.0.0.1:3000/; diff --git a/docker/rootfs/etc/nginx/conf.d/production.conf.template b/docker/rootfs/etc/nginx/conf.d/production.conf.template index 83293ebd3..be7aca196 100644 --- a/docker/rootfs/etc/nginx/conf.d/production.conf.template +++ b/docker/rootfs/etc/nginx/conf.d/production.conf.template @@ -18,8 +18,10 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; # The TCP peer before realip processing; the backend rate limits on this because - # X-Real-IP can be set by any client in the trusted private ranges + # X-Real-IP can be set by any client in the trusted private ranges. The secret, generated + # at startup, is how the backend knows this header came from here. proxy_set_header X-NPM-Peer-Addr $realip_remote_addr; + proxy_set_header X-NPM-Peer-Secret "{{NPM_PEER_SECRET}}"; proxy_pass http://127.0.0.1:3000/; proxy_read_timeout 15m; diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 608c1c1d8..1b25cb5fc 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -83,10 +83,6 @@ http { # Real IP Determination - # Loopback: lets an NPM proxy host forwarding to 127.0.0.1 (e.g. the admin UI on :81) - # pass the real client IP through instead of every request appearing as 127.0.0.1 - set_real_ip_from 127.0.0.1; - set_real_ip_from ::1; # Local subnets: set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; # Includes Docker subnet diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh index 3807c7d76..32252c727 100644 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh @@ -16,7 +16,16 @@ if ! [[ "$NPM_ADMIN_PORT" =~ ^[0-9]+$ ]]; then fi PRODFILE="/etc/nginx/conf.d/production.conf" -SED_REGEX="s/\{\{NPM_ADMIN_PORT\}\}/${NPM_ADMIN_PORT}/g" + +# A new secret on every start. The admin nginx sends it with X-NPM-Peer-Addr, and the backend +# only trusts that header when the secret matches, so a proxy host or stream forwarding to the +# backend port can't pass a forged one through. +NPM_PEER_SECRET="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" +if [ "${#NPM_PEER_SECRET}" -ne 64 ]; then + log_fatal "Failed to generate the backend peer secret" +fi + +SED_REGEX="s/\{\{NPM_ADMIN_PORT\}\}/${NPM_ADMIN_PORT}/g;s/\{\{NPM_PEER_SECRET\}\}/${NPM_PEER_SECRET}/g" if is_mounted "$PRODFILE"; then echo "WARNING: skipping ${PRODFILE} — mounted file" >&2 @@ -24,6 +33,8 @@ elif [ -f "$PRODFILE.template" ]; then if sed -E "$SED_REGEX" "$PRODFILE.template" > "$PRODFILE" && [ -s "$PRODFILE" ]; then # success log_info "Generated ${PRODFILE} from template" + # Hand the secret to the backend; with-contenv loads this directory into its environment + printf '%s' "$NPM_PEER_SECRET" > /var/run/s6/container_environment/NPM_PEER_SECRET else log_fatal "Failed to generate ${PRODFILE} from template" fi diff --git a/frontend/src/modals/DeadHostModal.tsx b/frontend/src/modals/DeadHostModal.tsx index ed375952e..3c432e976 100644 --- a/frontend/src/modals/DeadHostModal.tsx +++ b/frontend/src/modals/DeadHostModal.tsx @@ -25,7 +25,7 @@ interface Props extends InnerModalProps { id: number | "new"; } const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => { - const { data: currentUser } = useUser("me"); + const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me"); const { data, isLoading, error } = useDeadHost(id); const { mutate: setDeadHost } = useSetDeadHost(); const [errorMsg, setErrorMsg] = useState(null); @@ -63,17 +63,17 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => { return ( <> - {(isLoading || (error && !data)) && ( + {(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && ( - {!isLoading && error && ( + {!isLoading && (error || userError) && ( - {error?.message || "Unknown error"} + {error?.message || userError?.message || "Unknown error"} )} - {isLoading && } + {(isLoading || userIsLoading) && } )} - {!isLoading && data && ( + {!isLoading && !userIsLoading && data && currentUser && ( { setIsSubmitting(true); setErrorMsg(null); try { - await setPermissions(id, values); + // Proxy host grants only apply to "user" visibility with proxy hosts shown. Don't + // submit stale ones; the backend clears them too. + const grantsApply = values.visibility === "user" && values.proxyHosts !== "hidden"; + await setPermissions(id, { + ...values, + meta: { ...values.meta, proxy_host_ids: grantsApply ? values.meta?.proxy_host_ids || [] : [] }, + }); remove(); queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["user"] }); diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx index 78134c454..d83a81bcf 100644 --- a/frontend/src/modals/RedirectionHostModal.tsx +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -27,7 +27,7 @@ interface Props extends InnerModalProps { id: number | "new"; } const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) => { - const { data: currentUser } = useUser("me"); + const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me"); const { data, isLoading, error } = useRedirectionHost(id); const { mutate: setRedirectionHost } = useSetRedirectionHost(); const [errorMsg, setErrorMsg] = useState(null); @@ -65,17 +65,17 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = return ( <> - {(isLoading || (error && !data)) && ( + {(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && ( - {!isLoading && error && ( + {!isLoading && (error || userError) && ( - {error?.message || "Unknown error"} + {error?.message || userError?.message || "Unknown error"} )} - {isLoading && } + {(isLoading || userIsLoading) && } )} - {!isLoading && data && ( + {!isLoading && !userIsLoading && data && currentUser && ( { }); }); + it('Certificate in use by a host cannot be deleted', () => { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/certificates', + data: { + provider: 'other', + nice_name: 'In Use Certificate', + }, + }).then((cert) => { + cy.task('backendApiPostFiles', { + token: token, + path: `/api/nginx/certificates/${cert.id}/upload`, + files: { + certificate: certFile, + certificate_key: keyFile, + }, + }).then(() => { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/proxy-hosts', + data: { + domain_names: ['cert-in-use.example.com'], + forward_scheme: 'http', + forward_host: '1.1.1.1', + forward_port: 80, + access_list_id: '0', + certificate_id: cert.id, + meta: { dns_challenge: false }, + advanced_config: '', + locations: [], + block_exploits: false, + caching_enabled: false, + allow_websocket_upgrade: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false, + }, + }).then((host) => { + cy.task('backendApiDelete', { + token: token, + path: `/api/nginx/certificates/${cert.id}`, + returnOnError: true, + }).then((data) => { + cy.validateSwaggerSchema('delete', 400, '/nginx/certificates/{certID}', data); + expect(data.error.code).to.equal(400); + expect(data.error.message).to.contain('in use by 1 proxy host'); + }); + + // Still there + cy.task('backendApiGet', { + token: token, + path: `/api/nginx/certificates/${cert.id}`, + }).then((data) => { + expect(data.id).to.equal(cert.id); + }); + + // Once the host no longer uses it, it can be deleted + cy.task('backendApiDelete', { + token: token, + path: `/api/nginx/proxy-hosts/${host.id}`, + }); + cy.task('backendApiDelete', { + token: token, + path: `/api/nginx/certificates/${cert.id}`, + }).then((data) => { + expect(data).to.be.equal(true); + }); + }); + }); + }); + }); + it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => { cy.task('backendApiPost', { token: token, diff --git a/test/cypress/e2e/api/NonAdminHosts.cy.js b/test/cypress/e2e/api/NonAdminHosts.cy.js new file mode 100644 index 000000000..c05eca842 --- /dev/null +++ b/test/cypress/e2e/api/NonAdminHosts.cy.js @@ -0,0 +1,184 @@ +/// + +describe('Non-admin host and permission rules', () => { + const ADMIN_CONFIG = 'add_header X-Admin-Only "1";'; + const nonAdmin = { + email: 'nonadmin-hosts@example.com', + secret: 'changeme', + }; + + let token; + let userId; + let userToken; + let hostId; + + const hostPayload = (overrides) => Object.assign({ + domain_names: ['nonadmin-hosts.example.com'], + forward_scheme: 'http', + forward_host: '1.1.1.1', + forward_port: 80, + access_list_id: '0', + certificate_id: 0, + meta: { dns_challenge: false }, + advanced_config: '', + locations: [], + block_exploits: false, + caching_enabled: false, + allow_websocket_upgrade: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false, + }, overrides || {}); + + const location = (path, advancedConfig) => ({ + path: path, + forward_scheme: 'http', + forward_host: '1.1.1.1', + forward_port: 8080, + advanced_config: advancedConfig, + }); + + const getPermissions = () => cy.task('backendApiGet', { + token: token, + path: `/api/users/${userId}?expand=permissions`, + }).then((user) => user.permissions); + + before(() => { + cy.resetUsers(); + cy.getToken().then((tok) => { + token = tok; + + // Admin-owned host with an admin-written location config + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/proxy-hosts', + data: hostPayload({ locations: [location('/api', ADMIN_CONFIG)] }), + }).then((host) => { + hostId = host.id; + + cy.task('backendApiPost', { + token: token, + path: '/api/users', + data: { + name: 'Non Admin', + nickname: 'NonAdmin', + email: nonAdmin.email, + roles: [], + auth: { type: 'password', secret: nonAdmin.secret }, + }, + }).then((user) => { + userId = user.id; + cy.task('backendApiPut', { + token: token, + path: `/api/users/${userId}/permissions`, + data: { + visibility: 'user', + proxy_hosts: 'manage', + certificates: 'manage', + meta: { proxy_host_ids: [hostId] }, + }, + }); + cy.getToken(null, { identity: nonAdmin.email, secret: nonAdmin.secret }).then((tok2) => { + userToken = tok2; + }); + }); + }); + }); + }); + + it('Refused advanced config change does not request a certificate first', () => { + cy.task('backendApiGet', { token: token, path: '/api/nginx/certificates' }).then((before) => { + cy.task('backendApiPut', { + token: userToken, + path: `/api/nginx/proxy-hosts/${hostId}`, + data: { certificate_id: 'new', advanced_config: 'return 418;' }, + returnOnError: true, + }).then((data) => { + expect(data).to.have.property('error'); + expect(data.error.code).to.equal(403); + cy.task('backendApiGet', { token: token, path: '/api/nginx/certificates' }).then((after) => { + expect(after.length).to.equal(before.length); + }); + }); + }); + }); + + it('Non-admin cannot add new location advanced config', () => { + cy.task('backendApiPut', { + token: userToken, + path: `/api/nginx/proxy-hosts/${hostId}`, + data: { locations: [location('/api', ADMIN_CONFIG), location('/new', 'return 418;')] }, + returnOnError: true, + }).then((data) => { + expect(data).to.have.property('error'); + expect(data.error.code).to.equal(403); + }); + }); + + it('Non-admin can rename a location and keep its admin config', () => { + cy.task('backendApiPut', { + token: userToken, + path: `/api/nginx/proxy-hosts/${hostId}`, + data: { locations: [location('/v2', ADMIN_CONFIG), location('/api', '')] }, + }).then((data) => { + const byPath = Object.fromEntries(data.locations.map((l) => [l.path, l.advanced_config])); + expect(byPath['/v2']).to.equal(ADMIN_CONFIG); + expect(byPath['/api']).to.equal(''); + }); + }); + + it('Non-admin can delete and re-add a location at the same path', () => { + // The location form sends advanced_config "" for a new row + cy.task('backendApiPut', { + token: userToken, + path: `/api/nginx/proxy-hosts/${hostId}`, + data: { locations: [location('/v2', '')] }, + }).then((data) => { + expect(data.locations).to.have.length(1); + expect(data.locations[0].advanced_config).to.equal(''); + }); + }); + + it('Proxy host grants are cleared when proxy hosts are hidden', () => { + // Unknown ids are dropped + cy.task('backendApiPut', { + token: token, + path: `/api/users/${userId}/permissions`, + data: { proxy_hosts: 'manage', meta: { proxy_host_ids: [hostId, 999999] } }, + }); + getPermissions().then((perms) => { + expect(perms.meta.proxy_host_ids).to.deep.equal([hostId]); + }); + + cy.task('backendApiPut', { + token: token, + path: `/api/users/${userId}/permissions`, + data: { proxy_hosts: 'hidden' }, + }); + getPermissions().then((perms) => { + expect(perms.meta.proxy_host_ids).to.deep.equal([]); + }); + + // Showing proxy hosts again does not bring the old grant back + cy.task('backendApiPut', { + token: token, + path: `/api/users/${userId}/permissions`, + data: { proxy_hosts: 'manage' }, + }); + getPermissions().then((perms) => { + expect(perms.meta.proxy_host_ids).to.deep.equal([]); + }); + }); + + it('Proxy host grants are cleared when visibility is all', () => { + cy.task('backendApiPut', { + token: token, + path: `/api/users/${userId}/permissions`, + data: { visibility: 'all', proxy_hosts: 'manage', meta: { proxy_host_ids: [hostId] } }, + }); + getPermissions().then((perms) => { + expect(perms.meta.proxy_host_ids).to.deep.equal([]); + }); + }); +}); From 66ab4b24d1a07587ec0d481740ce57abefbebfaa Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Tue, 10 Nov 2026 08:20:02 +1000 Subject: [PATCH 76/76] Stop non-admins injecting nginx directives through host fields advanced_config is admin-only, but other fields are written into nginx config as-is and the schema barely limited them. A non-admin could add any directive through them: - location path, forward_host and forward_path (any non-empty string) - proxy host forward_host (a quote breaks out of set $server "...") - redirection forward_domain_name and stream forwarding_host (their pattern allowed ";", e.g. "example.com; return 200; #") - access list IPv6 addresses ("%.+" zone id allowed "::1%x; allow all") - domain names (a newline got out of the header comment) Validation - New lib/nginx-values.js holds the rules. Values can't contain characters that end or change an nginx token: whitespace, control characters, ; { } " ' # \, and no $ in a host. Location paths allow a modifier and an unquoted or double-quoted URI, so =, ~, ~* and ^~ locations and quoted regexes keep working. - The API schemas use the same patterns, including the certificate schema's own copy of the domain rule. A test fails if they drift. - generateConfig checks stored values again, so rows saved before these rules can't inject either. The check is about nginx structure only: a stored value the API would now reject, but which can't add directives, still renders. If a value is unsafe, nothing is written and the host is marked offline with the reason in nginx_err. The same check covers per-location access lists and the Let's Encrypt request config. - htpasswd user names can't contain ":" or control characters, which corrupted the file. Fixes along the way - IPv6 client addresses: drop zone ids, which nginx's allow/deny rejects anyway, and fix broken escapes (literal "d" for digits, an unescaped "."). - Rewrite the domain rule used by redirection and stream hosts so it can't backtrack exponentially. It accepts the same values. - Pattern errors still say "must match pattern", but no longer quote the regex. Tests - backend/test/nginx-values.test.js, run by `yarn test` and in scripts/ci/test-and-build. Real request schemas reject an injection set and accept valid values on every endpoint. Accepted values, rendered through the real templates and parsed using nginx's tokenizer rules, keep the same statement structure. The check refuses unsafe stored rows. - NginxValues.cy.js: injection attempts from a non-admin get 400, and hosts using valid edge-case values come up with nginx_online true. --- backend/internal/access-list.js | 6 + backend/internal/nginx.js | 42 +- backend/lib/nginx-values.js | 134 +++++ backend/lib/validator/api.js | 7 + backend/package.json | 1 + backend/schema/common.json | 7 +- .../schema/components/certificate-object.json | 2 +- .../schema/components/proxy-host-object.json | 7 +- .../components/redirection-host-object.json | 2 +- backend/schema/components/stream-object.json | 2 +- backend/test/nginx-values.test.js | 550 ++++++++++++++++++ scripts/ci/test-and-build | 2 +- test/cypress/e2e/api/NginxValues.cy.js | 217 +++++++ 13 files changed, 968 insertions(+), 11 deletions(-) create mode 100644 backend/lib/nginx-values.js create mode 100644 backend/test/nginx-values.test.js create mode 100644 test/cypress/e2e/api/NginxValues.cy.js diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 42f610e08..33b49c5d3 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -2,6 +2,7 @@ import fs from "node:fs"; import batchflow from "batchflow"; import _ from "lodash"; import errs from "../lib/error.js"; +import { isValid, unsafeValueError } from "../lib/nginx-values.js"; import { isMysql, isPostgres } from "../lib/config.js"; import utils from "../lib/utils.js"; import db from "../db.js"; @@ -500,6 +501,11 @@ const internalAccessList = { .sequential() .each((_i, item, next) => { if (item.password?.length) { + // One "user:hash" line each; a ":" or line break in the name would corrupt the file + if (!isValid("accessUsername", item.username)) { + reject(unsafeValueError(`Access List #${list.id}: user name is not allowed`)); + return; + } logger.info(`Adding: ${item.username}`); utils diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 4efd67f6f..06180e011 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -4,6 +4,7 @@ import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import _ from "lodash"; import errs from "../lib/error.js"; +import { assertAccessListRenderable, assertHostRenderable, isTokenSafe, unsafeValueError } from "../lib/nginx-values.js"; import utils from "../lib/utils.js"; import { debug, nginx as logger } from "../logger.js"; import accessListModel from "../models/access_list.js"; @@ -40,9 +41,30 @@ const internalNginx = { return internalNginx.deleteConfig(host_type, host, true); }) .then(() => { - return internalNginx.generateConfig(host_type, host); + return internalNginx.generateConfig(host_type, host).then( + () => true, + (err) => { + if (!err.unsafeNginxValue) { + throw err; + } + // Nothing was written. Report it on the host like a failed nginx test. + logger.error(err.message); + combined_meta = _.assign({}, host.meta, { + nginx_online: false, + nginx_err: err.message, + }); + return model + .query() + .where("id", host.id) + .patch({ meta: combined_meta }) + .then(() => false); + }, + ); }) - .then(() => { + .then((generated) => { + if (!generated) { + return; + } // Test nginx again and update meta with result return internalNginx .test() @@ -182,6 +204,10 @@ const internalNginx = { .first(); } if (locationAccessList) { + assertAccessListRenderable( + `Proxy Host #${host.id} location ${locationCopy.path}`, + locationAccessList, + ); locationCopy.access_list = locationAccessList; } else if ( locationCopy.access_list_id > 0 && @@ -227,6 +253,9 @@ const internalNginx = { await internalNginx.loadRenderRelations(nice_host_type, host); + // Values are written into the config as-is; refuse any that could add directives + assertHostRenderable(nice_host_type, host); + debug(logger, `Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); const renderEngine = utils.getRenderEngine(); @@ -374,6 +403,15 @@ const internalNginx = { debug(logger, "Generating LetsEncrypt Request Config:", certificate); const renderEngine = utils.getRenderEngine(); + const unsafe = (certificate.domain_names || []).find((name) => !isTokenSafe(name)); + if (typeof unsafe !== "undefined") { + return Promise.reject( + unsafeValueError( + `Certificate #${certificate.id}: domain name ${JSON.stringify(unsafe)} is not allowed`, + ), + ); + } + return new Promise((resolve, reject) => { let template = null; const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; diff --git a/backend/lib/nginx-values.js b/backend/lib/nginx-values.js new file mode 100644 index 000000000..0f5382623 --- /dev/null +++ b/backend/lib/nginx-values.js @@ -0,0 +1,134 @@ +import net from "node:net"; +import errs from "./error.js"; + +/** + * Rules for user-supplied values that the templates write into nginx config as-is. + * + * Anything that ends or changes an nginx token (whitespace, ";", "{", "}", quotes, "#" which + * starts a comment, and "\" which escapes the next character) would let the value add its own + * directives, which only admins may do (advanced_config). The API schema rejects these values + * with the same patterns (a test keeps the two in step). generateConfig() checks stored values + * again, so rows saved before these rules can't add directives either. That check is only about + * nginx structure, not format: a stored value in a format the API no longer accepts, but which + * can't add directives, still renders. + * + * Patterns are strings so they can be compared with the JSON schema; compile them with the "u" + * flag, as ajv does. + */ + +// One character that can't end or change an unquoted nginx token +const TOKEN_CHAR = String.raw`[^\s\p{Cc};{}"'#\\]`; +// Host part of a forward host: also no "$" (no variables in the upstream address) and no "/" +const HOST_CHARS = String.raw`[^\s\p{Cc};{}"'#\\$/]+`; +// The existing domain name rule ("a.b.c", no "*", no empty labels), written so it can't +// backtrack exponentially +const DOMAIN = String.raw`[^.*]+(?:\.[^.*]+)*\.?[^.]`; + +export const patterns = { + // server_name entries. The old character blacklist plus whitespace and control characters. + domainName: "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$", + // proxy_host forward_host and location forward_host: a host, optionally followed by a path + // (locations split "host/path" at render time) + forwardHost: String.raw`^${HOST_CHARS}(?:/${TOKEN_CHAR}*)?$`, + // location forward_path, appended straight after the port + forwardPath: String.raw`^${TOKEN_CHAR}*$`, + // location path: an optional modifier, then one token, either unquoted (a "\" must escape a + // visible character, so it can't swallow the space before "{") or double quoted. A modifier + // on its own isn't a location. + locationPath: String.raw`^(?!(?:=|~\*?|\^~)$)(?:(?:=|~\*?|\^~) +)?(?:"(?:[^"\\\p{Cc}]|\\[^\p{Cc}])+"|(?:[^\s\p{Cc};{}"'#\\]|\\[^\s\p{Cc}])+)$`, + // redirection forward_domain_name, e.g. "example.com:8443/path" + redirectDomain: String.raw`^(?=${TOKEN_CHAR}+$)${DOMAIN}$`, + // stream forwarding_host when it is a domain name (IPs are matched separately) + streamDomain: String.raw`^(?=${HOST_CHARS}$)${DOMAIN}$`, + // access list client address: IPv6 with an optional prefix. No zone id ("%eth0"): nginx's + // allow/deny rejects them, and the old "%.+" let anything through, including "; allow all" + clientIpv6: String.raw`^(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:)|(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::[0-9A-Fa-f]{1,4}){1,2}|:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){4}(?:(?::[0-9A-Fa-f]{1,4}){1,3}|(?::[0-9A-Fa-f]{1,4})?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){3}(?:(?::[0-9A-Fa-f]{1,4}){1,4}|(?::[0-9A-Fa-f]{1,4}){0,2}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){2}(?:(?::[0-9A-Fa-f]{1,4}){1,5}|(?::[0-9A-Fa-f]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:)(?:(?::[0-9A-Fa-f]{1,4}){1,6}|(?::[0-9A-Fa-f]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|(?::[0-9A-Fa-f]{1,4}){0,5}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:))(?:/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$`, + // htpasswd user name: a ":" or a line break would corrupt the file + accessUsername: String.raw`^[^:\p{Cc}]+$`, +}; + +const re = Object.fromEntries(Object.entries(patterns).map(([k, v]) => [k, new RegExp(v, "u")])); +const unsafeToken = /[\s\p{Cc};{}"'#\\]/u; +const ipv4WithPrefix = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?:\/(?:[0-9]|[1-2][0-9]|3[0-2]))?$/; + +export const isValid = (name, value) => typeof value === "string" && re[name].test(value); + +export const isValidClientAddress = (value) => + typeof value === "string" && (value === "all" || ipv4WithPrefix.test(value) || re.clientIpv6.test(value)); + +export const isValidStreamHost = (value) => + typeof value === "string" && (net.isIP(value) !== 0 || re.streamDomain.test(value)); + +/** + * Thrown when a stored value can't be written into nginx config safely + * + * @param {String} message + */ +export const unsafeValueError = (message) => { + const err = new errs.ConfigurationError(message); + err.unsafeNginxValue = true; + return err; +}; + +const describe = (value) => JSON.stringify(String(value)).slice(0, 80); + +/** + * @param {String} label e.g. "Proxy Host #3" + * @param {Object} [accessList] + * @throws {ConfigurationError} + */ +export const assertAccessListRenderable = (label, accessList) => { + for (const client of accessList?.clients || []) { + // "address" is rendered unquoted ("allow
    ;") + if (typeof client.address !== "string" || unsafeToken.test(client.address)) { + throw unsafeValueError(`${label}: access list client address ${describe(client.address)} is not allowed`); + } + } +}; + +// Can be written into config without ending or changing a token +export const isTokenSafe = (value) => typeof value === "string" && value.length > 0 && !unsafeToken.test(value); + +/** + * Checks every user-supplied value the host's template renders. Call before rendering. + * + * @param {String} hostType proxy_host, redirection_host, dead_host, stream or default + * @param {Object} host with access_list loaded, for proxy hosts + * @throws {ConfigurationError} + */ +export const assertHostRenderable = (hostType, host) => { + const label = `${hostType.replace(/_/g, " ")} #${host.id}`; + const check = (ok, what, value) => { + if (!ok) { + throw unsafeValueError(`${label}: ${what} ${describe(value)} is not allowed in nginx config`); + } + }; + + if (hostType !== "stream" && hostType !== "default") { + for (const name of host.domain_names || []) { + check(isTokenSafe(name), "domain name", name); + } + } + + if (hostType === "proxy_host") { + check(isTokenSafe(host.forward_host), "forward host", host.forward_host); + assertAccessListRenderable(label, host.access_list); + for (const loc of host.locations || []) { + // The path is several tokens (modifier, then a quoted or unquoted URI): its rule is the + // structural one + check(isValid("locationPath", loc.path), "location path", loc.path); + check(isTokenSafe(loc.forward_host), "location forward host", loc.forward_host); + if (loc.forward_path) { + check(isTokenSafe(loc.forward_path), "location forward path", loc.forward_path); + } + } + } + + if (hostType === "redirection_host") { + check(isTokenSafe(host.forward_domain_name), "forward domain", host.forward_domain_name); + } + + if (hostType === "stream") { + check(isTokenSafe(host.forwarding_host), "forwarding host", host.forwarding_host); + } +}; diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js index 0597ef635..cc25a4f35 100644 --- a/backend/lib/validator/api.js +++ b/backend/lib/validator/api.js @@ -35,6 +35,13 @@ const apiValidator = async (schema, payload /*, description*/) => { return payload; } + // Keep ajv's wording, which clients may match on, but not the regex it quotes: some are long + // and mean nothing to a user + for (const e of validate.errors || []) { + if (e.keyword === "pattern") { + e.message = "must match pattern"; + } + } const message = ajv.errorsText(validate.errors); const err = new errs.ValidationError(message); err.debug = { validationErrors: validate.errors, payload }; diff --git a/backend/package.json b/backend/package.json index 1e1646333..0a65fbe70 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "type": "module", "scripts": { "lint": "biome lint", + "test": "node --test test/", "prettier": "biome format --write .", "validate-schema": "node validate-schema.js", "regenerate-config": "node scripts/regenerate-config" diff --git a/backend/schema/common.json b/backend/schema/common.json index 00b06e005..3b12bd143 100644 --- a/backend/schema/common.json +++ b/backend/schema/common.json @@ -84,7 +84,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" + "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$" }, "example": ["example.com", "www.example.com"] }, @@ -147,7 +147,7 @@ }, { "type": "string", - "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" + "pattern": "^(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:)|(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::[0-9A-Fa-f]{1,4}){1,2}|:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){4}(?:(?::[0-9A-Fa-f]{1,4}){1,3}|(?::[0-9A-Fa-f]{1,4})?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){3}(?:(?::[0-9A-Fa-f]{1,4}){1,4}|(?::[0-9A-Fa-f]{1,4}){0,2}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){2}(?:(?::[0-9A-Fa-f]{1,4}){1,5}|(?::[0-9A-Fa-f]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:)(?:(?::[0-9A-Fa-f]{1,4}){1,6}|(?::[0-9A-Fa-f]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|(?::[0-9A-Fa-f]{1,4}){0,5}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:))(?:/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$" }, { "type": "string", @@ -164,7 +164,8 @@ "properties": { "username": { "type": "string", - "minLength": 1 + "minLength": 1, + "pattern": "^[^:\\p{Cc}]+$" }, "password": { "type": "string" diff --git a/backend/schema/components/certificate-object.json b/backend/schema/components/certificate-object.json index 80cd92bef..c86a54da7 100644 --- a/backend/schema/components/certificate-object.json +++ b/backend/schema/components/certificate-object.json @@ -31,7 +31,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" + "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$" }, "example": ["example.com", "www.example.com"] }, diff --git a/backend/schema/components/proxy-host-object.json b/backend/schema/components/proxy-host-object.json index 90e6e23bb..dcef18cfb 100644 --- a/backend/schema/components/proxy-host-object.json +++ b/backend/schema/components/proxy-host-object.json @@ -45,6 +45,7 @@ "type": "string", "minLength": 1, "maxLength": 255, + "pattern": "^[^\\s\\p{Cc};{}\"'#\\\\$/]+(?:/[^\\s\\p{Cc};{}\"'#\\\\]*)?$", "example": "127.0.0.1" }, "forward_port": { @@ -108,7 +109,8 @@ }, "path": { "type": "string", - "minLength": 1 + "minLength": 1, + "pattern": "^(?!(?:=|~\\*?|\\^~)$)(?:(?:=|~\\*?|\\^~) +)?(?:\"(?:[^\"\\\\\\p{Cc}]|\\\\[^\\p{Cc}])+\"|(?:[^\\s\\p{Cc};{}\"'#\\\\]|\\\\[^\\s\\p{Cc}])+)$" }, "forward_scheme": { "$ref": "#/properties/forward_scheme" @@ -120,7 +122,8 @@ "$ref": "#/properties/forward_port" }, "forward_path": { - "type": "string" + "type": "string", + "pattern": "^[^\\s\\p{Cc};{}\"'#\\\\]*$" }, "advanced_config": { "type": "string" diff --git a/backend/schema/components/redirection-host-object.json b/backend/schema/components/redirection-host-object.json index 58169720b..199053019 100644 --- a/backend/schema/components/redirection-host-object.json +++ b/backend/schema/components/redirection-host-object.json @@ -57,7 +57,7 @@ "forward_domain_name": { "description": "Domain Name", "type": "string", - "pattern": "^(?:[^.*]+\\.?)+[^.]$", + "pattern": "^(?=[^\\s\\p{Cc};{}\"'#\\\\]+$)[^.*]+(?:\\.[^.*]+)*\\.?[^.]$", "example": "jc21.com" }, "preserve_path": { diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json index 3a1cd5210..3b8cd4f55 100644 --- a/backend/schema/components/stream-object.json +++ b/backend/schema/components/stream-object.json @@ -39,7 +39,7 @@ { "description": "Domain Name", "type": "string", - "pattern": "^(?:[^.*]+\\.?)+[^.]$", + "pattern": "^(?=[^\\s\\p{Cc};{}\"'#\\\\$/]+$)[^.*]+(?:\\.[^.*]+)*\\.?[^.]$", "example": "example.com" }, { diff --git a/backend/test/nginx-values.test.js b/backend/test/nginx-values.test.js new file mode 100644 index 000000000..a70a986f3 --- /dev/null +++ b/backend/test/nginx-values.test.js @@ -0,0 +1,550 @@ +/** + * Regression tests: values a non-admin can set must never add nginx directives. + * + * Templates write these values into config as-is, so each one is checked three ways: + * - the API rejects injection attempts (real request schemas, real validator) + * - accepted values, rendered through the real templates, produce exactly the same nginx + * statement structure as a plain value (parsed with nginx's tokenizer rules) + * - generateConfig's render-time check refuses unsafe values already stored in the database + */ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { describe, it } from "node:test"; +import { + assertHostRenderable, + isValid, + isValidClientAddress, + isValidStreamHost, + patterns, +} from "../lib/nginx-values.js"; +import utils from "../lib/utils.js"; +import apiValidator from "../lib/validator/api.js"; +import { getCompiledSchema, getValidationSchema } from "../schema/index.js"; + +const TEMPLATES = new URL("../templates/", import.meta.url); + +// --------------------------------------------------------------------------------------------- +// A tokenizer following ngx_conf_read_token: whitespace separates, ";" ends a statement, "{" +// opens a block, "}" closes one, "#" at the start of a token comments to end of line, quotes +// group, "\" escapes the next character, and "${" doesn't open a block. +// --------------------------------------------------------------------------------------------- +const parseNginx = (text) => { + const root = []; + const stack = [root]; + let args = []; + let i = 0; + const n = text.length; + const isSpace = (c) => c === " " || c === "\t" || c === "\r" || c === "\n"; + + while (i < n) { + const c = text[i]; + if (isSpace(c)) { + i++; + } else if (c === "#") { + while (i < n && text[i] !== "\n") i++; + } else if (c === ";") { + if (!args.length) throw new Error(`unexpected ";" at ${i}`); + stack.at(-1).push({ name: args[0], args: args.slice(1) }); + args = []; + i++; + } else if (c === "{") { + if (!args.length) throw new Error(`unexpected "{" at ${i}`); + const block = { name: args[0], args: args.slice(1), block: [] }; + stack.at(-1).push(block); + stack.push(block.block); + args = []; + i++; + } else if (c === "}") { + if (args.length) throw new Error(`unexpected "}" at ${i}`); + if (stack.length === 1) throw new Error(`unbalanced "}" at ${i}`); + stack.pop(); + i++; + } else if (c === '"' || c === "'") { + let tok = c; + i++; + while (i < n && text[i] !== c) { + if (text[i] === "\\" && i + 1 < n) { + tok += text[i] + text[i + 1]; + i += 2; + } else { + tok += text[i++]; + } + } + if (i >= n) throw new Error("unterminated quoted string"); + tok += c; + i++; + if (i < n && !isSpace(text[i]) && text[i] !== ";" && text[i] !== "{") { + throw new Error(`unexpected "${text[i]}" after quoted string`); + } + args.push(tok); + } else { + let tok = ""; + while (i < n) { + const d = text[i]; + if (d === "\\" && i + 1 < n) { + tok += d + text[i + 1]; + i += 2; + continue; + } + if (d === "$" && text[i + 1] === "{") { + tok += "${"; + i += 2; + continue; + } + if (isSpace(d) || d === ";" || d === "{") break; + tok += d; + i++; + } + args.push(tok); + } + } + if (args.length) throw new Error("statement without ;"); + if (stack.length !== 1) throw new Error("unclosed block"); + return root; +}; + +// Statement names and argument counts, nested; values themselves don't matter +const shape = (stmts) => + stmts.map((s) => `${s.name}/${s.args.length}${s.block ? `{${shape(s.block).join(",")}}` : ""}`); + +const find = (stmts, name) => { + for (const s of stmts) { + if (s.name === name) return s; + if (s.block) { + const f = find(s.block, name); + if (f) return f; + } + } + return null; +}; + +const render = (file, ctx) => + utils.getRenderEngine().parseAndRender(fs.readFileSync(new URL(file, TEMPLATES), "utf8"), ctx); + +// Same structure as a plain value, or throws (a value that breaks parsing changes it too) +const sameShapeAs = async (file, makeCtx, plain, value) => { + const base = shape(parseNginx(await render(file, makeCtx(plain)))); + let got; + try { + got = shape(parseNginx(await render(file, makeCtx(value)))); + } catch { + return false; + } + return JSON.stringify(got) === JSON.stringify(base); +}; + +// --------------------------------------------------------------------------------------------- +// Corpora. "injections" add or change directives when rendered unchecked; "rejected" are other +// values the rules refuse (they would break or confuse the config without injecting). +// --------------------------------------------------------------------------------------------- +const cases = { + locationPath: { + valid: [ + "/", + "/api", + "/api/", + "= /exact", + "~ \\.php$", + "~* \\.(jpg|png|gif)$", + "^~ /static/", + '~ "^/v[0-9]{1,2}/"', + '"/with space"', + "/über", + "/a$b", + "/a\\;b", + ], + injections: [ + "/x { return 200; } location /y", + "/x {} include /etc/passwd; location /y", + "/a; } server { listen 8080", + "/a\nreturn 200 x;\nlocation /b", + '"/a" { return 200; } location "/b"', + "/a {", + ], + rejected: ["", " /a", "/a b", "/a#", "/a\\", '"/a', '"/a\\"', "'/a'", "~", "~ /a\tb", "/a}", "/a\u0000"], + }, + forwardHost: { + valid: ["127.0.0.1", "backend", "my_service.internal", "10.0.0.1/path/", "backend/$1", "[::1]", "host:8080"], + injections: [ + "a; return 200; #", + 'a"; return 200; set $x "', + "a:80; } location /x { return 200", + "a\nreturn 200;", + ], + rejected: ["", "a b", "a'", "a{", "a}", "a#b", "a\\b", "$http_host", "a$b", "/path", "a/b c"], + }, + forwardPath: { + valid: ["", "/", "/x", "/x/$1/"], + injections: ["/x; return 200", "/x\nreturn 200"], + rejected: ["/x y", "/x#", "/x{", "/x\\"], + }, + redirectDomain: { + valid: [ + "example.com", + "example.com:8443", + "example.com/path", + "sub.example.co.uk/a?b=c", + "example.com/$1", + "ab", + ], + injections: ["example.com; return 200 x; #", "example.com;\nreturn 200", "a.com { return 200; }"], + rejected: ["a b.com", "a.com#x", 'a.com"', "a.com\\", ".com", "a..b", "a", "a.com."], + }, + streamHost: { + valid: ["example.com", "1.2.3.4", "::1", "backend.internal"], + injections: ["a.com:80; } server { listen 9999", "a.com;\nlisten 1"], + rejected: ["a.com/x", "$host", "a b", "a.com#"], + }, + domainName: { + valid: ["example.com", "*.example.com", "xn--bcher-kva.example", "a-b.example"], + injections: ["a.com\nreturn 200 x", "a.com\n} server {"], + rejected: ["a\tb.com", "a;b", "a{b", "a b"], + }, + clientAddress: { + valid: ["192.168.0.1", "10.0.0.0/8", "all", "::1", "2001:db8::/32", "::ffff:192.168.1.1", "fe80::1"], + injections: ["::1%x; allow all", "::1%x\nallow all", "1.2.3.4; allow all"], + rejected: ["fe80::1%eth0", "s::1", "1.2.3.4/33", "::ffff:1d.1.1.1"], + }, + accessUsername: { + valid: ["admin", "john smith", "ü"], + injections: [], + rejected: ["a:b", "a\nroot:$apr1$x$y", "\u0000"], + }, +}; + +const checkers = { + locationPath: (v) => isValid("locationPath", v), + forwardHost: (v) => isValid("forwardHost", v), + forwardPath: (v) => isValid("forwardPath", v), + redirectDomain: (v) => isValid("redirectDomain", v), + streamHost: isValidStreamHost, + domainName: (v) => isValid("domainName", v), + clientAddress: isValidClientAddress, + accessUsername: (v) => isValid("accessUsername", v), +}; + +describe("nginx value rules", () => { + for (const [name, { valid, injections, rejected }] of Object.entries(cases)) { + it(`${name}: accepts valid values`, () => { + for (const v of valid) assert.ok(checkers[name](v), `should accept ${JSON.stringify(v)}`); + }); + it(`${name}: rejects injections and other unsafe values`, () => { + for (const v of [...injections, ...rejected]) { + assert.ok(!checkers[name](v), `should reject ${JSON.stringify(v)}`); + } + }); + } + + it("redirect domain rule matches the old rule for token-safe values", () => { + const old = /^(?:[^.*]+\.?)+[^.]$/u; + const corpus = [ + "ab", + "a.b", + "a.b.c", + "a..b", + ".a", + "a.", + "a*", + "a.*", + "*a", + "a*b", + "a.b:80/x", + "x", + "ab.", + "a.b.", + ]; + for (const v of corpus) { + assert.equal(isValid("redirectDomain", v), old.test(v), `differs for ${JSON.stringify(v)}`); + } + }); + + it("domain rules don't backtrack exponentially", () => { + const evil = `${"a".repeat(30000)}.`; + const start = process.hrtime.bigint(); + isValid("redirectDomain", evil); + isValidStreamHost(evil); + const ms = Number(process.hrtime.bigint() - start) / 1e6; + assert.ok(ms < 1000, `took ${ms}ms`); + }); +}); + +describe("API request validation", async () => { + await getCompiledSchema(); + + const location = (o) => ({ path: "/api", forward_scheme: "http", forward_host: "backend", forward_port: 80, ...o }); + const proxyHost = (o) => ({ + domain_names: ["example.com"], + forward_scheme: "http", + forward_host: "backend", + forward_port: 80, + ...o, + }); + const redirection = (o) => ({ + domain_names: ["example.com"], + forward_scheme: "http", + forward_http_code: 301, + forward_domain_name: "example.org", + ...o, + }); + const stream = (o) => ({ + incoming_port: 9000, + forwarding_host: "backend", + forwarding_port: 80, + tcp_forwarding: true, + udp_forwarding: false, + ...o, + }); + + // [field, endpoint, method, payload builder] + const endpoints = { + locationPath: [ + ["/nginx/proxy-hosts", "post", (v) => proxyHost({ locations: [location({ path: v })] })], + ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ path: v })] })], + ], + forwardHost: [ + ["/nginx/proxy-hosts", "post", (v) => proxyHost({ forward_host: v })], + ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ forward_host: v })], + ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ forward_host: v })] })], + ], + forwardPath: [["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ forward_path: v })] })]], + redirectDomain: [ + ["/nginx/redirection-hosts", "post", (v) => redirection({ forward_domain_name: v })], + ["/nginx/redirection-hosts/{hostID}", "put", (v) => ({ forward_domain_name: v })], + ], + streamHost: [ + ["/nginx/streams", "post", (v) => stream({ forwarding_host: v })], + ["/nginx/streams/{streamID}", "put", (v) => ({ forwarding_host: v })], + ], + domainName: [ + ["/nginx/proxy-hosts", "post", (v) => proxyHost({ domain_names: [v] })], + ["/nginx/redirection-hosts", "post", (v) => redirection({ domain_names: [v] })], + ["/nginx/dead-hosts", "post", (v) => ({ domain_names: [v] })], + ["/nginx/certificates", "post", (v) => ({ provider: "letsencrypt", domain_names: [v] })], + ], + clientAddress: [ + ["/nginx/access-lists", "post", (v) => ({ name: "x", clients: [{ directive: "allow", address: v }] })], + ["/nginx/access-lists/{listID}", "put", (v) => ({ clients: [{ directive: "allow", address: v }] })], + ], + accessUsername: [ + ["/nginx/access-lists", "post", (v) => ({ name: "x", items: [{ username: v, password: "pw" }] })], + ["/nginx/access-lists/{listID}", "put", (v) => ({ items: [{ username: v, password: "pw" }] })], + ], + }; + + const validate = (path, method, payload) => { + const schema = getValidationSchema(path, method); + assert.ok(schema, `no request schema for ${method} ${path}`); + return apiValidator(schema, payload); + }; + + for (const [name, list] of Object.entries(endpoints)) { + for (const [path, method, build] of list) { + it(`${method.toUpperCase()} ${path} ${name}: rejects injections`, async () => { + for (const v of [...cases[name].injections, ...cases[name].rejected]) { + if (v === "") continue; // minLength cases, covered by the rules above + await assert.rejects(validate(path, method, build(v)), /must/, `accepted ${JSON.stringify(v)}`); + } + }); + it(`${method.toUpperCase()} ${path} ${name}: accepts valid values`, async () => { + for (const v of cases[name].valid) { + if (name === "forwardHost" && v === "") continue; + await validate(path, method, build(v)); + } + }); + } + } + + it("schema patterns are the ones the render-time check uses", async () => { + const s = await getCompiledSchema(); + const body = (path, method) => s.paths[path][method].requestBody.content["application/json"].schema; + const ph = body("/nginx/proxy-hosts", "post").properties; + const loc = ph.locations.items.properties; + const al = body("/nginx/access-lists", "post").properties; + assert.equal(ph.domain_names.items.pattern, patterns.domainName); + assert.equal(body("/nginx/certificates", "post").properties.domain_names.items.pattern, patterns.domainName); + assert.equal(ph.forward_host.pattern, patterns.forwardHost); + assert.equal(loc.path.pattern, patterns.locationPath); + assert.equal(loc.forward_host.pattern, patterns.forwardHost); + assert.equal(loc.forward_path.pattern, patterns.forwardPath); + assert.equal( + body("/nginx/redirection-hosts", "post").properties.forward_domain_name.pattern, + patterns.redirectDomain, + ); + assert.equal(body("/nginx/streams", "post").properties.forwarding_host.anyOf[0].pattern, patterns.streamDomain); + assert.equal(al.clients.items.properties.address.oneOf[1].pattern, patterns.clientIpv6); + assert.equal(al.items.items.properties.username.pattern, patterns.accessUsername); + }); +}); + +describe("rendered templates", () => { + const locationCtx = (field) => (v) => ({ + path: "/api", + forward_scheme: "http", + forward_host: "backend", + forward_port: 8080, + forward_path: "", + advanced_config: "", + access_list_id: 0, + [field]: v, + }); + const proxyCtx = (field) => (v) => ({ + enabled: true, + id: 1, + domain_names: ["example.com"], + forward_scheme: "http", + forward_host: "backend", + forward_port: 80, + certificate_id: 0, + locations: "", + advanced_config: "", + use_default_location: true, + ipv6: true, + [field]: v, + }); + const redirectCtx = (v) => ({ + enabled: true, + id: 1, + domain_names: ["example.com"], + forward_scheme: "http", + forward_http_code: 301, + forward_domain_name: v, + preserve_path: true, + advanced_config: "", + use_default_location: true, + ipv6: true, + }); + const streamCtx = (v) => ({ + enabled: true, + id: 1, + incoming_port: 9000, + forwarding_host: v, + forwarding_port: 80, + tcp_forwarding: true, + udp_forwarding: true, + ipv6: true, + }); + const accessCtx = (v) => ({ + access_list_id: 1, + access_list: { items: [], clients: [{ directive: "allow", address: v }], satisfy_any: false }, + }); + + // A location modifier is a separate argument, so compare with a plain path using the same one + const plainPath = (v) => { + const m = v.match(/^(=|~\*?|\^~) +/); + return m ? `${m[1]} /api` : "/api"; + }; + + // [template, context builder, plain value (or builder), injections that apply to it] + const targets = { + locationPath: [["_location.conf", locationCtx("path"), plainPath]], + forwardHost: [ + ["_location.conf", locationCtx("forward_host"), "backend"], + // Rendered inside double quotes here, so only a quote can break out + ["proxy_host.conf", proxyCtx("forward_host"), "backend", ['a"; return 200; set $x "', 'a" ; } server { "']], + ], + forwardPath: [["_location.conf", locationCtx("forward_path"), "/x"]], + redirectDomain: [["redirection_host.conf", redirectCtx, "example.org"]], + streamHost: [["stream.conf", streamCtx, "backend"]], + domainName: [["proxy_host.conf", (v) => proxyCtx("domain_names")([v]), "example.com"]], + clientAddress: [["_access.conf", accessCtx, "10.0.0.1"]], + }; + + for (const [name, list] of Object.entries(targets)) { + for (const [file, ctx, plain, injections = cases[name].injections] of list) { + const plainFor = (v) => (typeof plain === "function" ? plain(v) : plain); + it(`${file} ${name}: accepted values keep the statement structure`, async () => { + for (const v of cases[name].valid) { + if (v === "" && name !== "forwardPath") continue; + // generateConfig brackets IPv6 stream hosts before rendering + const value = name === "streamHost" && v.includes(":") ? `[${v}]` : v; + assert.ok( + await sameShapeAs(file, ctx, plainFor(v), value), + `${JSON.stringify(v)} changed the config`, + ); + } + }); + it(`${file} ${name}: the injection corpus would change it unchecked`, async () => { + assert.ok(injections.length > 0); + for (const v of injections) { + assert.ok(!checkers[name](v), `rules accept ${JSON.stringify(v)}`); + assert.ok( + !(await sameShapeAs(file, ctx, plainFor(v), v)), + `${JSON.stringify(v)} is not an injection`, + ); + } + }); + } + } + + it("location path renders as the location's own arguments", async () => { + for (const v of cases.locationPath.valid) { + const loc = find(parseNginx(await render("_location.conf", locationCtx("path")(v))), "location"); + assert.equal(loc.args.join(" "), v.replace(/ +/, " ")); + } + }); +}); + +describe("render-time check (rows saved before validation)", () => { + const host = (o) => ({ + id: 7, + domain_names: ["example.com"], + forward_host: "backend", + locations: [], + ...o, + }); + + it("passes valid hosts", () => { + assertHostRenderable("proxy_host", host({ locations: [{ path: "~* \\.php$", forward_host: "10.0.0.1/x/" }] })); + assertHostRenderable("redirection_host", host({ forward_domain_name: "example.org/path" })); + assertHostRenderable("dead_host", host({})); + assertHostRenderable("stream", { id: 1, forwarding_host: "::1" }); + assertHostRenderable( + "proxy_host", + host({ access_list: { clients: [{ directive: "allow", address: "10.0.0.0/8" }] } }), + ); + }); + + it("still renders stored values the API no longer accepts but that can't add directives", () => { + // e.g. an IPv6 literal server name, as the host-header smoke test renders + assertHostRenderable("proxy_host", host({ domain_names: ["host.example.test", "[::1]"] })); + assertHostRenderable("proxy_host", host({ forward_host: "$upstream" })); + assertHostRenderable("redirection_host", host({ forward_domain_name: "a" })); + assertHostRenderable("stream", { id: 1, forwarding_host: "a.com/x" }); + }); + + const refuse = (type, row, what) => { + assert.throws( + () => assertHostRenderable(type, row), + (err) => err.unsafeNginxValue === true && err.message.includes(what), + ); + }; + + it("refuses every injected field", () => { + for (const v of cases.locationPath.injections) { + refuse("proxy_host", host({ locations: [{ path: v, forward_host: "b" }] }), "location path"); + } + for (const v of cases.forwardHost.injections) { + refuse("proxy_host", host({ forward_host: v }), "forward host"); + refuse("proxy_host", host({ locations: [{ path: "/a", forward_host: v }] }), "location forward host"); + } + for (const v of cases.forwardPath.injections) { + refuse( + "proxy_host", + host({ locations: [{ path: "/a", forward_host: "b", forward_path: v }] }), + "forward path", + ); + } + for (const v of cases.redirectDomain.injections) { + refuse("redirection_host", host({ forward_domain_name: v }), "forward domain"); + } + for (const v of cases.streamHost.injections) { + refuse("stream", { id: 1, forwarding_host: v }, "forwarding host"); + } + for (const v of cases.domainName.injections) { + refuse("dead_host", host({ domain_names: [v] }), "domain name"); + } + for (const v of cases.clientAddress.injections) { + refuse("proxy_host", host({ access_list: { clients: [{ directive: "allow", address: v }] } }), "address"); + } + }); +}); diff --git a/scripts/ci/test-and-build b/scripts/ci/test-and-build index fec906d60..c7886718b 100755 --- a/scripts/ci/test-and-build +++ b/scripts/ci/test-and-build @@ -12,7 +12,7 @@ docker run --rm \ -v "$(pwd)/backend:/app" \ -w /app \ "${TESTING_IMAGE}" \ - sh -c 'yarn install && yarn lint . && rm -rf node_modules' + sh -c 'yarn install && yarn lint . && yarn test && rm -rf node_modules' echo -e "${BLUE}❯ ${GREEN}Testing Complete${RESET}" # Build diff --git a/test/cypress/e2e/api/NginxValues.cy.js b/test/cypress/e2e/api/NginxValues.cy.js new file mode 100644 index 000000000..7f1749764 --- /dev/null +++ b/test/cypress/e2e/api/NginxValues.cy.js @@ -0,0 +1,217 @@ +/// + +// Values a non-admin can set are written into nginx config as-is. None of them may add +// directives (that is what advanced_config is for, and only admins may set it), but the valid +// forms people use, such as regex locations, must still produce a config nginx accepts. +describe('Values rendered into nginx config', () => { + const user = { + email: 'nginx-values@example.com', + secret: 'changeme', + }; + + let token; + let userToken; + + const location = (overrides) => Object.assign({ + path: '/api', + forward_scheme: 'http', + forward_host: '127.0.0.1', + forward_port: 8080, + advanced_config: '', + }, overrides); + + const proxyHost = (domain, overrides) => Object.assign({ + domain_names: [domain], + forward_scheme: 'http', + forward_host: '127.0.0.1', + forward_port: 80, + access_list_id: '0', + certificate_id: 0, + meta: { dns_challenge: false }, + advanced_config: '', + locations: [], + block_exploits: false, + caching_enabled: false, + allow_websocket_upgrade: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false, + }, overrides || {}); + + const expectRejected = (path, data) => { + cy.task('backendApiPost', { + token: userToken, + path: path, + data: data, + returnOnError: true, + }).then((res) => { + expect(res, JSON.stringify(data)).to.have.property('error'); + expect(res.error.code, JSON.stringify(data)).to.equal(400); + }); + }; + + const expectOnline = (path, id) => { + cy.task('backendApiGet', { + token: userToken, + path: `${path}/${id}`, + }).then((row) => { + expect(row.meta.nginx_err || null).to.equal(null); + expect(row.meta.nginx_online).to.equal(true); + }); + }; + + before(() => { + cy.resetUsers(); + cy.getToken().then((tok) => { + token = tok; + cy.task('backendApiPost', { + token: token, + path: '/api/users', + data: { + name: 'Nginx Values', + nickname: 'Values', + email: user.email, + roles: [], + auth: { type: 'password', secret: user.secret }, + }, + }).then((created) => { + cy.task('backendApiPut', { + token: token, + path: `/api/users/${created.id}/permissions`, + data: { + visibility: 'user', + proxy_hosts: 'manage', + redirection_hosts: 'manage', + dead_hosts: 'manage', + streams: 'manage', + access_lists: 'manage', + certificates: 'manage', + }, + }); + cy.getToken(null, { identity: user.email, secret: user.secret }).then((tok2) => { + userToken = tok2; + }); + }); + }); + }); + + it('Location paths and forward hosts that nginx accepts still work', () => { + cy.task('backendApiPost', { + token: userToken, + path: '/api/nginx/proxy-hosts', + data: proxyHost('nginx-values-ok.example.com', { + locations: [ + location({ path: '= /exact' }), + location({ path: '~* \\.(jpg|png|gif)$' }), + location({ path: '^~ /static/' }), + location({ path: '~ "^/v[0-9]{1,2}/"' }), + location({ path: '/sub', forward_host: '127.0.0.1/sub/' }), + ], + }), + }).then((host) => { + expectOnline('/api/nginx/proxy-hosts', host.id); + }); + }); + + it('Redirect targets with a port and path still work', () => { + cy.task('backendApiPost', { + token: userToken, + path: '/api/nginx/redirection-hosts', + data: { + domain_names: ['nginx-values-redirect.example.com'], + forward_scheme: 'https', + forward_http_code: 301, + forward_domain_name: 'example.org:8443/landing', + preserve_path: true, + certificate_id: 0, + meta: {}, + advanced_config: '', + block_exploits: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false, + }, + }).then((host) => { + expectOnline('/api/nginx/redirection-hosts', host.id); + }); + }); + + it('Location path cannot add directives', () => { + [ + '/x { return 200; } location /y', + '/a; } server { listen 8080', + '/a\nreturn 200 x;\nlocation /b', + '/a {', + '~', + ].forEach((path) => { + expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', { + locations: [location({ path })], + })); + }); + }); + + it('Forward hosts cannot add directives', () => { + [ + 'a; return 200; #', + 'a"; return 200; set $x "', + 'a:80; } location /x { return 200', + '$http_host', + ].forEach((forward_host) => { + expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', { forward_host })); + expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', { + locations: [location({ forward_host })], + })); + }); + expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', { + locations: [location({ forward_path: '/x; return 200' })], + })); + }); + + it('Redirect target cannot add directives', () => { + ['example.com; return 200 x; #', 'a.com { return 200; }'].forEach((forward_domain_name) => { + expectRejected('/api/nginx/redirection-hosts', { + domain_names: ['nginx-values-bad-redirect.example.com'], + forward_scheme: 'http', + forward_http_code: 301, + forward_domain_name, + }); + }); + }); + + it('Stream forwarding host cannot add directives', () => { + ['a.com:80; } server { listen 9999', 'a.com/x'].forEach((forwarding_host) => { + expectRejected('/api/nginx/streams', { + incoming_port: 1600, + forwarding_host, + forwarding_port: 80, + tcp_forwarding: true, + udp_forwarding: false, + }); + }); + }); + + it('Domain names cannot add directives', () => { + ['a.example.com\nreturn 200 x', 'a\tb.example.com'].forEach((domain) => { + expectRejected('/api/nginx/dead-hosts', { domain_names: [domain] }); + }); + }); + + it('Access list entries cannot add directives or corrupt the password file', () => { + ['::1%x; allow all', '1.2.3.4; allow all', 'fe80::1%eth0'].forEach((address) => { + expectRejected('/api/nginx/access-lists', { + name: 'Bad client', + items: [], + clients: [{ directive: 'deny', address }], + }); + }); + ['a:b', 'a\nroot:$apr1$x$y'].forEach((username) => { + expectRejected('/api/nginx/access-lists', { + name: 'Bad user', + items: [{ username, password: 'pw' }], + clients: [], + }); + }); + }); +});