重构:移除环境变量管理员初始化
This commit is contained in:
@@ -7,8 +7,6 @@ FRONTEND_PORT=15173
|
|||||||
JWT_SECRET=your_very_secure_jwt_secret_here_change_me_in_production
|
JWT_SECRET=your_very_secure_jwt_secret_here_change_me_in_production
|
||||||
DATA_ENCRYPTION_KEY=your_32_plus_char_data_encryption_key_change_me
|
DATA_ENCRYPTION_KEY=your_32_plus_char_data_encryption_key_change_me
|
||||||
# 仅用于空数据库的首次启动;已有管理员账号时不会被覆盖。
|
# 仅用于空数据库的首次启动;已有管理员账号时不会被覆盖。
|
||||||
INITIAL_ADMIN_USERNAME=skin_nh_admin
|
|
||||||
INITIAL_ADMIN_PASSWORD=change_me_to_a_secure_password
|
|
||||||
POSTGRES_USER=affiliate
|
POSTGRES_USER=affiliate
|
||||||
POSTGRES_PASSWORD=affiliate_dev_password
|
POSTGRES_PASSWORD=affiliate_dev_password
|
||||||
POSTGRES_DB=affiliate_dash
|
POSTGRES_DB=affiliate_dash
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ make dev
|
|||||||
启动后:
|
启动后:
|
||||||
- 前端:`http://localhost:15173`
|
- 前端:`http://localhost:15173`
|
||||||
- 后端:`http://localhost:18080`
|
- 后端:`http://localhost:18080`
|
||||||
- 空数据库首次启动时由 `INITIAL_ADMIN_USERNAME` / `INITIAL_ADMIN_PASSWORD` 创建平台管理员
|
|
||||||
|
|
||||||
按 `Ctrl+C` 停止所有服务。
|
按 `Ctrl+C` 停止所有服务。
|
||||||
|
|
||||||
@@ -61,7 +60,6 @@ make frontend # 前端 :15173
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# 编辑 .env,确保以下变量已配置:
|
# 编辑 .env,确保以下变量已配置:
|
||||||
# JWT_SECRET — 生产环境务必修改
|
# JWT_SECRET — 生产环境务必修改
|
||||||
# INITIAL_ADMIN_USERNAME / INITIAL_ADMIN_PASSWORD — 空数据库首次创建的平台管理员,不会覆盖已有账号
|
|
||||||
# OPEN_API_KEY — 皮肤源头对接 API Key
|
# OPEN_API_KEY — 皮肤源头对接 API Key
|
||||||
# OPEN_API_SECRET — 签名密钥
|
# OPEN_API_SECRET — 签名密钥
|
||||||
# POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB — PostgreSQL 初始化配置
|
# POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB — PostgreSQL 初始化配置
|
||||||
@@ -104,8 +102,6 @@ make docker-down # 停止并移除
|
|||||||
| `FRONTEND_PORT` | 前端开发端口 | `15173` |
|
| `FRONTEND_PORT` | 前端开发端口 | `15173` |
|
||||||
| `HTTP_PORT` / `HTTPS_PORT` | Caddy 对外 HTTP/HTTPS 端口 | `80` / `443` |
|
| `HTTP_PORT` / `HTTPS_PORT` | Caddy 对外 HTTP/HTTPS 端口 | `80` / `443` |
|
||||||
| `JWT_SECRET` | JWT 签名密钥 | 开发默认值 |
|
| `JWT_SECRET` | JWT 签名密钥 | 开发默认值 |
|
||||||
| `INITIAL_ADMIN_USERNAME` | 空数据库首次创建的平台管理员用户名 | 必填(仅首次) |
|
|
||||||
| `INITIAL_ADMIN_PASSWORD` | 空数据库首次创建的平台管理员密码 | 必填,至少 8 位(仅首次) |
|
|
||||||
| `DATABASE_URL` | PostgreSQL 连接串 | 本地开发默认值 |
|
| `DATABASE_URL` | PostgreSQL 连接串 | 本地开发默认值 |
|
||||||
| `DOCKER_DATABASE_URL` | Docker 后端容器内 PostgreSQL 连接串,通常无需设置 | Compose 内置默认值 |
|
| `DOCKER_DATABASE_URL` | Docker 后端容器内 PostgreSQL 连接串,通常无需设置 | Compose 内置默认值 |
|
||||||
| `POSTGRES_PORT` | PostgreSQL 映射到宿主机的端口 | `15432` |
|
| `POSTGRES_PORT` | PostgreSQL 映射到宿主机的端口 | `15432` |
|
||||||
|
|||||||
@@ -84,10 +84,6 @@ func main() {
|
|||||||
cfg.DeliveryLinkTTLMinutes,
|
cfg.DeliveryLinkTTLMinutes,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := authSvc.EnsureAdmin(cfg.InitialAdminUsername, cfg.InitialAdminPassword); err != nil {
|
|
||||||
log.Fatalf("ensure admin: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
openlog.Init(cfg.OpenAPIDebug)
|
openlog.Init(cfg.OpenAPIDebug)
|
||||||
|
|
||||||
h := &router.Handlers{
|
h := &router.Handlers{
|
||||||
@@ -122,7 +118,6 @@ func main() {
|
|||||||
addr := ":" + cfg.Port
|
addr := ":" + cfg.Port
|
||||||
log.Printf("游戏皮肤供货平台 API 启动: http://localhost%s", addr)
|
log.Printf("游戏皮肤供货平台 API 启动: http://localhost%s", addr)
|
||||||
log.Printf("数据库: PostgreSQL")
|
log.Printf("数据库: PostgreSQL")
|
||||||
log.Printf("管理员账号已初始化;请通过账号菜单及时修改初始密码")
|
|
||||||
log.Printf("开放接口鉴权: X-App-Key + X-Timestamp + X-Nonce + X-Sign (HMAC-SHA256)")
|
log.Printf("开放接口鉴权: X-App-Key + X-Timestamp + X-Nonce + X-Sign (HMAC-SHA256)")
|
||||||
if cfg.OpenAPIDebug {
|
if cfg.OpenAPIDebug {
|
||||||
log.Printf("开放接口调试日志: 开启 (OPEN_API_DEBUG=0 可关闭)")
|
log.Printf("开放接口调试日志: 开启 (OPEN_API_DEBUG=0 可关闭)")
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ type Config struct {
|
|||||||
JWTSecret string
|
JWTSecret string
|
||||||
DatabaseURL string
|
DatabaseURL string
|
||||||
Mode string // debug / release
|
Mode string // debug / release
|
||||||
// InitialAdminUsername 和 InitialAdminPassword 仅用于空数据库首次创建平台管理员。
|
|
||||||
InitialAdminUsername string
|
|
||||||
InitialAdminPassword string
|
|
||||||
// DataEncryptionKey 用于加密保存 API/回调密钥;为空时由 JWT_SECRET 派生。
|
// DataEncryptionKey 用于加密保存 API/回调密钥;为空时由 JWT_SECRET 派生。
|
||||||
DataEncryptionKey string
|
DataEncryptionKey string
|
||||||
// OpenAPIKey 皮肤源头开放接口标识(Header: X-Api-Key,可公开给对接方)
|
// OpenAPIKey 皮肤源头开放接口标识(Header: X-Api-Key,可公开给对接方)
|
||||||
@@ -70,8 +67,6 @@ func Load() *Config {
|
|||||||
JWTSecret: getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me"),
|
JWTSecret: getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me"),
|
||||||
DatabaseURL: getEnv("DATABASE_URL", "postgres://affiliate:affiliate_dev_password@127.0.0.1:15432/affiliate_dash?sslmode=disable&TimeZone=Asia/Shanghai"),
|
DatabaseURL: getEnv("DATABASE_URL", "postgres://affiliate:affiliate_dev_password@127.0.0.1:15432/affiliate_dash?sslmode=disable&TimeZone=Asia/Shanghai"),
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
InitialAdminUsername: getEnv("INITIAL_ADMIN_USERNAME", ""),
|
|
||||||
InitialAdminPassword: getEnv("INITIAL_ADMIN_PASSWORD", ""),
|
|
||||||
DataEncryptionKey: getEnv("DATA_ENCRYPTION_KEY", getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me")),
|
DataEncryptionKey: getEnv("DATA_ENCRYPTION_KEY", getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me")),
|
||||||
OpenAPIKey: getEnv("OPEN_API_KEY", "sk_source_dev_key_change_me"),
|
OpenAPIKey: getEnv("OPEN_API_KEY", "sk_source_dev_key_change_me"),
|
||||||
OpenAPISecret: getEnv("OPEN_API_SECRET", "sk_source_dev_secret_change_me"),
|
OpenAPISecret: getEnv("OPEN_API_SECRET", "sk_source_dev_secret_change_me"),
|
||||||
|
|||||||
@@ -153,49 +153,3 @@ func (s *AuthService) UpdateCurrentAccount(userID uint, in UpdateCurrentAccountI
|
|||||||
}
|
}
|
||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureAdmin creates the first platform administrator for an empty database.
|
|
||||||
// Existing administrator accounts are never changed by environment variables.
|
|
||||||
func (s *AuthService) EnsureAdmin(username, password string) error {
|
|
||||||
var count int64
|
|
||||||
s.db.Model(&model.User{}).Where("role = ?", model.RoleAdmin).Count(&count)
|
|
||||||
if count > 0 {
|
|
||||||
if s.tenant != nil {
|
|
||||||
var admins []model.User
|
|
||||||
if err := s.db.Where("role = ?", model.RoleAdmin).Find(&admins).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, admin := range admins {
|
|
||||||
if err := s.tenant.EnsureSelfMember(admin.ID, model.MemberRoleOwner, admin.Status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
username = strings.TrimSpace(username)
|
|
||||||
if len(username) < 3 {
|
|
||||||
return errors.New("空数据库首次启动必须设置 INITIAL_ADMIN_USERNAME(至少 3 位)")
|
|
||||||
}
|
|
||||||
if len(password) < 8 {
|
|
||||||
return errors.New("空数据库首次启动必须设置 INITIAL_ADMIN_PASSWORD(至少 8 位)")
|
|
||||||
}
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
admin := &model.User{
|
|
||||||
Username: username,
|
|
||||||
PasswordHash: string(hash),
|
|
||||||
Nickname: username,
|
|
||||||
Role: model.RoleAdmin,
|
|
||||||
Status: 1,
|
|
||||||
}
|
|
||||||
if err := s.db.Create(admin).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.tenant != nil {
|
|
||||||
return s.tenant.EnsureSelfMember(admin.ID, model.MemberRoleOwner, admin.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -336,26 +336,3 @@ func TestListMerchantAccountGroupsExcludesPlatformAdmins(t *testing.T) {
|
|||||||
t.Fatalf("unexpected merchant account group: %+v", groups[0])
|
t.Fatalf("unexpected merchant account group: %+v", groups[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureAdminUsesConfiguredCredentialsOnlyForEmptyDatabase(t *testing.T) {
|
|
||||||
db := newServiceTestDB(t)
|
|
||||||
tenant := NewTenantService(db)
|
|
||||||
auth := NewAuthService(db, nil, tenant)
|
|
||||||
if err := auth.EnsureAdmin("skin_nh_admin", "secure-password"); err != nil {
|
|
||||||
t.Fatalf("create configured admin: %v", err)
|
|
||||||
}
|
|
||||||
var created model.User
|
|
||||||
if err := db.Where("username = ?", "skin_nh_admin").First(&created).Error; err != nil {
|
|
||||||
t.Fatalf("configured admin should exist: %v", err)
|
|
||||||
}
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(created.PasswordHash), []byte("secure-password")); err != nil {
|
|
||||||
t.Fatalf("configured password should match: %v", err)
|
|
||||||
}
|
|
||||||
if err := auth.EnsureAdmin("replacement-admin", "another-password"); err != nil {
|
|
||||||
t.Fatalf("existing admin should not be replaced: %v", err)
|
|
||||||
}
|
|
||||||
var count int64
|
|
||||||
if err := db.Model(&model.User{}).Where("role = ?", model.RoleAdmin).Count(&count).Error; err != nil || count != 1 {
|
|
||||||
t.Fatalf("existing admin should remain unchanged, count=%d err=%v", count, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ services:
|
|||||||
- PORT=8080
|
- PORT=8080
|
||||||
- TZ=${TZ:-Asia/Shanghai}
|
- TZ=${TZ:-Asia/Shanghai}
|
||||||
- JWT_SECRET=${JWT_SECRET}
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
- INITIAL_ADMIN_USERNAME=${INITIAL_ADMIN_USERNAME:-}
|
|
||||||
- INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD:-}
|
|
||||||
- "DATABASE_URL=${DOCKER_DATABASE_URL:-postgres://${POSTGRES_USER:-affiliate}:${POSTGRES_PASSWORD:-affiliate_dev_password}@postgres:5432/${POSTGRES_DB:-affiliate_dash}?sslmode=disable&TimeZone=${TZ:-Asia/Shanghai}}"
|
- "DATABASE_URL=${DOCKER_DATABASE_URL:-postgres://${POSTGRES_USER:-affiliate}:${POSTGRES_PASSWORD:-affiliate_dev_password}@postgres:5432/${POSTGRES_DB:-affiliate_dash}?sslmode=disable&TimeZone=${TZ:-Asia/Shanghai}}"
|
||||||
- GIN_MODE=${GIN_MODE:-release}
|
- GIN_MODE=${GIN_MODE:-release}
|
||||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
|
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
|
||||||
|
|||||||
Reference in New Issue
Block a user