mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 04:22:33 +08:00
feat: 用户支持开启2FA
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
@@ -38,6 +37,6 @@ type SettingRepo interface {
|
||||
Set(key SettingKey, value string) error
|
||||
SetSlice(key SettingKey, value []string) error
|
||||
Delete(key SettingKey) error
|
||||
GetPanelSetting(ctx context.Context) (*request.PanelSetting, error)
|
||||
UpdatePanelSetting(ctx context.Context, setting *request.PanelSetting) (bool, error)
|
||||
GetPanelSetting() (*request.PanelSetting, error)
|
||||
UpdatePanelSetting(req *request.PanelSetting) (bool, error)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type UserRepo interface {
|
||||
Get(id uint) (*User, error)
|
||||
Create(username, password string) (*User, error)
|
||||
UpdatePassword(id uint, password string) error
|
||||
UpdateEmail(id uint, email string) error
|
||||
Delete(id uint) error
|
||||
CheckPassword(username, password string) (*User, error)
|
||||
IsTwoFA(username string) (bool, error)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
@@ -199,7 +197,7 @@ func (r *settingRepo) Delete(key biz.SettingKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSetting, error) {
|
||||
func (r *settingRepo) GetPanelSetting() (*request.PanelSetting, error) {
|
||||
name, err := r.Get(biz.SettingKeyName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -225,12 +223,6 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userID := cast.ToUint(ctx.Value("user_id"))
|
||||
user := new(biz.User)
|
||||
if err := r.db.Where("id = ?", userID).First(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crt, err := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -253,8 +245,6 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
BindUA: r.conf.Strings("http.bind_ua"),
|
||||
WebsitePath: websitePath,
|
||||
BackupPath: backupPath,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Port: uint(r.conf.Int("http.port")),
|
||||
HTTPS: r.conf.Bool("http.tls"),
|
||||
Cert: crt,
|
||||
@@ -262,43 +252,23 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.PanelSetting) (bool, error) {
|
||||
if err := r.Set(biz.SettingKeyName, setting.Name); err != nil {
|
||||
func (r *settingRepo) UpdatePanelSetting(req *request.PanelSetting) (bool, error) {
|
||||
if err := r.Set(biz.SettingKeyName, req.Name); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyChannel, setting.Channel); err != nil {
|
||||
if err := r.Set(biz.SettingKeyChannel, req.Channel); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyOfflineMode, cast.ToString(setting.OfflineMode)); err != nil {
|
||||
if err := r.Set(biz.SettingKeyOfflineMode, cast.ToString(req.OfflineMode)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyAutoUpdate, cast.ToString(setting.AutoUpdate)); err != nil {
|
||||
if err := r.Set(biz.SettingKeyAutoUpdate, cast.ToString(req.AutoUpdate)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyWebsitePath, setting.WebsitePath); err != nil {
|
||||
if err := r.Set(biz.SettingKeyWebsitePath, req.WebsitePath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyBackupPath, setting.BackupPath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 用户
|
||||
user := new(biz.User)
|
||||
userID := cast.ToUint(ctx.Value("user_id"))
|
||||
if err := r.db.Where("id = ?", userID).First(user).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
user.Username = setting.Username
|
||||
user.Email = setting.Email
|
||||
if setting.Password != "" {
|
||||
value, err := hash.NewArgon2id().Make(setting.Password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
user.Password = value
|
||||
}
|
||||
if err := r.db.Save(user).Error; err != nil {
|
||||
if err := r.Set(biz.SettingKeyBackupPath, req.BackupPath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -307,22 +277,22 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
restartFlag := false
|
||||
oldCert, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
|
||||
oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key"))
|
||||
if oldCert != setting.Cert || oldKey != setting.Key {
|
||||
if oldCert != req.Cert || oldKey != req.Key {
|
||||
if r.task.HasRunningTask() {
|
||||
return false, errors.New(r.t.Get("background task is running, modifying some settings is prohibited, please try again later"))
|
||||
}
|
||||
restartFlag = true
|
||||
}
|
||||
if _, err := cert.ParseCert(setting.Cert); err != nil {
|
||||
if _, err := cert.ParseCert(req.Cert); err != nil {
|
||||
return false, errors.New(r.t.Get("failed to parse certificate: %v", err))
|
||||
}
|
||||
if _, err := cert.ParseKey(setting.Key); err != nil {
|
||||
if _, err := cert.ParseKey(req.Key); err != nil {
|
||||
return false, errors.New(r.t.Get("failed to parse private key: %v", err))
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil {
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), req.Cert, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.key"), setting.Key, 0644); err != nil {
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.key"), req.Key, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -336,20 +306,20 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
return false, err
|
||||
}
|
||||
|
||||
if setting.Port != config.HTTP.Port {
|
||||
if os.TCPPortInUse(setting.Port) {
|
||||
if req.Port != config.HTTP.Port {
|
||||
if os.TCPPortInUse(req.Port) {
|
||||
return false, errors.New(r.t.Get("port is already in use"))
|
||||
}
|
||||
}
|
||||
|
||||
config.App.Locale = setting.Locale
|
||||
config.HTTP.Port = setting.Port
|
||||
config.HTTP.Entrance = setting.Entrance
|
||||
config.HTTP.TLS = setting.HTTPS
|
||||
config.HTTP.BindDomain = setting.BindDomain
|
||||
config.HTTP.BindIP = setting.BindIP
|
||||
config.HTTP.BindUA = setting.BindUA
|
||||
config.Session.Lifetime = setting.Lifetime
|
||||
config.App.Locale = req.Locale
|
||||
config.HTTP.Port = req.Port
|
||||
config.HTTP.Entrance = req.Entrance
|
||||
config.HTTP.TLS = req.HTTPS
|
||||
config.HTTP.BindDomain = req.BindDomain
|
||||
config.HTTP.BindIP = req.BindIP
|
||||
config.HTTP.BindUA = req.BindUA
|
||||
config.Session.Lifetime = req.Lifetime
|
||||
|
||||
// 放行端口
|
||||
fw := firewall.NewFirewall()
|
||||
|
||||
@@ -76,6 +76,16 @@ func (r *userRepo) UpdatePassword(id uint, password string) error {
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) UpdateEmail(id uint, email string) error {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Email = email
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) Delete(id uint) error {
|
||||
if id == 1 {
|
||||
return errors.New(r.t.Get("please don't do this"))
|
||||
|
||||
@@ -16,9 +16,6 @@ type PanelSetting struct {
|
||||
BindUA []string `json:"bind_ua"`
|
||||
WebsitePath string `json:"website_path" validate:"required"`
|
||||
BackupPath string `json:"backup_path" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"password"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Port uint `json:"port" validate:"required|min:1|max:65535"`
|
||||
HTTPS bool `json:"https"`
|
||||
Cert string `json:"cert" validate:"required"`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package request
|
||||
|
||||
type UserID struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
}
|
||||
|
||||
type UserLogin struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
@@ -23,15 +27,11 @@ type UserUpdatePassword struct {
|
||||
|
||||
type UserUpdateEmail struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
TwoFA string `json:"two_fa" validate:"required"`
|
||||
Email string `json:"email" validate:"required|email"`
|
||||
}
|
||||
|
||||
type UserUpdateTwoFA struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
TwoFA string `json:"two_fa" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
type UserDelete struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
ID uint `uri:"id" validate:"required|exists:users,id"`
|
||||
Secret string `json:"secret"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
@@ -114,6 +114,16 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
r.Get("/info", route.user.Info)
|
||||
})
|
||||
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", route.user.List)
|
||||
r.Post("/", route.user.Create)
|
||||
r.Post("/{id}/password", route.user.UpdatePassword)
|
||||
r.Post("/{id}/email", route.user.UpdateEmail)
|
||||
r.Get("/{id}/2fa", route.user.GenerateTwoFA)
|
||||
r.Post("/{id}/2fa", route.user.UpdateTwoFA)
|
||||
r.Delete("/{id}", route.user.Delete)
|
||||
})
|
||||
|
||||
r.Route("/dashboard", func(r chi.Router) {
|
||||
r.Get("/panel", route.dashboard.Panel)
|
||||
r.Get("/home_apps", route.dashboard.HomeApps)
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewSettingService(setting biz.SettingRepo) *SettingService {
|
||||
}
|
||||
|
||||
func (s *SettingService) Get(w http.ResponseWriter, r *http.Request) {
|
||||
setting, err := s.settingRepo.GetPanelSetting(r.Context())
|
||||
setting, err := s.settingRepo.GetPanelSetting()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -36,7 +36,7 @@ func (s *SettingService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
restart := false
|
||||
if restart, err = s.settingRepo.UpdatePanelSetting(r.Context(), req); err != nil {
|
||||
if restart, err = s.settingRepo.UpdatePanelSetting(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -217,3 +220,74 @@ func (s *UserService) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateEmail(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserUpdateEmail](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.userRepo.UpdateEmail(req.ID, req.Email); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *UserService) GenerateTwoFA(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
img, url, secret, err := s.userRepo.GenerateTwoFA(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err = png.Encode(buf, img); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"img": base64.StdEncoding.EncodeToString(buf.Bytes()),
|
||||
"url": url,
|
||||
"secret": secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateTwoFA(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserUpdateTwoFA](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.userRepo.UpdateTwoFA(req.ID, req.Code, req.Secret); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *UserService) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.userRepo.Delete(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user