- Add TenantOptionalAuth middleware to allow access to public content without requiring authentication. - Introduce ListPublicPublished and PublicDetail methods in the content service to retrieve publicly accessible content. - Create tenant_public HTTP routes for listing and showing public content, including preview and main asset retrieval. - Enhance content tests to cover scenarios for public content access and permissions. - Update specifications to reflect the new public content access features and rules.
137 lines
3.9 KiB
Go
137 lines
3.9 KiB
Go
package middlewares
|
||
|
||
import (
|
||
"quyun/v2/app/errorx"
|
||
"quyun/v2/app/services"
|
||
"quyun/v2/database/models"
|
||
"quyun/v2/pkg/consts"
|
||
"quyun/v2/providers/jwt"
|
||
|
||
"github.com/gofiber/fiber/v3"
|
||
)
|
||
|
||
func (f *Middlewares) TenantResolve(c fiber.Ctx) error {
|
||
tenantCode := c.Params("tenantCode")
|
||
if tenantCode == "" {
|
||
return errorx.ErrMissingParameter.WithMsg("缺少 tenantCode")
|
||
}
|
||
|
||
tenantModel, err := services.Tenant.FindByCode(c, tenantCode)
|
||
if err != nil {
|
||
f.log.WithField("tenant_code", tenantCode).WithError(err).Warn("middlewares.tenant.resolve.failed")
|
||
return err
|
||
}
|
||
|
||
f.log.WithFields(map[string]any{
|
||
"tenant_id": tenantModel.ID,
|
||
"tenant_code": tenantCode,
|
||
}).Info("middlewares.tenant.resolve.ok")
|
||
|
||
c.Locals(consts.CtxKeyTenant, tenantModel)
|
||
return c.Next()
|
||
}
|
||
|
||
func (f *Middlewares) TenantAuth(c fiber.Ctx) error {
|
||
authHeader := c.Get(jwt.HttpHeader)
|
||
if authHeader == "" {
|
||
f.log.Info("middlewares.tenant.auth.missing_token")
|
||
return errorx.ErrTokenMissing
|
||
}
|
||
|
||
claims, err := f.jwt.Parse(authHeader)
|
||
if err != nil {
|
||
f.log.WithError(err).Warn("middlewares.tenant.auth.invalid_token")
|
||
switch err {
|
||
case jwt.TokenExpired:
|
||
return errorx.ErrTokenExpired
|
||
case jwt.TokenMalformed, jwt.TokenNotValidYet, jwt.TokenInvalid:
|
||
return errorx.ErrTokenInvalid
|
||
default:
|
||
return errorx.ErrTokenInvalid
|
||
}
|
||
}
|
||
if claims.UserID == 0 {
|
||
f.log.Warn("middlewares.tenant.auth.missing_user_id")
|
||
return errorx.ErrTokenInvalid
|
||
}
|
||
|
||
f.log.WithFields(map[string]any{
|
||
"user_id": claims.UserID,
|
||
}).Info("middlewares.tenant.auth.ok")
|
||
|
||
c.Locals(consts.CtxKeyClaims, claims)
|
||
return c.Next()
|
||
}
|
||
|
||
// TenantOptionalAuth 在 token 存在时解析并写入 claims,但允许无 token 的请求继续。
|
||
// 用于“公开只读”类接口:可匿名访问,但若携带 token 则可以得到更准确的 has_access 等判断。
|
||
func (f *Middlewares) TenantOptionalAuth(c fiber.Ctx) error {
|
||
authHeader := c.Get(jwt.HttpHeader)
|
||
if authHeader == "" {
|
||
f.log.Debug("middlewares.tenant.optional_auth.no_token")
|
||
return c.Next()
|
||
}
|
||
|
||
claims, err := f.jwt.Parse(authHeader)
|
||
if err != nil {
|
||
f.log.WithError(err).Warn("middlewares.tenant.optional_auth.invalid_token")
|
||
switch err {
|
||
case jwt.TokenExpired:
|
||
return errorx.ErrTokenExpired
|
||
case jwt.TokenMalformed, jwt.TokenNotValidYet, jwt.TokenInvalid:
|
||
return errorx.ErrTokenInvalid
|
||
default:
|
||
return errorx.ErrTokenInvalid
|
||
}
|
||
}
|
||
if claims.UserID == 0 {
|
||
f.log.Warn("middlewares.tenant.optional_auth.missing_user_id")
|
||
return errorx.ErrTokenInvalid
|
||
}
|
||
|
||
f.log.WithFields(map[string]any{
|
||
"user_id": claims.UserID,
|
||
}).Debug("middlewares.tenant.optional_auth.ok")
|
||
|
||
c.Locals(consts.CtxKeyClaims, claims)
|
||
return c.Next()
|
||
}
|
||
|
||
func (f *Middlewares) TenantRequireMember(c fiber.Ctx) error {
|
||
tenantModel, ok := c.Locals(consts.CtxKeyTenant).(*models.Tenant)
|
||
if !ok || tenantModel == nil {
|
||
f.log.Error("middlewares.tenant.require_member.missing_tenant_context")
|
||
return errorx.ErrInternalError.WithMsg("tenant context missing")
|
||
}
|
||
|
||
claims, ok := c.Locals(consts.CtxKeyClaims).(*jwt.Claims)
|
||
if !ok || claims == nil {
|
||
f.log.Error("middlewares.tenant.require_member.missing_claims_context")
|
||
return errorx.ErrInternalError.WithMsg("claims context missing")
|
||
}
|
||
|
||
tenantUser, err := services.Tenant.FindTenantUser(c, tenantModel.ID, claims.UserID)
|
||
if err != nil {
|
||
f.log.WithFields(map[string]any{
|
||
"tenant_id": tenantModel.ID,
|
||
"user_id": claims.UserID,
|
||
}).WithError(err).Warn("middlewares.tenant.require_member.denied")
|
||
return errorx.ErrPermissionDenied.WithMsg("不属于该租户")
|
||
}
|
||
|
||
userModel, err := services.User.FindByID(c, claims.UserID)
|
||
if err != nil {
|
||
f.log.WithField("user_id", claims.UserID).WithError(err).Warn("middlewares.tenant.require_member.load_user_failed")
|
||
return err
|
||
}
|
||
|
||
f.log.WithFields(map[string]any{
|
||
"tenant_id": tenantModel.ID,
|
||
"user_id": claims.UserID,
|
||
}).Info("middlewares.tenant.require_member.ok")
|
||
|
||
c.Locals(consts.CtxKeyTenantUser, tenantUser)
|
||
c.Locals(consts.CtxKeyUser, userModel)
|
||
return c.Next()
|
||
}
|