init push
This commit is contained in:
11
backend/middleware/api_token.go
Normal file
11
backend/middleware/api_token.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
)
|
||||
|
||||
type APITokenRepository interface {
|
||||
GetByTokenWithCache(ctx context.Context, token string) (*domain.APIToken, error)
|
||||
}
|
||||
29
backend/middleware/auth.go
Normal file
29
backend/middleware/auth.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/repo/pg"
|
||||
)
|
||||
|
||||
type AuthMiddleware interface {
|
||||
Authorize(next echo.HandlerFunc) echo.HandlerFunc
|
||||
ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc
|
||||
ValidateKBUserPerm(role consts.UserKBPermission) echo.MiddlewareFunc
|
||||
ValidateLicenseEdition(edition ...consts.LicenseEdition) echo.MiddlewareFunc
|
||||
MustGetUserID(c echo.Context) (string, bool)
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(config *config.Config, logger *log.Logger, userAccessRepo *pg.UserAccessRepository, apiTokenRepo *pg.APITokenRepo) (AuthMiddleware, error) {
|
||||
switch config.Auth.Type {
|
||||
case "jwt":
|
||||
return NewJWTMiddleware(config, logger, userAccessRepo, apiTokenRepo), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid auth type: %s", config.Auth.Type)
|
||||
}
|
||||
}
|
||||
285
backend/middleware/jwt.go
Normal file
285
backend/middleware/jwt.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
echoMiddleware "github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/repo/pg"
|
||||
)
|
||||
|
||||
type JWTMiddleware struct {
|
||||
config *config.Config
|
||||
jwtMiddleware echo.MiddlewareFunc
|
||||
logger *log.Logger
|
||||
userAccessRepo *pg.UserAccessRepository
|
||||
apiTokenRepo *pg.APITokenRepo
|
||||
}
|
||||
|
||||
func NewJWTMiddleware(config *config.Config, logger *log.Logger, userAccessRepo *pg.UserAccessRepository, apiTokenRepo *pg.APITokenRepo) *JWTMiddleware {
|
||||
jwtMiddleware := echoMiddleware.WithConfig(echoMiddleware.Config{
|
||||
SigningKey: []byte(config.Auth.JWT.Secret),
|
||||
ErrorHandler: func(c echo.Context, err error) error {
|
||||
logger.Error("jwt auth failed", log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
},
|
||||
})
|
||||
return &JWTMiddleware{
|
||||
config: config,
|
||||
jwtMiddleware: jwtMiddleware,
|
||||
logger: logger.WithModule("middleware.jwt"),
|
||||
userAccessRepo: userAccessRepo,
|
||||
apiTokenRepo: apiTokenRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) Authorize(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
authHeader := c.Request().Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
if !strings.Contains(token, ".") {
|
||||
return m.validateAPIToken(c, token, next)
|
||||
}
|
||||
}
|
||||
|
||||
return m.jwtMiddleware(func(c echo.Context) error {
|
||||
if userID, ok := m.MustGetUserID(c); ok {
|
||||
ctx := context.WithValue(c.Request().Context(), domain.CtxAuthInfoKey, &domain.CtxAuthInfo{
|
||||
IsToken: false,
|
||||
Permission: consts.UserKBPermissionNull,
|
||||
UserId: userID,
|
||||
})
|
||||
|
||||
req := c.Request().WithContext(ctx)
|
||||
c.SetRequest(req)
|
||||
|
||||
m.userAccessRepo.UpdateAccessTime(userID)
|
||||
}
|
||||
return next(c)
|
||||
})(c)
|
||||
}
|
||||
}
|
||||
|
||||
// validateAPIToken validates API token and sets user context
|
||||
func (m *JWTMiddleware) validateAPIToken(c echo.Context, token string, next echo.HandlerFunc) error {
|
||||
if m.apiTokenRepo == nil {
|
||||
m.logger.Debug("API token repository not available")
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
apiToken, err := m.apiTokenRepo.GetByTokenWithCache(c.Request().Context(), token)
|
||||
if err != nil || apiToken == nil {
|
||||
m.logger.Error("failed to get API token", log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
ctx := context.WithValue(c.Request().Context(), domain.CtxAuthInfoKey, &domain.CtxAuthInfo{
|
||||
IsToken: true,
|
||||
Permission: apiToken.Permission,
|
||||
UserId: apiToken.UserID,
|
||||
KBId: apiToken.KbId,
|
||||
})
|
||||
|
||||
req := c.Request().WithContext(ctx)
|
||||
c.SetRequest(req)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
authInfo := domain.GetAuthInfoFromCtx(c.Request().Context())
|
||||
if authInfo == nil {
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
if authInfo.IsToken {
|
||||
// token 视为普通用户 没有管理员相关权限
|
||||
if role == consts.UserRoleAdmin {
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "token not support admin role",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
valid, err := m.userAccessRepo.ValidateRole(authInfo.UserId, role)
|
||||
|
||||
if err != nil || !valid {
|
||||
m.logger.Error("ValidateRole check", log.Any("user_id", authInfo.UserId), log.Any("valid", valid))
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "StatusForbidden ValidateRole",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) ValidateKBUserPerm(perm consts.UserKBPermission) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
authInfo := domain.GetAuthInfoFromCtx(c.Request().Context())
|
||||
if authInfo == nil {
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
kbId, _ := GetKbID(c)
|
||||
|
||||
if authInfo.IsToken {
|
||||
|
||||
if authInfo.KBId != kbId {
|
||||
m.logger.Error("ValidateKBUserPerm ValidateTokenKBPerm kbId", "authInfo.KBId", authInfo.KBId, "kbId", kbId)
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateTokenKBPerm kbId",
|
||||
})
|
||||
}
|
||||
|
||||
if perm == consts.UserKBPermissionNotNull {
|
||||
if authInfo.Permission == consts.UserKBPermissionNull {
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateTokenKBPerm",
|
||||
})
|
||||
}
|
||||
} else if authInfo.Permission != consts.UserKBPermissionFullControl && authInfo.Permission != perm {
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateTokenKBPerm",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 正常用户请求
|
||||
valid, err := m.userAccessRepo.ValidateKBPerm(kbId, authInfo.UserId, perm)
|
||||
if err != nil || !valid {
|
||||
if err != nil {
|
||||
m.logger.Error("ValidateKBUserPerm ValidateKBPerm failed", log.Error(err))
|
||||
} else {
|
||||
m.logger.Info("ValidateKBUserPerm ValidateKBPerm failed", log.String("kb_id", kbId), log.String("user_id", authInfo.UserId))
|
||||
}
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateKBPerm",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdition) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
edition, ok := c.Get("edition").(consts.LicenseEdition)
|
||||
if !ok {
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateLicenseEdition",
|
||||
})
|
||||
}
|
||||
|
||||
if !slices.Contains(needEditions, edition) {
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateLicenseEdition",
|
||||
})
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) MustGetUserID(c echo.Context) (string, bool) {
|
||||
user, ok := c.Get("user").(*jwt.Token)
|
||||
if !ok || user == nil {
|
||||
return "", false
|
||||
}
|
||||
claims, ok := user.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
id, ok := claims["id"].(string)
|
||||
return id, ok
|
||||
}
|
||||
|
||||
func GetKbID(c echo.Context) (string, error) {
|
||||
switch c.Request().Method {
|
||||
case http.MethodGet, http.MethodDelete:
|
||||
var kbId string
|
||||
if strings.Contains(c.Request().URL.Path, "knowledge_base") {
|
||||
kbId = c.QueryParam("id")
|
||||
if kbId != "" {
|
||||
return kbId, nil
|
||||
}
|
||||
}
|
||||
|
||||
kbId = c.QueryParam("kb_id")
|
||||
if kbId != "" {
|
||||
return kbId, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
||||
case http.MethodPost, http.MethodPatch, http.MethodPut:
|
||||
|
||||
bodyBytes, err := io.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(bodyBytes, &m); err == nil {
|
||||
if strings.Contains(c.Request().URL.Path, "knowledge_base") {
|
||||
if id, exists := m["id"].(string); exists && id != "" {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
||||
if id, exists := m["kb_id"].(string); exists && id != "" {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
10
backend/middleware/provider.go
Normal file
10
backend/middleware/provider.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
NewAuthMiddleware,
|
||||
NewShareAuthMiddleware,
|
||||
NewReadonlyMiddleware,
|
||||
NewSessionMiddleware,
|
||||
)
|
||||
57
backend/middleware/readonly.go
Normal file
57
backend/middleware/readonly.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
)
|
||||
|
||||
type ReadOnlyMiddleware struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewReadonlyMiddleware(logger *log.Logger) *ReadOnlyMiddleware {
|
||||
return &ReadOnlyMiddleware{
|
||||
logger: logger.WithModule("middleware.readonly"),
|
||||
}
|
||||
}
|
||||
|
||||
// echo read only middleware, if request method is not get, return 403 forbidden
|
||||
func (readonly *ReadOnlyMiddleware) ReadOnly(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
readonlyMode := os.Getenv("READONLY") == "1" || strings.ToLower(os.Getenv("READONLY")) == "true"
|
||||
return func(c echo.Context) error {
|
||||
if !readonlyMode {
|
||||
return next(c)
|
||||
}
|
||||
path := c.Request().URL.Path
|
||||
// only check /api/v1 path
|
||||
if strings.HasPrefix(path, "/api/v1") {
|
||||
method := c.Request().Method
|
||||
// skip get
|
||||
// skip /api/v1/user/login
|
||||
if !isReadOnlyMethod(method) && path != "/api/v1/user/login" {
|
||||
readonly.logger.Warn("readonly mode rejected request",
|
||||
"method", method,
|
||||
"path", path)
|
||||
return c.JSON(503, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "API is in read-only mode",
|
||||
})
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func isReadOnlyMethod(method string) bool {
|
||||
switch method {
|
||||
case "GET", "HEAD", "OPTIONS":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
67
backend/middleware/session.go
Normal file
67
backend/middleware/session.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/boj/redistore"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/store/cache"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionKey = "SessionKey"
|
||||
)
|
||||
|
||||
type SessionMiddleware struct {
|
||||
logger *log.Logger
|
||||
store *redistore.RediStore
|
||||
}
|
||||
|
||||
func NewSessionMiddleware(logger *log.Logger, config *config.Config, cache *cache.Cache) (*SessionMiddleware, error) {
|
||||
|
||||
secretKey, err := cache.GetOrSet(context.Background(), SessionKey, uuid.New().String(), time.Duration(0))
|
||||
if err != nil {
|
||||
logger.Error("session store create secret key failed: %v", log.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := redistore.NewRediStore(
|
||||
10,
|
||||
"tcp",
|
||||
config.Redis.Addr,
|
||||
"",
|
||||
config.Redis.Password,
|
||||
[]byte(secretKey.(string)),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("init session store failed: %v", log.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: 30 * 86400,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
return &SessionMiddleware{
|
||||
logger: logger.WithModule("middleware.session"),
|
||||
store: store,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SessionMiddleware) Session() echo.MiddlewareFunc {
|
||||
return session.MiddlewareWithConfig(session.Config{
|
||||
Store: s.store,
|
||||
})
|
||||
}
|
||||
127
backend/middleware/share_auth.go
Normal file
127
backend/middleware/share_auth.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/usecase"
|
||||
)
|
||||
|
||||
type ShareAuthMiddleware struct {
|
||||
logger *log.Logger
|
||||
kbUsecase *usecase.KnowledgeBaseUsecase
|
||||
}
|
||||
|
||||
func NewShareAuthMiddleware(logger *log.Logger, kbUsecase *usecase.KnowledgeBaseUsecase) *ShareAuthMiddleware {
|
||||
return &ShareAuthMiddleware{
|
||||
logger: logger.WithModule("middleware.share_auth"),
|
||||
kbUsecase: kbUsecase,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ShareAuthMiddleware) CheckForbidden(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
kbID := c.Request().Header.Get("X-KB-ID")
|
||||
if kbID == "" {
|
||||
h.logger.Error("kb_id is empty")
|
||||
return c.JSON(http.StatusBadRequest, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "kb_id is required",
|
||||
})
|
||||
}
|
||||
|
||||
kb, err := h.kbUsecase.GetKnowledgeBase(c.Request().Context(), kbID)
|
||||
if err != nil {
|
||||
h.logger.Error("get knowledge base failed", log.String("kb_id", kbID), log.Error(err))
|
||||
sentry.CaptureException(err)
|
||||
return c.JSON(http.StatusInternalServerError, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "failed to get knowledge base detail",
|
||||
})
|
||||
}
|
||||
|
||||
if kb.AccessSettings.IsForbidden {
|
||||
h.logger.Warn("access forbidden", log.String("kb_id", kbID))
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "access is forbidden",
|
||||
})
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ShareAuthMiddleware) Authorize(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
kbID := c.Request().Header.Get("X-KB-ID")
|
||||
if kbID == "" {
|
||||
h.logger.Error("kb_id is empty")
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
kb, err := h.kbUsecase.GetKnowledgeBase(c.Request().Context(), kbID)
|
||||
if err != nil {
|
||||
h.logger.Error("get knowledge base failed", log.String("kb_id", kbID), log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
if kb.AccessSettings.IsForbidden {
|
||||
h.logger.Warn("access forbidden", log.String("kb_id", kbID))
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "access is forbidden",
|
||||
})
|
||||
}
|
||||
|
||||
// 未开启认证
|
||||
if !kb.AccessSettings.EnterpriseAuth.Enabled && !kb.AccessSettings.SimpleAuth.Enabled {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
sess, err := session.Get(domain.SessionName, c)
|
||||
if err != nil {
|
||||
h.logger.Error("session get failed", log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
KbIDSess, ok := sess.Values["kb_id"].(string)
|
||||
if !ok || kbID == "" || KbIDSess != kb.ID {
|
||||
h.logger.Error("kb_id valid failed", log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
|
||||
// 企业认证
|
||||
if kb.AccessSettings.EnterpriseAuth.Enabled {
|
||||
userId, ok := sess.Values["user_id"].(uint)
|
||||
if !ok || userId == 0 {
|
||||
h.logger.Error("session user_id get failed", log.Error(err))
|
||||
return c.JSON(http.StatusUnauthorized, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
c.Set("user_id", userId)
|
||||
return next(c)
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user