订单接口最小化与私有文件访问加固
- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计 - 用户 token 增加版本控制,冻结/改密/退出即时撤销会话 - 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie - 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属 - 公开商品接口返回最小字段,隐藏号主身份与内部状态 - 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -329,8 +330,9 @@ func TestApplyPublicListingURLsHidesSourceChannel(t *testing.T) {
|
||||
"https://example.com/account.png",
|
||||
},
|
||||
AssetSummary: map[string]any{
|
||||
"import_meta": map[string]any{"uploader_name": "客服1"},
|
||||
"price_breakdown": map[string]any{"buyer_total_price": 100},
|
||||
"import_meta": map[string]any{"uploader_name": "客服1"},
|
||||
"price_breakdown": map[string]any{"buyer_total_price": 100},
|
||||
"screenshot_groups": map[string]any{"coin": []string{"https://example.com/account.png"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -345,6 +347,78 @@ func TestApplyPublicListingURLsHidesSourceChannel(t *testing.T) {
|
||||
if _, ok := item.AssetSummary["price_breakdown"]; ok {
|
||||
t.Fatal("expected price_breakdown hidden from public listing")
|
||||
}
|
||||
if _, ok := item.AssetSummary["screenshot_groups"]; ok {
|
||||
t.Fatal("expected screenshot_groups hidden from public listing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicListingDTOsDoNotSerializeSensitiveFields(t *testing.T) {
|
||||
item := ListingDTO{
|
||||
ID: 1,
|
||||
ListingNo: "SP000001",
|
||||
AccountID: 2,
|
||||
OwnerID: 3,
|
||||
OwnerPhone: "13800001234",
|
||||
OwnerNickname: "号主",
|
||||
SourceChannel: "外部渠道",
|
||||
IsExternalUpload: true,
|
||||
Title: "测试账号",
|
||||
Description: "公开描述",
|
||||
ScreenshotURLS: []string{"/api/listings/1/screenshots/0"},
|
||||
Status: "published",
|
||||
ReviewStatus: "approved",
|
||||
HandoffMode: "platform",
|
||||
SettlementMode: "platform_managed",
|
||||
ManagedAdminID: uint64Pointer(4),
|
||||
ReviewReason: "内部审核备注",
|
||||
ListingGroupConversationID: 5,
|
||||
AssetSummary: map[string]any{
|
||||
"season_insurance": "3*3",
|
||||
"contact_phone": "13900005678",
|
||||
"remark": "首页不应携带",
|
||||
},
|
||||
}
|
||||
|
||||
listRaw, err := json.Marshal(PublicListResult{Items: publicListingListItems([]ListingDTO{item})})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal public list error = %v", err)
|
||||
}
|
||||
detailRaw, err := json.Marshal(item.toPublicDetail())
|
||||
if err != nil {
|
||||
t.Fatalf("marshal public detail error = %v", err)
|
||||
}
|
||||
for _, field := range []string{
|
||||
"account_id",
|
||||
"owner_id",
|
||||
"owner_phone",
|
||||
"owner_nickname",
|
||||
"source_channel",
|
||||
"is_external_upload",
|
||||
"status",
|
||||
"review_status",
|
||||
"handoff_mode",
|
||||
"settlement_mode",
|
||||
"managed_admin_id",
|
||||
"review_reason",
|
||||
"listing_group_conversation_id",
|
||||
} {
|
||||
if strings.Contains(string(listRaw), field) || strings.Contains(string(detailRaw), field) {
|
||||
t.Fatalf("public response contains sensitive field %q", field)
|
||||
}
|
||||
}
|
||||
if strings.Contains(string(listRaw), "description") || strings.Contains(string(listRaw), "screenshot_urls") {
|
||||
t.Fatalf("public list contains detail-only fields: %s", listRaw)
|
||||
}
|
||||
if strings.Contains(string(listRaw), "contact_phone") || strings.Contains(string(listRaw), "首页不应携带") {
|
||||
t.Fatalf("public list contains non-display asset fields: %s", listRaw)
|
||||
}
|
||||
if strings.Contains(string(detailRaw), "contact_phone") {
|
||||
t.Fatalf("public detail contains non-public asset field: %s", detailRaw)
|
||||
}
|
||||
}
|
||||
|
||||
func uint64Pointer(value uint64) *uint64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
func TestParseExternalUploadItemsAcceptsSingleObject(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user