fix: align customer payment encryption and protect original values
Deploy olons-admin production / deploy (push) Successful in 9m19s

This commit is contained in:
richjohnrambo
2026-10-22 22:33:42 +08:00
parent 70ef3d9d04
commit 3d69d68a5d
8 changed files with 371 additions and 46 deletions
@@ -95,6 +95,9 @@ type AppMemberEditInp struct {
}
func (in *AppMemberEditInp) Filter(ctx context.Context) error {
if err := rejectMaskedSensitiveFields(in.RealName, in.IdNo); err != nil {
return err
}
if in.Id == 0 {
return gerror.New("后台不支持创建会员,请由会员在C端完成注册")
}
@@ -156,6 +159,7 @@ type AppMemberPaymentListInp struct {
func (in *AppMemberPaymentListInp) Filter(ctx context.Context) error { return nil }
type AppMemberPaymentModel struct {
SensitiveUnavailable bool `json:"sensitiveUnavailable"`
Id uint64 `json:"id"`
MemberId uint64 `json:"memberId"`
AccountType int `json:"accountType"`
@@ -207,6 +211,9 @@ type AppMemberPaymentEditInp struct {
}
func (in *AppMemberPaymentEditInp) Filter(ctx context.Context) error {
if err := rejectMaskedSensitiveFields(in.Holder, in.IdentityNo, in.Mobile, in.AccountNo, in.DigitalRMBCode); err != nil {
return err
}
if !dict.HasOptionKey(consts.PaymentAccountTypeOptions, in.AccountType) {
return gerror.New("收款账户类型不合法")
}
@@ -260,12 +267,24 @@ type AppMemberAddressEditInp struct {
}
func (in *AppMemberAddressEditInp) Filter(ctx context.Context) error {
if err := rejectMaskedSensitiveFields(in.Receiver, in.Mobile, in.Address); err != nil {
return err
}
if in.Id == 0 && (strings.TrimSpace(in.Receiver) == "" || strings.TrimSpace(in.Mobile) == "" || strings.TrimSpace(in.Address) == "") {
return gerror.New("新增地址时收货人、手机号和详细地址不能为空")
}
return nil
}
func rejectMaskedSensitiveFields(values ...string) error {
for _, value := range values {
if strings.Contains(value, "*") {
return gerror.New("不能保存带星号的脱敏资料,请填写完整信息或留空保留原值")
}
}
return nil
}
type AppMemberAddressDeleteInp struct {
Id uint64 `json:"id" v:"required|min:1#地址ID不能为空|地址ID不能为空"`
}
@@ -0,0 +1,58 @@
package sysin
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gdb"
)
func TestPaymentCipherFieldsScanWithoutJSONExposure(t *testing.T) {
const ciphertext = "test-ciphertext-not-a-real-bank-account"
record := gdb.Record{}
for _, field := range []string{"holder_cipher", "identity_no_cipher", "mobile_cipher", "account_no_cipher", "digital_rmb_code_cipher"} {
record[field] = gvar.New(ciphertext)
}
var row AppMemberPaymentModel
if err := record.Struct(&row); err != nil {
t.Fatal(err)
}
for name, actual := range map[string]string{"holder": row.HolderCipher, "identity": row.IdentityNoCipher, "mobile": row.MobileCipher, "account": row.AccountNoCipher, "digital": row.DigitalRmbCodeCipher} {
if actual != ciphertext {
t.Errorf("%s ciphertext was not loaded by database scan", name)
}
}
data, err := json.Marshal(row)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(data), ciphertext) {
t.Fatal("internal ciphertext must not be exposed in API JSON")
}
}
func TestPaymentEditRejectsMaskedValues(t *testing.T) {
for _, field := range []string{"holder", "account", "identity", "mobile", "digital"} {
t.Run(field, func(t *testing.T) {
in := &AppMemberPaymentEditInp{Id: 1, MemberId: 1, AccountType: 1}
switch field {
case "holder":
in.Holder = "王*"
case "account":
in.AccountNo = "6222********5937"
case "identity":
in.IdentityNo = "1234********5678"
case "mobile":
in.Mobile = "138****1234"
case "digital":
in.DigitalRMBCode = "0021********8708"
}
if err := in.Filter(context.Background()); err == nil {
t.Fatal("masked values must never overwrite originals")
}
})
}
}