fix(security,deps): 认证端点节流 + 镜像信息收敛 + 两条依赖公告
认证(src/routes/auth.php): - login / register / confirm-password 加 throttle:5,1,forgot-password 加 throttle:6,1。 实测发现登录原本并非零防护 —— Breeze 的 LoginRequest 有按(邮箱|IP)的 5 次/60 秒 RateLimiter(CACHE_DRIVER=file,真生效),它挡单账号撞库;路由级节流补的是 『换邮箱撒网 / 换 IP 轮换』这个口,两者互补。 - verification.verify(6,1) 与 verification.send(3,1) 原样未动。 - 已知取舍(未改,已登记):TrustProxies 配的是 $proxies='*',节流键取自 X-Forwarded-For 且外层 nginx 用 $proxy_add_x_forwarded_for 追加 → 客户端自带的假 XFF 会排在前面并被采信, 即限流键可伪造。修法是把信任代理收窄到 nginx 的地址/网段。 镜像信息收敛(Dockerfile): - 新增 /usr/local/etc/php/conf.d/zz-lsky-hardening.ini(expose_php=0 / display_errors=0), zz- 前缀保证不被别的 ini 覆盖;构建期用 php -r 真读回 ini_get 断言。 - 新增 /etc/apache2/conf-enabled/zz-lsky-hardening.conf(ServerTokens Prod / ServerSignature Off)。放 conf-enabled 而不是 vhost 模板:ServerTokens 是全局指令, 写进 VirtualHost 会被 Apache 拒绝加载。 - 自证从 8 条加到 9 条(新增 routes/auth.php 钉住),本地实算 9/9 通过。 依赖公告(OSV 扫描到 11 条,本次清掉两条有修复版本的): - psy/psysh v0.11.12 → v0.11.23(它在生产依赖树里、随镜像发布;Dockerfile 的 composer.lock md5 自证同步为新值),并从 osv-scanner.toml 删掉那条白名单。 - sweetalert2 11.4.6 → 11.22.4(前端依赖,会打进浏览器包)。升级后新版默认样式 div:where(.swal2-container) .swal2-input 与我们的皮肤权重打平并靠注入顺序取胜, 实测把输入框高度从 38px 压成 36.75px → 把皮肤前缀从 html 提到 html:root 修掉, 并同步 3 条断言;jsdom 290/290 全绿。 - OSV 门禁复跑:exit 0,已过滤清单里只剩 laravel 9.x 那 4 条(上游无修复版本) 与 debugbar / phpunit 等 dev-only 项。
This commit is contained in:
1 parent
235f72e68c
commit
d77ba67d5c
12 files changed
+157
-73
No files matched your search
+62
-2
@@ -16,6 +16,11 @@
|
||||
# 4. install-php-extensions 钉到具体版本,不再用 latest
|
||||
# 5. 构建期自证:① 我们从不修改的上游文件按上游 md5 校验(快照没被动过)
|
||||
# ② 补丁产物与「有意偏离上游」的 composer.lock 按预期 md5 校验(改了不更新就构建失败)
|
||||
# 6. 运行时加固(F23,2026-09-30):PHP 关掉 expose_php / display_errors
|
||||
# (/usr/local/etc/php/conf.d/zz-lsky-hardening.ini)、Apache 关掉版本号外泄
|
||||
# (/etc/apache2/conf-enabled/zz-lsky-hardening.conf:ServerTokens Prod + ServerSignature Off),
|
||||
# 两处都带构建期断言;CI 的「Verify published image」再对真实响应头断言一次。
|
||||
# 7. 认证类 POST 加路由级节流(src/routes/auth.php:login/register/forgot-password/confirm-password)
|
||||
#
|
||||
# 构建:docker build -t lsky-pro-docker .
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -104,10 +109,14 @@ RUN printf '%s\n' \
|
||||
# app/Services/ImageService.php 同样是有意偏离的:移除 SVG 支持时删掉了几个「只可能因为
|
||||
# svg 数据而命中」的分支(上传跳过图片处理 / 跳过违规扫描的 in_array svg、makeThumbnail
|
||||
# 里 svg 直拷原文件),详见 README「与上游的差异」第 7 条。
|
||||
# 这三个文件以后只要被改动(哪怕手滑),构建就会红 —— md5 必须随改动同步更新。
|
||||
# routes/auth.php 也是有意偏离(F23 防爆破):给 login / register / forgot-password /
|
||||
# confirm-password 四个 POST 端点加了 throttle(详见文件内注释)。它同样被钉住,
|
||||
# 改了不更新这行 md5 就构建失败。
|
||||
# 这几个文件以后只要被改动(哪怕手滑),构建就会红 —— md5 必须随改动同步更新。
|
||||
RUN printf '%s\n' \
|
||||
'8136e73b50315d783f105dd4ac9971bb ./config/convention.php' \
|
||||
'f6167a0726f8f2892494952a14c2bf49 ./routes/web.php' \
|
||||
'c1ab546f3e7f5237c1d45435171858ce ./routes/auth.php' \
|
||||
'e7edc0fc9dc8c654c4debca0e1d2eac4 ./app/Services/ImageService.php' \
|
||||
| md5sum -c -
|
||||
|
||||
@@ -117,10 +126,13 @@ RUN printf '%s\n' \
|
||||
# laravel/framework 9.52.22(9.x 最后一个补丁;9.52.21 -> 9.52.22 是一次安全修复)、symfony/* 6.4 LTS(Laravel 9 的 ^6.0 允许)、
|
||||
# guzzlehttp/guzzle 7.15.5、guzzlehttp/psr7 2.13.1、phpseclib 3.0.57、
|
||||
# league/commonmark 2.10.3、aws/aws-sdk-php 3.398.1、laminas-diactoros 2.26.0
|
||||
# psy/psysh 0.11.23(2026-09-30:原 0.11.12 有 GHSA-4486-gxhx-5mg7 —— CWD 下 .psysh.php 自动加载
|
||||
# 导致本地提权;上游在同一条 0.11 线发了修复,随 laravel/tinker 一起进镜像,故一并升掉。
|
||||
# osv-scanner.toml 里对应那条白名单已随之删除=扫描真的干净,而不是被豁免。)
|
||||
# —— 修掉了除「Laravel 9 框架自身那几条(9.x 线没有修复版本)」以外的已知公告。
|
||||
# 所以它不再等于上游值 —— 但仍钉 md5:任何改动都必须同步更新这里(防止有人别处悄悄改)。
|
||||
RUN printf '%s\n' \
|
||||
'47dc4ba720293bffe2d3ee1992775e02 ./composer.lock' \
|
||||
'12ab233331630b2dd1c48cee9e5ce927 ./composer.lock' \
|
||||
| md5sum -c -
|
||||
|
||||
# --no-dev:镜像只装运行时依赖。开发包(debugbar / ignition / whoops / phpunit / faker / sail…)
|
||||
@@ -242,6 +254,33 @@ RUN sed -i \
|
||||
&& ! grep -rn '/etc/ssl/certs/ssl-cert-snakeoil\|/etc/ssl/private/ssl-cert-snakeoil' /etc/apache2/ \
|
||||
&& grep -q 'lsky-selfsigned.crt' /etc/apache2/sites-available/default-ssl.conf
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F23:Apache 版本号外泄 —— 关掉 Server 头里的精确版本与页脚签名。
|
||||
# 实测(2026-09-30,本机 docker run 已发布镜像):响应头 `Server: Apache/2.4.68 (Debian)`。
|
||||
# (同一次探测还看到 `X-Powered-By: PHP/8.3.35` —— 那条由下面的 PHP ini 加固负责。)
|
||||
# ServerTokens Prod → Server 头只发 "Apache"(不带版本 / 模块 / OS);
|
||||
# ServerSignature Off → 错误页与目录列表不再追加 "Apache/2.4.68 (Debian) Server at …" 页脚
|
||||
# (Debian 出厂默认本就是 Off,这里显式写死:基镜像换版本时默认值可能变)。
|
||||
# 为什么放 conf-enabled 的独立文件、而不是 000-default.conf.template 那个 vhost 模板:
|
||||
# ① ServerTokens 是**全局(main server config)指令**,写进 <VirtualHost> 里 Apache 会直接
|
||||
# 拒绝加载该配置("ServerTokens not allowed in <VirtualHost> context")→ 整站起不来;
|
||||
# 它必须落在全局上下文,而模板整份就是 vhost。
|
||||
# ② 那个模板同时管 HTTPS vhost、目录权限等一堆东西,改它等于改 vhost 行为,风险面远大于
|
||||
# 「加一个 conf-enabled 文件」。
|
||||
# ③ apache2.conf 先 IncludeOptional mods-enabled/ 再 IncludeOptional conf-enabled/,入口
|
||||
# 脚本写 mpm.conf 也走这里;conf-enabled 不会被入口脚本覆写,换镜像照样生效。
|
||||
# 构建期自证:① 文件真的在;② 两行**逐行精确**在里面(grep -qx,不是模糊包含)。
|
||||
# 语法由真容器自证(CI 的 smoke 步骤会起容器跑 Apache,配置写错站点就起不来)。
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN printf '%s\n' \
|
||||
'# fork 加固(F23):不对外泄露 Apache 精确版本' \
|
||||
'ServerTokens Prod' \
|
||||
'ServerSignature Off' \
|
||||
> /etc/apache2/conf-enabled/zz-lsky-hardening.conf \
|
||||
&& test -f /etc/apache2/conf-enabled/zz-lsky-hardening.conf \
|
||||
&& grep -qx 'ServerTokens Prod' /etc/apache2/conf-enabled/zz-lsky-hardening.conf \
|
||||
&& grep -qx 'ServerSignature Off' /etc/apache2/conf-enabled/zz-lsky-hardening.conf
|
||||
|
||||
# ftp 必须**显式**装进运行时镜像:官方 php:8.1 镜像自带 ftp,php:8.3 的没有
|
||||
# (2026-09-28 换 8.3 时 CI 实测:builder 阶段 composer install 直接失败,
|
||||
# 报 league/flysystem-ftp requires ext-ftp)。它是 Lsky 的 FTP 存储驱动要用的扩展,
|
||||
@@ -275,6 +314,27 @@ RUN apt-get update && \
|
||||
chown -R www-data:root /var/www; \
|
||||
chmod -R g=u /var/www
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F23:PHP ini 加固 —— 关掉「版本号外泄」与「错误直出」。
|
||||
# 实测(2026-09-30,本机 docker run 已发布镜像):响应头带 `X-Powered-By: PHP/8.3.35`。
|
||||
# 官方 php 镜像**不带 php.ini**(只有 php.ini-production/-development 两个样本),于是
|
||||
# PHP 编译期的默认值直接生效:expose_php=1、display_errors=1。
|
||||
# expose_php=0 → 不再发 X-Powered-By(少给扫描器一条精确版本情报);
|
||||
# display_errors=0 → 运行期错误不往 HTTP 响应里吐(Laravel 自己的错误处理/日志不受影响)。
|
||||
# 文件名用 zz- 前缀:conf.d 里的 ini 按**文件名 ASCII 序**加载、后加载的覆盖先前的,
|
||||
# zz- 排最后 = 以后基镜像或扩展安装器再加什么 ini,都盖不掉这两项(顺序就是这里的关键)。
|
||||
# 只改镜像的 PHP 配置,不碰应用代码,也不动现有那几份 ini(upload / opcache / memory)。
|
||||
# 构建期自证:用 php -r **真读回 ini_get()** 断言已是关闭态 —— 不是 grep 配置文件里有没有那两行
|
||||
# (那只证明"文件写了",证明不了"PHP 真的读到了")。判定用 filter_var(FILTER_VALIDATE_BOOL),
|
||||
# 兼容 Off / false / 0 / 空串 几种写法;CLI 与 Apache 模块用的是同一份 /usr/local/etc/php/conf.d。
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN printf '%s\n' \
|
||||
'expose_php=0' \
|
||||
'display_errors=0' \
|
||||
> /usr/local/etc/php/conf.d/zz-lsky-hardening.ini \
|
||||
&& test -f /usr/local/etc/php/conf.d/zz-lsky-hardening.ini \
|
||||
&& php -r 'foreach (["expose_php", "display_errors"] as $k) { if (filter_var(ini_get($k), FILTER_VALIDATE_BOOL)) { fwrite(STDERR, "ini 加固自证失败:".$k." 仍开着 (".var_export(ini_get($k), true).")".PHP_EOL); exit(1); } echo $k."=".var_export(ini_get($k), true)." 已关闭".PHP_EOL; }'
|
||||
|
||||
# F21:不再随镜像发货证书。
|
||||
# 原来这里是 `COPY ./ssl /etc/ssl` —— 把仓库里那对 Debian snakeoil 证书(ssl/certs + ssl/private,
|
||||
# 在 GitHub 上人人可见)拷进镜像,模板的 HTTPS vhost 直接引用它 = 每个使用者共用同一份公开私钥。
|
||||
|
||||
+13
-26
@@ -42,16 +42,12 @@ ignoreUntil = 2027-06-30
|
||||
reason = "laravel/framework 9.x:上游修复在 12.69.0/13.30.0,9.x(EOL) 无补丁;镜像里 APP_DEBUG 关闭,调试页不对外"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2) psy/psysh v0.11.12 —— 生产依赖(随 laravel/tinker 进镜像)
|
||||
# 上游已在 **同一条 0.11 线** 发修复(0.11.23),是可修的。
|
||||
# 但改它必须动 src/composer.lock,而 Dockerfile 对 composer.lock 有 md5 自证,
|
||||
# 牵一发动全身;不在本次「只加门禁」范围内 —— 记为待办,到期前处理。
|
||||
# 想根治:把 psy/psysh 升到 >=0.11.23 并同步更新 Dockerfile 里的 lock md5 自证。
|
||||
# 2) psy/psysh —— **已修**(2026-09-30),不再需要白名单。
|
||||
# 原 v0.11.12 命中 GHSA-4486-gxhx-5mg7(CVE-2026-25129:CWD 下 .psysh.php 自动加载
|
||||
# 导致本地提权);它是**生产依赖**(随 laravel/tinker 进镜像),且上游在同一条 0.11 线
|
||||
# 发了修复。已把 src/composer.lock 升到 v0.11.23,并同步更新 Dockerfile 里的 lock md5 自证
|
||||
# (47dc4ba7… → 12ab2333…)。没有白名单兜底 —— 若 lock 被回退,这条会被扫描重新报出来。
|
||||
# -----------------------------------------------------------------------------
|
||||
[[IgnoredVulns]]
|
||||
id = "GHSA-4486-gxhx-5mg7" # CVE-2026-25129:CWD 下 .psysh.php 自动加载导致本地提权
|
||||
ignoreUntil = 2026-12-31
|
||||
reason = "psy/psysh 0.11.12:同线 0.11.23 已修,但升级需改 composer.lock + Dockerfile md5 自证(待办);镜像内 psysh 仅本机可触达,无远程面"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 3) maximebf/debugbar v1.18.2 —— **开发依赖**(packages-dev)
|
||||
@@ -79,21 +75,12 @@ ignoreUntil = 2026-12-31
|
||||
reason = "phpunit 是 packages-dev 开发依赖,不进 --no-dev 镜像;同线 9.6.33 已修,升级需动 lock(待办)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 5) sweetalert2@11.4.6 —— 只被 test/ 的行为测试用(test/package-lock.json),
|
||||
# 是测试工具链依赖,不随镜像发布(镜像里没有 sweetalert2)。
|
||||
# 上游已发 11.22.4(同线可修)→ 升级 test/package-lock.json 即可,属待办。
|
||||
# 5) sweetalert2 —— **已修**(2026-09-30),不再需要白名单。
|
||||
# 它既是 test/ 行为测试的依赖(test/package-lock.json),也作为前端依赖被打进
|
||||
# 镜像里的 public/js/app.js(resources/js/bootstrap.js 里 require 的)—— 所以本轮
|
||||
# 把它从 11.4.6 升到 11.22.4(三条公告的总修复版本),src/ 与 test/ 两侧同步升。
|
||||
# 锁定后:src/package-lock.json 与 test/package-lock.json 都解析到 11.22.4
|
||||
# (swal-theme.test.mjs 有「两侧版本逐字一致」断言兜底),并重建了 public/js/app.js。
|
||||
# 三条公告(GHSA-457r-cqc8-9vj9 / GHSA-8jh9-wqpf-q52c / GHSA-pg98-6v7f-2xfv)随之消失,
|
||||
# 白名单条目已删除 —— 若任一侧的锁被回退,会被扫描重新报出来。
|
||||
# -----------------------------------------------------------------------------
|
||||
[[IgnoredVulns]]
|
||||
id = "GHSA-457r-cqc8-9vj9"
|
||||
ignoreUntil = 2026-12-31
|
||||
reason = "sweetalert2 仅为 test/ 行为测试依赖,不进镜像;同线 11.22.4 已修,升 test/package-lock.json 即消(待办)"
|
||||
|
||||
[[IgnoredVulns]]
|
||||
id = "GHSA-8jh9-wqpf-q52c"
|
||||
ignoreUntil = 2026-12-31
|
||||
reason = "同上:test-only 依赖,不进镜像,同线可修"
|
||||
|
||||
[[IgnoredVulns]]
|
||||
id = "GHSA-pg98-6v7f-2xfv"
|
||||
ignoreUntil = 2026-12-31
|
||||
reason = "同上:test-only 依赖,不进镜像,同线可修"
|
||||
Generated
+12
-8
@@ -5162,16 +5162,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
"version": "v0.11.12",
|
||||
"version": "v0.11.23",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bobthecow/psysh.git",
|
||||
"reference": "52cb7c47d403c31c0adc9bf7710fc355f93c20f7"
|
||||
"reference": "e9ac4fae08b0bc7fb4f8391b220daa1dc55f03bf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/52cb7c47d403c31c0adc9bf7710fc355f93c20f7",
|
||||
"reference": "52cb7c47d403c31c0adc9bf7710fc355f93c20f7",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/e9ac4fae08b0bc7fb4f8391b220daa1dc55f03bf",
|
||||
"reference": "e9ac4fae08b0bc7fb4f8391b220daa1dc55f03bf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5199,8 +5199,12 @@
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": false,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "0.11.x-dev"
|
||||
"dev-0.11": "0.11.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -5232,9 +5236,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bobthecow/psysh/issues",
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.12"
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.23"
|
||||
},
|
||||
"time": "2023-01-29T21:24:40+00:00"
|
||||
"time": "2026-01-30T05:01:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "qiniu/php-sdk",
|
||||
@@ -11400,5 +11404,5 @@
|
||||
"php": "^8.0"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
Generated
+8
-8
@@ -34,7 +34,7 @@
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^14.0.1",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sweetalert2": "^11.3.3",
|
||||
"sweetalert2": "11.22.4",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"toastr": "^2.1.4",
|
||||
"viewerjs": "^1.10.2"
|
||||
@@ -16056,13 +16056,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.4.6",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.6.tgz",
|
||||
"integrity": "sha512-7UDJV8etrJQ6vJGh88k7D63qBKtwEl8JV7zIxmO2Frp8ZGQDgQQC6f9ivqiVwLRxoEyfxsn/v3T6Zykk7/vlEg==",
|
||||
"version": "11.22.4",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.22.4.tgz",
|
||||
"integrity": "sha512-JwcRODfozxiKmspFp+xctZ2izAmLAKbRPcoLMEW7LdugN/YmNrX1LT7hdBW87qsgupEO1ukBBuB17KzKFKW0tg==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://sweetalert2.github.io/#donations"
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/tableize-object": {
|
||||
@@ -30403,9 +30403,9 @@
|
||||
}
|
||||
},
|
||||
"sweetalert2": {
|
||||
"version": "11.4.6",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.6.tgz",
|
||||
"integrity": "sha512-7UDJV8etrJQ6vJGh88k7D63qBKtwEl8JV7zIxmO2Frp8ZGQDgQQC6f9ivqiVwLRxoEyfxsn/v3T6Zykk7/vlEg==",
|
||||
"version": "11.22.4",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.22.4.tgz",
|
||||
"integrity": "sha512-JwcRODfozxiKmspFp+xctZ2izAmLAKbRPcoLMEW7LdugN/YmNrX1LT7hdBW87qsgupEO1ukBBuB17KzKFKW0tg==",
|
||||
"dev": true
|
||||
},
|
||||
"tableize-object": {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^14.0.1",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sweetalert2": "^11.3.3",
|
||||
"sweetalert2": "11.22.4",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"toastr": "^2.1.4",
|
||||
"viewerjs": "^1.10.2"
|
||||
|
||||
@@ -1 +1 @@
|
||||
.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);float:right;font-size:20px;font-weight:700;line-height:1;opacity:.8;position:relative;right:-.3em;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;top:-.3em}.toast-close-button:focus,.toast-close-button:hover{color:#000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);opacity:.4;text-decoration:none}.rtl .toast-close-button{float:left;left:-.3em;right:.3em}button.toast-close-button{-webkit-appearance:none;background:0 0;border:0;cursor:pointer;padding:0}.toast-top-center{right:0;top:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{right:0;top:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{left:12px;top:12px}.toast-top-right{right:12px;top:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{pointer-events:none;position:fixed;z-index:999999}#toast-container *{box-sizing:border-box}#toast-container>div{background-position:15px;background-repeat:no-repeat;border-radius:3px;box-shadow:0 0 12px #999;color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);margin:0 0 6px;opacity:.8;overflow:hidden;padding:15px 15px 15px 50px;pointer-events:auto;position:relative;width:300px}#toast-container>div.rtl{background-position:right 15px center;direction:rtl;padding:15px 50px 15px 15px}#toast-container>div:hover{box-shadow:0 0 12px #000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);opacity:1}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{margin-left:auto;margin-right:auto;width:300px}#toaLine truncated
|
||||
.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);float:right;font-size:20px;font-weight:700;line-height:1;opacity:.8;position:relative;right:-.3em;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;top:-.3em}.toast-close-button:focus,.toast-close-button:hover{color:#000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);opacity:.4;text-decoration:none}.rtl .toast-close-button{float:left;left:-.3em;right:.3em}button.toast-close-button{-webkit-appearance:none;background:0 0;border:0;cursor:pointer;padding:0}.toast-top-center{right:0;top:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{right:0;top:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{left:12px;top:12px}.toast-top-right{right:12px;top:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{pointer-events:none;position:fixed;z-index:999999}#toast-container *{box-sizing:border-box}#toast-container>div{background-position:15px;background-repeat:no-repeat;border-radius:3px;box-shadow:0 0 12px #999;color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);margin:0 0 6px;opacity:.8;overflow:hidden;padding:15px 15px 15px 50px;pointer-events:auto;position:relative;width:300px}#toast-container>div.rtl{background-position:right 15px center;direction:rtl;padding:15px 50px 15px 15px}#toast-container>div:hover{box-shadow:0 0 12px #000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);opacity:1}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{margin-left:auto;margin-right:auto;width:300px}#toaLine truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! For license information please see app.js.LICENSE.txt */
|
||||
(()=>{var e,t={192:(e,t,n)=>{"use strict";var r,o,i,a,s=!1,u=!1,l=[];function c(e){!function(e){l.includes(e)||l.push(e);u||s||(s=!0,queueMicrotask(d))}(e)}function f(e){let t=l.indexOf(e);-1!==t&&l.splice(t,1)}function d(){s=!1,u=!0;for(let e=0;e<l.length;e++)l[e]();l.length=0,u=!1}var p=!0;function h(e){o=e}var g=[],m=[],y=[];function v(e,t){"function"==typeof t?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,m.push(t))}function w(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach((([n,r])=>{(void 0===t||t.includes(n))&&(r.forEach((e=>e())),delete e._x_attributeCleanups[n])}))}var b=new MutationObserver(O),x=!1;function _(){b.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),x=!0}function E(){(k=k.concat(b.takeRecords())).length&&!C&&(C=!0,queueMicrotask((()=>{O(k),k.length=0,C=!1}))),b.disconnect(),x=!1}var k=[],C=!1;function A(e){if(!x)return e();E();let t=e();return _(),t}var T=!1,S=[];function O(e){if(T)return void(S=S.concat(e));let t=[],n=[],r=new Map,o=new Map;for(let i=0;i<e.length;i++)if(!e[i].target._x_ignoreMutationObserver&&("childList"===e[i].type&&(e[i].addedNodes.forEach((e=>1===e.nodeType&&t.push(e))),e[i].removedNodes.forEach((e=>1===e.nodeType&&n.push(e)))),"attributes"===e[i].type)){let t=e[i].target,n=e[i].attributeName,a=e[i].oldValue,s=()=>{r.has(t)||r.set(t,[]),r.get(t).push({name:n,value:t.getAttribute(n)})},u=()=>{o.has(t)||o.set(t,[]),o.get(t).push(n)};t.hasAttribute(n)&&null===a?s():t.hasAttribute(n)?(u(),s()):u()}o.forEach(((e,t)=>{w(t,e)})),r.forEach(((e,t)=>{g.forEach((n=>n(t,e)))}));for(let e of n)if(!t.includes(e)&&(m.forEach((t=>t(e))),e._x_cleanups))for(;e._x_cleanups.length;)e._x_cleanups.pop()();t.forEach((e=>{e._x_ignoreSelf=!0,e._x_ignore=!0}));for(let e of t)n.includes(e)||e.isConnected&&(delete e._x_ignoreSelf,delete e._x_ignore,y.forEach((t=>t(e))),e._x_ignore=!0,e._x_ignoreSelf=!0);t.forEach((e=>{delete e._x_ignoreSelf,delete e._x_ignore})),t=null,n=null,r=null,o=null}function j(e){return B(L(e))}function R(e,t,n){return e._x_dataStack=[t,...L(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter((e=>e!==t))}}function P(e,t){let n=e._x_dataStack[0];Object.entries(t).forEach((([e,t])=>{n[e]=t}))}function L(e){return e._x_dataStack?e._x_dataStack:"function"==typeof ShadowRoot&&e instanceof ShadowRoot?L(e.host):e.parentNode?L(e.parentNode):[]}function B(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap((e=>Object.keys(e))))),has:(t,n)=>e.some((e=>e.hasOwnProperty(n))),get:(n,r)=>(e.find((e=>{if(e.hasOwnProperty(r)){let n=Object.getOwnPropertyDescriptor(e,r);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let o=n.get,i=n.set,a=n;o=o&&o.bind(t),i=i&&i.bind(t),o&&(o._x_alreadyBound=!0),i&&(i._x_alreadyBound=!0),Object.defineProperty(e,r,{...a,get:o,set:i})}return!0}return!1}))||{})[r],set:(t,n,r)=>{let o=e.find((e=>e.hasOwnProperty(n)));return o?o[n]=r:e[e.length-1][n]=r,!0}});return t}function D(e){let t=(n,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach((([o,{value:i,enumerable:a}])=>{if(!1===a||void 0===i)return;let s=""===r?o:`${r}.${o}`;var u;"object"==typeof i&&null!==i&&i._x_interceptor?n[o]=i.initialize(e,s,o):"object"!=typeof(u=i)||Array.isArray(u)||null===u||i===n||i instanceof Element||t(i,s)}))};return t(e)}function N(e,t=()=>{}){let n={initialValue:void 0,_x_interceptor:!0,initialize(t,n,r){return e(this.initialValue,(()=>function(e,t){return t.split(".").reduce(((e,t)=>e[t]),e)}(t,n)),(e=>I(t,n,e)),n,r)}};return t(n),e=>{if("object"==typeof e&&null!==e&&e._x_interceptor){let t=n.initialize.bind(n);n.initialize=(r,o,i)=>{let a=e.initialize(r,o,i);return n.initialValue=a,t(r,o,i)}}else n.initialValue=e;return n}}function I(e,t,n){if("string"==typeof t&&(t=t.split(".")),1!==t.length){if(0===t.length)throw error;return e[t[0]]||(e[t[0]]={}),I(e[t[0]],t.slice(1),n)}e[t[0]]=n}var M={};function q(e,t){M[e]=t}function U(e,t){return Object.entries(M).forEach((([n,r])=>{Object.defineProperty(e,`$${n}`,{get(){let[e,n]=oe(t);return e={interceptor:N,...e},v(t,n),r(t,e)},enumerable:!1})})),e}function z(e,t,n,...r){try{return n(...r)}catch(n){F(n,e,t)}}function F(e,t,n=void 0){Object.assign(e,{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message}\n\n${n?'Expression: "'+n+'"\n\n':""}`,t),setTimeout((()=>{throw e}),0)}function H(e,t,n={}){let r;return W(e,t)((e=>r=e),n),r}function W(...e){return V(...e)}var V=Y;function Y(e,t){let n={};U(n,e);let r=[n,...L(e)];if("function"==typeof t)return function(e,t){return(n=()=>{},{scope:r={},params:o=[]}={})=>{X(n,t.apply(B([r,...e]),o))}}(r,t);let o=function(e,t,n){let r=function(e,t){if(K[e])return K[e];let n=Object.getPrototypeOf((async function(){})).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;const o=()=>{try{return new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return
Line truncated
|
||||
(()=>{var e,t={192:(e,t,n)=>{"use strict";var r,o,i,a,s=!1,u=!1,l=[];function c(e){!function(e){l.includes(e)||l.push(e);u||s||(s=!0,queueMicrotask(d))}(e)}function f(e){let t=l.indexOf(e);-1!==t&&l.splice(t,1)}function d(){s=!1,u=!0;for(let e=0;e<l.length;e++)l[e]();l.length=0,u=!1}var p=!0;function h(e){o=e}var g=[],m=[],w=[];function v(e,t){"function"==typeof t?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,m.push(t))}function y(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach((([n,r])=>{(void 0===t||t.includes(n))&&(r.forEach((e=>e())),delete e._x_attributeCleanups[n])}))}var b=new MutationObserver(O),x=!1;function _(){b.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),x=!0}function E(){(k=k.concat(b.takeRecords())).length&&!A&&(A=!0,queueMicrotask((()=>{O(k),k.length=0,A=!1}))),b.disconnect(),x=!1}var k=[],A=!1;function C(e){if(!x)return e();E();let t=e();return _(),t}var T=!1,S=[];function O(e){if(T)return void(S=S.concat(e));let t=[],n=[],r=new Map,o=new Map;for(let i=0;i<e.length;i++)if(!e[i].target._x_ignoreMutationObserver&&("childList"===e[i].type&&(e[i].addedNodes.forEach((e=>1===e.nodeType&&t.push(e))),e[i].removedNodes.forEach((e=>1===e.nodeType&&n.push(e)))),"attributes"===e[i].type)){let t=e[i].target,n=e[i].attributeName,a=e[i].oldValue,s=()=>{r.has(t)||r.set(t,[]),r.get(t).push({name:n,value:t.getAttribute(n)})},u=()=>{o.has(t)||o.set(t,[]),o.get(t).push(n)};t.hasAttribute(n)&&null===a?s():t.hasAttribute(n)?(u(),s()):u()}o.forEach(((e,t)=>{y(t,e)})),r.forEach(((e,t)=>{g.forEach((n=>n(t,e)))}));for(let e of n)if(!t.includes(e)&&(m.forEach((t=>t(e))),e._x_cleanups))for(;e._x_cleanups.length;)e._x_cleanups.pop()();t.forEach((e=>{e._x_ignoreSelf=!0,e._x_ignore=!0}));for(let e of t)n.includes(e)||e.isConnected&&(delete e._x_ignoreSelf,delete e._x_ignore,w.forEach((t=>t(e))),e._x_ignore=!0,e._x_ignoreSelf=!0);t.forEach((e=>{delete e._x_ignoreSelf,delete e._x_ignore})),t=null,n=null,r=null,o=null}function j(e){return B(P(e))}function R(e,t,n){return e._x_dataStack=[t,...P(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter((e=>e!==t))}}function L(e,t){let n=e._x_dataStack[0];Object.entries(t).forEach((([e,t])=>{n[e]=t}))}function P(e){return e._x_dataStack?e._x_dataStack:"function"==typeof ShadowRoot&&e instanceof ShadowRoot?P(e.host):e.parentNode?P(e.parentNode):[]}function B(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap((e=>Object.keys(e))))),has:(t,n)=>e.some((e=>e.hasOwnProperty(n))),get:(n,r)=>(e.find((e=>{if(e.hasOwnProperty(r)){let n=Object.getOwnPropertyDescriptor(e,r);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let o=n.get,i=n.set,a=n;o=o&&o.bind(t),i=i&&i.bind(t),o&&(o._x_alreadyBound=!0),i&&(i._x_alreadyBound=!0),Object.defineProperty(e,r,{...a,get:o,set:i})}return!0}return!1}))||{})[r],set:(t,n,r)=>{let o=e.find((e=>e.hasOwnProperty(n)));return o?o[n]=r:e[e.length-1][n]=r,!0}});return t}function D(e){let t=(n,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach((([o,{value:i,enumerable:a}])=>{if(!1===a||void 0===i)return;let s=""===r?o:`${r}.${o}`;var u;"object"==typeof i&&null!==i&&i._x_interceptor?n[o]=i.initialize(e,s,o):"object"!=typeof(u=i)||Array.isArray(u)||null===u||i===n||i instanceof Element||t(i,s)}))};return t(e)}function N(e,t=()=>{}){let n={initialValue:void 0,_x_interceptor:!0,initialize(t,n,r){return e(this.initialValue,(()=>function(e,t){return t.split(".").reduce(((e,t)=>e[t]),e)}(t,n)),(e=>I(t,n,e)),n,r)}};return t(n),e=>{if("object"==typeof e&&null!==e&&e._x_interceptor){let t=n.initialize.bind(n);n.initialize=(r,o,i)=>{let a=e.initialize(r,o,i);return n.initialValue=a,t(r,o,i)}}else n.initialValue=e;return n}}function I(e,t,n){if("string"==typeof t&&(t=t.split(".")),1!==t.length){if(0===t.length)throw error;return e[t[0]]||(e[t[0]]={}),I(e[t[0]],t.slice(1),n)}e[t[0]]=n}var M={};function q(e,t){M[e]=t}function U(e,t){return Object.entries(M).forEach((([n,r])=>{Object.defineProperty(e,`$${n}`,{get(){let[e,n]=oe(t);return e={interceptor:N,...e},v(t,n),r(t,e)},enumerable:!1})})),e}function z(e,t,n,...r){try{return n(...r)}catch(n){H(n,e,t)}}function H(e,t,n=void 0){Object.assign(e,{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message}\n\n${n?'Expression: "'+n+'"\n\n':""}`,t),setTimeout((()=>{throw e}),0)}function F(e,t,n={}){let r;return W(e,t)((e=>r=e),n),r}function W(...e){return V(...e)}var V=Y;function Y(e,t){let n={};U(n,e);let r=[n,...P(e)];if("function"==typeof t)return function(e,t){return(n=()=>{},{scope:r={},params:o=[]}={})=>{X(n,t.apply(B([r,...e]),o))}}(r,t);let o=function(e,t,n){let r=function(e,t){if(K[e])return K[e];let n=Object.getPrototypeOf((async function(){})).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;const o=()=>{try{return new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return
Line truncated
|
||||
@@ -197,10 +197,15 @@ input:checked + .slider:before {
|
||||
* 12px 圆角、13/14px 字号、16px semibold 标题、令牌色(亮/暗自动正确)、
|
||||
* 按钮 = 全局 ls-btn / ls-btn-primary、输入框 = ls-input。
|
||||
* 注意:sweetalert2 的样式是运行时由 JS 注入到 <head> 末尾的(排在 common.css 之后),
|
||||
* 所以这里统一加 `html ` 前缀把权重提到 (0,2,1) 压过它的 (0,2,0),不写 !important。
|
||||
* 所以这里统一加 `html:root ` 前缀把权重提到 (0,2,1) 压过它的 (0,1,1),不写 !important。
|
||||
* (11.4 时代它的默认规则是纯类选择器,`html ` 一位元素就够了;11.22 起它改成
|
||||
* `div:where(.swal2-container) .swal2-input { height: 2.625em; padding: 0 .75em }`
|
||||
* 这种「祖先 :where 包裹 + 类」写法,权重正好 = `html `.swal2-x(都是 (0,1,1)),
|
||||
* 而它的样式后注入 → 平权重时按顺序赢,输入框高度会被它顶回 36.75px。
|
||||
* `:root` 是伪类,加在 html 上把我们的权重再抬一位到 (0,2,1),重新稳压它。)
|
||||
* 颜色全部走设计令牌,亮/暗两套自动生效 —— 所以下面不再需要单独的暗色覆盖。
|
||||
* ------------------------------------------------------------------------- */
|
||||
html {
|
||||
html:root {
|
||||
// 遮罩:与 x-modal 的 bg-black/50 + backdrop-blur-[2px] 对齐
|
||||
.swal2-container.swal2-backdrop-show,
|
||||
.swal2-container.swal2-noanimation {
|
||||
|
||||
+17
-4
@@ -15,9 +15,21 @@
|
||||
RegisteredUserController::class, 'create'
|
||||
])->middleware('guest')->middleware(CheckIsEnableRegistration::class)->name('register');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fork 加固(防爆破):认证类 POST 端点加路由级节流。
|
||||
// 背景:这些路由原来**一个 throttle 都没有**(全仓只有 verification.verify 的 6,1、
|
||||
// verification.send 的 3,1、api 的 3,1)。Breeze 的 LoginRequest 里那个 RateLimiter 是
|
||||
// 按 (邮箱|IP) 计数的(5 次失败 / 60 秒衰减),只能挡「盯着同一个账号撞库」;
|
||||
// 换着邮箱撒网、或每个 IP 轮着来,它一点不挡。下面的 throttle 是**按路由+IP** 的硬上限,
|
||||
// 两个限流互补:前者防单账号被撞,后者防同 IP 批量请求(邮箱枚举 / 注册滥用 / 邮件轰炸)。
|
||||
// 注意(已知取舍,见 README):应用里 TrustProxies 是 $proxies='*',IP 取自
|
||||
// X-Forwarded-For —— 前面有反向代理时拿到的是真实客户端 IP;**若站点直接暴露、没有代理
|
||||
// 覆写这个头,攻击者能伪造它绕过按 IP 的限流**。这一条不改(改它会影响所有取 IP 的地方)。
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Route::post('/register', [
|
||||
RegisteredUserController::class, 'store'
|
||||
])->middleware('guest')->middleware(CheckIsEnableRegistration::class);
|
||||
])->middleware(['guest', CheckIsEnableRegistration::class, 'throttle:5,1']);
|
||||
|
||||
Route::get('/login', [
|
||||
AuthenticatedSessionController::class, 'create',
|
||||
@@ -25,7 +37,7 @@
|
||||
|
||||
Route::post('/login', [
|
||||
AuthenticatedSessionController::class, 'store'
|
||||
])->middleware('guest');
|
||||
])->middleware(['guest', 'throttle:5,1']);
|
||||
|
||||
Route::get('/forgot-password', [
|
||||
PasswordResetLinkController::class, 'create',
|
||||
@@ -33,7 +45,7 @@
|
||||
|
||||
Route::post('/forgot-password', [
|
||||
PasswordResetLinkController::class, 'store',
|
||||
])->middleware('guest')->name('password.email');
|
||||
])->middleware(['guest', 'throttle:6,1'])->name('password.email');
|
||||
|
||||
Route::get('/reset-password/{token}', [
|
||||
NewPasswordController::class, 'create',
|
||||
@@ -59,9 +71,10 @@
|
||||
ConfirmablePasswordController::class, 'show',
|
||||
])->middleware('auth')->name('password.confirm');
|
||||
|
||||
// 同样是「验密码」的端点(Breeze 的密码确认),登录态下可无限次提交密码 —— 一并加节流。
|
||||
Route::post('/confirm-password', [
|
||||
ConfirmablePasswordController::class, 'store',
|
||||
])->middleware('auth');
|
||||
])->middleware(['auth', 'throttle:5,1']);
|
||||
|
||||
Route::post('/logout', [
|
||||
AuthenticatedSessionController::class, 'destroy',
|
||||
|
||||
Generated
+5
-6
@@ -9,7 +9,7 @@
|
||||
"jquery": "3.7.1",
|
||||
"jsdom": "^30.1.1",
|
||||
"less": "4.1.2",
|
||||
"sweetalert2": "11.4.6"
|
||||
"sweetalert2": "11.22.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22"
|
||||
@@ -667,14 +667,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.4.6",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.6.tgz",
|
||||
"integrity": "sha512-7UDJV8etrJQ6vJGh88k7D63qBKtwEl8JV7zIxmO2Frp8ZGQDgQQC6f9ivqiVwLRxoEyfxsn/v3T6Zykk7/vlEg==",
|
||||
"version": "11.22.4",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.22.4.tgz",
|
||||
"integrity": "sha512-JwcRODfozxiKmspFp+xctZ2izAmLAKbRPcoLMEW7LdugN/YmNrX1LT7hdBW87qsgupEO1ukBBuB17KzKFKW0tg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://sweetalert2.github.io/#donations"
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"//": "约定:npm test 用 shell 通配跑全量 *.test.mjs(新增测试文件不用改这里),任一个失败即非 0 退出;需要 Node >= 22.22(jsdom 30 的 engines 要求,Node 18 会报 ERR_REQUIRE_ESM)",
|
||||
"//deps": "sweetalert2 / less 在这里**正经声明**:swal-theme.test.mjs 要读 sweetalert2 自己注入的 CSS、并调 lessc 编译 common.less。以前它直接去读 ../src/node_modules,换个环境(CI 新拉的 checkout 没有 src/node_modules)就跑不了。这两个包用**精确版本**(= src/package-lock.json 里应用构建期实际用的那份):CSS 断言依赖 sweetalert2 的具体默认样式,换版本会真的改变结果(实测 11.26 就会红)。src 升级时这里要同步跟着升(swal-theme.test.mjs 里有逐字一致性断言兜底)。",
|
||||
"//deps": "sweetalert2 / less 在这里**正经声明**:swal-theme.test.mjs 要读 sweetalert2 自己注入的 CSS、并调 lessc 编译 common.less。以前它直接去读 ../src/node_modules,换个环境(CI 新拉的 checkout 没有 src/node_modules)就跑不了。这两个包用**精确版本**(= src/package-lock.json 里应用构建期实际用的那份):CSS 断言依赖 sweetalert2 的具体默认样式,换版本会真的改变结果 —— 实测升级 11.4.6 → 11.22.4 时它把默认规则改成 `div:where(.swal2-container) .swal2-xxx`(权重从 (0,1,0) 抬到 (0,1,1)),输入框高度那几条断言当场变红,直到把 common.less 的皮肤前缀从 `html ` 抬到 `html:root `。src 升级时这里要同步跟着升(swal-theme.test.mjs 里有逐字一致性断言兜底)。",
|
||||
"engines": {
|
||||
"node": ">=22.22"
|
||||
},
|
||||
@@ -15,6 +15,6 @@
|
||||
"jquery": "3.7.1",
|
||||
"jsdom": "^30.1.1",
|
||||
"less": "4.1.2",
|
||||
"sweetalert2": "11.4.6"
|
||||
"sweetalert2": "11.22.4"
|
||||
}
|
||||
}
|
||||
+28
-12
@@ -181,8 +181,8 @@ console.log('\n[③ 旧弹窗] 三个确认框改用与新弹窗同一套皮肤
|
||||
|
||||
check('皮肤在同一份 common.less 里升级(没有另起一套文件)',
|
||||
fs.readdirSync(path.join(SRC, 'resources', 'css')).filter((f) => /swal/i.test(f)).length === 0);
|
||||
// 皮肤段 = 代码里的 `html { … }`(sweetalert2 主题)到 `html.dark { … }` 之间
|
||||
const swalSection = less.slice(less.indexOf('html {'), less.indexOf('html.dark {'));
|
||||
// 皮肤段 = 代码里的 `html:root { … }`(sweetalert2 主题)到 `html.dark { … }` 之间
|
||||
const swalSection = less.slice(less.indexOf('html:root {'), less.indexOf('html.dark {'));
|
||||
check('旧的「只在 html.dark 里覆盖」已升级成亮/暗共用(html.dark 里不再有 swal2 规则)',
|
||||
!/html\.dark\s*\{[\s\S]*?\.swal2-/.test(less));
|
||||
check('皮肤段里没有 !important(靠 html 前缀提权重;文件原有的 [x-cloak] 那条不算)',
|
||||
@@ -242,17 +242,30 @@ console.log('\n[③ 层叠] 复刻运行时注入顺序(common.css 在前、sw
|
||||
onlyOurs.window.close?.();
|
||||
|
||||
// 选择器权重(本测试只用得到 元素/类 这几种,够用了)
|
||||
// `:where(...)` 内部**不计分**(CSS 规定,jsdom 也照此实现)—— 先剥掉再数。
|
||||
const spec = (sel) => {
|
||||
const s = sel.trim();
|
||||
const s = sel.trim().replace(/:where\([^()]*\)/g, '');
|
||||
const ids = (s.match(/#[\w-]+/g) || []).length;
|
||||
const cls = (s.match(/\.[\w-]+/g) || []).length + (s.match(/\[[^\]]+\]/g) || []).length
|
||||
+ (s.match(/(?<!:):[a-z-]+/g) || []).length;
|
||||
const els = (s.match(/(^|[\s>+~])[a-zA-Z][\w-]*/g) || []).length;
|
||||
return ids * 10000 + cls * 100 + els;
|
||||
};
|
||||
check('权重:sweetalert2 用 `.swal2-popup { border: none }` 关边框,我们那条 `html .swal2-popup` 权重更高',
|
||||
/\.swal2-popup\s*\{[^}]*border:\s*none/.test(swalCss) && spec('html .swal2-popup') > spec('.swal2-popup'),
|
||||
`ours=${spec('html .swal2-popup')} swal=${spec('.swal2-popup')}`);
|
||||
// sweetalert2 11.22 起:默认边框从字面量 `border: none` 改成变量声明
|
||||
// (`:root { --swal2-border: none }`),基础卡片规则写成
|
||||
// `div:where(.swal2-container) div:where(.swal2-popup) { border: var(--swal2-border); … }`。
|
||||
check('sweetalert2 默认无边框::root 里 `--swal2-border: none`,基础卡片规则用 `border: var(--swal2-border)`',
|
||||
/--swal2-border:\s*none/.test(swalCss)
|
||||
&& /div:where\(\.swal2-container\)\s+div:where\(\.swal2-popup\)\s*\{[^}]*border:\s*var\(--swal2-border\)/.test(swalCss));
|
||||
check('权重:我们的 `html:root .swal2-popup`((0,2,1))压过它那条基础规则 `div:where(…) div:where(…)`((0,0,2))',
|
||||
spec('html:root .swal2-popup') > spec('div:where(.swal2-container) div:where(.swal2-popup)'),
|
||||
`ours=${spec('html:root .swal2-popup')} swal=${spec('div:where(.swal2-container) div:where(.swal2-popup)')}`);
|
||||
// 输入框是这场「权重竞赛」里最吃紧的一处:11.22 的
|
||||
// `div:where(.swal2-container) .swal2-input { height: 2.625em; padding: 0 .75em }` 是 (0,1,1),
|
||||
// 与旧的 `html .swal2-input` 平权重、靠后注入取胜 —— 所以前缀必须抬到 `html:root`((0,2,1))。
|
||||
check('权重:我们的 `html:root .swal2-input`((0,2,1))压过它 `div:where(.swal2-container) .swal2-input`((0,1,1))',
|
||||
spec('html:root .swal2-input') > spec('div:where(.swal2-container) .swal2-input'),
|
||||
`ours=${spec('html:root .swal2-input')} swal=${spec('div:where(.swal2-container) .swal2-input')}`);
|
||||
expect('弹窗卡片:文字左对齐', '.swal2-popup', 'text-align', 'left');
|
||||
expect('标题 16px(默认 30px)', '.swal2-title', 'font-size', '16px');
|
||||
expect('标题 semibold 600', '.swal2-title', 'font-weight', '600');
|
||||
@@ -269,14 +282,17 @@ console.log('\n[③ 层叠] 复刻运行时注入顺序(common.css 在前、sw
|
||||
expect('校验提示 13px', '.swal2-validation-message', 'font-size', '13px');
|
||||
check('警示图标缩到 55px(默认 80px)', val('.swal2-icon', 'width') === '55px', val('.swal2-icon', 'width'));
|
||||
|
||||
// 反证:不加 html 前缀、权重不够的写法会输给 sweetalert2(说明上面这些不是白写的)
|
||||
// 反证:前缀只加一位(`html `)的写法会输给 sweetalert2 的
|
||||
// `div:where(.swal2-container) .swal2-input`((0,1,1) 平权重 + 它的样式后注入 → 它赢),
|
||||
// 说明把前缀抬到 `html:root `((0,2,1))不是白加的。卡片那条早先还有平权重问题,
|
||||
// 11.22 起它的基础规则换成 `div:where(…) div:where(…)`((0,0,2)),纯类覆盖本来就够 —— 换成输入框这条更贴现实。
|
||||
const weak = new JSDOM(`<!DOCTYPE html><html><head>
|
||||
<style>.swal2-popup { border-radius: 12px; }</style>
|
||||
<style>html .swal2-input { height: 38px; }</style>
|
||||
<style>${swalCss}</style></head><body>
|
||||
<div class="swal2-container"><div class="swal2-popup"></div></div></body></html>`);
|
||||
const weakVal = weak.window.getComputedStyle(weak.window.document.querySelector('.swal2-popup')).borderRadius;
|
||||
check('(对照)不带 html 前缀的 .swal2-popup 覆盖不住 sweetalert2 注入的默认值',
|
||||
weakVal !== '12px', `border-radius=${JSON.stringify(weakVal)}`);
|
||||
<div class="swal2-container"><div class="swal2-popup"><input class="swal2-input" value="x"></div></div></body></html>`);
|
||||
const weakVal = weak.window.getComputedStyle(weak.window.document.querySelector('.swal2-input')).height;
|
||||
check('(对照)只带 `html ` 前缀的 .swal2-input 覆盖不住 sweetalert2 注入的默认值',
|
||||
weakVal !== '38px', `height=${JSON.stringify(weakVal)}`);
|
||||
dom.window.close?.();
|
||||
weak.window.close?.();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user