mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
118 lines
3.5 KiB
Go
118 lines
3.5 KiB
Go
package middleware
|
||
|
||
import (
|
||
"net"
|
||
"net/http"
|
||
"slices"
|
||
"strings"
|
||
|
||
"github.com/go-chi/chi/v5"
|
||
"github.com/go-rat/chix"
|
||
"github.com/go-rat/sessions"
|
||
"github.com/knadh/koanf/v2"
|
||
"github.com/leonelquinteros/gotext"
|
||
"github.com/tnb-labs/panel/pkg/punycode"
|
||
)
|
||
|
||
// Entrance 确保通过正确的入口访问
|
||
func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) func(next http.Handler) http.Handler {
|
||
return func(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
sess, err := session.GetSession(r)
|
||
if err != nil {
|
||
Abort(w, http.StatusInternalServerError, "%v", err)
|
||
return
|
||
}
|
||
|
||
entrance := strings.TrimSuffix(conf.String("http.entrance"), "/")
|
||
if !strings.HasPrefix(entrance, "/") {
|
||
entrance = "/" + entrance
|
||
}
|
||
|
||
// 情况一:设置了绑定域名、IP、UA,且请求不符合要求,返回错误
|
||
host, _, err := net.SplitHostPort(r.Host)
|
||
if err != nil {
|
||
host = r.Host
|
||
}
|
||
if strings.Contains(host, "xn--") {
|
||
if decoded, err := punycode.DecodeDomain(host); err == nil {
|
||
host = decoded
|
||
}
|
||
}
|
||
if len(conf.Strings("http.bind_domain")) > 0 && !slices.Contains(conf.Strings("http.bind_domain"), host) {
|
||
Abort(w, http.StatusTeapot, t.Get("invalid request domain: %s", r.Host))
|
||
return
|
||
}
|
||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||
if err != nil {
|
||
ip = r.RemoteAddr
|
||
}
|
||
if len(conf.Strings("http.bind_ip")) > 0 {
|
||
allowed := false
|
||
requestIP := net.ParseIP(ip)
|
||
if requestIP != nil {
|
||
for _, allowedIP := range conf.Strings("http.bind_ip") {
|
||
if strings.Contains(allowedIP, "/") {
|
||
// CIDR
|
||
if _, ipNet, err := net.ParseCIDR(allowedIP); err == nil && ipNet.Contains(requestIP) {
|
||
allowed = true
|
||
break
|
||
}
|
||
} else {
|
||
// IP
|
||
if allowedIP == ip {
|
||
allowed = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if !allowed {
|
||
Abort(w, http.StatusTeapot, t.Get("invalid request ip: %s", ip))
|
||
return
|
||
}
|
||
}
|
||
if len(conf.Strings("http.bind_ua")) > 0 && !slices.Contains(conf.Strings("http.bind_ua"), r.UserAgent()) {
|
||
Abort(w, http.StatusTeapot, t.Get("invalid request user agent: %s", r.UserAgent()))
|
||
return
|
||
}
|
||
|
||
// 情况二:请求路径与入口路径相同或未设置访问入口,标记通过验证并重定向
|
||
if (strings.TrimSuffix(r.URL.Path, "/") == entrance || entrance == "/") &&
|
||
r.Header.Get("Authorization") == "" {
|
||
sess.Put("verify_entrance", true)
|
||
// 设置入口的情况下进行重定向
|
||
if entrance != "/" {
|
||
render := chix.NewRender(w, r)
|
||
defer render.Release()
|
||
render.Redirect("/login")
|
||
return
|
||
}
|
||
}
|
||
|
||
// 情况三:通过APIKey+入口路径访问,重写请求路径并跳过验证
|
||
if strings.HasPrefix(r.URL.Path, entrance) && r.Header.Get("Authorization") != "" {
|
||
// 只在设置了入口路径的情况下,才进行重写
|
||
if entrance != "/" {
|
||
if rctx := chi.RouteContext(r.Context()); rctx != nil {
|
||
rctx.RoutePath = strings.TrimPrefix(rctx.RoutePath, entrance)
|
||
r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
|
||
}
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
|
||
// 情况四:非调试模式且未通过验证的请求,返回错误
|
||
if !conf.Bool("app.debug") &&
|
||
sess.Missing("verify_entrance") &&
|
||
r.URL.Path != "/robots.txt" {
|
||
Abort(w, http.StatusTeapot, t.Get("invalid access entrance"))
|
||
return
|
||
}
|
||
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
}
|