mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: Added password encryption for login functionality (#7764)
This commit is contained in:
parent
4f57dfc76e
commit
aaaa5980b5
@ -15,6 +15,7 @@ type DBOption func(*gorm.DB) *gorm.DB
|
|||||||
type ICommonRepo interface {
|
type ICommonRepo interface {
|
||||||
WithByID(id uint) DBOption
|
WithByID(id uint) DBOption
|
||||||
WithByName(name string) DBOption
|
WithByName(name string) DBOption
|
||||||
|
WithByLowerName(name string) DBOption
|
||||||
WithByType(tp string) DBOption
|
WithByType(tp string) DBOption
|
||||||
WithOrderBy(orderStr string) DBOption
|
WithOrderBy(orderStr string) DBOption
|
||||||
WithOrderRuleBy(orderBy, order string) DBOption
|
WithOrderRuleBy(orderBy, order string) DBOption
|
||||||
@ -45,6 +46,12 @@ func (c *CommonRepo) WithByName(name string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CommonRepo) WithByLowerName(name string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
return g.Where("LOWER(name) = LOWER(?)", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
|
func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
|
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
|
||||||
|
@ -16,6 +16,7 @@ type ISettingRepo interface {
|
|||||||
Create(key, value string) error
|
Create(key, value string) error
|
||||||
Update(key, value string) error
|
Update(key, value string) error
|
||||||
WithByKey(key string) DBOption
|
WithByKey(key string) DBOption
|
||||||
|
UpdateOrCreate(key, value string) error
|
||||||
|
|
||||||
CreateMonitorBase(model model.MonitorBase) error
|
CreateMonitorBase(model model.MonitorBase) error
|
||||||
BatchCreateMonitorIO(ioList []model.MonitorIO) error
|
BatchCreateMonitorIO(ioList []model.MonitorIO) error
|
||||||
@ -85,3 +86,7 @@ func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
|
|||||||
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
|
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
|
||||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *SettingRepo) UpdateOrCreate(key, value string) error {
|
||||||
|
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Assign(model.Setting{Key: key, Value: value}).FirstOrCreate(&model.Setting{}).Error
|
||||||
|
}
|
||||||
|
@ -326,7 +326,8 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
|||||||
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
|
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {
|
|
||||||
|
if list, _ := appInstallRepo.ListBy(commonRepo.WithByLowerName(req.Name)); len(list) > 0 {
|
||||||
err = buserr.New(constant.ErrAppNameExist)
|
err = buserr.New(constant.ErrAppNameExist)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
@ -15,6 +13,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthService struct{}
|
type AuthService struct{}
|
||||||
@ -38,16 +37,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||||
}
|
}
|
||||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
if nameSetting.Value != info.Name {
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
|
||||||
}
|
|
||||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, constant.ErrAuth
|
return nil, constant.ErrAuth
|
||||||
}
|
}
|
||||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
if err = checkPassword(info.Password); err != nil {
|
||||||
return nil, constant.ErrAuth
|
return nil, err
|
||||||
}
|
}
|
||||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -83,17 +77,12 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||||
}
|
}
|
||||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
if nameSetting.Value != info.Name {
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
|
||||||
}
|
|
||||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
|
||||||
return nil, constant.ErrAuth
|
return nil, constant.ErrAuth
|
||||||
}
|
}
|
||||||
|
if err = checkPassword(info.Password); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -219,3 +208,28 @@ func (u *AuthService) IsLogin(c *gin.Context) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkPassword(password string) error {
|
||||||
|
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
|
||||||
|
|
||||||
|
privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||||
|
}
|
||||||
|
existPassword, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
|
||||||
|
return constant.ErrAuth
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -42,6 +44,7 @@ type ISettingService interface {
|
|||||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||||
GenerateApiKey() (string, error)
|
GenerateApiKey() (string, error)
|
||||||
UpdateApiConfig(req dto.ApiInterfaceConfig) error
|
UpdateApiConfig(req dto.ApiInterfaceConfig) error
|
||||||
|
GenerateRSAKey() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewISettingService() ISettingService {
|
func NewISettingService() ISettingService {
|
||||||
@ -516,3 +519,47 @@ func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
|
|||||||
global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
|
global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exportPrivateKeyToPEM(privateKey *rsa.PrivateKey) string {
|
||||||
|
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: privateKeyBytes,
|
||||||
|
})
|
||||||
|
return string(privateKeyPEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportPublicKeyToPEM(publicKey *rsa.PublicKey) (string, error) {
|
||||||
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "PUBLIC KEY",
|
||||||
|
Bytes: publicKeyBytes,
|
||||||
|
})
|
||||||
|
return string(publicKeyPEM), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SettingService) GenerateRSAKey() error {
|
||||||
|
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
|
||||||
|
pubKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
|
||||||
|
if priKey.Value != "" && pubKey.Value != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
|
||||||
|
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||||
|
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = settingRepo.UpdateOrCreate("PASSWORD_PUBLIC_KEY", publicKeyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ func Init() {
|
|||||||
go syncInstalledApp()
|
go syncInstalledApp()
|
||||||
go syncRuntime()
|
go syncRuntime()
|
||||||
go syncSSL()
|
go syncSSL()
|
||||||
|
generateKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncApp() {
|
func syncApp() {
|
||||||
@ -38,3 +39,9 @@ func syncSSL() {
|
|||||||
global.LOG.Errorf("sync ssl status error : %s", err.Error())
|
global.LOG.Errorf("sync ssl status error : %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateKey() {
|
||||||
|
if err := service.NewISettingService().GenerateRSAKey(); err != nil {
|
||||||
|
global.LOG.Errorf("generate rsa key error : %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -113,9 +113,7 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
|
|||||||
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
||||||
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
|
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
|
||||||
rootRouter.Static("/api/v1/images", "./uploads")
|
rootRouter.Static("/api/v1/images", "./uploads")
|
||||||
rootRouter.Use(func(c *gin.Context) {
|
|
||||||
c.Next()
|
|
||||||
})
|
|
||||||
rootRouter.GET("/assets/*filepath", func(c *gin.Context) {
|
rootRouter.GET("/assets/*filepath", func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Cache-Control", fmt.Sprintf("private, max-age=%d", 3600))
|
c.Writer.Header().Set("Cache-Control", fmt.Sprintf("private, max-age=%d", 3600))
|
||||||
staticServer := http.FileServer(http.FS(web.Assets))
|
staticServer := http.FileServer(http.FS(web.Assets))
|
||||||
@ -158,6 +156,7 @@ func Routers() *gin.Engine {
|
|||||||
|
|
||||||
Router.Use(middleware.WhiteAllow())
|
Router.Use(middleware.WhiteAllow())
|
||||||
Router.Use(middleware.BindDomain())
|
Router.Use(middleware.BindDomain())
|
||||||
|
Router.Use(middleware.SetPasswordPublicKey())
|
||||||
|
|
||||||
Router.NoRoute(func(c *gin.Context) {
|
Router.NoRoute(func(c *gin.Context) {
|
||||||
if checkFrontendPath(c) {
|
if checkFrontendPath(c) {
|
||||||
|
22
backend/middleware/password_rsa.go
Normal file
22
backend/middleware/password_rsa.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetPasswordPublicKey() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
cookieKey, _ := c.Cookie("panel_public_key")
|
||||||
|
settingRepo := repo.NewISettingRepo()
|
||||||
|
key, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
|
||||||
|
base64Key := base64.StdEncoding.EncodeToString([]byte(key.Value))
|
||||||
|
if base64Key == cookieKey {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetCookie("panel_public_key", base64Key, 7*24*60*60, "/", "", false, false)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,14 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
@ -102,3 +107,75 @@ func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) {
|
|||||||
ciphertext = unPadding(ciphertext)
|
ciphertext = unPadding(ciphertext)
|
||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseRSAPrivateKey(privateKeyPEM string) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(privateKeyPEM))
|
||||||
|
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
||||||
|
return nil, errors.New("failed to decode PEM block containing the private key")
|
||||||
|
}
|
||||||
|
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesDecrypt(ciphertext, key, iv []byte) ([]byte, error) {
|
||||||
|
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
|
||||||
|
return nil, errors.New("invalid AES key length: must be 16, 24, or 32 bytes")
|
||||||
|
}
|
||||||
|
if len(iv) != aes.BlockSize {
|
||||||
|
return nil, errors.New("invalid IV length: must be 16 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
mode.CryptBlocks(ciphertext, ciphertext)
|
||||||
|
ciphertext = pkcs7Unpad(ciphertext)
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkcs7Unpad(data []byte) []byte {
|
||||||
|
length := len(data)
|
||||||
|
padLength := int(data[length-1])
|
||||||
|
return data[:length-padLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecryptPassword(encryptedData string, privateKey *rsa.PrivateKey) (string, error) {
|
||||||
|
parts := strings.Split(encryptedData, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return "", errors.New("encrypted data format error")
|
||||||
|
}
|
||||||
|
keyCipher := parts[0]
|
||||||
|
ivBase64 := parts[1]
|
||||||
|
ciphertextBase64 := parts[2]
|
||||||
|
|
||||||
|
encryptedAESKey, err := base64.StdEncoding.DecodeString(keyCipher)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("failed to decode keyCipher")
|
||||||
|
}
|
||||||
|
|
||||||
|
aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedAESKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("failed to decode AES Key")
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("failed to decrypt the encrypted data")
|
||||||
|
}
|
||||||
|
iv, err := base64.StdEncoding.DecodeString(ivBase64)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("failed to decode the IV")
|
||||||
|
}
|
||||||
|
|
||||||
|
password, err := aesDecrypt(ciphertext, aesKey, iv)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(password), nil
|
||||||
|
}
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-yaml": "^6.1.2",
|
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-php": "^6.0.1",
|
"@codemirror/lang-php": "^6.0.1",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.10.2",
|
"@codemirror/language": "^6.10.2",
|
||||||
"@codemirror/legacy-modes": "^6.4.0",
|
"@codemirror/legacy-modes": "^6.4.0",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
@ -37,11 +37,13 @@
|
|||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"element-plus": "^2.7.5",
|
"element-plus": "^2.7.5",
|
||||||
"fit2cloud-ui-plus": "^1.2.0",
|
"fit2cloud-ui-plus": "^1.2.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
|
"jsencrypt": "^3.3.2",
|
||||||
"md-editor-v3": "^2.11.3",
|
"md-editor-v3": "^2.11.3",
|
||||||
"monaco-editor": "^0.50.0",
|
"monaco-editor": "^0.50.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
@ -54,11 +56,12 @@
|
|||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-demi": "^0.14.6",
|
"vue-demi": "^0.14.6",
|
||||||
"vue-i18n": "^10.0.5",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.3.3"
|
"vue-router": "^4.3.3",
|
||||||
|
"vue-virtual-scroller": "^2.0.0-beta.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.15.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||||
"@typescript-eslint/parser": "^5.22.0",
|
"@typescript-eslint/parser": "^5.22.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
@ -73,7 +76,7 @@
|
|||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"postcss-html": "^1.4.1",
|
"postcss-html": "^1.4.1",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"rollup-plugin-visualizer": "^5.5.4",
|
"rollup-plugin-visualizer": "^5.14.0",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"standard-version": "^9.5.0",
|
"standard-version": "^9.5.0",
|
||||||
"stylelint": "^15.10.1",
|
"stylelint": "^15.10.1",
|
||||||
@ -81,7 +84,7 @@
|
|||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"unplugin-auto-import": "^0.16.4",
|
"unplugin-auto-import": "^0.16.4",
|
||||||
"unplugin-vue-components": "^0.25.0",
|
"unplugin-vue-components": "^0.25.0",
|
||||||
"vite": "^5.2.13",
|
"vite": "^6.0.7",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-html": "^3.2.2",
|
"vite-plugin-html": "^3.2.2",
|
||||||
@ -90,7 +93,7 @@
|
|||||||
"vue-tsc": "^0.29.8"
|
"vue-tsc": "^0.29.8"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "npm:esbuild-wasm@latest"
|
"esbuild": "npm:esbuild-wasm@0.24.2"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
@ -289,7 +289,7 @@ const checkAppName = (rule: any, value: any, callback: any) => {
|
|||||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||||
callback(new Error(i18n.global.t('commons.rule.appName')));
|
callback(new Error(i18n.global.t('commons.rule.appName')));
|
||||||
} else {
|
} else {
|
||||||
const reg = /^(?![_-])[a-z0-9_-]{1,29}[a-zA-Z0-9]$/;
|
const reg = /^(?![_-])[a-zA-Z0-9_-]{1,29}[a-zA-Z0-9]$/;
|
||||||
if (!reg.test(value) && value !== '') {
|
if (!reg.test(value) && value !== '') {
|
||||||
callback(new Error(i18n.global.t('commons.rule.appName')));
|
callback(new Error(i18n.global.t('commons.rule.appName')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1451,8 +1451,7 @@ const message = {
|
|||||||
confDockerProxy: 'Configurar proxy do Docker',
|
confDockerProxy: 'Configurar proxy do Docker',
|
||||||
restartNowHelper: 'Configurar o proxy do Docker exige reiniciar o serviço Docker.',
|
restartNowHelper: 'Configurar o proxy do Docker exige reiniciar o serviço Docker.',
|
||||||
restartNow: 'Reiniciar imediatamente',
|
restartNow: 'Reiniciar imediatamente',
|
||||||
systemIPWarning:
|
systemIPWarning: 'O endereço do sistema não está definido no momento. Defina-o primeiro no painel de controle.',
|
||||||
'O endereço do sistema não está definido no momento. Defina-o primeiro no painel de controle.',
|
|
||||||
systemIPWarning1:
|
systemIPWarning1:
|
||||||
'O endereço do sistema atual está definido como {0}, e o redirecionamento rápido não é possível!',
|
'O endereço do sistema atual está definido como {0}, e o redirecionamento rápido não é possível!',
|
||||||
defaultNetwork: 'Placa de rede',
|
defaultNetwork: 'Placa de rede',
|
||||||
|
@ -3,6 +3,8 @@ import i18n from '@/lang';
|
|||||||
import useClipboard from 'vue-clipboard3';
|
import useClipboard from 'vue-clipboard3';
|
||||||
const { toClipboard } = useClipboard();
|
const { toClipboard } = useClipboard();
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
import JSEncrypt from 'jsencrypt';
|
||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
export function deepCopy<T>(obj: any): T {
|
export function deepCopy<T>(obj: any): T {
|
||||||
let newObj: any;
|
let newObj: any;
|
||||||
@ -615,3 +617,61 @@ export const escapeProxyURL = (url: string): string => {
|
|||||||
|
|
||||||
return url.replace(/[\/:?#[\]@!$&'()*+,;=%~]/g, (match) => encodeMap[match] || match);
|
return url.replace(/[\/:?#[\]@!$&'()*+,;=%~]/g, (match) => encodeMap[match] || match);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getCookie(name: string) {
|
||||||
|
const value = `; ${document.cookie}`;
|
||||||
|
const parts = value.split(`; ${name}=`);
|
||||||
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rsaEncrypt(data: string, publicKey: string) {
|
||||||
|
if (!data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const jsEncrypt = new JSEncrypt();
|
||||||
|
jsEncrypt.setPublicKey(publicKey);
|
||||||
|
return jsEncrypt.encrypt(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function aesEncrypt(data: string, key: string) {
|
||||||
|
const keyBytes = CryptoJS.enc.Utf8.parse(key);
|
||||||
|
const iv = CryptoJS.lib.WordArray.random(16);
|
||||||
|
const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
|
||||||
|
iv: iv,
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
return iv.toString(CryptoJS.enc.Base64) + ':' + encrypted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function urlDecode(value: string): string {
|
||||||
|
return decodeURIComponent(value.replace(/\+/g, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAESKey(): string {
|
||||||
|
const keyLength = 16;
|
||||||
|
const randomBytes = new Uint8Array(keyLength);
|
||||||
|
crypto.getRandomValues(randomBytes);
|
||||||
|
return Array.from(randomBytes)
|
||||||
|
.map((b) => b.toString(16).padStart(2, '0'))
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const encryptPassword = (password: string) => {
|
||||||
|
if (!password) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let rsaPublicKeyText = getCookie('panel_public_key');
|
||||||
|
if (!rsaPublicKeyText) {
|
||||||
|
console.log('RSA public key not found');
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
rsaPublicKeyText = urlDecode(rsaPublicKeyText);
|
||||||
|
|
||||||
|
const aesKey = generateAESKey();
|
||||||
|
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', '');
|
||||||
|
const rsaPublicKey = atob(rsaPublicKeyText);
|
||||||
|
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey);
|
||||||
|
const passwordCipher = aesEncrypt(password, aesKey);
|
||||||
|
return `${keyCipher}:${passwordCipher}`;
|
||||||
|
};
|
||||||
|
@ -180,6 +180,7 @@ import { MsgSuccess } from '@/utils/message';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { getSettingInfo } from '@/api/modules/setting';
|
import { getSettingInfo } from '@/api/modules/setting';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { encryptPassword } from '@/utils/util';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const themeConfig = computed(() => globalStore.themeConfig);
|
const themeConfig = computed(() => globalStore.themeConfig);
|
||||||
@ -314,7 +315,7 @@ const login = (formEl: FormInstance | undefined) => {
|
|||||||
}
|
}
|
||||||
let requestLoginForm = {
|
let requestLoginForm = {
|
||||||
name: loginForm.name,
|
name: loginForm.name,
|
||||||
password: loginForm.password,
|
password: encryptPassword(loginForm.password),
|
||||||
ignoreCaptcha: globalStore.ignoreCaptcha,
|
ignoreCaptcha: globalStore.ignoreCaptcha,
|
||||||
captcha: loginForm.captcha,
|
captcha: loginForm.captcha,
|
||||||
captchaID: captcha.captchaID,
|
captchaID: captcha.captchaID,
|
||||||
@ -370,7 +371,7 @@ const mfaLogin = async (auto: boolean) => {
|
|||||||
if ((!auto && mfaLoginForm.code) || (auto && mfaLoginForm.code.length === 6)) {
|
if ((!auto && mfaLoginForm.code) || (auto && mfaLoginForm.code.length === 6)) {
|
||||||
isLoggingIn = true;
|
isLoggingIn = true;
|
||||||
mfaLoginForm.name = loginForm.name;
|
mfaLoginForm.name = loginForm.name;
|
||||||
mfaLoginForm.password = loginForm.password;
|
mfaLoginForm.password = encryptPassword(loginForm.password);
|
||||||
const res = await mfaLoginApi(mfaLoginForm);
|
const res = await mfaLoginApi(mfaLoginForm);
|
||||||
if (res.code === 406) {
|
if (res.code === 406) {
|
||||||
errMfaInfo.value = true;
|
errMfaInfo.value = true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user