Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions frontend/components/common/markdown/ContentRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,24 @@ const markdownComponents: Components = {
const target = e.target as HTMLImageElement;
const errorDiv = document.createElement('div');
errorDiv.className = 'bg-muted border border-border rounded-lg p-4 text-center text-muted-foreground my-6';
errorDiv.innerHTML = `

const iconDiv = document.createElement('div');
iconDiv.innerHTML = `
<svg class="w-12 h-12 mx-auto mb-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd" />
</svg>
<div class="text-sm">图片加载失败</div>
<div class="text-xs text-gray-400 dark:text-gray-500 mt-1">${alt || src}</div>
`;
</svg>`;

const msgDiv = document.createElement('div');
msgDiv.className = 'text-sm';
msgDiv.textContent = '图片加载失败';

const detailDiv = document.createElement('div');
detailDiv.className = 'text-xs text-gray-400 dark:text-gray-500 mt-1';
detailDiv.textContent = alt || src || '';

errorDiv.appendChild(iconDiv);
errorDiv.appendChild(msgDiv);
errorDiv.appendChild(detailDiv);
target.parentNode?.replaceChild(errorDiv, target);
}}
/>
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/services/core/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ function showRiskBlockedDialog(riskInfo: RiskInfo): void {
apiClient.interceptors.request.use(
(config) => {
config.withCredentials = true;
config.headers['X-Requested-With'] = 'XMLHttpRequest';
return config;
},
(error) => Promise.reject(error),
Expand Down
1 change: 1 addition & 0 deletions frontend/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro
'User-Agent': 'CDK-Frontend-Middleware',
'Referer': request.headers.get('referer') || '',
'Origin': request.headers.get('origin') || '',
'X-Requested-With': 'XMLHttpRequest',
},
body: Object.keys(backendBody).length > 0 ? JSON.stringify(backendBody) : undefined,
credentials: 'include',
Expand Down
7 changes: 4 additions & 3 deletions internal/apps/oauth/err.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
package oauth

const (
UnAuthorized = "未登录"
InvalidState = "非法登录请求"
BannedAccount = "账号已被封禁"
UnAuthorized = "未登录"
InvalidState = "非法登录请求"
BannedAccount = "账号已被封禁"
UsernameConflict = "用户名冲突,请稍后重试或联系管理员"
)
12 changes: 1 addition & 11 deletions internal/apps/oauth/utils.go

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是在处理L站用户注销而在cdk系统继续存在的场景

Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ import (
"io"
"time"

"fmt"

"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/linux-do/cdk/internal/config"
"github.com/linux-do/cdk/internal/db"
"github.com/linux-do/cdk/internal/otel_trace"
Expand Down Expand Up @@ -113,14 +110,7 @@ func doOAuth(ctx context.Context, code string) (*User, error) {
err = db.DB(ctx).Transaction(func(tx *gorm.DB) error {
var holder User
if conflictErr := tx.Where("username = ? AND id != ?", userInfo.Username, userInfo.Id).First(&holder).Error; conflictErr == nil {
// 存在冲突 -> 将占用者改名并注销
newParams := map[string]interface{}{
"username": fmt.Sprintf("%s已注销: %s", holder.Username, uuid.NewString()),
"is_active": false,
}
if updateErr := tx.Model(&holder).Updates(newParams).Error; updateErr != nil {
return updateErr
}
return errors.New(UsernameConflict)
}

// 根据 ID 处理当前用户的 更新 或 创建
Expand Down
22 changes: 22 additions & 0 deletions internal/router/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
package router

import (
"net/http"
"strconv"
"strings"
"time"

"github.com/gin-gonic/gin"
Expand All @@ -36,6 +38,26 @@ import (
"go.opentelemetry.io/otel/trace"
)

// csrfMiddleware 校验状态变更请求必须携带 X-Requested-With 头,
// 防止跨站请求伪造(CSRF)。OAuth callback 和支付回调因来源为外部跳转,放行。
func csrfMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
if method == "POST" || method == "PUT" || method == "DELETE" || method == "PATCH" {
path := c.Request.URL.Path
if strings.HasSuffix(path, "/oauth/callback") || strings.HasSuffix(path, "/payment/notify") {
c.Next()
return
}
if c.GetHeader("X-Requested-With") != "XMLHttpRequest" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error_msg": "CSRF 验证失败", "data": nil})
return
}
}
c.Next()
}
}

func loggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 初始化 Trace
Expand Down
7 changes: 4 additions & 3 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"context"
"fmt"
"log"
"net/http"
"strconv"

"github.com/gin-contrib/sessions"
Expand Down Expand Up @@ -78,12 +79,12 @@ func Serve() {
Domain: config.Config.App.SessionDomain,
MaxAge: config.Config.App.SessionAge,
HttpOnly: config.Config.App.SessionHttpOnly,
Secure: config.Config.App.SessionSecure, // 若用 HTTPS 可以设 true
Secure: config.Config.App.SessionSecure,
SameSite: http.SameSiteLaxMode,
},
)
r.Use(sessions.Sessions(config.Config.App.SessionCookieName, sessionStore))

// 补充中间件
r.Use(csrfMiddleware())
r.Use(otelgin.Middleware(config.Config.App.AppName), loggerMiddleware())

apiGroup := r.Group(config.Config.App.APIPrefix)
Expand Down
Loading