diff --git a/backend/app/api/v1/auth.go b/backend/app/api/v1/auth.go index c2d4c4a07..9343c55c6 100644 --- a/backend/app/api/v1/auth.go +++ b/backend/app/api/v1/auth.go @@ -1,11 +1,14 @@ package v1 import ( + "errors" + "github.com/1Panel-dev/1Panel/app/api/v1/helper" "github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/utils/captcha" + "github.com/1Panel-dev/1Panel/utils/encrypt" "github.com/gin-gonic/gin" ) @@ -46,6 +49,37 @@ func (b *BaseApi) Captcha(c *gin.Context) { captcha, err := captcha.CreateCaptcha() if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return } helper.SuccessWithData(c, captcha) } + +func (b *BaseApi) GetSafetyStatus(c *gin.Context) { + if err := authService.SafetyStatus(c); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) SafeEntrance(c *gin.Context) { + code, exist := c.Params.Get("code") + if !exist { + helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) + return + } + ok, err := authService.VerifyCode(code) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) + return + } + if !ok { + helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) + return + } + codeWithMD5 := encrypt.Md5(code) + cookieValue, _ := encrypt.StringEncrypt(codeWithMD5) + c.SetCookie(codeWithMD5, cookieValue, 86400, "", "", false, false) + + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/service/auth.go b/backend/app/service/auth.go index db5702ca0..ab69ec8e4 100644 --- a/backend/app/service/auth.go +++ b/backend/app/service/auth.go @@ -16,6 +16,8 @@ import ( type AuthService struct{} type IAuthService interface { + SafetyStatus(c *gin.Context) error + VerifyCode(code string) (bool, error) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) LogOut(c *gin.Context) error } @@ -89,3 +91,30 @@ func (u *AuthService) LogOut(c *gin.Context) error { } return nil } + +func (u *AuthService) VerifyCode(code string) (bool, error) { + setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) + if err != nil { + return false, err + } + return setting.Value == code, nil +} + +func (u *AuthService) SafetyStatus(c *gin.Context) error { + setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) + if err != nil { + return err + } + codeWithEcrypt, err := c.Cookie(encrypt.Md5(setting.Value)) + if err != nil { + return err + } + code, err := encrypt.StringDecrypt(codeWithEcrypt) + if err != nil { + return err + } + if code != encrypt.Md5(setting.Value) { + return errors.New("code not match") + } + return nil +} diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 1a8674340..67e57dd41 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -8,6 +8,7 @@ const ( CodeSuccess = 200 CodeErrBadRequest = 400 CodeErrUnauthorized = 401 + CodeErrUnSafety = 402 CodeErrForbidden = 403 CodeErrNotFound = 404 CodeErrInternalServer = 500 @@ -33,5 +34,6 @@ var ( ErrTypeInvalidParams = "ErrInvalidParams" ErrTypeToken = "ErrToken" ErrTypeTokenTimeOut = "ErrTokenTimeOut" - ErrTypeNotLogin = "ErrTypeNotLogin" + ErrTypeNotLogin = "ErrNotLogin" + ErrTypeNotSafety = "ErrNotSafety" ) diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index a5c51128a..24f650651 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -8,4 +8,5 @@ ErrInternalServer: "Service internal error: {{ .detail }}" ErrRecordExist: "Record already exists: {{ .detail }}" ErrRecordNotFound: "Records not found: {{ .detail }}" ErrStructTransform: "Type conversion failure: {{ .detail }}" -ErrTypeNotLogin: "User is not Login" \ No newline at end of file +ErrNotLogin: "User is not Login: {{ .detail }}" +ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}" \ No newline at end of file diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index cb2d0bf05..0023c170d 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -8,4 +8,5 @@ ErrInternalServer: "服务内部错误: {{ .detail }}" ErrRecordExist: "记录已存在: {{ .detail }}" ErrRecordNotFound: "记录未能找到: {{ .detail }}" ErrStructTransform: "类型转换失败: {{ .detail }}" -ErrTypeNotLogin: "用户未登录" \ No newline at end of file +ErrNotLogin: "用户未登录: {{ .detail }}" +ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}" \ No newline at end of file diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index 6fe8e871f..ca5b46ce9 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -81,7 +81,7 @@ var AddTableSetting = &gormigrate.Migration{ if err := tx.Create(&model.Setting{Key: "ServerPort", Value: "4004"}).Error; err != nil { return err } - if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "/89dc6ae8"}).Error; err != nil { + if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "89dc6ae8"}).Error; err != nil { return err } if err := tx.Create(&model.Setting{Key: "PasswordTimeOut", Value: time.Now().AddDate(0, 0, 10).Format("2016.01.02 15:04:05")}).Error; err != nil { diff --git a/backend/init/router/router.go b/backend/init/router/router.go index 008e43d16..a12cf8de5 100644 --- a/backend/init/router/router.go +++ b/backend/init/router/router.go @@ -3,6 +3,7 @@ package router import ( "html/template" + v1 "github.com/1Panel-dev/1Panel/app/api/v1" "github.com/1Panel-dev/1Panel/docs" "github.com/1Panel-dev/1Panel/i18n" "github.com/1Panel-dev/1Panel/middleware" @@ -19,8 +20,11 @@ func Routers() *gin.Engine { Router.Use(middleware.LoadCsrfToken()) docs.SwaggerInfo.BasePath = "/api/v1" - Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) + Router.Use(i18n.GinI18nLocalize()) + Router.GET("/api/v1/info", v1.ApiGroupApp.BaseApi.GetSafetyStatus) + Router.GET("/api/v1/:code", v1.ApiGroupApp.BaseApi.SafeEntrance) + Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) Router.SetFuncMap(template.FuncMap{ "Localize": ginI18n.GetMessage, @@ -35,7 +39,9 @@ func Routers() *gin.Engine { c.JSON(200, "ok") }) } + PrivateGroup := Router.Group("/api/v1") + PrivateGroup.Use(middleware.SafetyAuth()) { systemRouter.InitBaseRouter(PrivateGroup) systemRouter.InitHostRouter(PrivateGroup) diff --git a/backend/middleware/safety.go b/backend/middleware/safety.go new file mode 100644 index 000000000..5bc07b1ba --- /dev/null +++ b/backend/middleware/safety.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/1Panel-dev/1Panel/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/app/service" + "github.com/1Panel-dev/1Panel/constant" + "github.com/gin-gonic/gin" +) + +func SafetyAuth() gin.HandlerFunc { + return func(c *gin.Context) { + if err := service.NewIAuthService().SafetyStatus(c); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, nil) + return + } + c.Next() + } +} diff --git a/backend/utils/encrypt/encrypt.go b/backend/utils/encrypt/encrypt.go index 5204ad528..c682ca8e1 100644 --- a/backend/utils/encrypt/encrypt.go +++ b/backend/utils/encrypt/encrypt.go @@ -4,8 +4,10 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "crypto/md5" "crypto/rand" "encoding/base64" + "encoding/hex" "fmt" "io" @@ -38,6 +40,12 @@ func StringDecrypt(text string) (string, error) { return "", err } +func Md5(str string) string { + h := md5.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} + func padding(plaintext []byte, blockSize int) []byte { padding := blockSize - len(plaintext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a61064b73..81e8ef5fb 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -49,6 +49,12 @@ class RequestHttp { }); return Promise.reject(data); } + if (data.code == ResultEnum.UNSAFETY) { + router.replace({ + path: '/login', + }); + return data; + } if (data.code && data.code !== ResultEnum.SUCCESS) { ElMessage.error(data.msg); return Promise.reject(data); diff --git a/frontend/src/api/interface/user.ts b/frontend/src/api/interface/user.ts deleted file mode 100644 index 456e1bed2..000000000 --- a/frontend/src/api/interface/user.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommonModel, ReqPage } from '.'; - -export namespace User { - export interface User extends CommonModel { - name: string; - email: string; - password: string; - } - export interface UserCreate { - username: string; - email: string; - } - - export interface ReqGetUserParams extends ReqPage { - info?: string; - email?: string; - } -} diff --git a/frontend/src/api/modules/login.ts b/frontend/src/api/modules/auth.ts similarity index 69% rename from frontend/src/api/modules/login.ts rename to frontend/src/api/modules/auth.ts index 6481acff6..0af18b9d0 100644 --- a/frontend/src/api/modules/login.ts +++ b/frontend/src/api/modules/auth.ts @@ -12,3 +12,11 @@ export const getCaptcha = () => { export const logOutApi = () => { return http.post(`/auth/logout`); }; + +export const entrance = (code: string) => { + return http.get(`/${code}`); +}; + +export const loginStatus = () => { + return http.get('/info'); +}; diff --git a/frontend/src/api/modules/user.ts b/frontend/src/api/modules/user.ts deleted file mode 100644 index f24eab011..000000000 --- a/frontend/src/api/modules/user.ts +++ /dev/null @@ -1,23 +0,0 @@ -import http from '@/api'; -import { ResPage } from '../interface'; -import { User } from '../interface/user'; - -export const getUserList = (params: User.ReqGetUserParams) => { - return http.post>(`/users/search`, params); -}; - -export const addUser = (params: User.User) => { - return http.post(`/users`, params); -}; - -export const getUserById = (id: number) => { - return http.get(`/users/${id}`); -}; - -export const editUser = (params: User.User) => { - return http.put(`/users/` + params.id, params); -}; - -export const deleteUser = (params: { ids: number[] }) => { - return http.post(`/users/del`, params); -}; diff --git a/frontend/src/components/app-layout/menu/index.vue b/frontend/src/components/app-layout/menu/index.vue index 2d9d0a55d..fd6167059 100644 --- a/frontend/src/components/app-layout/menu/index.vue +++ b/frontend/src/components/app-layout/menu/index.vue @@ -41,7 +41,7 @@ import { loadingSvg } from '@/utils/svg'; import Logo from './components/logo.vue'; import SubItem from './components/sub-item.vue'; import router, { menuList } from '@/routers/router'; -import { logOutApi } from '@/api/modules/login'; +import { logOutApi } from '@/api/modules/auth'; import i18n from '@/lang'; import { ElMessageBox, ElMessage } from 'element-plus'; import { GlobalStore } from '@/store'; diff --git a/frontend/src/components/switch-dark/index.vue b/frontend/src/components/switch-dark/index.vue deleted file mode 100644 index a8280e037..000000000 --- a/frontend/src/components/switch-dark/index.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/frontend/src/enums/http-enum.ts b/frontend/src/enums/http-enum.ts index 9544c3e79..3c6e11fdf 100644 --- a/frontend/src/enums/http-enum.ts +++ b/frontend/src/enums/http-enum.ts @@ -2,6 +2,7 @@ export enum ResultEnum { SUCCESS = 200, ERROR = 500, OVERDUE = 401, + UNSAFETY = 402, FORBIDDEN = 403, TIMEOUT = 100000, TYPE = 'success', diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index b9abbe69c..ed1694e89 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -8,6 +8,8 @@ export default { sync: 'Sync', delete: 'Delete', edit: 'Edit', + enable: 'Enable', + disable: 'Disable', confirm: 'Confirm', cancel: 'Cancel', reset: 'Reset', @@ -49,6 +51,15 @@ export default { }, login: { captchaHelper: 'Please enter the verification code', + safeEntrance: 'Please use the correct entry to log in to the panel', + reason: 'Cause of error:', + reasonHelper: + 'At present, the newly installed machine has enabled the security entrance login. The newly installed machine will have a random 8-character security entrance name, which can also be modified in the panel Settings. If you do not record or do not remember, you can use the following methods to solve the problem', + solution: 'The solution:', + solutionHelper: + 'Run the following command on the SSH terminal to solve the problem: 1. View the /etc/init.d/bt default command on the panel', + warnning: + 'Note: [Closing the security entrance] will make your panel login address directly exposed to the Internet, very dangerous, please exercise caution', }, rule: { username: 'Please enter a username', @@ -87,7 +98,6 @@ export default { }, menu: { home: 'Overview', - demo: 'Example', terminal: 'Terminal', apps: 'App Store', website: 'Website', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f1db3ddd5..130561994 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -61,6 +61,13 @@ export default { }, login: { captchaHelper: '请输入验证码', + safeEntrance: '请使用正确的入口登录面板', + reason: '错误原因:', + reasonHelper: + '当前新安装的已经开启了安全入口登录,新装机器都会随机一个8位字符的安全入口名称,亦可以在面板设置处修改,如您没记录或不记得了,可以使用以下方式解决', + solution: '解决方法:', + solutionHelper: '在SSH终端输入以下一种命令来解决 1.查看面板入口:/etc/init.d/bt default', + warnning: '注意:【关闭安全入口】将使您的面板登录地址被直接暴露在互联网上,非常危险,请谨慎操作', }, rule: { username: '请输入用户名', @@ -101,7 +108,6 @@ export default { }, menu: { home: '概览', - demo: '样例', monitor: '监控', terminal: '终端', operations: '操作日志', @@ -287,7 +293,7 @@ export default { panelPort: '面板端口', portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口', safeEntrance: '安全入口', - safeEntranceHelper: '面板管理入口,设置后只能通过指定安全入口登录面板,如: /89dc6ae8', + safeEntranceHelper: '面板管理入口,设置后只能通过指定安全入口登录面板,如: 89dc6ae8', passwordTimeout: '密码过期时间', timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码', complexity: '密码复杂度验证', diff --git a/frontend/src/routers/demo.ts b/frontend/src/routers/demo.ts deleted file mode 100644 index ad2b7c862..000000000 --- a/frontend/src/routers/demo.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Layout } from '@/routers/constant'; - -// demo -const demoRouter = { - sort: 1, - path: '/demos', - component: Layout, - redirect: '/demos/table', - meta: { - icon: 'apple', - title: 'menu.demo', - }, - children: [ - { - path: '/demos/table', - name: 'Table', - component: () => import('@/views/demos/table/index.vue'), - }, - { - path: '/demos/table/:op/:id?', - name: 'DemoOperate', - props: true, - hidden: true, - component: () => import('@/views/demos/table/operate/index.vue'), - meta: { - activeMenu: '/demos/table', - }, - }, - ], -}; - -export default demoRouter; diff --git a/frontend/src/routers/router.ts b/frontend/src/routers/router.ts index 5eec148c8..1edd036c0 100644 --- a/frontend/src/routers/router.ts +++ b/frontend/src/routers/router.ts @@ -1,10 +1,10 @@ -import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { Layout } from '@/routers/constant'; const modules = import.meta.globEager('./modules/*.ts'); const homeRouter: RouteRecordRaw = { - path: '/', + path: '/home', component: Layout, redirect: '/home/index', meta: { @@ -55,8 +55,9 @@ menuList.unshift(homeRouter); export const routes: RouteRecordRaw[] = [ homeRouter, { - path: '/login', + path: '/login/:code?', name: 'login', + props: true, component: () => import('@/views/login/index.vue'), meta: { requiresAuth: false, @@ -70,7 +71,7 @@ export const routes: RouteRecordRaw[] = [ }, ]; const router = createRouter({ - history: createWebHashHistory(), + history: createWebHistory(), routes: routes as RouteRecordRaw[], strict: false, scrollBehavior: () => ({ left: 0, top: 0 }), diff --git a/frontend/src/views/demos/table/index.vue b/frontend/src/views/demos/table/index.vue deleted file mode 100644 index 53e386ee1..000000000 --- a/frontend/src/views/demos/table/index.vue +++ /dev/null @@ -1,101 +0,0 @@ - - diff --git a/frontend/src/views/demos/table/operate/index.vue b/frontend/src/views/demos/table/operate/index.vue deleted file mode 100644 index a954f70d3..000000000 --- a/frontend/src/views/demos/table/operate/index.vue +++ /dev/null @@ -1,89 +0,0 @@ - - diff --git a/frontend/src/views/host/terminal/index.vue b/frontend/src/views/host/terminal/index.vue index e136d8405..386b3b08c 100644 --- a/frontend/src/views/host/terminal/index.vue +++ b/frontend/src/views/host/terminal/index.vue @@ -47,7 +47,6 @@ @change="quickInput" style="width: 25%" :placeholder="$t('terminal.quickCommand')" - >