mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: 2fa相关接口
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -18,8 +19,13 @@ type User struct {
|
||||
}
|
||||
|
||||
type UserRepo interface {
|
||||
Create(username, password string) (*User, error)
|
||||
CheckPassword(username, password string) (*User, error)
|
||||
List(page, limit uint) ([]*User, int64, error)
|
||||
Get(id uint) (*User, error)
|
||||
Save(user *User) error
|
||||
Create(username, password string) (*User, error)
|
||||
UpdatePassword(id uint, password string) error
|
||||
Delete(id uint) error
|
||||
CheckPassword(username, password string) (*User, error)
|
||||
IsTwoFA(username string) (bool, error)
|
||||
GenerateTwoFA(id uint) (image.Image, string, string, error)
|
||||
UpdateTwoFA(id uint, code, secret string) error
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/spf13/cast"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
@@ -382,19 +380,3 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
|
||||
return restartFlag, nil
|
||||
}
|
||||
|
||||
// GetTwoFA 生成两步验证密钥
|
||||
// TODO: 即将废弃
|
||||
func (r *settingRepo) GetTwoFA() (*otp.Key, error) {
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "RatPanel",
|
||||
AccountName: "admin",
|
||||
SecretSize: 32,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/spf13/cast"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -24,6 +28,22 @@ func NewUserRepo(t *gotext.Locale, db *gorm.DB) biz.UserRepo {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *userRepo) List(page, limit uint) ([]*biz.User, int64, error) {
|
||||
users := make([]*biz.User, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.User{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&users).Error
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
func (r *userRepo) Get(id uint) (*biz.User, error) {
|
||||
user := new(biz.User)
|
||||
if err := r.db.First(user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepo) Create(username, password string) (*biz.User, error) {
|
||||
value, err := r.hasher.Make(password)
|
||||
if err != nil {
|
||||
@@ -41,6 +61,34 @@ func (r *userRepo) Create(username, password string) (*biz.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepo) UpdatePassword(id uint, password string) error {
|
||||
value, err := r.hasher.Make(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Password = value
|
||||
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"))
|
||||
}
|
||||
|
||||
user := new(biz.User)
|
||||
if err := r.db.First(user, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.db.Delete(user).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) CheckPassword(username, password string) (*biz.User, error) {
|
||||
user := new(biz.User)
|
||||
if err := r.db.Where("username = ?", username).First(user).Error; err != nil {
|
||||
@@ -58,15 +106,66 @@ func (r *userRepo) CheckPassword(username, password string) (*biz.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepo) Get(id uint) (*biz.User, error) {
|
||||
func (r *userRepo) IsTwoFA(username string) (bool, error) {
|
||||
user := new(biz.User)
|
||||
if err := r.db.First(user, id).Error; err != nil {
|
||||
return nil, err
|
||||
if err := r.db.Where("username = ?", username).First(user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, errors.New(r.t.Get("username or password error"))
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return user.TwoFA != "", nil
|
||||
}
|
||||
|
||||
func (r *userRepo) Save(user *biz.User) error {
|
||||
func (r *userRepo) GenerateTwoFA(id uint) (image.Image, string, string, error) {
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "RatPanel",
|
||||
AccountName: cast.ToString(id),
|
||||
SecretSize: 32,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
return img, key.URL(), key.Secret(), nil
|
||||
}
|
||||
|
||||
func (r *userRepo) UpdateTwoFA(id uint, code, secret string) error {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存前先验证一次,防止错误开启
|
||||
if !totp.Validate(code, secret) {
|
||||
return errors.New(r.t.Get("invalid 2fa code"))
|
||||
}
|
||||
|
||||
user.TwoFA = secret
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) CheckTwoFA(id uint, code string) (bool, error) {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if user.TwoFA == "" {
|
||||
return true, nil // 未开启2FA,无需验证
|
||||
}
|
||||
|
||||
if !totp.Validate(code, user.TwoFA) {
|
||||
return false, errors.New(r.t.Get("invalid 2fa code"))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
package request
|
||||
|
||||
type UserLogin struct {
|
||||
Username string `json:"username" form:"username" validate:"required"`
|
||||
Password string `json:"password" form:"password" validate:"required"`
|
||||
SafeLogin bool `json:"safe_login" form:"safe_login"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
SafeLogin bool `json:"safe_login"`
|
||||
PassCode string `json:"pass_code"`
|
||||
}
|
||||
|
||||
type UserIsTwoFA struct {
|
||||
Username string `uri:"username" validate:"required"`
|
||||
}
|
||||
|
||||
type UserCreate struct {
|
||||
Username string `json:"username" validate:"required|notExists:users,username"`
|
||||
Password string `json:"password" validate:"required|password"`
|
||||
}
|
||||
|
||||
type UserUpdatePassword struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Password string `json:"password" validate:"required|password"`
|
||||
}
|
||||
|
||||
type UserUpdateEmail struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
TwoFA string `json:"two_fa" validate:"required"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/go-rat/sessions"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -87,6 +88,13 @@ func (s *UserService) Login(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.TwoFA != "" {
|
||||
if !totp.Validate(req.PassCode, user.TwoFA) {
|
||||
Error(w, http.StatusForbidden, s.t.Get("invalid 2fa code"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 安全登录下,将当前客户端与会话绑定
|
||||
// 安全登录只在未启用面板 HTTPS 时生效
|
||||
ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
|
||||
@@ -128,6 +136,17 @@ func (s *UserService) IsLogin(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, sess.Has("user_id"))
|
||||
}
|
||||
|
||||
func (s *UserService) IsTwoFA(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserIsTwoFA](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
twoFA, _ := s.userRepo.IsTwoFA(req.Username)
|
||||
Success(w, twoFA)
|
||||
}
|
||||
|
||||
func (s *UserService) Info(w http.ResponseWriter, r *http.Request) {
|
||||
userID := cast.ToUint(r.Context().Value("user_id"))
|
||||
if userID == 0 {
|
||||
@@ -148,3 +167,53 @@ func (s *UserService) Info(w http.ResponseWriter, r *http.Request) {
|
||||
"email": user.Email,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) List(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.Paginate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
users, total, err := s.userRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": users,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := s.userRepo.Create(req.Username, req.Password)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, user)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserUpdatePassword](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.userRepo.UpdatePassword(req.ID, req.Password); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
otp "github.com/pquerna/otp"
|
||||
|
||||
request "github.com/tnb-labs/panel/internal/http/request"
|
||||
)
|
||||
|
||||
@@ -71,6 +73,118 @@ func (_c *SettingRepo_Delete_Call) RunAndReturn(run func(biz.SettingKey) error)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GenerateAPIKey provides a mock function with no fields
|
||||
func (_m *SettingRepo) GenerateAPIKey() (string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateAPIKey")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SettingRepo_GenerateAPIKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateAPIKey'
|
||||
type SettingRepo_GenerateAPIKey_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GenerateAPIKey is a helper method to define mock.On call
|
||||
func (_e *SettingRepo_Expecter) GenerateAPIKey() *SettingRepo_GenerateAPIKey_Call {
|
||||
return &SettingRepo_GenerateAPIKey_Call{Call: _e.mock.On("GenerateAPIKey")}
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateAPIKey_Call) Run(run func()) *SettingRepo_GenerateAPIKey_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateAPIKey_Call) Return(_a0 string, _a1 error) *SettingRepo_GenerateAPIKey_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateAPIKey_Call) RunAndReturn(run func() (string, error)) *SettingRepo_GenerateAPIKey_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GenerateTwoFAKey provides a mock function with no fields
|
||||
func (_m *SettingRepo) GenerateTwoFAKey() (*otp.Key, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateTwoFAKey")
|
||||
}
|
||||
|
||||
var r0 *otp.Key
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (*otp.Key, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() *otp.Key); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*otp.Key)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SettingRepo_GenerateTwoFAKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateTwoFAKey'
|
||||
type SettingRepo_GenerateTwoFAKey_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GenerateTwoFAKey is a helper method to define mock.On call
|
||||
func (_e *SettingRepo_Expecter) GenerateTwoFAKey() *SettingRepo_GenerateTwoFAKey_Call {
|
||||
return &SettingRepo_GenerateTwoFAKey_Call{Call: _e.mock.On("GenerateTwoFAKey")}
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateTwoFAKey_Call) Run(run func()) *SettingRepo_GenerateTwoFAKey_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateTwoFAKey_Call) Return(_a0 *otp.Key, _a1 error) *SettingRepo_GenerateTwoFAKey_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SettingRepo_GenerateTwoFAKey_Call) RunAndReturn(run func() (*otp.Key, error)) *SettingRepo_GenerateTwoFAKey_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: key, defaultValue
|
||||
func (_m *SettingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, error) {
|
||||
_va := make([]interface{}, len(defaultValue))
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package biz
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
biz "github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
// UserTokenRepo is an autogenerated mock type for the UserTokenRepo type
|
||||
type UserTokenRepo struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type UserTokenRepo_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *UserTokenRepo) EXPECT() *UserTokenRepo_Expecter {
|
||||
return &UserTokenRepo_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: userID, ips
|
||||
func (_m *UserTokenRepo) Create(userID uint, ips []string) (*biz.UserToken, error) {
|
||||
ret := _m.Called(userID, ips)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 *biz.UserToken
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(uint, []string) (*biz.UserToken, error)); ok {
|
||||
return rf(userID, ips)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(uint, []string) *biz.UserToken); ok {
|
||||
r0 = rf(userID, ips)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*biz.UserToken)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(uint, []string) error); ok {
|
||||
r1 = rf(userID, ips)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UserTokenRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type UserTokenRepo_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Create is a helper method to define mock.On call
|
||||
// - userID uint
|
||||
// - ips []string
|
||||
func (_e *UserTokenRepo_Expecter) Create(userID interface{}, ips interface{}) *UserTokenRepo_Create_Call {
|
||||
return &UserTokenRepo_Create_Call{Call: _e.mock.On("Create", userID, ips)}
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Create_Call) Run(run func(userID uint, ips []string)) *UserTokenRepo_Create_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(uint), args[1].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Create_Call) Return(_a0 *biz.UserToken, _a1 error) *UserTokenRepo_Create_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Create_Call) RunAndReturn(run func(uint, []string) (*biz.UserToken, error)) *UserTokenRepo_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: id
|
||||
func (_m *UserTokenRepo) Get(id uint) (*biz.UserToken, error) {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 *biz.UserToken
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(uint) (*biz.UserToken, error)); ok {
|
||||
return rf(id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(uint) *biz.UserToken); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*biz.UserToken)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(uint) error); ok {
|
||||
r1 = rf(id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UserTokenRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||
type UserTokenRepo_Get_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Get is a helper method to define mock.On call
|
||||
// - id uint
|
||||
func (_e *UserTokenRepo_Expecter) Get(id interface{}) *UserTokenRepo_Get_Call {
|
||||
return &UserTokenRepo_Get_Call{Call: _e.mock.On("Get", id)}
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Get_Call) Run(run func(id uint)) *UserTokenRepo_Get_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(uint))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Get_Call) Return(_a0 *biz.UserToken, _a1 error) *UserTokenRepo_Get_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Get_Call) RunAndReturn(run func(uint) (*biz.UserToken, error)) *UserTokenRepo_Get_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: user
|
||||
func (_m *UserTokenRepo) Save(user *biz.UserToken) error {
|
||||
ret := _m.Called(user)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Save")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*biz.UserToken) error); ok {
|
||||
r0 = rf(user)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UserTokenRepo_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
|
||||
type UserTokenRepo_Save_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Save is a helper method to define mock.On call
|
||||
// - user *biz.UserToken
|
||||
func (_e *UserTokenRepo_Expecter) Save(user interface{}) *UserTokenRepo_Save_Call {
|
||||
return &UserTokenRepo_Save_Call{Call: _e.mock.On("Save", user)}
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Save_Call) Run(run func(user *biz.UserToken)) *UserTokenRepo_Save_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*biz.UserToken))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Save_Call) Return(_a0 error) *UserTokenRepo_Save_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserTokenRepo_Save_Call) RunAndReturn(run func(*biz.UserToken) error) *UserTokenRepo_Save_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewUserTokenRepo creates a new instance of UserTokenRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewUserTokenRepo(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *UserTokenRepo {
|
||||
mock := &UserTokenRepo{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -495,10 +495,10 @@ msgstr ""
|
||||
msgid "Index Hit Rate"
|
||||
msgstr ""
|
||||
|
||||
#: internal/service/cli.go:831
|
||||
#: internal/service/cli.go:836
|
||||
#: internal/service/cli.go:841
|
||||
#: internal/service/cli.go:840
|
||||
#: internal/service/cli.go:845
|
||||
#: internal/service/cli.go:850
|
||||
#: internal/service/cli.go:854
|
||||
msgid "Initialization failed: %v"
|
||||
msgstr ""
|
||||
|
||||
@@ -1196,10 +1196,6 @@ msgstr ""
|
||||
msgid "Zip is a library for handling ZIP files"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:94
|
||||
msgid "api signature expired"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:159
|
||||
msgid "app %s already installed"
|
||||
msgstr ""
|
||||
@@ -1210,7 +1206,6 @@ msgstr ""
|
||||
|
||||
#: internal/data/app.go:210
|
||||
#: internal/data/app.go:265
|
||||
#: internal/http/middleware/must_install.go:48
|
||||
msgid "app %s not installed"
|
||||
msgstr ""
|
||||
|
||||
@@ -1225,12 +1220,8 @@ msgstr ""
|
||||
msgid "app %s requires panel version %s, current version %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_install.go:29
|
||||
msgid "app not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/setting.go:314
|
||||
#: internal/data/setting.go:375
|
||||
#: internal/data/setting.go:354
|
||||
#: internal/data/setting.go:412
|
||||
msgid "background task is running, modifying some settings is prohibited, please try again later"
|
||||
msgstr ""
|
||||
|
||||
@@ -1261,10 +1252,6 @@ msgstr ""
|
||||
msgid "check server connection failed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:122
|
||||
msgid "client ip/ua changed, please login again"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/backup.go:564
|
||||
msgid "could not find .sql backup file"
|
||||
msgstr ""
|
||||
@@ -1522,12 +1509,12 @@ msgid "failed to load MySQL root password: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/cert.go:92
|
||||
#: internal/data/setting.go:319
|
||||
#: internal/data/setting.go:359
|
||||
msgid "failed to parse certificate: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/cert.go:95
|
||||
#: internal/data/setting.go:322
|
||||
#: internal/data/setting.go:362
|
||||
msgid "failed to parse private key: %v"
|
||||
msgstr ""
|
||||
|
||||
@@ -1639,34 +1626,10 @@ msgstr ""
|
||||
msgid "get service port failed, please check if it is installed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:101
|
||||
msgid "invalid access entrance"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:84
|
||||
msgid "invalid api signature"
|
||||
msgstr ""
|
||||
|
||||
#: internal/service/user.go:78
|
||||
msgid "invalid key, please refresh the page"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:50
|
||||
msgid "invalid request domain: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:63
|
||||
msgid "invalid request ip: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:72
|
||||
msgid "invalid request user agent: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:136
|
||||
msgid "invalid user id, please login again"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/php/app.go:473
|
||||
msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"
|
||||
msgstr ""
|
||||
@@ -1712,22 +1675,6 @@ msgstr ""
|
||||
msgid "open file error: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:38
|
||||
msgid "panel is closed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:30
|
||||
msgid "panel is maintaining, please refresh later"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:22
|
||||
msgid "panel is upgrading, please refresh later"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:46
|
||||
msgid "panel run error, please check or contact support"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/php/app.go:328
|
||||
msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"
|
||||
msgstr ""
|
||||
@@ -1759,7 +1706,7 @@ msgstr ""
|
||||
msgid "please retry the manual obtain operation"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/setting.go:343
|
||||
#: internal/data/setting.go:383
|
||||
msgid "port is already in use"
|
||||
msgstr ""
|
||||
|
||||
@@ -1792,10 +1739,6 @@ msgstr ""
|
||||
msgid "runtime directory does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:107
|
||||
msgid "session expired, please login again"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/php/app.go:333
|
||||
msgid "sqlsrv is a driver for connecting to SQL Server"
|
||||
msgstr ""
|
||||
|
||||
@@ -64,7 +64,8 @@ msgstr ""
|
||||
#: src/views/apps/toolbox/IndexView.vue:66
|
||||
#: src/views/apps/toolbox/IndexView.vue:72
|
||||
#: src/views/apps/toolbox/IndexView.vue:81
|
||||
#: src/views/setting/IndexView.vue:46
|
||||
#: src/views/setting/SettingBase.vue:39
|
||||
#: src/views/setting/SettingHttps.vue:27
|
||||
#: src/views/website/EditView.vue:115
|
||||
msgid "Saved successfully"
|
||||
msgstr ""
|
||||
@@ -369,7 +370,7 @@ msgstr ""
|
||||
#: src/views/cert/CertView.vue:497
|
||||
#: src/views/cert/CertView.vue:573
|
||||
#: src/views/cert/UploadCertModal.vue:38
|
||||
#: src/views/setting/SettingSafe.vue:54
|
||||
#: src/views/setting/SettingHttps.vue:45
|
||||
#: src/views/website/EditView.vue:355
|
||||
msgid "Certificate"
|
||||
msgstr ""
|
||||
@@ -911,7 +912,8 @@ msgstr ""
|
||||
#: src/views/apps/toolbox/IndexView.vue:109
|
||||
#: src/views/file/EditModal.vue:31
|
||||
#: src/views/file/ListTable.vue:723
|
||||
#: src/views/setting/IndexView.vue:63
|
||||
#: src/views/setting/SettingBase.vue:97
|
||||
#: src/views/setting/SettingHttps.vue:62
|
||||
#: src/views/website/EditView.vue:215
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
@@ -1609,7 +1611,7 @@ msgstr ""
|
||||
#: src/views/database/UpdateServerModal.vue:86
|
||||
#: src/views/database/UserList.vue:40
|
||||
#: src/views/login/IndexView.vue:115
|
||||
#: src/views/setting/SettingBase.vue:49
|
||||
#: src/views/setting/SettingBase.vue:67
|
||||
#: src/views/ssh/CreateModal.vue:83
|
||||
#: src/views/ssh/UpdateModal.vue:89
|
||||
msgid "Username"
|
||||
@@ -1675,7 +1677,7 @@ msgstr ""
|
||||
#: src/views/database/UpdateUserModal.vue:49
|
||||
#: src/views/database/UserList.vue:50
|
||||
#: src/views/login/IndexView.vue:123
|
||||
#: src/views/setting/SettingBase.vue:52
|
||||
#: src/views/setting/SettingBase.vue:70
|
||||
#: src/views/ssh/CreateModal.vue:77
|
||||
#: src/views/ssh/CreateModal.vue:86
|
||||
#: src/views/ssh/UpdateModal.vue:83
|
||||
@@ -2290,7 +2292,7 @@ msgstr ""
|
||||
#: src/views/cert/CertView.vue:509
|
||||
#: src/views/cert/CertView.vue:585
|
||||
#: src/views/cert/UploadCertModal.vue:46
|
||||
#: src/views/setting/SettingSafe.vue:61
|
||||
#: src/views/setting/SettingHttps.vue:52
|
||||
#: src/views/ssh/CreateModal.vue:78
|
||||
#: src/views/ssh/CreateModal.vue:89
|
||||
#: src/views/ssh/UpdateModal.vue:84
|
||||
@@ -3478,7 +3480,7 @@ msgstr ""
|
||||
#: src/views/database/UpdateServerModal.vue:76
|
||||
#: src/views/firewall/ForwardView.vue:32
|
||||
#: src/views/firewall/RuleView.vue:49
|
||||
#: src/views/setting/SettingBase.vue:58
|
||||
#: src/views/setting/SettingBase.vue:76
|
||||
#: src/views/ssh/CreateModal.vue:68
|
||||
#: src/views/ssh/UpdateModal.vue:74
|
||||
#: src/views/website/IndexView.vue:420
|
||||
@@ -4126,115 +4128,78 @@ msgstr ""
|
||||
msgid "Time Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/IndexView.vue:49
|
||||
msgid "Panel is restarting, page will refresh in 3 seconds"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/IndexView.vue:67
|
||||
#: src/views/setting/IndexView.vue:17
|
||||
msgid "Basic"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/IndexView.vue:70
|
||||
msgid "Safe"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:20
|
||||
msgid "Stable"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:24
|
||||
msgid "Beta"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:34
|
||||
msgid "Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:40
|
||||
#: src/views/setting/SettingBase.vue:41
|
||||
msgid "Panel Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:43
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:46
|
||||
msgid "Update Channel"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:50
|
||||
#: src/views/setting/SettingBase.vue:53
|
||||
#: src/views/setting/SettingSafe.vue:26
|
||||
msgid "admin"
|
||||
#: src/views/setting/SettingBase.vue:42
|
||||
msgid "Panel is restarting, page will refresh in 3 seconds"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:55
|
||||
msgid "Certificate Default Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:56
|
||||
msgid "admin@yourdomain.com"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:59
|
||||
msgid "8888"
|
||||
msgid "Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:61
|
||||
msgid "Default Website Directory"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:62
|
||||
msgid "/www/wwwroot"
|
||||
msgid "Panel Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:64
|
||||
msgid "Default Backup Directory"
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:65
|
||||
msgid "/www/backup"
|
||||
#: src/views/setting/SettingBase.vue:68
|
||||
#: src/views/setting/SettingBase.vue:71
|
||||
#: src/views/setting/SettingBase.vue:80
|
||||
msgid "admin"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:12
|
||||
msgid "Login Timeout"
|
||||
#: src/views/setting/SettingBase.vue:73
|
||||
msgid "Certificate Default Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:15
|
||||
msgid "120"
|
||||
#: src/views/setting/SettingBase.vue:74
|
||||
msgid "admin@yourdomain.com"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:21
|
||||
#: src/views/website/ProxyBuilderModal.vue:188
|
||||
msgid "minutes"
|
||||
#: src/views/setting/SettingBase.vue:77
|
||||
msgid "8888"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:25
|
||||
#: src/views/setting/SettingBase.vue:79
|
||||
msgid "Access Entrance"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:28
|
||||
msgid "Bind Domain"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:35
|
||||
msgid "Bind IP"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:38
|
||||
msgid "Bind UA"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:45
|
||||
#: src/views/setting/SettingBase.vue:82
|
||||
msgid "Offline Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:48
|
||||
#: src/views/setting/SettingBase.vue:85
|
||||
msgid "Auto Update"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingSafe.vue:51
|
||||
#: src/views/setting/SettingBase.vue:88
|
||||
msgid "Default Website Directory"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:89
|
||||
msgid "/www/wwwroot"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:91
|
||||
msgid "Default Backup Directory"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingBase.vue:92
|
||||
msgid "/www/backup"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingHttps.vue:36
|
||||
msgid "Incorrect certificates may cause the panel to be inaccessible. Please proceed with caution!"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/setting/SettingHttps.vue:42
|
||||
msgid "Panel HTTPS"
|
||||
msgstr ""
|
||||
|
||||
@@ -4752,6 +4717,10 @@ msgstr ""
|
||||
msgid "Cache time (minutes)"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/website/ProxyBuilderModal.vue:188
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/website/ProxyBuilderModal.vue:191
|
||||
msgid "Content Replacement"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user