diff --git a/README.md b/README.md index a080b431..94d26664 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ frp is an open source project with its ongoing development made possible entirel

Gold Sponsors

+

+ + +
+ The sovereign cloud that puts you in control +
+ An open source, self-hosted alternative to public clouds, built for data ownership and privacy +
+

+
## Recall.ai - API for meeting recordings @@ -40,16 +50,6 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and The complete IDE crafted for professional Go developers

- -

- - -
- The sovereign cloud that puts you in control -
- An open source, self-hosted alternative to public clouds, built for data ownership and privacy -
-

## What is frp? @@ -81,6 +81,7 @@ frp also offers a P2P connect mode. * [Split Configures Into Different Files](#split-configures-into-different-files) * [Server Dashboard](#server-dashboard) * [Client Admin UI](#client-admin-ui) + * [Dynamic Proxy Management (Store)](#dynamic-proxy-management-store) * [Monitor](#monitor) * [Prometheus](#prometheus) * [Authenticating the Client](#authenticating-the-client) @@ -149,7 +150,9 @@ We sincerely appreciate your support for frp. ## Architecture -![architecture](/doc/pic/architecture.png) +

+ architecture +

## Example Usage @@ -593,7 +596,7 @@ Then visit `https://[serverAddr]:7500` to see the dashboard in secure HTTPS conn ### Client Admin UI -The Client Admin UI helps you check and manage frpc's configuration. +The Client Admin UI helps you check and manage frpc's configuration and proxies. Configure an address for admin UI to enable this feature: @@ -606,6 +609,19 @@ webServer.password = "admin" Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin`. +#### Dynamic Proxy Management (Store) + +You can dynamically create, update, and delete proxies and visitors at runtime through the Web UI or API, without restarting frpc. + +To enable this feature, configure `store.path` to specify a file for persisting the configurations: + +```toml +[store] +path = "./db.json" +``` + +Proxies and visitors managed through the Store are saved to disk and automatically restored on frpc restart. They work alongside proxies defined in the configuration file — Store entries take precedence when names conflict. + ### Monitor When web server is enabled, frps will save monitor data in cache for 7 days. It will be cleared after process restart. diff --git a/README_zh.md b/README_zh.md index 1b4b6862..9a03b031 100644 --- a/README_zh.md +++ b/README_zh.md @@ -15,6 +15,16 @@ frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者

Gold Sponsors

+

+ + +
+ The sovereign cloud that puts you in control +
+ An open source, self-hosted alternative to public clouds, built for data ownership and privacy +
+

+
## Recall.ai - API for meeting recordings @@ -42,16 +52,6 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and The complete IDE crafted for professional Go developers

- -

- - -
- The sovereign cloud that puts you in control -
- An open source, self-hosted alternative to public clouds, built for data ownership and privacy -
-

