mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 13:48:43 +08:00
Merge pull request #4666 from TTopoo/redis-username-support
fix(config): support Redis ACL username
This commit is contained in:
@@ -544,6 +544,7 @@ database:
|
||||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
jwt:
|
||||
|
||||
@@ -1403,6 +1403,7 @@ func (d *DatabaseConfig) DSNWithTimezone(tz string) string {
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
// 连接池与超时配置(性能优化:可配置化连接池参数)
|
||||
@@ -1981,6 +1982,7 @@ func setDefaults() {
|
||||
// Redis
|
||||
viper.SetDefault("redis.host", "localhost")
|
||||
viper.SetDefault("redis.port", 6379)
|
||||
viper.SetDefault("redis.username", "")
|
||||
viper.SetDefault("redis.password", "")
|
||||
viper.SetDefault("redis.db", 0)
|
||||
viper.SetDefault("redis.dial_timeout_seconds", 5)
|
||||
|
||||
@@ -37,6 +37,15 @@ func TestLoadServerTimingConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadRedisUsernameFromEnvironment(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
t.Setenv("REDIS_USERNAME", "app-user")
|
||||
|
||||
cfg, err := Load()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "app-user", cfg.Redis.Username)
|
||||
}
|
||||
|
||||
func TestLoadHTTPIngressSafetyDefaults(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
cfg, err := Load()
|
||||
|
||||
@@ -33,6 +33,7 @@ func InitRedis(cfg *config.Config) *redis.Client {
|
||||
func buildRedisOptions(cfg *config.Config) *redis.Options {
|
||||
opts := &redis.Options{
|
||||
Addr: cfg.Redis.Address(),
|
||||
Username: cfg.Redis.Username,
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
DialTimeout: time.Duration(cfg.Redis.DialTimeoutSeconds) * time.Second, // 建连超时
|
||||
|
||||
@@ -13,6 +13,7 @@ func TestBuildRedisOptions(t *testing.T) {
|
||||
Redis: config.RedisConfig{
|
||||
Host: "localhost",
|
||||
Port: 6379,
|
||||
Username: "app-user",
|
||||
Password: "secret",
|
||||
DB: 2,
|
||||
DialTimeoutSeconds: 5,
|
||||
@@ -25,6 +26,7 @@ func TestBuildRedisOptions(t *testing.T) {
|
||||
|
||||
opts := buildRedisOptions(cfg)
|
||||
require.Equal(t, "localhost:6379", opts.Addr)
|
||||
require.Equal(t, "app-user", opts.Username)
|
||||
require.Equal(t, "secret", opts.Password)
|
||||
require.Equal(t, 2, opts.DB)
|
||||
require.Equal(t, 5*time.Second, opts.DialTimeout)
|
||||
|
||||
@@ -178,6 +178,7 @@ func testDatabase(c *gin.Context) {
|
||||
type TestRedisRequest struct {
|
||||
Host string `json:"host" binding:"required"`
|
||||
Port int `json:"port" binding:"required"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DB int `json:"db"`
|
||||
EnableTLS bool `json:"enable_tls"`
|
||||
@@ -204,10 +205,16 @@ func testRedis(c *gin.Context) {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid Redis database number (0-15)")
|
||||
return
|
||||
}
|
||||
req.Username = strings.TrimSpace(req.Username)
|
||||
if len(req.Username) > 128 {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid Redis username")
|
||||
return
|
||||
}
|
||||
|
||||
cfg := &RedisConfig{
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
DB: req.DB,
|
||||
EnableTLS: req.EnableTLS,
|
||||
@@ -252,6 +259,7 @@ func install(c *gin.Context) {
|
||||
req.Database.User = strings.TrimSpace(req.Database.User)
|
||||
req.Database.DBName = strings.TrimSpace(req.Database.DBName)
|
||||
req.Redis.Host = strings.TrimSpace(req.Redis.Host)
|
||||
req.Redis.Username = strings.TrimSpace(req.Redis.Username)
|
||||
|
||||
// ========== COMPREHENSIVE INPUT VALIDATION ==========
|
||||
// Database validation
|
||||
@@ -285,6 +293,10 @@ func install(c *gin.Context) {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid Redis database number")
|
||||
return
|
||||
}
|
||||
if len(req.Redis.Username) > 128 {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid Redis username")
|
||||
return
|
||||
}
|
||||
|
||||
// Admin validation
|
||||
if !validateEmail(req.Admin.Email) {
|
||||
|
||||
@@ -95,6 +95,7 @@ type DatabaseConfig struct {
|
||||
type RedisConfig struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port int `json:"port" yaml:"port"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
DB int `json:"db" yaml:"db"`
|
||||
EnableTLS bool `json:"enable_tls" yaml:"enable_tls"`
|
||||
@@ -251,6 +252,7 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error {
|
||||
func TestRedisConnection(cfg *RedisConfig) error {
|
||||
opts := &redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
}
|
||||
@@ -570,6 +572,7 @@ func AutoSetupFromEnv() error {
|
||||
Redis: RedisConfig{
|
||||
Host: getEnvOrDefault("REDIS_HOST", "localhost"),
|
||||
Port: getEnvIntOrDefault("REDIS_PORT", 6379),
|
||||
Username: getEnvOrDefault("REDIS_USERNAME", ""),
|
||||
Password: getEnvOrDefault("REDIS_PASSWORD", ""),
|
||||
DB: getEnvIntOrDefault("REDIS_DB", 0),
|
||||
EnableTLS: getEnvOrDefault("REDIS_ENABLE_TLS", "false") == "true",
|
||||
|
||||
@@ -105,6 +105,29 @@ func TestWriteConfigFileKeepsDefaultUserConcurrency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteConfigFileIncludesRedisUsername(t *testing.T) {
|
||||
t.Setenv("DATA_DIR", t.TempDir())
|
||||
|
||||
if err := writeConfigFile(&SetupConfig{
|
||||
Redis: RedisConfig{
|
||||
Host: "redis",
|
||||
Port: 6379,
|
||||
Username: "app-user",
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("writeConfigFile() error = %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(GetConfigFilePath())
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile() error = %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(data), "username: app-user") {
|
||||
t.Fatalf("config missing Redis username, got:\n%s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDatabaseConnectionDSNsUsesPostgresForBootstrap(t *testing.T) {
|
||||
cfg := &DatabaseConfig{
|
||||
Host: "db",
|
||||
|
||||
@@ -176,6 +176,8 @@ DATABASE_CONN_MAX_IDLE_TIME_MINUTES=5
|
||||
# -----------------------------------------------------------------------------
|
||||
# Redis 监听端口(同时用于应用连接和 Redis 服务端,默认 6379)
|
||||
REDIS_PORT=6379
|
||||
# Redis ACL username; leave empty for the default user
|
||||
REDIS_USERNAME=
|
||||
# Leave empty for no password (default for local development)
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
|
||||
@@ -920,6 +920,9 @@ redis:
|
||||
# Redis port
|
||||
# Redis 端口
|
||||
port: 6379
|
||||
# Redis ACL username (leave empty for default user)
|
||||
# Redis ACL 用户名(使用默认用户时留空)
|
||||
username: ""
|
||||
# Redis password (leave empty if no password is set)
|
||||
# Redis 密码(如果未设置密码则留空)
|
||||
password: ""
|
||||
|
||||
@@ -36,6 +36,7 @@ services:
|
||||
- DATABASE_SSLMODE=disable
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_USERNAME=${REDIS_USERNAME:-}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@sub2api.local}
|
||||
|
||||
@@ -74,6 +74,7 @@ services:
|
||||
# =======================================================================
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_USERNAME=${REDIS_USERNAME:-}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||
|
||||
@@ -60,6 +60,7 @@ services:
|
||||
# =======================================================================
|
||||
- REDIS_HOST=${REDIS_HOST:?REDIS_HOST is required}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_USERNAME=${REDIS_USERNAME:-}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||
|
||||
@@ -70,6 +70,7 @@ services:
|
||||
# =======================================================================
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_USERNAME=${REDIS_USERNAME:-}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface DatabaseConfig {
|
||||
export interface RedisConfig {
|
||||
host: string
|
||||
port: number
|
||||
username: string
|
||||
password: string
|
||||
db: number
|
||||
enable_tls: boolean
|
||||
|
||||
@@ -215,8 +215,10 @@ export default {
|
||||
description: 'Connect to your Redis server',
|
||||
host: 'Host',
|
||||
port: 'Port',
|
||||
username: 'Username (optional)',
|
||||
password: 'Password (optional)',
|
||||
database: 'Database',
|
||||
usernamePlaceholder: 'Leave empty for default user',
|
||||
passwordPlaceholder: 'Password',
|
||||
enableTls: 'Enable TLS',
|
||||
enableTlsHint: 'Use TLS when connecting to Redis (public CA certs)'
|
||||
|
||||
@@ -215,8 +215,10 @@ export default {
|
||||
description: '连接到您的 Redis 服务器',
|
||||
host: '主机',
|
||||
port: '端口',
|
||||
username: '用户名(可选)',
|
||||
password: '密码(可选)',
|
||||
database: '数据库',
|
||||
usernamePlaceholder: '默认用户留空',
|
||||
passwordPlaceholder: '密码',
|
||||
enableTls: '启用 TLS',
|
||||
enableTlsHint: '连接 Redis 时使用 TLS(公共 CA 证书)'
|
||||
|
||||
@@ -217,6 +217,15 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.redis.username') }}</label>
|
||||
<input
|
||||
v-model="formData.redis.username"
|
||||
type="text"
|
||||
class="input"
|
||||
:placeholder="t('setup.redis.usernamePlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.redis.password') }}</label>
|
||||
<input
|
||||
@@ -542,6 +551,7 @@ const formData = reactive<InstallRequest>({
|
||||
redis: {
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
username: '',
|
||||
password: '',
|
||||
db: 0,
|
||||
enable_tls: false
|
||||
|
||||
Reference in New Issue
Block a user