移除管理员数据库模型,改为通过配置管理管理员身份和认证逻辑,并更新相关逻辑和文档
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
site:
|
site:
|
||||||
name: 文件暂存柜
|
name: 文件暂存柜
|
||||||
description: 临时文件中转服务
|
description: 临时文件中转服务
|
||||||
logo: "https://www.hxuanyu.com/upload/favicon.png"
|
logo: https://www.hxuanyu.com/upload/favicon.png
|
||||||
security:
|
security:
|
||||||
admin_password_hash: $2a$10$Bm0TEmU4uj.bVHYiIPFBheUkcdg6XHpsanLvmpoAtgU1UnKbo9.vy
|
admin_password_hash: $2a$10$Bm0TEmU4uj.bVHYiIPFBheUkcdg6XHpsanLvmpoAtgU1UnKbo9.vy
|
||||||
pickup_code_length: 6
|
pickup_code_length: 6
|
||||||
@@ -15,7 +15,7 @@ upload:
|
|||||||
storage:
|
storage:
|
||||||
type: local
|
type: local
|
||||||
local:
|
local:
|
||||||
path: storage_data_test
|
path: storage_data
|
||||||
webdav:
|
webdav:
|
||||||
url: https://dav.example.com
|
url: https://dav.example.com
|
||||||
username: user
|
username: user
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
| 字段名 | 类型 | 含义 | 示例 |
|
| 字段名 | 类型 | 含义 | 示例 |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| `admin_password_hash` | string | 管理员密码的 bcrypt 哈希值 | `$2a$10$...` |
|
| `admin_password_hash` | string | 管理员密码的 bcrypt 哈希值。可以通过更新配置接口修改,修改后立即生效,且不再依赖数据库存储。 | `$2a$10$...` |
|
||||||
| `pickup_code_length` | int | 自动生成的取件码长度。变更后系统将自动对存量取件码进行右侧补零或截取以适配新长度。 | `6` |
|
| `pickup_code_length` | int | 自动生成的取件码长度。变更后系统将自动对存量取件码进行右侧补零或截取以适配新长度。 | `6` |
|
||||||
| `pickup_fail_limit` | int | 单个 IP 对单个取件码尝试失败的最大次数,超过后将被临时封禁 | `5` |
|
| `pickup_fail_limit` | int | 单个 IP 对单个取件码尝试失败的最大次数,超过后将被临时封禁 | `5` |
|
||||||
| `jwt_secret` | string | 用于签发管理端 JWT Token 的密钥,建议设置为复杂随机字符串 | `file-relay-secret` |
|
| `jwt_secret` | string | 用于签发管理端 JWT Token 的密钥,建议设置为复杂随机字符串 | `file-relay-secret` |
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"FileRelay/internal/auth"
|
"FileRelay/internal/auth"
|
||||||
"FileRelay/internal/bootstrap"
|
"FileRelay/internal/config"
|
||||||
"FileRelay/internal/model"
|
"FileRelay/internal/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -43,27 +42,24 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var admin model.Admin
|
passwordHash := config.GlobalConfig.Security.AdminPasswordHash
|
||||||
if err := bootstrap.DB.First(&admin).Error; err != nil {
|
if passwordHash == "" {
|
||||||
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, "Admin not found"))
|
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, "Admin password hash not configured"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.PasswordHash), []byte(req.Password)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)); err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, model.ErrorResponse(model.CodeUnauthorized, "Incorrect password"))
|
c.JSON(http.StatusUnauthorized, model.ErrorResponse(model.CodeUnauthorized, "Incorrect password"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := auth.GenerateToken(admin.ID)
|
// 使用固定 ID 1 代表管理员(因为不再有数据库记录)
|
||||||
|
token, err := auth.GenerateToken(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, "Failed to generate token"))
|
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, "Failed to generate token"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新登录时间
|
|
||||||
now := time.Now()
|
|
||||||
bootstrap.DB.Model(&admin).Update("last_login", &now)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.SuccessResponse(LoginResponse{
|
c.JSON(http.StatusOK, model.SuccessResponse(LoginResponse{
|
||||||
Token: token,
|
Token: token,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ func InitDB() {
|
|||||||
&model.FileBatch{},
|
&model.FileBatch{},
|
||||||
&model.FileItem{},
|
&model.FileItem{},
|
||||||
&model.APIToken{},
|
&model.APIToken{},
|
||||||
&model.Admin{},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to migrate database: %v", err)
|
log.Fatalf("Failed to migrate database: %v", err)
|
||||||
@@ -74,31 +73,28 @@ func ReloadStorage() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initAdmin() {
|
func initAdmin() {
|
||||||
var count int64
|
passwordHash := config.GlobalConfig.Security.AdminPasswordHash
|
||||||
DB.Model(&model.Admin{}).Count(&count)
|
if passwordHash == "" {
|
||||||
if count == 0 {
|
// 生成随机密码
|
||||||
passwordHash := config.GlobalConfig.Security.AdminPasswordHash
|
password := generateRandomPassword(12)
|
||||||
if passwordHash == "" {
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
// 生成随机密码
|
if err != nil {
|
||||||
password := generateRandomPassword(12)
|
log.Fatalf("Failed to generate password hash: %v", err)
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to generate password hash: %v", err)
|
|
||||||
}
|
|
||||||
passwordHash = string(hash)
|
|
||||||
fmt.Printf("**************************************************\n")
|
|
||||||
fmt.Printf("NO ADMIN PASSWORD CONFIGURED. GENERATED RANDOM PASSWORD:\n")
|
|
||||||
fmt.Printf("Password: %s\n", password)
|
|
||||||
fmt.Printf("Please save this password or configure admin_password_hash in config.yaml\n")
|
|
||||||
fmt.Printf("**************************************************\n")
|
|
||||||
}
|
}
|
||||||
|
passwordHash = string(hash)
|
||||||
|
fmt.Printf("**************************************************\n")
|
||||||
|
fmt.Printf("NO ADMIN PASSWORD CONFIGURED. GENERATED RANDOM PASSWORD:\n")
|
||||||
|
fmt.Printf("Password: %s\n", password)
|
||||||
|
fmt.Printf("Please save this password or configure admin_password_hash in config.yaml\n")
|
||||||
|
fmt.Printf("**************************************************\n")
|
||||||
|
|
||||||
admin := &model.Admin{
|
// 将生成的哈希保存回配置文件
|
||||||
PasswordHash: passwordHash,
|
config.GlobalConfig.Security.AdminPasswordHash = passwordHash
|
||||||
|
if err := config.SaveConfig(); err != nil {
|
||||||
|
fmt.Printf("Warning: Failed to save generated password hash to config: %v\n", err)
|
||||||
}
|
}
|
||||||
DB.Create(admin)
|
|
||||||
fmt.Println("Admin account initialized.")
|
|
||||||
}
|
}
|
||||||
|
fmt.Println("Admin authentication initialized via config.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRandomPassword(length int) string {
|
func generateRandomPassword(length int) string {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Admin struct {
|
// AdminSession 管理员会话信息 (不再存库,仅用于 JWT 或 API 交互)
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
type AdminSession struct {
|
||||||
PasswordHash string `json:"-"`
|
ID uint `json:"id"`
|
||||||
LastLogin *time.Time `json:"last_login"`
|
LastLogin *time.Time `json:"last_login"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user