## 为什么使用 frp ? diff --git a/Release.md b/Release.md index 9bee09dc..c42987d3 100644 --- a/Release.md +++ b/Release.md @@ -1,10 +1,3 @@ -## Features +## Fixes -* Added a built-in `store` capability for frpc, including persisted store source (`[store] path = "..."`), Store CRUD admin APIs (`/api/store/proxies*`, `/api/store/visitors*`) with runtime reload, and Store management pages in the frpc web dashboard. - -## Improvements - -* Kept proxy/visitor names as raw config names during completion; moved user-prefix handling to explicit wire-level naming logic. -* Added `noweb` build tag to allow compiling without frontend assets. `make build` now auto-detects missing `web/*/dist` directories and skips embedding, so a fresh clone can build without running `make web` first. The dashboard gracefully returns 404 when assets are not embedded. -* Improved config parsing errors: for `.toml` files, syntax errors now return immediately with parser position details (line/column when available) instead of falling through to YAML/JSON parsing, and TOML type mismatches report field-level errors without misleading line numbers. -* OIDC auth now caches the access token and refreshes it before expiry, avoiding a new token request on every heartbeat. Falls back to per-request fetch when the provider omits `expires_in`. +* Fixed a configuration-dependent authentication bypass in `type = "http"` proxies when `routeByHTTPUser` is used together with `httpUser` / `httpPassword`. This affected proxy-style requests. Proxy-style authentication failures now return `407 Proxy Authentication Required`. diff --git a/client/connector.go b/client/connector.go index 03d7fc29..51536750 100644 --- a/client/connector.go +++ b/client/connector.go @@ -119,6 +119,7 @@ func (c *defaultConnectorImpl) Open() error { fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024 session, err := fmux.Client(conn, fmuxCfg) if err != nil { + conn.Close() return err } c.muxSession = session diff --git a/doc/pic/architecture.jpg b/doc/pic/architecture.jpg new file mode 100644 index 00000000..5d9b78b3 Binary files /dev/null and b/doc/pic/architecture.jpg differ diff --git a/doc/pic/architecture.png b/doc/pic/architecture.png deleted file mode 100644 index 2ee54fc2..00000000 Binary files a/doc/pic/architecture.png and /dev/null differ diff --git a/go.mod b/go.mod index bfede7a9..e3a1ddd8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25.0 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/coreos/go-oidc/v3 v3.14.1 - github.com/fatedier/golib v0.5.1 + github.com/fatedier/golib v0.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.0 @@ -13,7 +13,7 @@ require ( github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/gomega v1.36.3 github.com/pelletier/go-toml/v2 v2.2.0 - github.com/pion/stun/v2 v2.0.0 + github.com/pion/stun/v3 v3.1.1 github.com/pires/go-proxyproto v0.7.0 github.com/prometheus/client_golang v1.19.1 github.com/quic-go/quic-go v0.55.0 @@ -22,15 +22,15 @@ require ( github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.17.1 github.com/vishvananda/netlink v1.3.0 github.com/xtaci/kcp-go/v5 v5.6.13 - golang.org/x/crypto v0.41.0 - golang.org/x/net v0.43.0 + golang.org/x/crypto v0.49.0 + golang.org/x/net v0.52.0 golang.org/x/oauth2 v0.28.0 - golang.org/x/sync v0.16.0 - golang.org/x/time v0.5.0 + golang.org/x/sync v0.20.0 + golang.org/x/time v0.10.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 gopkg.in/ini.v1 v1.67.0 k8s.io/apimachinery v0.28.8 @@ -38,7 +38,7 @@ require ( ) require ( - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -51,10 +51,9 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.12.0 // indirect - github.com/pion/dtls/v2 v2.2.7 // indirect - github.com/pion/logging v0.2.2 // indirect - github.com/pion/transport/v2 v2.2.1 // indirect - github.com/pion/transport/v3 v3.0.1 // indirect + github.com/pion/dtls/v3 v3.0.10 // indirect + github.com/pion/logging v0.2.4 // indirect + github.com/pion/transport/v4 v4.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect @@ -66,11 +65,12 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/vishvananda/netns v0.0.4 // indirect + github.com/wlynxg/anet v0.0.5 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/tools v0.42.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index f73117a3..4a5082c0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= +github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M= -github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ= +github.com/fatedier/golib v0.6.0 h1:/mgBZZbkbMhIEZoXf7nV8knpUDzas/b+2ruYKxx1lww= +github.com/fatedier/golib v0.6.0/go.mod h1:ArUGvPg2cOw/py2RAuBt46nNZH2VQ5Z70p109MAZpJw= github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6 h1:u92UUy6FURPmNsMBUuongRWC0rBqN6gd01Dzu+D21NE= github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6/go.mod h1:c5/tk6G0dSpXGzJN7Wk1OEie8grdSJAmeawId9Zvd34= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= @@ -78,16 +78,14 @@ github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= -github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg= +github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= +github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= +github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -128,11 +126,10 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI= github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU= @@ -149,11 +146,12 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk= github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= @@ -161,89 +159,54 @@ go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= diff --git a/pkg/nathole/discovery.go b/pkg/nathole/discovery.go index f25cd463..6fa0140f 100644 --- a/pkg/nathole/discovery.go +++ b/pkg/nathole/discovery.go @@ -19,7 +19,7 @@ import ( "net" "time" - "github.com/pion/stun/v2" + "github.com/pion/stun/v3" ) var responseTimeout = 3 * time.Second diff --git a/pkg/nathole/utils.go b/pkg/nathole/utils.go index 5f32142e..2a882830 100644 --- a/pkg/nathole/utils.go +++ b/pkg/nathole/utils.go @@ -21,7 +21,7 @@ import ( "strconv" "github.com/fatedier/golib/crypto" - "github.com/pion/stun/v2" + "github.com/pion/stun/v3" "github.com/fatedier/frp/pkg/msg" ) diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index c98b7cfb..14f04ede 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -14,7 +14,7 @@ package version -var version = "0.68.0" +var version = "0.68.1" func Full() string { return version diff --git a/pkg/util/vhost/http.go b/pkg/util/vhost/http.go index d12e7916..cac88e35 100644 --- a/pkg/util/vhost/http.go +++ b/pkg/util/vhost/http.go @@ -187,16 +187,25 @@ func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byE return nil, fmt.Errorf("%v: %s %s %s", ErrNoRouteFound, host, reqRouteInfo.URL, reqRouteInfo.HTTPUser) } -func (rp *HTTPReverseProxy) CheckAuth(domain, location, routeByHTTPUser, user, passwd string) bool { - vr, ok := rp.getVhost(domain, location, routeByHTTPUser) - if ok { - checkUser := vr.payload.(*RouteConfig).Username - checkPasswd := vr.payload.(*RouteConfig).Password - if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) { +func checkRouteAuthByRequest(req *http.Request, rc *RouteConfig) bool { + if rc == nil { + return true + } + if rc.Username == "" && rc.Password == "" { + return true + } + + if req.URL.Host != "" { + proxyAuth := req.Header.Get("Proxy-Authorization") + if proxyAuth == "" { return false } + user, passwd, ok := httppkg.ParseBasicAuth(proxyAuth) + return ok && user == rc.Username && passwd == rc.Password } - return true + + user, passwd, ok := req.BasicAuth() + return ok && user == rc.Username && passwd == rc.Password } // getVhost tries to get vhost router by route policy. @@ -266,18 +275,25 @@ func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req *http.Req go libio.Join(remote, client) } -func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Request { - user := "" - // If url host isn't empty, it's a proxy request. Get http user from Proxy-Authorization header. +func getRequestRouteUser(req *http.Request) string { if req.URL.Host != "" { proxyAuth := req.Header.Get("Proxy-Authorization") - if proxyAuth != "" { - user, _, _ = httppkg.ParseBasicAuth(proxyAuth) + if proxyAuth == "" { + // Preserve legacy proxy-mode routing when clients send only Authorization, + // so requests still hit the matched route and return 407 instead of 404. + // Auth validation intentionally does not share this fallback. + user, _, _ := req.BasicAuth() + return user } + user, _, _ := httppkg.ParseBasicAuth(proxyAuth) + return user } - if user == "" { - user, _, _ = req.BasicAuth() - } + user, _, _ := req.BasicAuth() + return user +} + +func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Request { + user := getRequestRouteUser(req) reqRouteInfo := &RequestRouteInfo{ URL: req.URL.Path, @@ -297,16 +313,19 @@ func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Requ } func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - domain, _ := httppkg.CanonicalHost(req.Host) - location := req.URL.Path - user, passwd, _ := req.BasicAuth() - if !rp.CheckAuth(domain, location, user, user, passwd) { - rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + newreq := rp.injectRequestInfoToCtx(req) + rc := newreq.Context().Value(RouteConfigKey).(*RouteConfig) + if !checkRouteAuthByRequest(req, rc) { + if req.URL.Host != "" { + rw.Header().Set("Proxy-Authenticate", `Basic realm="Restricted"`) + http.Error(rw, http.StatusText(http.StatusProxyAuthRequired), http.StatusProxyAuthRequired) + } else { + rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + } return } - newreq := rp.injectRequestInfoToCtx(req) if req.Method == http.MethodConnect { rp.connectHandler(rw, newreq) } else { diff --git a/pkg/util/vhost/http_test.go b/pkg/util/vhost/http_test.go new file mode 100644 index 00000000..237ae903 --- /dev/null +++ b/pkg/util/vhost/http_test.go @@ -0,0 +1,102 @@ +package vhost + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + httppkg "github.com/fatedier/frp/pkg/util/http" +) + +func TestCheckRouteAuthByRequest(t *testing.T) { + rc := &RouteConfig{ + Username: "alice", + Password: "secret", + } + + t.Run("accepts nil route config", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + require.True(t, checkRouteAuthByRequest(req, nil)) + }) + + t.Run("accepts route without credentials", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + require.True(t, checkRouteAuthByRequest(req, &RouteConfig{})) + }) + + t.Run("accepts authorization header", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.SetBasicAuth("alice", "secret") + require.True(t, checkRouteAuthByRequest(req, rc)) + }) + + t.Run("accepts proxy authorization header", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("alice", "secret")) + require.True(t, checkRouteAuthByRequest(req, rc)) + }) + + t.Run("rejects authorization fallback for proxy request", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.SetBasicAuth("alice", "secret") + require.False(t, checkRouteAuthByRequest(req, rc)) + }) + + t.Run("rejects wrong proxy authorization even when authorization matches", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.SetBasicAuth("alice", "secret") + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("alice", "wrong")) + require.False(t, checkRouteAuthByRequest(req, rc)) + }) + + t.Run("rejects when neither header matches", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.SetBasicAuth("alice", "wrong") + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("alice", "wrong")) + require.False(t, checkRouteAuthByRequest(req, rc)) + }) + + t.Run("rejects proxy authorization on direct request", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("alice", "secret")) + require.False(t, checkRouteAuthByRequest(req, rc)) + }) +} + +func TestGetRequestRouteUser(t *testing.T) { + t.Run("proxy request uses proxy authorization username", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.Host = "target.example.com" + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("proxy-user", "proxy-pass")) + req.SetBasicAuth("direct-user", "direct-pass") + + require.Equal(t, "proxy-user", getRequestRouteUser(req)) + }) + + t.Run("connect request keeps proxy authorization routing", func(t *testing.T) { + req := httptest.NewRequest("CONNECT", "http://target.example.com:443", nil) + req.Host = "target.example.com:443" + req.Header.Set("Proxy-Authorization", httppkg.BasicAuth("proxy-user", "proxy-pass")) + req.SetBasicAuth("direct-user", "direct-pass") + + require.Equal(t, "proxy-user", getRequestRouteUser(req)) + }) + + t.Run("direct request uses authorization username", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.Host = "example.com" + req.SetBasicAuth("direct-user", "direct-pass") + + require.Equal(t, "direct-user", getRequestRouteUser(req)) + }) + + t.Run("proxy request does not fall back when proxy authorization is invalid", func(t *testing.T) { + req := httptest.NewRequest("GET", "http://target.example.com/", nil) + req.Host = "target.example.com" + req.Header.Set("Proxy-Authorization", "Basic !!!") + req.SetBasicAuth("direct-user", "direct-pass") + + require.Empty(t, getRequestRouteUser(req)) + }) +} diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index efd098ab..7713fb3e 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -241,6 +241,31 @@ func (f *Framework) AllocPort() int { return port } +func (f *Framework) AllocPortExcludingRanges(ranges ...[2]int) int { + for range 1000 { + port := f.portAllocator.Get() + ExpectTrue(port > 0, "alloc port failed") + + inExcludedRange := false + for _, portRange := range ranges { + if port >= portRange[0] && port <= portRange[1] { + inExcludedRange = true + break + } + } + if inExcludedRange { + f.portAllocator.Release(port) + continue + } + + f.allocatedPorts = append(f.allocatedPorts, port) + return port + } + + Failf("alloc port outside excluded ranges failed") + return 0 +} + func (f *Framework) ReleasePort(port int) { f.portAllocator.Release(port) } diff --git a/test/e2e/legacy/basic/server.go b/test/e2e/legacy/basic/server.go index a01f44aa..3407a165 100644 --- a/test/e2e/legacy/basic/server.go +++ b/test/e2e/legacy/basic/server.go @@ -20,6 +20,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { ginkgo.It("Ports Whitelist", func() { serverConf := consts.LegacyDefaultServerConfig clientConf := consts.LegacyDefaultClientConfig + tcpPortNotAllowed := f.AllocPortExcludingRanges([2]int{10000, 11000}, [2]int{11002, 11002}, [2]int{12000, 13000}) + udpPortNotAllowed := f.AllocPortExcludingRanges([2]int{10000, 11000}, [2]int{11002, 11002}, [2]int{12000, 13000}) serverConf += ` allow_ports = 10000-11000,11002,12000-13000 @@ -37,8 +39,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { [tcp-port-not-allowed] type = tcp local_port = {{ .%s }} - remote_port = 11001 - `, framework.TCPEchoServerPort) + remote_port = %d + `, framework.TCPEchoServerPort, tcpPortNotAllowed) clientConf += fmt.Sprintf(` [tcp-port-unavailable] type = tcp @@ -55,8 +57,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { [udp-port-not-allowed] type = udp local_port = {{ .%s }} - remote_port = 11003 - `, framework.UDPEchoServerPort) + remote_port = %d + `, framework.UDPEchoServerPort, udpPortNotAllowed) f.RunProcesses(serverConf, []string{clientConf}) @@ -65,7 +67,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { framework.NewRequestExpect(f).PortName(tcpPortName).Ensure() // Not Allowed - framework.NewRequestExpect(f).Port(11001).ExpectError(true).Ensure() + framework.NewRequestExpect(f).Port(tcpPortNotAllowed).ExpectError(true).Ensure() // Unavailable, already bind by frps framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure() @@ -76,7 +78,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { // Not Allowed framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { - r.UDP().Port(11003) + r.UDP().Port(udpPortNotAllowed) }).ExpectError(true).Ensure() }) diff --git a/test/e2e/v1/basic/http.go b/test/e2e/v1/basic/http.go index b594f1ea..1a44ba21 100644 --- a/test/e2e/v1/basic/http.go +++ b/test/e2e/v1/basic/http.go @@ -144,6 +144,79 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() { Ensure() }) + ginkgo.It("HTTP proxy mode uses proxy auth consistently", func() { + vhostHTTPPort := f.AllocPort() + serverConf := getDefaultServerConf(vhostHTTPPort) + + backendPort := f.AllocPort() + f.RunServer("", newHTTPServer(backendPort, "PRIVATE")) + + clientConf := consts.DefaultClientConfig + clientConf += fmt.Sprintf(` + [[proxies]] + name = "protected" + type = "http" + localPort = %d + customDomains = ["normal.example.com"] + routeByHTTPUser = "alice" + httpUser = "alice" + httpPassword = "secret" + `, backendPort) + + f.RunProcesses(serverConf, []string{clientConf}) + + proxyURLWithAuth := func(username, password string) string { + if username == "" { + return fmt.Sprintf("http://127.0.0.1:%d", vhostHTTPPort) + } + return fmt.Sprintf("http://%s:%s@127.0.0.1:%d", username, password, vhostHTTPPort) + } + + framework.NewRequestExpect(f).Explain("direct no auth").Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com") + }). + Ensure(framework.ExpectResponseCode(http.StatusNotFound)) + + framework.NewRequestExpect(f).Explain("direct correct auth").Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com").HTTPAuth("alice", "secret") + }). + ExpectResp([]byte("PRIVATE")). + Ensure() + + framework.NewRequestExpect(f).Explain("direct wrong auth").Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com").HTTPAuth("alice", "wrong") + }). + Ensure(framework.ExpectResponseCode(http.StatusUnauthorized)) + + framework.NewRequestExpect(f).Explain("proxy correct proxy auth"). + RequestModify(func(r *request.Request) { + r.HTTP().Addr("normal.example.com").Proxy(proxyURLWithAuth("alice", "secret")) + }). + ExpectResp([]byte("PRIVATE")). + Ensure() + + framework.NewRequestExpect(f).Explain("proxy wrong proxy auth"). + RequestModify(func(r *request.Request) { + r.HTTP().Addr("normal.example.com").Proxy(proxyURLWithAuth("alice", "wrong")) + }). + Ensure(framework.ExpectResponseCode(http.StatusProxyAuthRequired)) + + framework.NewRequestExpect(f).Explain("proxy request ignores authorization header"). + RequestModify(func(r *request.Request) { + r.HTTP().Addr("normal.example.com").Proxy(proxyURLWithAuth("", "")).HTTPAuth("alice", "secret") + }). + Ensure(framework.ExpectResponseCode(http.StatusProxyAuthRequired)) + + framework.NewRequestExpect(f).Explain("proxy wrong proxy auth with correct authorization"). + RequestModify(func(r *request.Request) { + r.HTTP().Addr("normal.example.com").Proxy(proxyURLWithAuth("alice", "wrong")).HTTPAuth("alice", "secret") + }). + Ensure(framework.ExpectResponseCode(http.StatusProxyAuthRequired)) + }) + ginkgo.It("HTTP Basic Auth", func() { vhostHTTPPort := f.AllocPort() serverConf := getDefaultServerConf(vhostHTTPPort) diff --git a/test/e2e/v1/basic/server.go b/test/e2e/v1/basic/server.go index f2714abb..04ae01b7 100644 --- a/test/e2e/v1/basic/server.go +++ b/test/e2e/v1/basic/server.go @@ -20,6 +20,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { ginkgo.It("Ports Whitelist", func() { serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig + tcpPortNotAllowed := f.AllocPortExcludingRanges([2]int{10000, 11000}, [2]int{11002, 11002}, [2]int{12000, 13000}) + udpPortNotAllowed := f.AllocPortExcludingRanges([2]int{10000, 11000}, [2]int{11002, 11002}, [2]int{12000, 13000}) serverConf += ` allowPorts = [ @@ -43,8 +45,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { name = "tcp-port-not-allowed" type = "tcp" localPort = {{ .%s }} - remotePort = 11001 - `, framework.TCPEchoServerPort) + remotePort = %d + `, framework.TCPEchoServerPort, tcpPortNotAllowed) clientConf += fmt.Sprintf(` [[proxies]] name = "tcp-port-unavailable" @@ -64,8 +66,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { name = "udp-port-not-allowed" type = "udp" localPort = {{ .%s }} - remotePort = 11003 - `, framework.UDPEchoServerPort) + remotePort = %d + `, framework.UDPEchoServerPort, udpPortNotAllowed) f.RunProcesses(serverConf, []string{clientConf}) @@ -74,7 +76,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { framework.NewRequestExpect(f).PortName(tcpPortName).Ensure() // Not Allowed - framework.NewRequestExpect(f).Port(11001).ExpectError(true).Ensure() + framework.NewRequestExpect(f).Port(tcpPortNotAllowed).ExpectError(true).Ensure() // Unavailable, already bind by frps framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure() @@ -85,7 +87,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { // Not Allowed framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { - r.UDP().Port(11003) + r.UDP().Port(udpPortNotAllowed) }).ExpectError(true).Ensure() }) diff --git a/web/frpc/src/router/index.ts b/web/frpc/src/router/index.ts index 2eb36339..75457207 100644 --- a/web/frpc/src/router/index.ts +++ b/web/frpc/src/router/index.ts @@ -1,8 +1,12 @@ import { createRouter, createWebHashHistory } from 'vue-router' import { ElMessage } from 'element-plus' import ClientConfigure from '../views/ClientConfigure.vue' +import ProxyDetail from '../views/ProxyDetail.vue' import ProxyEdit from '../views/ProxyEdit.vue' +import ProxyList from '../views/ProxyList.vue' +import VisitorDetail from '../views/VisitorDetail.vue' import VisitorEdit from '../views/VisitorEdit.vue' +import VisitorList from '../views/VisitorList.vue' import { useProxyStore } from '../stores/proxy' const router = createRouter({ @@ -15,12 +19,12 @@ const router = createRouter({ { path: '/proxies', name: 'ProxyList', - component: () => import('../views/ProxyList.vue'), + component: ProxyList, }, { path: '/proxies/detail/:name', name: 'ProxyDetail', - component: () => import('../views/ProxyDetail.vue'), + component: ProxyDetail, }, { path: '/proxies/create', @@ -37,12 +41,12 @@ const router = createRouter({ { path: '/visitors', name: 'VisitorList', - component: () => import('../views/VisitorList.vue'), + component: VisitorList, }, { path: '/visitors/detail/:name', name: 'VisitorDetail', - component: () => import('../views/VisitorDetail.vue'), + component: VisitorDetail, }, { path: '/visitors/create', diff --git a/web/package-lock.json b/web/package-lock.json index 15c6f2d9..bd274637 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -640,11 +640,10 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -721,11 +720,10 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1337,11 +1335,10 @@ } }, "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=12" @@ -1405,11 +1402,10 @@ } }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -2255,11 +2251,10 @@ } }, "node_modules/@vue/language-core/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -2572,11 +2567,10 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, @@ -3105,11 +3099,10 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, - "license": "MIT" + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true }, "node_modules/destr": { "version": "2.0.5", @@ -3755,11 +3748,10 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4998,16 +4990,14 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" }, "node_modules/lodash-unified": { "version": "1.0.3", @@ -5274,11 +5264,10 @@ "license": "MIT" }, "node_modules/npm-run-all/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5686,11 +5675,10 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -6735,11 +6723,10 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -6965,11 +6952,10 @@ } }, "node_modules/unctx/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -7084,11 +7070,10 @@ } }, "node_modules/unimport/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -7153,11 +7138,10 @@ "license": "MIT" }, "node_modules/unplugin-auto-import/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -7258,11 +7242,10 @@ } }, "node_modules/unplugin-element-plus/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -7344,11 +7327,10 @@ "license": "MIT" }, "node_modules/unplugin-vue-components/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -7479,11 +7461,10 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7586,11 +7567,10 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" },