init: 1.0.0

This commit is contained in:
Chang lue Tsen
2025-04-25 12:08:29 +09:00
commit 8addcc584b
1031 changed files with 76472 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
package middleware
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strings"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/result"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/svc"
pkgaes "github.com/perfect-panel/ppanel-server/pkg/aes"
)
const (
noWritten = -1
defaultStatus = http.StatusOK
key = "123456"
)
func AppMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
if !strings.Contains(c.Request.URL.Path, "/v1/app") {
c.Next()
return
}
rw := NewResponseWriter(c, svc)
if !rw.Decrypt() {
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext"))
c.Abort()
return
}
c.Writer = rw
c.Next()
rw.FlushAbort()
}
}
func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *ResponseWriter) {
rw = &ResponseWriter{
c: c,
body: new(bytes.Buffer),
ResponseWriter: c.Writer,
}
applicationConfig, err := srvCtx.ApplicationModel.FindOneConfig(c, 1)
if err != nil {
logger.Errorf("[AppMiddleware] find application config error: %v", err.Error())
return
}
if strings.ToUpper(applicationConfig.EncryptionMethod) == "AES" && applicationConfig.EncryptionKey != "" {
rw.encryptionKey = applicationConfig.EncryptionKey
rw.encryptionMethod = applicationConfig.EncryptionMethod
rw.encryption = true
}
return
}
func (rw *ResponseWriter) Encrypt() {
if !rw.encryption {
return
}
buf := rw.body.Bytes()
params := map[string]interface{}{}
err := json.Unmarshal(buf, &params)
if err != nil {
return
}
data := params["data"]
if data != nil {
var jsonData []byte
str, ok := data.(string)
if ok {
jsonData = []byte(str)
} else {
jsonData, _ = json.Marshal(data)
}
encrypt, iv, err := pkgaes.Encrypt(jsonData, rw.encryptionKey)
if err != nil {
return
}
params["data"] = map[string]interface{}{
"data": encrypt,
"time": iv,
}
}
marshal, _ := json.Marshal(params)
rw.body.Reset()
rw.body.Write(marshal)
}
func (rw *ResponseWriter) Decrypt() bool {
if !rw.encryption {
return true
}
//判断url链接中是否存在data和iv数据,存在就进行解密并设置回去
query := rw.c.Request.URL.Query()
dataStr := query.Get("data")
timeStr := query.Get("time")
if dataStr != "" && timeStr != "" {
decrypt, err := pkgaes.Decrypt(dataStr, rw.encryptionKey, timeStr)
if err == nil {
params := map[string]interface{}{}
err = json.Unmarshal([]byte(decrypt), &params)
if err == nil {
for k, v := range params {
query.Set(k, fmt.Sprintf("%v", v))
}
query.Del("data")
query.Del("time")
rw.c.Request.RequestURI = fmt.Sprintf("%s?%s", rw.c.Request.RequestURI[:strings.Index(rw.c.Request.RequestURI, "?")], query.Encode())
rw.c.Request.URL.RawQuery = query.Encode()
}
}
}
//判断body是否存在数据,存在就尝试解密,并设置回去
body, err := io.ReadAll(rw.c.Request.Body)
if err != nil {
return true
}
if len(body) == 0 {
return true
}
params := map[string]interface{}{}
err = json.Unmarshal(body, &params)
data := params["data"]
nonce := params["time"]
if err != nil || data == nil {
return false
}
str, ok := data.(string)
if !ok {
return false
}
iv, ok := nonce.(string)
if !ok {
return false
}
decrypt, err := pkgaes.Decrypt(str, rw.encryptionKey, iv)
if err != nil {
return false
}
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
return true
}
func (rw *ResponseWriter) FlushAbort() {
defer rw.c.Abort()
responseBody := rw.body.String()
fmt.Println("Original Response Body:", responseBody)
rw.flush = true
if rw.encryption {
rw.Encrypt()
}
_, err := rw.Write(rw.body.Bytes())
if err != nil {
return
}
}
type ResponseWriter struct {
http.ResponseWriter
size int
status int
flush bool
body *bytes.Buffer
c *gin.Context
encryption bool
encryptionKey string
encryptionMethod string
}
func (rw *ResponseWriter) Unwrap() http.ResponseWriter {
return rw.ResponseWriter
}
//nolint:unused
func (rw *ResponseWriter) reset(writer http.ResponseWriter) {
rw.ResponseWriter = writer
rw.size = noWritten
rw.status = defaultStatus
}
func (rw *ResponseWriter) WriteHeader(code int) {
if code > 0 && rw.status != code {
if rw.Written() {
return
}
rw.status = code
}
}
func (rw *ResponseWriter) WriteHeaderNow() {
if !rw.Written() {
rw.size = 0
rw.ResponseWriter.WriteHeader(rw.status)
}
}
func (rw *ResponseWriter) Write(data []byte) (n int, err error) {
if rw.flush {
rw.WriteHeaderNow()
n, err = rw.ResponseWriter.Write(data)
rw.size += n
} else {
rw.body.Write(data)
}
return
}
func (rw *ResponseWriter) WriteString(s string) (n int, err error) {
if rw.flush {
rw.WriteHeaderNow()
n, err = rw.ResponseWriter.Write([]byte(s))
rw.size += n
} else {
rw.body.Write([]byte(s))
}
return
}
func (rw *ResponseWriter) Status() int {
return rw.status
}
func (rw *ResponseWriter) Size() int {
return rw.size
}
func (rw *ResponseWriter) Written() bool {
return rw.size != noWritten
}
// Hijack implements the http.Hijacker interface.
func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if rw.size < 0 {
rw.size = 0
}
return rw.ResponseWriter.(http.Hijacker).Hijack()
}
// CloseNotify implements the http.CloseNotifier interface.
func (rw *ResponseWriter) CloseNotify() <-chan bool {
// 通过 r.Context().Done() 来监听请求的取消
done := rw.c.Request.Context().Done()
closed := make(chan bool)
// 当上下文被取消时,通过 closed channel 发送通知
go func() {
<-done
closed <- true
}()
return closed
}
// Flush implements the http.Flusher interface.
func (rw *ResponseWriter) Flush() {
rw.WriteHeaderNow()
rw.ResponseWriter.(http.Flusher).Flush()
}
func (rw *ResponseWriter) Pusher() (pusher http.Pusher) {
if pusher, ok := rw.ResponseWriter.(http.Pusher); ok {
return pusher
}
return nil
}
+85
View File
@@ -0,0 +1,85 @@
package middleware
import (
"context"
"fmt"
"strings"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/jwt"
"github.com/perfect-panel/ppanel-server/pkg/result"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
)
func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
jwtConfig := svc.Config.JwtAuth
// get token from header
token := c.GetHeader("Authorization")
if token == "" {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Token Empty")
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenEmpty), "Token Empty"))
c.Abort()
return
}
// parse token
claims, err := jwt.ParseJwtToken(token, jwtConfig.AccessSecret)
if err != nil {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] ParseJwtToken", logger.Field("error", err.Error()), logger.Field("token", token))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenExpire), "Token Invalid"))
c.Abort()
return
}
// get user id from token
userId := int64(claims["UserId"].(float64))
// get session id from token
sessionId := claims["SessionId"].(string)
// get session id from redis
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
value, err := svc.Redis.Get(c, sessionIdCacheKey).Result()
if err != nil {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Redis Get", logger.Field("error", err.Error()), logger.Field("sessionId", sessionId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
//verify user id
if value != fmt.Sprintf("%v", userId) {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Invalid Access", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
userInfo, err := svc.UserModel.FindOne(c, userId)
if err != nil {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] UserModel FindOne", logger.Field("error", err.Error()), logger.Field("userId", userId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Database Query Error"))
c.Abort()
return
}
// admin verify
paths := strings.Split(c.Request.URL.Path, "/")
if tool.StringSliceContains(paths, "admin") && !*userInfo.IsAdmin {
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Not Admin User", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
+28
View File
@@ -0,0 +1,28 @@
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
func CorsMiddleware(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if origin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
} else {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
}
// c.Writer.Header().Set("Access-Control-Allow-Origin", c.Request.Host)
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range")
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Max-Age", "172800")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
+110
View File
@@ -0,0 +1,110 @@
package middleware
import (
"bytes"
"encoding/json"
"io"
"time"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
func LoggerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
// get response body
w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
c.Writer = w
// get request body
var requestBody []byte
if c.Request.Body != nil {
// c.Request.Body It can only be read once, and after reading, it needs to be reassigned to c.Request Body
requestBody, _ = io.ReadAll(c.Request.Body)
// After reading, reassign c.Request Body For subsequent operations
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
}
// start time
start := time.Now()
c.Next()
// Start recording logs
cost := time.Since(start)
responseStatus := c.Writer.Status()
logs := []logger.LogField{
{
Key: "status",
Value: responseStatus,
},
{
Key: "request",
Value: c.Request.Method + " " + c.Request.URL.String(),
},
{
Key: "query",
Value: c.Request.URL.RawQuery,
},
{
Key: "ip",
Value: c.ClientIP(),
},
{
Key: "user-agent",
Value: c.Request.UserAgent(),
},
}
if c.Errors.Last() != nil {
var e *xerr.CodeError
var errMessage string
if errors.As(c.Errors.Last().Err, &e) {
errMessage = e.GetErrMsg()
} else {
errMessage = c.Errors.Last().Error()
}
logs = append(logs, logger.Field("error", errMessage))
}
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
// request content
logs = append(logs, logger.Field("request_body", string(maskSensitiveFields(requestBody, []string{"password", "old_password", "new_password"}))))
// response content
logs = append(logs, logger.Field("response_body", w.body.String()))
}
logs = append(logs, logger.Field("duration", cost))
if responseStatus >= 500 && responseStatus <= 599 {
logger.WithContext(c.Request.Context()).Errorw("HTTP Error", logs...)
} else {
logger.WithContext(c.Request.Context()).Infow("HTTP Request", logs...)
}
}
}
func maskSensitiveFields(data []byte, fieldsToMask []string) []byte {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
return data
}
for _, field := range fieldsToMask {
if _, exists := jsonData[field]; exists {
jsonData[field] = "***" // use *** to mask sensitive fields
}
}
maskedData, err := json.Marshal(jsonData)
if err != nil {
return data
}
return maskedData
}
+38
View File
@@ -0,0 +1,38 @@
package middleware
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
type PaymentParams struct {
Platform string `uri:"platform"`
Token string `uri:"token"`
}
func NotifyMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
var params PaymentParams
// Get platform and token from uri
if err := c.ShouldBindUri(&params); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
c.Abort()
return
}
config, err := svc.PaymentModel.FindOneByPaymentToken(ctx, params.Token)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
c.Abort()
return
}
ctx = context.WithValue(ctx, constant.CtxKeyPlatform, config.Platform)
ctx = context.WithValue(ctx, constant.CtxKeyPayment, config)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
@@ -0,0 +1,35 @@
package middleware
import (
"strings"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/logic/subscribe"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
)
func PanDomainMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
if svc.Config.Subscribe.PanDomain {
domain := c.Request.Host
domainArr := strings.Split(domain, ".")
domainFirst := domainArr[0]
request := types.SubscribeRequest{
Token: domainFirst,
Flag: domainArr[1],
UA: c.Request.Header.Get("User-Agent"),
}
l := subscribe.NewSubscribeLogic(c, svc)
resp, err := l.Generate(&request)
if err != nil {
return
}
c.Header("subscription-userinfo", resp.Header)
c.String(200, "%s", string(resp.Config))
c.Abort()
return
}
c.Next()
}
}
+19
View File
@@ -0,0 +1,19 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
func ServerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
if key, ok := c.GetQuery("secret_key"); ok {
if key == svc.Config.Node.NodeSecret {
c.Next()
return
}
}
c.String(403, "Forbidden")
c.Abort()
}
}
+111
View File
@@ -0,0 +1,111 @@
package middleware
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/trace"
)
// statusByWriter returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func statusByWriter(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
func requestAttributes(req *http.Request) []attribute.KeyValue {
protoN := strings.SplitN(req.Proto, "/", 2)
remoteAddrN := strings.SplitN(req.RemoteAddr, ":", 2)
return []attribute.KeyValue{
semconv.HTTPRequestMethodKey.String(req.Method),
semconv.HTTPUserAgentKey.String(req.UserAgent()),
semconv.HTTPRequestContentLengthKey.Int64(req.ContentLength),
semconv.URLFullKey.String(req.URL.String()),
semconv.URLSchemeKey.String(req.URL.Scheme),
semconv.URLFragmentKey.String(req.URL.Fragment),
semconv.URLPathKey.String(req.URL.Path),
semconv.URLQueryKey.String(req.URL.RawQuery),
semconv.NetworkProtocolNameKey.String(strings.ToLower(protoN[0])),
semconv.NetworkProtocolVersionKey.String(protoN[1]),
semconv.ClientAddressKey.String(remoteAddrN[0]),
semconv.ClientPortKey.String(remoteAddrN[1]),
}
}
func TraceMiddleware(_ *svc.ServiceContext) func(ctx *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
tracer := trace.TracerFromContext(ctx)
spanName := c.FullPath()
method := c.Request.Method
ctx, span := tracer.Start(
ctx,
fmt.Sprintf("%s %s", method, spanName),
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
)
defer span.End()
requestId, err := uuid.NewV7()
if err != nil {
logger.Errorw(
"failed to generate request id in uuid v7 format, fallback to uuid v4",
logger.Field("error", err),
)
requestId = uuid.New()
}
c.Header(trace.RequestIdKey, requestId.String())
span.SetAttributes(requestAttributes(c.Request)...)
span.SetAttributes(
attribute.String("http.request_id", requestId.String()),
semconv.HTTPRouteKey.String(c.FullPath()),
)
// context with request host
ctx = context.WithValue(ctx, constant.CtxKeyRequestHost, c.Request.Host)
// restructure context
c.Request = c.Request.WithContext(ctx)
c.Next()
// handle response related attributes
status := c.Writer.Status()
span.SetStatus(statusByWriter(status))
if status > 0 {
span.SetAttributes(semconv.HTTPResponseStatusCodeKey.Int(status))
}
if len(c.Errors) > 0 {
span.SetStatus(codes.Error, c.Errors.String())
for _, err := range c.Errors {
span.RecordError(err.Err)
}
}
span.SetAttributes(semconv.HTTPResponseBodySizeKey.Int(c.Writer.Size()))
}
}