fix: restore payment account details
This commit is contained in:
@@ -70,6 +70,7 @@ type AuthConfig struct {
|
||||
DataEncryptionKey string `json:"data_encryption_key"`
|
||||
LookupHMACKey string `json:"lookup_hmac_key"`
|
||||
FinanceMasterKey string `json:"finance_master_key"`
|
||||
FinanceLegacyMasterKeys []string `json:"finance_legacy_master_keys"`
|
||||
AllowDevHeaderAuth bool `json:"allow_dev_header_auth"`
|
||||
RequiredAPIKey bool `json:"required_api_key"`
|
||||
}
|
||||
@@ -85,7 +86,11 @@ func Load() (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
configuredFinanceMasterKey := strings.TrimSpace(cfg.Auth.FinanceMasterKey)
|
||||
applyEnv(cfg)
|
||||
if configuredFinanceMasterKey != "" && configuredFinanceMasterKey != strings.TrimSpace(cfg.Auth.FinanceMasterKey) {
|
||||
cfg.Auth.FinanceLegacyMasterKeys = append(cfg.Auth.FinanceLegacyMasterKeys, configuredFinanceMasterKey)
|
||||
}
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -134,6 +139,9 @@ func applyEnv(cfg *Config) {
|
||||
setString(&cfg.Auth.DataEncryptionKey, "OLONS_SERVER_DATA_ENCRYPTION_KEY")
|
||||
setString(&cfg.Auth.LookupHMACKey, "OLONS_SERVER_LOOKUP_HMAC_KEY")
|
||||
setString(&cfg.Auth.FinanceMasterKey, "OLONS_SERVER_FINANCE_MASTER_KEY")
|
||||
if keys := strings.TrimSpace(os.Getenv("OLONS_SERVER_FINANCE_LEGACY_MASTER_KEYS")); keys != "" {
|
||||
cfg.Auth.FinanceLegacyMasterKeys = append(cfg.Auth.FinanceLegacyMasterKeys, splitCSV(keys)...)
|
||||
}
|
||||
setBool(&cfg.Auth.AllowDevHeaderAuth, "OLONS_SERVER_ALLOW_DEV_HEADER_AUTH")
|
||||
setBool(&cfg.Auth.RequiredAPIKey, "OLONS_SERVER_REQUIRED_API_KEY")
|
||||
ApplyUploadEnv(&cfg.Upload)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultConfigUsesOlonsAdmin(t *testing.T) {
|
||||
cfg := defaultConfig()
|
||||
@@ -17,6 +21,24 @@ func TestConfigRejectsOtherDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPreservesConfiguredFinanceKeyAsLegacyWhenEnvironmentOverridesIt(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
data := []byte(`{"auth":{"finance_master_key":"legacy-finance-master-key-with-at-least-32-characters"}}`)
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("OLONS_SERVER_CONFIG", path)
|
||||
t.Setenv("OLONS_SERVER_FINANCE_MASTER_KEY", "current-finance-master-key-with-at-least-32-characters")
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cfg.Auth.FinanceLegacyMasterKeys) != 1 ||
|
||||
cfg.Auth.FinanceLegacyMasterKeys[0] != "legacy-finance-master-key-with-at-least-32-characters" {
|
||||
t.Fatalf("configured finance key was not retained for historical data: %#v", cfg.Auth.FinanceLegacyMasterKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUploadSystemConfig(t *testing.T) {
|
||||
cfg := UploadConfig{Driver: "local", Region: "us-east-1"}
|
||||
ApplyUploadSystemConfig(&cfg, map[string]string{
|
||||
|
||||
+40
-45
@@ -35,7 +35,6 @@ const (
|
||||
var (
|
||||
tradePasswordPattern = regexp.MustCompile(`^[0-9]{6}$`)
|
||||
idempotencyPattern = regexp.MustCompile(`^[A-Za-z0-9._:\-]{8,80}$`)
|
||||
identityNoPattern = regexp.MustCompile(`^[1-9][0-9]{14}(?:[0-9]{2}[0-9X])?$`)
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
@@ -62,6 +61,7 @@ type Service struct {
|
||||
repository Repository
|
||||
redis *redis.Client
|
||||
aead cipher.AEAD
|
||||
legacyAEADs []cipher.AEAD
|
||||
lookupKey []byte
|
||||
ready bool
|
||||
now func() time.Time
|
||||
@@ -71,7 +71,6 @@ type PaymentAccountInput struct {
|
||||
ID int64 `json:"id"`
|
||||
BankName string `json:"bank_name"`
|
||||
HolderName string `json:"holder_name"`
|
||||
IdentityNo string `json:"identity_no"`
|
||||
AccountNo string `json:"account_no"`
|
||||
DigitalRMBCode string `json:"digital_rmb_code"`
|
||||
WechatQRAttachmentID int64 `json:"wechat_qr_attachment_id"`
|
||||
@@ -84,7 +83,6 @@ type PaymentAccount struct {
|
||||
BankCode string `json:"bank_code"`
|
||||
BankName string `json:"bank_name"`
|
||||
HolderMask string `json:"holder_mask"`
|
||||
IdentityNoMask string `json:"identity_no_mask"`
|
||||
AccountNoMask string `json:"account_no_mask"`
|
||||
DigitalRMBCodeMask string `json:"digital_rmb_code_mask"`
|
||||
WechatQRAttachmentID int64 `json:"wechat_qr_attachment_id"`
|
||||
@@ -99,7 +97,6 @@ type PaymentAccountDetail struct {
|
||||
ID int64 `json:"id"`
|
||||
BankName string `json:"bank_name"`
|
||||
HolderName string `json:"holder_name"`
|
||||
IdentityNo string `json:"identity_no"`
|
||||
AccountNo string `json:"account_no"`
|
||||
DigitalRMBCode string `json:"digital_rmb_code"`
|
||||
WechatQRURL string `json:"wechat_qr_url"`
|
||||
@@ -266,20 +263,39 @@ func New(repository Repository, redisClient *redis.Client, cfg config.AuthConfig
|
||||
return service
|
||||
}
|
||||
}
|
||||
block, err := aes.NewCipher(encryptionKey)
|
||||
if err != nil {
|
||||
return service
|
||||
}
|
||||
aead, err := cipher.NewGCM(block)
|
||||
aead, err := newAEAD(encryptionKey)
|
||||
if err != nil {
|
||||
return service
|
||||
}
|
||||
service.aead = aead
|
||||
seenLegacyKeys := make(map[string]struct{}, len(cfg.FinanceLegacyMasterKeys))
|
||||
for _, legacyMasterKey := range cfg.FinanceLegacyMasterKeys {
|
||||
legacyMasterKey = strings.TrimSpace(legacyMasterKey)
|
||||
if len([]byte(legacyMasterKey)) < 32 || legacyMasterKey == masterKey {
|
||||
continue
|
||||
}
|
||||
if _, exists := seenLegacyKeys[legacyMasterKey]; exists {
|
||||
continue
|
||||
}
|
||||
legacyAEAD, legacyErr := newAEAD(deriveKey(legacyMasterKey, "data-encryption"))
|
||||
if legacyErr == nil {
|
||||
service.legacyAEADs = append(service.legacyAEADs, legacyAEAD)
|
||||
seenLegacyKeys[legacyMasterKey] = struct{}{}
|
||||
}
|
||||
}
|
||||
service.lookupKey = lookupKey
|
||||
service.ready = repository != nil && redisClient != nil
|
||||
return service
|
||||
}
|
||||
|
||||
func newAEAD(key []byte) (cipher.AEAD, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewGCM(block)
|
||||
}
|
||||
|
||||
func deriveKey(masterKey, purpose string) []byte {
|
||||
mac := hmac.New(sha256.New, []byte(masterKey))
|
||||
_, _ = mac.Write([]byte("olons-finance:" + purpose + ":v1"))
|
||||
@@ -312,20 +328,13 @@ func (s *Service) PaymentAccountDetails(ctx context.Context, tenantID, memberID
|
||||
items := make([]PaymentAccountDetail, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
holderName, accountNo := row.HolderMask, row.AccountNoMask
|
||||
identityNo, digitalRMBCode := row.IdentityNoMask, row.DigitalRMBCodeMask
|
||||
digitalRMBCode := row.DigitalRMBCodeMask
|
||||
masked := true
|
||||
if s.ready {
|
||||
decryptedHolder, holderErr := s.decrypt(row.HolderCipher)
|
||||
decryptedAccount, accountErr := s.decrypt(row.AccountNoCipher)
|
||||
if holderErr == nil && accountErr == nil {
|
||||
holderName, accountNo, masked = decryptedHolder, decryptedAccount, false
|
||||
if row.IdentityNoCipher != "" {
|
||||
if value, decryptErr := s.decrypt(row.IdentityNoCipher); decryptErr == nil {
|
||||
identityNo = value
|
||||
} else {
|
||||
masked = true
|
||||
}
|
||||
}
|
||||
if row.DigitalRMBCodeCipher != "" {
|
||||
if value, decryptErr := s.decrypt(row.DigitalRMBCodeCipher); decryptErr == nil {
|
||||
digitalRMBCode = value
|
||||
@@ -337,7 +346,7 @@ func (s *Service) PaymentAccountDetails(ctx context.Context, tenantID, memberID
|
||||
}
|
||||
items = append(items, PaymentAccountDetail{
|
||||
ID: row.ID, BankName: row.BankName, HolderName: holderName,
|
||||
IdentityNo: identityNo, AccountNo: accountNo, DigitalRMBCode: digitalRMBCode,
|
||||
AccountNo: accountNo, DigitalRMBCode: digitalRMBCode,
|
||||
WechatQRURL: row.WechatQRURL, AlipayQRURL: row.AlipayQRURL,
|
||||
WechatQRAttachmentID: row.WechatQRAttachmentID, AlipayQRAttachmentID: row.AlipayQRAttachmentID,
|
||||
VerifyStatus: verifyStatusName(row.VerifyStatus), IsDefault: row.IsDefault == 1, Masked: masked,
|
||||
@@ -386,10 +395,6 @@ func (s *Service) paymentAccountCreation(tenantID, memberID int64, input Payment
|
||||
if err != nil {
|
||||
return store.PaymentAccountCreation{}, err
|
||||
}
|
||||
identityNoCipher, err := s.encrypt(input.IdentityNo)
|
||||
if err != nil {
|
||||
return store.PaymentAccountCreation{}, err
|
||||
}
|
||||
accountCipher, err := s.encrypt(input.AccountNo)
|
||||
if err != nil {
|
||||
return store.PaymentAccountCreation{}, err
|
||||
@@ -406,8 +411,7 @@ func (s *Service) paymentAccountCreation(tenantID, memberID int64, input Payment
|
||||
return store.PaymentAccountCreation{
|
||||
TenantID: tenantID, MemberID: memberID, AccountType: 1, BankCode: "BANK", BankName: input.BankName,
|
||||
HolderCipher: holderCipher, HolderMask: maskName(input.HolderName),
|
||||
IdentityNoCipher: identityNoCipher, IdentityNoHash: s.lookupHash(input.IdentityNo),
|
||||
IdentityNoMask: maskIdentityNo(input.IdentityNo), AccountNoCipher: accountCipher,
|
||||
AccountNoCipher: accountCipher,
|
||||
AccountNoHash: s.lookupHash(input.AccountNo), AccountNoMask: maskAccount(input.AccountNo),
|
||||
DigitalRMBCodeCipher: digitalRMBCodeCipher, DigitalRMBCodeHash: digitalRMBCodeHash,
|
||||
DigitalRMBCodeMask: digitalRMBCodeMask,
|
||||
@@ -454,13 +458,6 @@ func (s *Service) CreateWithdrawal(ctx context.Context, tenantID, memberID int64
|
||||
if err != nil {
|
||||
return nil, store.ErrPaymentAccountInvalid
|
||||
}
|
||||
identityNo := ""
|
||||
if account.IdentityNoCipher != "" {
|
||||
identityNo, err = s.decrypt(account.IdentityNoCipher)
|
||||
if err != nil {
|
||||
return nil, store.ErrPaymentAccountInvalid
|
||||
}
|
||||
}
|
||||
digitalRMBCode := ""
|
||||
if account.DigitalRMBCodeCipher != "" {
|
||||
digitalRMBCode, err = s.decrypt(account.DigitalRMBCodeCipher)
|
||||
@@ -471,7 +468,7 @@ func (s *Service) CreateWithdrawal(ctx context.Context, tenantID, memberID int64
|
||||
snapshot, err := json.Marshal(map[string]any{
|
||||
"account_type": accountTypeName(account.AccountType), "bank_code": account.BankCode,
|
||||
"bank_name": account.BankName, "holder_name": holderName,
|
||||
"identity_no": identityNo, "account_no": accountNo,
|
||||
"account_no": accountNo,
|
||||
"digital_rmb_code": digitalRMBCode,
|
||||
"wechat_qr_attachment_id": account.WechatQRAttachmentID,
|
||||
"alipay_qr_attachment_id": account.AlipayQRAttachmentID,
|
||||
@@ -849,10 +846,9 @@ func (s *Service) verifyTradePassword(ctx context.Context, tenantID, memberID in
|
||||
func normalizePaymentAccount(input *PaymentAccountInput) error {
|
||||
input.BankName = strings.TrimSpace(input.BankName)
|
||||
input.HolderName = strings.TrimSpace(input.HolderName)
|
||||
input.IdentityNo = strings.ToUpper(strings.NewReplacer(" ", "", "-", "").Replace(input.IdentityNo))
|
||||
input.AccountNo = strings.NewReplacer(" ", "", "-", "").Replace(strings.TrimSpace(input.AccountNo))
|
||||
input.DigitalRMBCode = strings.NewReplacer(" ", "", "-", "").Replace(strings.TrimSpace(input.DigitalRMBCode))
|
||||
if !validHumanName(input.HolderName) || !identityNoPattern.MatchString(input.IdentityNo) ||
|
||||
if !validHumanName(input.HolderName) ||
|
||||
utf8.RuneCountInString(input.BankName) < 2 || utf8.RuneCountInString(input.BankName) > 100 ||
|
||||
len(input.AccountNo) < 8 || len(input.AccountNo) > 32 || !allDigits(input.AccountNo) ||
|
||||
(input.DigitalRMBCode != "" && (len(input.DigitalRMBCode) < 6 || len(input.DigitalRMBCode) > 32 || !allDigits(input.DigitalRMBCode))) ||
|
||||
@@ -865,7 +861,7 @@ func normalizePaymentAccount(input *PaymentAccountInput) error {
|
||||
|
||||
func paymentAccountFromStore(row store.PaymentAccount) PaymentAccount {
|
||||
return PaymentAccount{ID: row.ID, AccountType: accountTypeName(row.AccountType), BankCode: row.BankCode,
|
||||
BankName: row.BankName, HolderMask: row.HolderMask, IdentityNoMask: row.IdentityNoMask,
|
||||
BankName: row.BankName, HolderMask: row.HolderMask,
|
||||
AccountNoMask: row.AccountNoMask, DigitalRMBCodeMask: row.DigitalRMBCodeMask,
|
||||
WechatQRAttachmentID: row.WechatQRAttachmentID,
|
||||
AlipayQRAttachmentID: row.AlipayQRAttachmentID,
|
||||
@@ -1042,12 +1038,18 @@ func (s *Service) decrypt(value string) (string, error) {
|
||||
if err != nil || len(payload) <= s.aead.NonceSize() {
|
||||
return "", errors.New("encrypted value is invalid")
|
||||
}
|
||||
plain, err := s.aead.Open(nil, payload[:s.aead.NonceSize()], payload[s.aead.NonceSize():], nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
aeads := append([]cipher.AEAD{s.aead}, s.legacyAEADs...)
|
||||
for _, aead := range aeads {
|
||||
if len(payload) <= aead.NonceSize() {
|
||||
continue
|
||||
}
|
||||
plain, decryptErr := aead.Open(nil, payload[:aead.NonceSize()], payload[aead.NonceSize():], nil)
|
||||
if decryptErr == nil {
|
||||
return string(plain), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("encrypted value cannot be decrypted")
|
||||
}
|
||||
|
||||
func (s *Service) lookupHash(value string) string {
|
||||
mac := hmac.New(sha256.New, s.lookupKey)
|
||||
@@ -1116,13 +1118,6 @@ func maskAccount(value string) string {
|
||||
return string(runes[:prefix]) + strings.Repeat("*", len(runes)-prefix-4) + string(runes[len(runes)-4:])
|
||||
}
|
||||
|
||||
func maskIdentityNo(value string) string {
|
||||
if len(value) <= 8 {
|
||||
return strings.Repeat("*", len(value))
|
||||
}
|
||||
return value[:4] + strings.Repeat("*", len(value)-8) + value[len(value)-4:]
|
||||
}
|
||||
|
||||
func validHumanName(value string) bool {
|
||||
count := utf8.RuneCountInString(value)
|
||||
if count < 2 || count > 64 {
|
||||
|
||||
@@ -135,22 +135,21 @@ func TestQuoteHonorsExplicitDisabledWithdrawalSwitch(t *testing.T) {
|
||||
|
||||
func TestNormalizePaymentAccount(t *testing.T) {
|
||||
input := PaymentAccountInput{BankName: "中国工商银行", HolderName: "张三",
|
||||
IdentityNo: "11010119900101123X", AccountNo: "6222 0000-0000 1234",
|
||||
AccountNo: "6222 0000-0000 1234",
|
||||
DigitalRMBCode: "1234 5678",
|
||||
WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9}
|
||||
if err := normalizePaymentAccount(&input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if input.IdentityNo != "11010119900101123X" || input.AccountNo != "6222000000001234" ||
|
||||
input.DigitalRMBCode != "12345678" ||
|
||||
maskAccount(input.AccountNo) != "6222********1234" || maskIdentityNo(input.IdentityNo) != "1101**********123X" {
|
||||
if input.AccountNo != "6222000000001234" || input.DigitalRMBCode != "12345678" ||
|
||||
maskAccount(input.AccountNo) != "6222********1234" {
|
||||
t.Fatalf("unexpected normalized input: %#v", input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePaymentAccountAllowsEmptyDigitalRMBCode(t *testing.T) {
|
||||
input := PaymentAccountInput{BankName: "中国工商银行", HolderName: "张三",
|
||||
IdentityNo: "11010119900101123X", AccountNo: "6222000000001234",
|
||||
AccountNo: "6222000000001234",
|
||||
WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9}
|
||||
if err := normalizePaymentAccount(&input); err != nil {
|
||||
t.Fatalf("empty digital RMB code should be optional: %v", err)
|
||||
@@ -167,11 +166,10 @@ func TestPaymentAccountDetailsReturnsOriginalFields(t *testing.T) {
|
||||
FinanceMasterKey: "test-finance-master-key-with-at-least-32-characters",
|
||||
})
|
||||
holder, _ := service.encrypt("张三")
|
||||
identity, _ := service.encrypt("11010119900101123X")
|
||||
account, _ := service.encrypt("6222000000001234")
|
||||
digital, _ := service.encrypt("123456")
|
||||
repository.details = []store.PaymentAccount{{
|
||||
ID: 7, BankName: "中国工商银行", HolderCipher: holder, IdentityNoCipher: identity,
|
||||
ID: 7, BankName: "中国工商银行", HolderCipher: holder,
|
||||
AccountNoCipher: account, DigitalRMBCodeCipher: digital,
|
||||
WechatQRURL: "/wechat.png", AlipayQRURL: "/alipay.png",
|
||||
WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9, VerifyStatus: 2,
|
||||
@@ -180,7 +178,7 @@ func TestPaymentAccountDetailsReturnsOriginalFields(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 || items[0].HolderName != "张三" || items[0].IdentityNo != "11010119900101123X" ||
|
||||
if len(items) != 1 || items[0].HolderName != "张三" ||
|
||||
items[0].AccountNo != "6222000000001234" || items[0].DigitalRMBCode != "123456" ||
|
||||
items[0].WechatQRURL != "/wechat.png" || items[0].AlipayQRURL != "/alipay.png" ||
|
||||
items[0].WechatQRAttachmentID != 8 || items[0].AlipayQRAttachmentID != 9 {
|
||||
@@ -188,13 +186,38 @@ func TestPaymentAccountDetailsReturnsOriginalFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentAccountDetailsDecryptsWithLegacyMasterKey(t *testing.T) {
|
||||
repository := &fakeRepository{}
|
||||
legacyMasterKey := "legacy-finance-master-key-with-at-least-32-characters"
|
||||
legacyService := New(repository, &redis.Client{}, config.AuthConfig{FinanceMasterKey: legacyMasterKey})
|
||||
holder, _ := legacyService.encrypt("张三")
|
||||
account, _ := legacyService.encrypt("6222000000001234")
|
||||
digital, _ := legacyService.encrypt("123456")
|
||||
repository.details = []store.PaymentAccount{{
|
||||
ID: 7, BankName: "中国工商银行", HolderCipher: holder, AccountNoCipher: account,
|
||||
DigitalRMBCodeCipher: digital, WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9,
|
||||
}}
|
||||
service := New(repository, &redis.Client{}, config.AuthConfig{
|
||||
FinanceMasterKey: "current-finance-master-key-with-at-least-32-characters",
|
||||
FinanceLegacyMasterKeys: []string{legacyMasterKey},
|
||||
})
|
||||
items, err := service.PaymentAccountDetails(context.Background(), 1, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 || items[0].Masked || items[0].HolderName != "张三" ||
|
||||
items[0].AccountNo != "6222000000001234" || items[0].DigitalRMBCode != "123456" {
|
||||
t.Fatalf("unexpected legacy account details: %#v", items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePaymentAccountEncryptsEditedFields(t *testing.T) {
|
||||
repository := &paymentAccountRepository{}
|
||||
service := New(repository, &redis.Client{}, config.AuthConfig{
|
||||
FinanceMasterKey: "test-finance-master-key-with-at-least-32-characters",
|
||||
})
|
||||
result, err := service.UpdatePaymentAccount(context.Background(), 1, 2, PaymentAccountInput{
|
||||
ID: 7, BankName: "中国工商银行", HolderName: "李四", IdentityNo: "11010119900101123X",
|
||||
ID: 7, BankName: "中国工商银行", HolderName: "李四",
|
||||
AccountNo: "6222000000001234", WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -226,7 +249,7 @@ func TestPaymentAccountDetailsFallsBackToMasksWhenHistoricalKeyIsUnavailable(t *
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 || !items[0].Masked || items[0].HolderName != "张*" ||
|
||||
items[0].IdentityNo != "1101**********123X" || items[0].AccountNo != "6222********1234" ||
|
||||
items[0].AccountNo != "6222********1234" ||
|
||||
items[0].DigitalRMBCode != "12****78" {
|
||||
t.Fatalf("unexpected masked fallback: %#v", items)
|
||||
}
|
||||
@@ -238,7 +261,7 @@ func TestCreatePaymentAccountDoesNotStoreEmptyDigitalRMBCode(t *testing.T) {
|
||||
FinanceMasterKey: "test-finance-master-key-with-at-least-32-characters",
|
||||
})
|
||||
_, err := service.CreatePaymentAccount(context.Background(), 0, 1, PaymentAccountInput{
|
||||
BankName: "中国工商银行", HolderName: "张三", IdentityNo: "11010119900101123X",
|
||||
BankName: "中国工商银行", HolderName: "张三",
|
||||
AccountNo: "6222000000001234", WechatQRAttachmentID: 8, AlipayQRAttachmentID: 9,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -247,6 +270,9 @@ func TestCreatePaymentAccountDoesNotStoreEmptyDigitalRMBCode(t *testing.T) {
|
||||
if repository.created.DigitalRMBCodeCipher != "" || repository.created.DigitalRMBCodeHash != "" || repository.created.DigitalRMBCodeMask != "" {
|
||||
t.Fatalf("empty digital RMB code must not be encrypted, hashed or masked: %#v", repository.created)
|
||||
}
|
||||
if repository.created.IdentityNoCipher != "" || repository.created.IdentityNoHash != "" || repository.created.IdentityNoMask != "" {
|
||||
t.Fatalf("identity number must no longer be collected: %#v", repository.created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskAccountShortValueDoesNotLeak(t *testing.T) {
|
||||
|
||||
@@ -52,15 +52,10 @@
|
||||
|
||||
<AsyncState v-else-if="formType === 'bank'" :loading="bankLoading" :error="bankLoadError" @retry="loadPaymentAccounts">
|
||||
<form class="m-plain-form m-bank-form" @submit.prevent="saveBank">
|
||||
<p v-if="existingAccount?.masked" class="m-bound-account-hint">历史资料无法完整读取,请重新填写姓名、身份证号和银行卡号后保存</p>
|
||||
<p v-if="existingAccount?.masked" class="m-bound-account-hint">历史资料无法完整读取,请重新填写姓名和银行卡号后保存</p>
|
||||
<label
|
||||
><span>姓名</span><input v-model.trim="bank.holder_name" placeholder="请输入姓名"
|
||||
/></label>
|
||||
<label
|
||||
><span>身份证号</span
|
||||
><input v-model.trim="bank.identity_no" maxlength="18" placeholder="请输入你的身份证号"
|
||||
/></label>
|
||||
<div class="m-bank-form__divider"></div>
|
||||
<label
|
||||
><span>银行卡号</span
|
||||
><input v-model.trim="bank.account_no" inputmode="numeric" placeholder="请输入你的银行卡号"
|
||||
@@ -266,7 +261,6 @@
|
||||
id: number;
|
||||
bank_name: string;
|
||||
holder_name: string;
|
||||
identity_no: string;
|
||||
account_no: string;
|
||||
digital_rmb_code: string;
|
||||
wechat_qr_url: string;
|
||||
@@ -282,7 +276,6 @@
|
||||
const bank = reactive({
|
||||
bank_name: '',
|
||||
holder_name: '',
|
||||
identity_no: '',
|
||||
account_no: '',
|
||||
digital_rmb_code: '',
|
||||
wechat_qr_attachment_id: 0,
|
||||
@@ -344,7 +337,7 @@
|
||||
bankLoadError.value = '';
|
||||
error.value = '';
|
||||
existingAccount.value = null;
|
||||
Object.assign(bank, { bank_name: '', holder_name: '', identity_no: '', account_no: '', digital_rmb_code: '',
|
||||
Object.assign(bank, { bank_name: '', holder_name: '', account_no: '', digital_rmb_code: '',
|
||||
wechat_qr_attachment_id: 0, alipay_qr_attachment_id: 0 });
|
||||
qr.wechat.url = '';
|
||||
qr.alipay.url = '';
|
||||
@@ -359,7 +352,6 @@
|
||||
bank.alipay_qr_attachment_id = account.alipay_qr_attachment_id;
|
||||
if (!account.masked) {
|
||||
bank.holder_name = account.holder_name;
|
||||
bank.identity_no = account.identity_no;
|
||||
bank.account_no = account.account_no;
|
||||
bank.digital_rmb_code = account.digital_rmb_code;
|
||||
}
|
||||
@@ -371,14 +363,11 @@
|
||||
}
|
||||
async function saveBank() {
|
||||
error.value = '';
|
||||
const identityNo = bank.identity_no.replace(/[ -]/g, '');
|
||||
const accountNo = bank.account_no.replace(/[ -]/g, '');
|
||||
const digitalRMBCode = bank.digital_rmb_code.replace(/[ -]/g, '');
|
||||
const validationError =
|
||||
bank.holder_name.length < 2
|
||||
? '请输入至少 2 个字符的姓名'
|
||||
: !/^[1-9]\d{14}(?:\d{2}[\dXx])?$/.test(identityNo)
|
||||
? '请输入正确的 15 位或 18 位身份证号'
|
||||
: !/^\d{8,32}$/.test(accountNo)
|
||||
? '请输入 8 至 32 位银行卡号'
|
||||
: bank.bank_name.length < 2
|
||||
|
||||
Reference in New Issue
Block a user