feat(backend): unified response helpers, refresh token, security hardening
- new resp package (OK/Page/Fail/BadRequest/.../InternalErr): unified error
envelope {detail, code?}, sanitized 500s (full error logged server-side,
generic message to client - stops DB error leakage), pagination envelope
{items,total,page,page_size}; migrate 200+ inline c.JSON error sites
- refresh token: 7d rotation tokens (SHA-256 hashed at rest), public
POST /auth/refresh with single-use rotation, POST /auth/logout revokes
all user refresh tokens (endpoint previously missing - frontend 404)
- security: gh-ost command line password masked in task status payload;
Instance.MarshalJSON guarantees encrypted passwords never leave the API
(removes reliance on per-handler manual masking); production (DEBUG=false)
with default JWT secret now auto-generates and persists a random secret
- safe.Go recover wrapper for background goroutines (instance probe,
gh-ost async)
- delete dead service layer (zero callers)
- fix pre-existing go vet unkeyed bson.E literals
This commit is contained in:
1 parent
d5b8b8116c
commit
16f1f7d65f
26 files changed
+916
-963
No files matched your search
@@ -1,19 +1,27 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"sql-platform/internal/model"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// DefaultJWTSecret 仅用于本地 DEBUG;生产模式(DEBUG=false)会被 EnsureJWTSecret 替换为随机密钥
|
||||
const DefaultJWTSecret = "sql-platform-secret-key-change-in-production"
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
DBPath string
|
||||
JWTSecret string
|
||||
JWTExpire int // hours
|
||||
GhOstPath string
|
||||
Debug bool
|
||||
Port string
|
||||
DBPath string
|
||||
JWTSecret string
|
||||
JWTExpire int // hours
|
||||
GhOstPath string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
var C Config
|
||||
@@ -24,13 +32,49 @@ func Init() {
|
||||
C = Config{
|
||||
Port: getEnv("PORT", "9000"),
|
||||
DBPath: getEnv("DB_PATH", "../backend/sqlite3.db"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "sql-platform-secret-key-change-in-production"),
|
||||
JWTSecret: getEnv("JWT_SECRET", DefaultJWTSecret),
|
||||
JWTExpire: getEnvInt("JWT_EXPIRE_HOURS", 24),
|
||||
GhOstPath: getEnv("GH_OST_PATH", "gh-ost"),
|
||||
Debug: getEnvBool("DEBUG", true),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureJWTSecret 治理默认 JWT 密钥:
|
||||
// - 显式配置了 JWT_SECRET(非默认值)→ 直接使用
|
||||
// - DEBUG 模式使用默认密钥 → 打印告警(开发便利优先)
|
||||
// - 生产模式使用默认密钥 → 自动生成随机密钥并持久化到 system_settings(重启复用;
|
||||
// 首次切换会使存量会话失效一次)
|
||||
//
|
||||
// 必须在 model.DB 初始化并完成 AutoMigrate 之后调用。
|
||||
func EnsureJWTSecret() {
|
||||
if C.JWTSecret != DefaultJWTSecret {
|
||||
return
|
||||
}
|
||||
if C.Debug {
|
||||
log.Println("[WARN] 正在使用默认 JWT 密钥(DEBUG 模式);生产环境请设置 JWT_SECRET 环境变量")
|
||||
return
|
||||
}
|
||||
|
||||
var s model.SystemSetting
|
||||
if err := model.DB.Where("`key` = ?", "jwt_secret").Take(&s).Error; err == nil && s.Value != "" {
|
||||
C.JWTSecret = s.Value
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 32)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
log.Fatalf("生成随机 JWT 密钥失败: %v", err)
|
||||
}
|
||||
secret := hex.EncodeToString(buf)
|
||||
if err := model.DB.Where(model.SystemSetting{Key: "jwt_secret"}).
|
||||
Assign(model.SystemSetting{Value: secret, ValueType: "string", Group: "security", Label: "JWT Secret"}).
|
||||
FirstOrCreate(&s).Error; err != nil {
|
||||
log.Fatalf("持久化 JWT 密钥失败: %v", err)
|
||||
}
|
||||
C.JWTSecret = secret
|
||||
log.Println("[INFO] 已自动生成随机 JWT 密钥并持久化(未显式配置 JWT_SECRET 的生产部署,存量会话将失效一次)")
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
|
||||
Reference in new issue
Block a user