AES 字段加密迁配置并兼容旧密文

移除字段加密硬编码主密钥,改为 FIELD_ENCRYPTION_KEY 注入。

保留 FIELD_ENCRYPTION_LEGACY_KEY 透明回退旧密文,新写入统一使用主密钥;生产环境校验主密钥和 legacy 密钥长度、占位符及相等关系,并统一生产环境判断口径。

补充配置与旧密文兼容回归测试。
This commit is contained in:
yml
2026-06-14 17:53:24 +08:00
parent 64f7ed3753
commit 8fef037be3
15 changed files with 519 additions and 82 deletions
+47 -6
View File
@@ -16,6 +16,8 @@ type Config struct {
RedisDB int
JWTSecret string
PaymentConfigEncryptionKey string
FieldEncryptionKey string
FieldEncryptionLegacyKey string
ExternalUploadSecret string
ExternalUploadAllowedIPs []string
BootstrapAdminUsername string
@@ -72,11 +74,16 @@ func Load() Config {
RedisDB: getEnvInt("REDIS_DB", 0),
JWTSecret: getEnv("JWT_SECRET", "change-me"),
PaymentConfigEncryptionKey: getEnv("PAYMENT_CONFIG_ENCRYPTION_KEY", ""),
ExternalUploadSecret: getEnv("EXTERNAL_UPLOAD_SECRET", ""),
ExternalUploadAllowedIPs: getEnvList("EXTERNAL_UPLOAD_ALLOWED_IPS"),
BootstrapAdminUsername: getEnv("ADMIN_BOOTSTRAP_USERNAME", ""),
BootstrapAdminPassword: getEnv("ADMIN_BOOTSTRAP_PASSWORD", ""),
BootstrapAdminNickname: getEnv("ADMIN_BOOTSTRAP_NICKNAME", "超级管理员"),
FieldEncryptionKey: getEnv("FIELD_ENCRYPTION_KEY", ""),
// 旧密钥回退:生产环境必须显式设置(空 = 禁用回退,用于密钥轮换收敛);
// 非生产默认填历史硬编码密钥,兼容开发/测试库的存量密文。不能用 getEnv 的 fallback——
// 那样生产删掉 env 会重新注入已泄露的硬编码密钥,破坏轮换闭环。
FieldEncryptionLegacyKey: fieldEncryptionLegacyKey(getEnv("APP_ENV", "development")),
ExternalUploadSecret: getEnv("EXTERNAL_UPLOAD_SECRET", ""),
ExternalUploadAllowedIPs: getEnvList("EXTERNAL_UPLOAD_ALLOWED_IPS"),
BootstrapAdminUsername: getEnv("ADMIN_BOOTSTRAP_USERNAME", ""),
BootstrapAdminPassword: getEnv("ADMIN_BOOTSTRAP_PASSWORD", ""),
BootstrapAdminNickname: getEnv("ADMIN_BOOTSTRAP_NICKNAME", "超级管理员"),
Storage: StorageConfig{
Endpoint: getEnv("STORAGE_ENDPOINT", "http://localhost:9000"),
Bucket: getEnv("STORAGE_BUCKET", "hfb-sys"),
@@ -110,7 +117,7 @@ func Load() Config {
}
func (c Config) ValidateProductionSecurity() error {
if strings.ToLower(strings.TrimSpace(c.AppEnv)) != "production" {
if !IsProductionEnv(c.AppEnv) {
return nil
}
if strings.TrimSpace(c.JWTSecret) == "" || isPlaceholder(c.JWTSecret) || len([]byte(c.JWTSecret)) < 32 {
@@ -120,6 +127,17 @@ func (c Config) ValidateProductionSecurity() error {
if isPlaceholder(c.PaymentConfigEncryptionKey) || (keyLen != 16 && keyLen != 24 && keyLen != 32) {
return errors.New("PAYMENT_CONFIG_ENCRYPTION_KEY must be 16, 24, or 32 bytes in production")
}
fieldKeyLen := len([]byte(c.FieldEncryptionKey))
if c.FieldEncryptionKey == "" || isPlaceholder(c.FieldEncryptionKey) || (fieldKeyLen != 16 && fieldKeyLen != 24 && fieldKeyLen != 32) {
return errors.New("FIELD_ENCRYPTION_KEY must be 16, 24, or 32 bytes in production")
}
legacyKeyLen := len([]byte(c.FieldEncryptionLegacyKey))
if c.FieldEncryptionLegacyKey != "" && (isPlaceholder(c.FieldEncryptionLegacyKey) || (legacyKeyLen != 16 && legacyKeyLen != 24 && legacyKeyLen != 32)) {
return errors.New("FIELD_ENCRYPTION_LEGACY_KEY must be empty or 16, 24, or 32 bytes in production")
}
if c.FieldEncryptionKey == c.FieldEncryptionLegacyKey {
return errors.New("FIELD_ENCRYPTION_KEY must differ from FIELD_ENCRYPTION_LEGACY_KEY in production (set a new primary key to rotate)")
}
if c.BootstrapAdminPassword != "" && isPlaceholder(c.BootstrapAdminPassword) {
return errors.New("ADMIN_BOOTSTRAP_PASSWORD must not use the example placeholder in production")
}
@@ -134,6 +152,24 @@ func getEnv(key, fallback string) string {
return value
}
// historicFieldEncryptionKey 是迁移前 pkg/crypto 的硬编码密钥,仅用于解密历史存量密文。
const historicFieldEncryptionKey = "hfb-sys-2024-secret-key-32bytes!"
// fieldEncryptionLegacyKey 解析 FIELD_ENCRYPTION_LEGACY_KEY
// - 生产环境:必须显式设置,空表示禁用回退(密钥轮换收敛后删除 env 即关闭旧密钥)。
// 不能用硬编码默认值,否则轮换闭环不成立。
// - 非生产环境:未设置时回退到历史硬编码密钥,兼容开发/测试库的存量密文(零配置)。
func fieldEncryptionLegacyKey(appEnv string) string {
if IsProductionEnv(appEnv) {
// 生产显式空 = 禁用;未设置也视为禁用。
return os.Getenv("FIELD_ENCRYPTION_LEGACY_KEY")
}
if v := os.Getenv("FIELD_ENCRYPTION_LEGACY_KEY"); v != "" {
return v
}
return historicFieldEncryptionKey
}
func getEnvInt(key string, fallback int) int {
value := os.Getenv(key)
if value == "" {
@@ -178,3 +214,8 @@ func isPlaceholder(value string) bool {
normalized := strings.ToLower(strings.TrimSpace(value))
return normalized == "change-me" || strings.HasPrefix(normalized, "change-") || strings.Contains(normalized, "change-to-")
}
// IsProductionEnv 判断 AppEnv 是否为生产环境(与 ValidateProductionSecurity 同口径)。
func IsProductionEnv(appEnv string) bool {
return strings.ToLower(strings.TrimSpace(appEnv)) == "production"
}
+104
View File
@@ -0,0 +1,104 @@
package config
import (
"os"
"testing"
)
const historicKey = "hfb-sys-2024-secret-key-32bytes!"
func TestIsProductionEnv(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"production", true},
{"PRODUCTION", true},
{" production ", true},
{"development", false},
{"", false},
{"staging", false},
}
for _, c := range cases {
if got := IsProductionEnv(c.in); got != c.want {
t.Fatalf("IsProductionEnv(%q) = %v, want %v", c.in, got, c.want)
}
}
}
// TestFieldEncryptionLegacyKeyNonProductionDefaultsToHistoric 验证非生产环境未设置 legacy 时
// 回退到历史硬编码密钥(开发态零配置兼容旧密文)。
func TestFieldEncryptionLegacyKeyNonProductionDefaultsToHistoric(t *testing.T) {
unsetEnv(t, "FIELD_ENCRYPTION_LEGACY_KEY")
if got := fieldEncryptionLegacyKey("development"); got != historicKey {
t.Fatalf("non-production default legacy = %q, want %q", got, historicKey)
}
}
// TestFieldEncryptionLegacyKeyNonProductionExplicitOverride 验证非生产环境显式设置 legacy 时用该值。
func TestFieldEncryptionLegacyKeyNonProductionExplicitOverride(t *testing.T) {
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "explicit-legacy-16bytes!")
if got := fieldEncryptionLegacyKey("development"); got != "explicit-legacy-16bytes!" {
t.Fatalf("non-production explicit legacy = %q, want explicit-legacy-16bytes!", got)
}
}
// TestFieldEncryptionLegacyKeyProductionEmptyDisablesFallback 验证生产环境未设置/留空 legacy
// 返回空(禁用回退),而不是注入历史硬编码密钥。
// 这是密钥轮换闭环的关键:删除 env 必须真正关闭旧密钥。
func TestFieldEncryptionLegacyKeyProductionEmptyDisablesFallback(t *testing.T) {
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "")
if got := fieldEncryptionLegacyKey("production"); got != "" {
t.Fatalf("production empty legacy = %q, want empty (fallback disabled); injecting historic key breaks rotation closure", got)
}
}
// TestFieldEncryptionLegacyKeyProductionExplicitValue 验证生产环境显式设置 legacy 时用该值
// (轮换期间保留旧密钥解密存量)。
func TestFieldEncryptionLegacyKeyProductionExplicitValue(t *testing.T) {
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "prod-old-key-32bytes-0123456789")
if got := fieldEncryptionLegacyKey("production"); got != "prod-old-key-32bytes-0123456789" {
t.Fatalf("production explicit legacy = %q, want prod-old-key-32bytes-0123456789", got)
}
}
// TestValidateProductionSecurityRejectsPrimaryEqualsLegacy 验证生产环境 primary 等于 legacy 时报错。
func TestValidateProductionSecurityRejectsPrimaryEqualsLegacy(t *testing.T) {
t.Setenv("APP_ENV", "production")
t.Setenv("JWT_SECRET", "a-very-long-random-jwt-secret-at-least-32-bytes!!")
t.Setenv("PAYMENT_CONFIG_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
t.Setenv("FIELD_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "0123456789abcdef0123456789abcdef")
cfg := Load()
if err := cfg.ValidateProductionSecurity(); err == nil {
t.Fatal("ValidateProductionSecurity should reject FIELD_ENCRYPTION_KEY == LEGACY_KEY")
}
}
// TestValidateProductionSecurityRejectsInvalidLegacyKey 验证生产环境显式设置 legacy 时也校验长度。
func TestValidateProductionSecurityRejectsInvalidLegacyKey(t *testing.T) {
t.Setenv("APP_ENV", "PRODUCTION")
t.Setenv("JWT_SECRET", "a-very-long-random-jwt-secret-at-least-32-bytes!!")
t.Setenv("PAYMENT_CONFIG_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
t.Setenv("FIELD_ENCRYPTION_KEY", "abcdef0123456789abcdef0123456789")
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "too-short")
cfg := Load()
if err := cfg.ValidateProductionSecurity(); err == nil {
t.Fatal("ValidateProductionSecurity should reject invalid FIELD_ENCRYPTION_LEGACY_KEY")
}
}
func unsetEnv(t *testing.T, key string) {
t.Helper()
old, ok := os.LookupEnv(key)
if err := os.Unsetenv(key); err != nil {
t.Fatalf("unset %s: %v", key, err)
}
t.Cleanup(func() {
if ok {
_ = os.Setenv(key, old)
return
}
_ = os.Unsetenv(key)
})
}
+3 -3
View File
@@ -232,10 +232,10 @@ func newFlowServices(db *gorm.DB) flowServices {
order: order.NewService(orderRepo),
payment: payment.NewService(paymentRepo),
paymentConfig: paymentconfig.NewService(configRepo),
paymentAccount: paymentaccount.NewService(paymentaccount.NewRepository(db)),
paymentAccount: paymentaccount.NewService(paymentaccount.NewRepository(db, &crypto.MockEncryptor{})),
dispute: dispute.NewService(disputeRepo),
wallet: wallet.NewService(walletRepo),
withdrawal: withdrawal.NewService(withdrawal.NewRepository(db, walletRepo)),
withdrawal: withdrawal.NewService(withdrawal.NewRepository(db, walletRepo, &crypto.MockEncryptor{})),
finance: adminfinance.NewService(adminfinance.NewRepository(db)),
}
}
@@ -362,7 +362,7 @@ func seedUsers(t *testing.T, db *gorm.DB) (model.User, model.User, uint64) {
func seedRealname(t *testing.T, db *gorm.DB, userID uint64, name string, verifiedAt time.Time) {
t.Helper()
encryptedName, err := crypto.Encrypt(name)
encryptedName, err := (&crypto.MockEncryptor{}).Encrypt(name)
if err != nil {
t.Fatalf("加密实名姓名失败: %v", err)
}
@@ -13,14 +13,16 @@ import (
"gorm.io/gorm"
)
// 删除加密密钥常量
type Repository struct {
db *gorm.DB
db *gorm.DB
encryptor crypto.Encryptor
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
func NewRepository(db *gorm.DB, encryptor crypto.Encryptor) *Repository {
if encryptor == nil {
encryptor = &crypto.MockEncryptor{}
}
return &Repository{db: db, encryptor: encryptor}
}
func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) {
@@ -73,7 +75,7 @@ func (r *Repository) FindByID(ctx context.Context, userID, id uint64) (*PaymentA
func (r *Repository) Create(ctx context.Context, userID uint64, req CreatePaymentAccountRequest) (*PaymentAccountDTO, error) {
db := r.db.WithContext(ctx)
// 加密账号
encryptedNo, err := crypto.Encrypt(req.AccountNo)
encryptedNo, err := r.encryptor.Encrypt(req.AccountNo)
if err != nil {
return nil, err
}
@@ -217,7 +219,7 @@ func (r *Repository) ValidateRealname(ctx context.Context, userID uint64, accoun
// 验证姓名匹配 - 使用加密字段进行精确匹配
if realname.EncryptedName != "" {
// 有加密字段,解密后精确匹配
decryptedName, err := crypto.Decrypt(realname.EncryptedName)
decryptedName, err := r.encryptor.Decrypt(realname.EncryptedName)
if err != nil {
// 解密失败,降级到前缀匹配
return r.validateByMaskedName(realname.MaskedName, accountName)
@@ -256,7 +258,7 @@ func (r *Repository) validateByMaskedName(maskedName, accountName string) error
func (r *Repository) toDTO(account model.UserPaymentAccount) (*PaymentAccountDTO, error) {
// 解密账号并脱敏
decrypted, err := crypto.Decrypt(account.AccountNo)
decrypted, err := r.encryptor.Decrypt(account.AccountNo)
if err != nil {
decrypted = account.AccountNo // 降级处理
}
@@ -314,5 +316,5 @@ func (r *Repository) GetDecryptedAccountNo(ctx context.Context, userID, id uint6
if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ?", id, userID).First(&account).Error; err != nil {
return "", err
}
return crypto.Decrypt(account.AccountNo)
return r.encryptor.Decrypt(account.AccountNo)
}
@@ -16,14 +16,16 @@ import (
const cloudMarketProviderName = "aliyun_cloudmarket"
type CloudMarketConfig struct {
URL string
AppCode string
URL string
AppCode string
Encryptor crypto.Encryptor
}
type CloudMarketProvider struct {
url string
appCode string
client *http.Client
url string
appCode string
client *http.Client
encryptor crypto.Encryptor
}
type cloudMarketResponse struct {
@@ -42,10 +44,15 @@ func NewCloudMarketProvider(cfg CloudMarketConfig) (*CloudMarketProvider, error)
if _, err := url.ParseRequestURI(endpoint); err != nil {
return nil, fmt.Errorf("realname cloud market url invalid: %w", err)
}
encryptor := cfg.Encryptor
if encryptor == nil {
encryptor = &crypto.MockEncryptor{}
}
return &CloudMarketProvider{
url: endpoint,
appCode: appCode,
client: &http.Client{Timeout: 10 * time.Second},
url: endpoint,
appCode: appCode,
client: &http.Client{Timeout: 10 * time.Second},
encryptor: encryptor,
}, nil
}
@@ -108,11 +115,11 @@ func (p *CloudMarketProvider) Start(ctx context.Context, req StartRequest) (Prov
}
// 加密完整信息
encryptedName, err := crypto.Encrypt(req.Name)
encryptedName, err := p.encryptor.Encrypt(req.Name)
if err != nil {
return ProviderResult{}, err
}
encryptedIDNo, err := crypto.Encrypt(req.IDNo)
encryptedIDNo, err := p.encryptor.Encrypt(req.IDNo)
if err != nil {
return ProviderResult{}, err
}
@@ -5,6 +5,8 @@ import (
"net/http"
"net/http/httptest"
"testing"
"hfb_sys/backend/pkg/crypto"
)
func TestCloudMarketProviderStartVerified(t *testing.T) {
@@ -27,7 +29,8 @@ func TestCloudMarketProviderStartVerified(t *testing.T) {
provider, err := NewCloudMarketProvider(CloudMarketConfig{
URL: server.URL,
AppCode: "test-code",
AppCode: "test-code",
Encryptor: &crypto.MockEncryptor{},
})
if err != nil {
t.Fatalf("创建 Provider 失败:%v", err)
@@ -60,7 +63,8 @@ func TestCloudMarketProviderStartRejected(t *testing.T) {
provider, err := NewCloudMarketProvider(CloudMarketConfig{
URL: server.URL,
AppCode: "test-code",
AppCode: "test-code",
Encryptor: &crypto.MockEncryptor{},
})
if err != nil {
t.Fatalf("创建 Provider 失败:%v", err)
@@ -90,7 +94,8 @@ func TestCloudMarketProviderStartRateLimited(t *testing.T) {
provider, err := NewCloudMarketProvider(CloudMarketConfig{
URL: server.URL,
AppCode: "test-code",
AppCode: "test-code",
Encryptor: &crypto.MockEncryptor{},
})
if err != nil {
t.Fatalf("创建 Provider 失败:%v", err)
+10 -5
View File
@@ -33,10 +33,15 @@ type ProviderResult struct {
FailReason string
}
type MockProvider struct{}
type MockProvider struct {
encryptor crypto.Encryptor
}
func NewMockProvider() *MockProvider {
return &MockProvider{}
func NewMockProvider(encryptor crypto.Encryptor) *MockProvider {
if encryptor == nil {
encryptor = &crypto.MockEncryptor{}
}
return &MockProvider{encryptor: encryptor}
}
func (p *MockProvider) Name() string {
@@ -53,11 +58,11 @@ func (p *MockProvider) Start(_ context.Context, req StartRequest) (ProviderResul
}
// 加密完整信息
encryptedName, err := crypto.Encrypt(req.Name)
encryptedName, err := p.encryptor.Encrypt(req.Name)
if err != nil {
return ProviderResult{}, err
}
encryptedIDNo, err := crypto.Encrypt(req.IDNo)
encryptedIDNo, err := p.encryptor.Encrypt(req.IDNo)
if err != nil {
return ProviderResult{}, err
}
@@ -5,7 +5,6 @@ import (
"encoding/json"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/pkg/crypto"
)
// 转换为用户DTO
@@ -64,7 +63,7 @@ func (r *Repository) toDetailDTO(ctx context.Context, w model.WithdrawalRequest)
var paymentAccount model.UserPaymentAccount
if err := db.First(&paymentAccount, *w.PaymentAccountID).Error; err == nil {
// 解密账号
decrypted, err := crypto.Decrypt(paymentAccount.AccountNo)
decrypted, err := r.encryptor.Decrypt(paymentAccount.AccountNo)
if err == nil {
fullAccountNo = decrypted
}
@@ -2,6 +2,7 @@ package withdrawal
import (
"hfb_sys/backend/internal/modules/wallet"
"hfb_sys/backend/pkg/crypto"
"gorm.io/gorm"
)
@@ -9,12 +10,17 @@ import (
type Repository struct {
db *gorm.DB
walletRepo *wallet.Repository
encryptor crypto.Encryptor
}
func NewRepository(db *gorm.DB, walletRepo *wallet.Repository) *Repository {
func NewRepository(db *gorm.DB, walletRepo *wallet.Repository, encryptor crypto.Encryptor) *Repository {
if encryptor == nil {
encryptor = &crypto.MockEncryptor{}
}
return &Repository{
db: db,
walletRepo: walletRepo,
encryptor: encryptor,
}
}
+52 -10
View File
@@ -34,6 +34,7 @@ import (
"hfb_sys/backend/internal/modules/withdrawal"
_ "hfb_sys/backend/docs" // Swagger 文档
"hfb_sys/backend/pkg/crypto"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@@ -42,7 +43,7 @@ import (
)
func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
if cfg.AppEnv == "production" {
if config.IsProductionEnv(cfg.AppEnv) {
gin.SetMode(gin.ReleaseMode)
}
@@ -58,13 +59,53 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
engine.GET("/health", health.Check)
// Swagger 文档路由(仅在非生产环境)
if cfg.AppEnv != "production" {
if !config.IsProductionEnv(cfg.AppEnv) {
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
if cfg.RateLimit.Enabled {
engine.Use(middleware.RateLimitPerMinute(cfg.RateLimit.RequestsPerMinute, deps.Redis))
}
// 业务字段加密器(实名/收款账号):主密钥 + legacy 回退,用于密钥轮换期间透明解出旧密文。
// 生产环境必须配置 FIELD_ENCRYPTION_KEYValidateProductionSecurity 已校验)。
// 开发/测试缺 FIELD_ENCRYPTION_KEY 时用 legacy 密钥构造单密钥加密器,
// 保证零配置兼容历史硬编码密钥加密的存量密文(非生产 legacy 默认填历史硬编码值)。
var fieldEncryptor crypto.Encryptor
primary := cfg.FieldEncryptionKey
legacy := cfg.FieldEncryptionLegacyKey
switch {
case primary != "":
encryptor, err := crypto.NewFieldEncryptor(primary, legacy)
if err != nil {
if config.IsProductionEnv(cfg.AppEnv) {
logger.Fatal("FIELD_ENCRYPTION_KEY invalid", zap.Error(err))
}
logger.Warn("FIELD_ENCRYPTION_KEY invalid, fallback to legacy-only", zap.Error(err))
// primary 非法时退回 legacy 单密钥(若有),否则 MockEncryptor
if legacy != "" {
if e, eErr := crypto.NewFieldEncryptor(legacy); eErr == nil {
fieldEncryptor = e
}
}
} else {
fieldEncryptor = encryptor
}
case legacy != "":
// 开发态零配置:用 legacy 单密钥,能解历史存量密文。
encryptor, err := crypto.NewFieldEncryptor(legacy)
if err == nil {
fieldEncryptor = encryptor
logger.Warn("FIELD_ENCRYPTION_KEY not set, using legacy-only encryptor for dev compatibility")
}
}
if fieldEncryptor == nil {
if config.IsProductionEnv(cfg.AppEnv) {
logger.Fatal("FIELD_ENCRYPTION_KEY not set")
}
fieldEncryptor = &crypto.MockEncryptor{}
logger.Warn("FIELD_ENCRYPTION_KEY and legacy both unset, using MockEncryptor")
}
jwtManager := auth.NewJWTManager(cfg.JWTSecret)
var userRepo *auth.UserRepository
if deps.DB != nil {
@@ -108,7 +149,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
if deps.DB != nil {
realnameRepo = realname.NewRepository(deps.DB)
}
realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, logger), logger)
realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, fieldEncryptor, logger), logger)
realnameHandler := realname.NewHandler(realnameService)
var listingRepo *listing.Repository
if deps.DB != nil {
@@ -149,13 +190,13 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
walletHandler := wallet.NewHandler(walletService)
var paymentAccountRepo *paymentaccount.Repository
if deps.DB != nil {
paymentAccountRepo = paymentaccount.NewRepository(deps.DB)
paymentAccountRepo = paymentaccount.NewRepository(deps.DB, fieldEncryptor)
}
paymentAccountService := paymentaccount.NewService(paymentAccountRepo)
paymentAccountHandler := paymentaccount.NewHandler(paymentAccountService)
var withdrawalRepo *withdrawal.Repository
if deps.DB != nil {
withdrawalRepo = withdrawal.NewRepository(deps.DB, walletRepo)
withdrawalRepo = withdrawal.NewRepository(deps.DB, walletRepo, fieldEncryptor)
}
withdrawalService := withdrawal.NewService(withdrawalRepo)
withdrawalHandler := withdrawal.NewHandler(withdrawalService)
@@ -174,7 +215,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
}
}
if encryptor == nil {
if cfg.AppEnv == "production" {
if config.IsProductionEnv(cfg.AppEnv) {
logger.Fatal("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid")
}
encryptor = &paymentconfig.MockEncryptor{}
@@ -556,12 +597,13 @@ func newSMSProvider(cfg config.Config, logger *zap.Logger) smsintegration.Provid
}
}
func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provider {
func newRealnameProvider(cfg config.Config, encryptor crypto.Encryptor, logger *zap.Logger) realname.Provider {
switch strings.ToLower(strings.TrimSpace(cfg.Realname.Provider)) {
case "cloudmarket", "aliyun_cloudmarket":
provider, err := realname.NewCloudMarketProvider(realname.CloudMarketConfig{
URL: cfg.Realname.CloudMarketURL,
AppCode: cfg.Realname.CloudMarketAppCode,
URL: cfg.Realname.CloudMarketURL,
AppCode: cfg.Realname.CloudMarketAppCode,
Encryptor: encryptor,
})
if err != nil {
logger.Warn("cloud market realname provider unavailable; realname verify will fail", zap.Error(err))
@@ -569,6 +611,6 @@ func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provide
}
return provider
default:
return realname.NewMockProvider()
return realname.NewMockProvider(encryptor)
}
}