Files
CLICD/backend/internal/api/settings.go
T

178 lines
5.4 KiB
Go

package api
import (
"encoding/json"
"net/http"
"time"
"clicd/internal/config"
"golang.org/x/crypto/bcrypt"
)
type LoginLog struct {
Time string `json:"time"`
Username string `json:"username"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
Success bool `json:"success"`
}
var loginLogs = make([]LoginLog, 0)
// HandleLanguage returns or updates the global panel language.
func HandleLanguage(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: map[string]string{
"language": config.NormalizeLanguage(config.AppConfig.Language),
}})
case http.MethodPost, http.MethodPut:
var req struct {
Language string `json:"language"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
return
}
config.AppConfig.Language = config.NormalizeLanguage(req.Language)
if err := config.SaveConfig(); err != nil {
jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "Failed to save language"})
return
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: map[string]string{
"language": config.AppConfig.Language,
}})
default:
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
}
}
// RecordLoginLog adds a login attempt to the log (persisted to config)
func RecordLoginLog(username, ip, userAgent string, success bool) {
config.AddLoginLog(username, ip, userAgent, success)
log := LoginLog{
Time: time.Now().UTC().Format("2006-01-02 15:04:05 UTC"),
Username: username,
IP: ip,
UserAgent: userAgent,
Success: success,
}
loginLogs = append(loginLogs, log)
if len(loginLogs) > 200 {
loginLogs = loginLogs[len(loginLogs)-200:]
}
}
// RestoreLoginLogs restores login logs from config
func RestoreLoginLogs() {
for _, l := range config.AppConfig.LoginLogs {
loginLogs = append(loginLogs, LoginLog{
Time: l.Time,
Username: l.Username,
IP: l.IP,
UserAgent: l.UserAgent,
Success: l.Success,
})
}
}
// HandleLoginLogs returns login history
func HandleLoginLogs(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
return
}
if !requireScope(w, r, "loginlog:read") {
return
}
// Return in reverse (newest first)
reversed := make([]LoginLog, len(loginLogs))
for i, l := range loginLogs {
reversed[len(loginLogs)-1-i] = l
}
if reversed == nil {
reversed = []LoginLog{}
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: reversed})
}
// HandleAdminPasswordChange changes admin password
func HandleAdminPasswordChange(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
return
}
var req struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
return
}
if len(req.NewPassword) < 6 {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "新密码至少 6 位"})
return
}
if err := bcrypt.CompareHashAndPassword([]byte(config.AppConfig.AdminPassHash), []byte(req.OldPassword)); err != nil {
jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "当前密码不正确"})
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "密码加密失败"})
return
}
config.AppConfig.AdminPassHash = string(hash)
if err := config.SaveConfig(); err != nil {
jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "保存配置失败"})
return
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: "密码修改成功"})
}
// HandleAdminUsernameChange changes admin username
func HandleAdminUsernameChange(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
return
}
var req struct {
NewUsername string `json:"new_username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
return
}
if len(req.NewUsername) < 3 {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "用户名至少 3 位"})
return
}
if err := bcrypt.CompareHashAndPassword([]byte(config.AppConfig.AdminPassHash), []byte(req.Password)); err != nil {
jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "密码不正确"})
return
}
config.AppConfig.AdminUser = req.NewUsername
if err := config.SaveConfig(); err != nil {
jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "保存配置失败"})
return
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: "用户名修改成功"})
}