修复跨域请求问题
This commit is contained in:
@@ -20,7 +20,7 @@ func main() {
|
||||
database.InitCache()
|
||||
|
||||
router := gin.New()
|
||||
router.Use(gin.Logger(), gin.Recovery())
|
||||
router.Use(gin.Logger(), gin.Recovery(), middleware.CORS())
|
||||
|
||||
api := router.Group("/api")
|
||||
registerV1(api.Group("/v1"))
|
||||
|
||||
@@ -22,6 +22,7 @@ type Config struct {
|
||||
AppPort string
|
||||
AppURL string
|
||||
PluginRoot string
|
||||
AppDebug bool
|
||||
}
|
||||
|
||||
var AppConfig *Config
|
||||
@@ -46,9 +47,22 @@ func LoadConfig() {
|
||||
AppPort: getEnv("APP_PORT", "8080"),
|
||||
AppURL: getEnv("APP_URL", ""),
|
||||
PluginRoot: getEnv("PLUGIN_ROOT", "reference\\LDNET-GA-Theme\\plugin"),
|
||||
AppDebug: getEnvBool("APP_DEBUG", false),
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
raw := getEnv(key, "")
|
||||
if raw == "" {
|
||||
return defaultValue
|
||||
}
|
||||
value, err := strconv.ParseBool(raw)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"xboard-go/internal/config"
|
||||
"xboard-go/internal/database"
|
||||
"xboard-go/internal/model"
|
||||
"xboard-go/internal/service"
|
||||
@@ -185,11 +186,14 @@ func SendEmailVerify(c *gin.Context) {
|
||||
}
|
||||
|
||||
service.StoreEmailVerifyCode(email, code, 10*time.Minute)
|
||||
SuccessMessage(c, "email verify code generated", gin.H{
|
||||
data := gin.H{
|
||||
"email": req.Email,
|
||||
"debug_code": code,
|
||||
"expires_in": 600,
|
||||
})
|
||||
}
|
||||
if config.AppConfig.AppDebug {
|
||||
data["debug_code"] = code
|
||||
}
|
||||
SuccessMessage(c, "email verify code generated", data)
|
||||
}
|
||||
|
||||
func ForgetPassword(c *gin.Context) {
|
||||
|
||||
@@ -97,7 +97,7 @@ func UserGetSubscribe(c *gin.Context) {
|
||||
"next_reset_at": user.NextResetAt,
|
||||
"plan": user.Plan,
|
||||
"subscribe_url": strings.TrimRight(baseURL, "/") + "/s/" + user.Token,
|
||||
"reset_day": nil,
|
||||
"reset_day": user.ResetDay,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ type adminAppViewData struct {
|
||||
}
|
||||
|
||||
func UserThemePage(c *gin.Context) {
|
||||
baseURL := service.GetAppURL()
|
||||
if origin := requestOrigin(c.Request); origin != "" {
|
||||
baseURL = origin
|
||||
}
|
||||
|
||||
config := map[string]any{
|
||||
"accent": service.MustGetString("nebula_theme_color", "aurora"),
|
||||
"slogan": service.MustGetString("nebula_hero_slogan", "One control center for login, subscriptions, sessions, and device visibility."),
|
||||
@@ -47,6 +52,7 @@ func UserThemePage(c *gin.Context) {
|
||||
"psbNo": service.MustGetString("psb_no", ""),
|
||||
"staticCdnUrl": service.MustGetString("nebula_static_cdn_url", ""),
|
||||
"isRegisterEnabled": !service.MustGetBool("stop_register", false),
|
||||
"baseUrl": baseURL,
|
||||
}
|
||||
|
||||
payload := userThemeViewData{
|
||||
@@ -119,23 +125,34 @@ func requestOrigin(r *http.Request) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 1. Try X-Forwarded-Proto (High priority for proxies)
|
||||
scheme := r.Header.Get("X-Forwarded-Proto")
|
||||
if scheme == "" {
|
||||
if r.TLS != nil {
|
||||
// Fallback for some proxies
|
||||
if r.Header.Get("X-Forwarded-Ssl") == "on" || r.Header.Get("X-Forwarded-Scheme") == "https" {
|
||||
scheme = "https"
|
||||
} else if r.TLS != nil {
|
||||
scheme = "https"
|
||||
} else {
|
||||
scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try X-Forwarded-Host or Host
|
||||
host := r.Header.Get("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = r.Host
|
||||
}
|
||||
|
||||
// 3. Fallback to Origin header if still empty (rare but possible)
|
||||
if host == "" {
|
||||
if origin := r.Header.Get("Origin"); origin != "" {
|
||||
return origin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 4. Handle Port if present in Forwarded headers
|
||||
if forwardedPort := r.Header.Get("X-Forwarded-Port"); forwardedPort != "" && !strings.Contains(host, ":") {
|
||||
defaultPort := map[string]string{
|
||||
"http": "80",
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"xboard-go/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Auth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "未登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "无效的认证格式"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := utils.VerifyToken(parts[1])
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "登录已过期"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("is_admin", claims.IsAdmin)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
isAdmin, exists := c.Get("is_admin")
|
||||
if !exists || !isAdmin.(bool) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"message": "权限不足"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,21 @@ func Auth() gin.HandlerFunc {
|
||||
|
||||
func AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
isAdmin, exists := c.Get("is_admin")
|
||||
if !exists || !isAdmin.(bool) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Select("is_admin").First(&user, userID).Error; err != nil || !user.IsAdmin {
|
||||
c.JSON(http.StatusForbidden, gin.H{"message": "admin access required"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("is_admin", user.IsAdmin)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
28
internal/middleware/cors.go
Normal file
28
internal/middleware/cors.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
origin = "*"
|
||||
}
|
||||
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ type User struct {
|
||||
NextResetAt *int64 `gorm:"column:next_reset_at" json:"next_reset_at"`
|
||||
LastResetAt *int64 `gorm:"column:last_reset_at" json:"last_reset_at"`
|
||||
ResetCount int `gorm:"column:reset_count;default:0" json:"reset_count"`
|
||||
ResetDay *int `gorm:"column:reset_day" json:"reset_day"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
|
||||
@@ -298,6 +298,16 @@ func BuildNodeConfig(node *model.Server) NodeServerConfig {
|
||||
}
|
||||
|
||||
func ApplyTrafficDelta(userID int, node *model.Server, upload, download int64) {
|
||||
groupIDs := parseIntSlice(node.GroupIDs)
|
||||
if len(groupIDs) > 0 {
|
||||
var count int64
|
||||
if err := database.DB.Model(&model.User{}).
|
||||
Where("id = ? AND group_id IN ?", userID, groupIDs).
|
||||
Count(&count).Error; err != nil || count == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rate := CurrentRate(node)
|
||||
scaledUpload := int64(math.Round(float64(upload) * rate))
|
||||
scaledDownload := int64(math.Round(float64(download) * rate))
|
||||
|
||||
Reference in New Issue
Block a user