diff --git a/core/app/api/v2/auth.go b/core/app/api/v2/auth.go index 6493d0c6a..c1f242a43 100644 --- a/core/app/api/v2/auth.go +++ b/core/app/api/v2/auth.go @@ -461,6 +461,28 @@ func (b *BaseApi) UpdateCurrentUser(c *gin.Context) { helper.Success(c) } +// @Tags Auth +// @Summary Reset system password expired +// @Accept json +// @Param request body dto.PasswordUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/auth/expired/reset [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置过期密码","formatEN":"reset an expired Password"} +func (b *BaseApi) ResetPassword(c *gin.Context) { + var req dto.PasswordUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := xpack.AuthProvider.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + func saveLoginLogs(c *gin.Context, err error) { var logs model.LoginLog if err != nil { diff --git a/core/app/api/v2/setting.go b/core/app/api/v2/setting.go index 0b2544f61..d59512b63 100644 --- a/core/app/api/v2/setting.go +++ b/core/app/api/v2/setting.go @@ -17,7 +17,6 @@ import ( "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/common" - "github.com/1Panel-dev/1Panel/core/utils/xpack" "github.com/gin-gonic/gin" ) @@ -338,28 +337,6 @@ func (b *BaseApi) UpdatePort(c *gin.Context) { helper.Success(c) } -// @Tags System Setting -// @Summary Reset system password expired -// @Accept json -// @Param request body dto.PasswordUpdate true "request" -// @Success 200 -// @Security ApiKeyAuth -// @Security Timestamp -// @Router /core/settings/expired/handle [post] -// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置过期密码","formatEN":"reset an expired Password"} -func (b *BaseApi) HandlePasswordExpired(c *gin.Context) { - var req dto.PasswordUpdate - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - if err := xpack.AuthProvider.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil { - helper.InternalServer(c, err) - return - } - helper.Success(c) -} - func (b *BaseApi) ReloadSSL(c *gin.Context) { clientIP := c.ClientIP() if clientIP != "127.0.0.1" { diff --git a/core/app/service/logs.go b/core/app/service/logs.go index 2ff423d3a..99d56a85e 100644 --- a/core/app/service/logs.go +++ b/core/app/service/logs.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net/http" + "os" "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" @@ -126,7 +127,7 @@ func runRemoteShellScript(url string, args ...string) error { if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { return fmt.Errorf("download script failed, status code: %d", statusCode) } - _, err = cmd.NewCommandMgr().RunPipe(cmd.PipeCommand{ + _, err = cmd.NewCommandMgr(cmd.WithOutputFile(os.DevNull)).RunPipe(cmd.PipeCommand{ Name: "sh", Args: append([]string{"-s"}, args...), Stdin: bytes.NewReader(script), diff --git a/core/cmd/server/conf/app.yaml b/core/cmd/server/conf/app.yaml index 5660c4dce..1da4fc8c7 100644 --- a/core/cmd/server/conf/app.yaml +++ b/core/cmd/server/conf/app.yaml @@ -4,7 +4,7 @@ base: is_demo: false is_offline: false is_fxplay: false - is_xpackee: false + is_xpackee: true port: 9999 username: admin password: admin123 diff --git a/core/middleware/password_expired.go b/core/middleware/password_expired.go index dd146aa09..012bb15d6 100644 --- a/core/middleware/password_expired.go +++ b/core/middleware/password_expired.go @@ -16,9 +16,11 @@ import ( func PasswordExpired() gin.HandlerFunc { return func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") || - c.Request.URL.Path == "/api/v2/core/settings/expired/handle" || c.Request.URL.Path == "/api/v2/core/settings/search" || - c.Request.URL.Path == "/api/v2/core/settings/search/base" { + c.Request.URL.Path == "/api/v2/core/settings/search/base" || + c.Request.URL.Path == "/api/v2/core/xpackee/licenses/info" || + c.Request.URL.Path == "/api/v2/core/xpackee/licenses/status" || + c.Request.URL.Path == "/api/v2/core/xpackee/licenses/upload" { c.Next() return } diff --git a/core/router/ro_base.go b/core/router/ro_base.go index 4375983a4..d69f50ec5 100644 --- a/core/router/ro_base.go +++ b/core/router/ro_base.go @@ -26,14 +26,17 @@ func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) { authRouter.POST("/mfa", baseApi.LoadMFA) authRouter.POST("/mfa/bind", baseApi.MFABind) + authRouter.POST("/passkey/register/begin", baseApi.PasskeyRegisterBegin) authRouter.POST("/passkey/register/finish", baseApi.PasskeyRegisterFinish) authRouter.GET("/passkey/list", baseApi.PasskeyList) authRouter.POST("/passkey/del", baseApi.PasskeyDelete) + authRouter.POST("/api/generate", baseApi.GenerateApiKey) authRouter.POST("/api/update", baseApi.UpdateApiConfig) authRouter.GET("/current", baseApi.GetCurrentUser) authRouter.POST("/current/update", baseApi.UpdateCurrentUser) + authRouter.POST("/expired/reset", baseApi.ResetPassword) } } diff --git a/core/router/ro_setting.go b/core/router/ro_setting.go index c8886ea08..40b97cc47 100644 --- a/core/router/ro_setting.go +++ b/core/router/ro_setting.go @@ -18,10 +18,10 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { noAuthRouter := Router.Group("settings") baseApi := v2.ApiGroupApp.BaseApi { - router.POST("/search", baseApi.GetSettingInfo) router.POST("/search/base", baseApi.GetSettingBaseInfo) settingRouter.POST("/by", baseApi.GetSettingByKey) + settingRouter.POST("/search", baseApi.GetSettingInfo) settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo) settingRouter.GET("/search/available", baseApi.GetSystemAvailable) settingRouter.POST("/update", baseApi.UpdateSetting) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 5afc61b74..dc1a7b1bf 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -94,7 +94,15 @@ class RequestHttp { } if (data.code == ResultEnum.ERRXPACKEE) { globalStore.isXpackEELicensed = false; - router.push({ name: 'XpackEELicenseRequired' }); + const routeName = router.currentRoute.value.name; + if ( + globalStore.isLogin && + routeName !== 'entrance' && + routeName !== 'login' && + routeName !== 'XpackEELicenseRequired' + ) { + router.push({ name: 'XpackEELicenseRequired' }); + } return Promise.reject(data); } if (data.code == ResultEnum.NodeUnBind) { diff --git a/frontend/src/api/interface/auth.ts b/frontend/src/api/interface/auth.ts index 8de183e11..bdea4ad01 100644 --- a/frontend/src/api/interface/auth.ts +++ b/frontend/src/api/interface/auth.ts @@ -90,4 +90,8 @@ export namespace Login { ipWhiteList: string; apiKeyValidityTime: string; } + export interface PasswordUpdate { + oldPassword: string; + newPassword: string; + } } diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index bf09ab12c..27b3fce81 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -152,10 +152,6 @@ export namespace Setting { key: string; sslID: number; } - export interface PasswordUpdate { - oldPassword: string; - newPassword: string; - } export interface PortUpdate { serverPort: number; } diff --git a/frontend/src/api/modules/auth.ts b/frontend/src/api/modules/auth.ts index 7a2b6e403..1b2d8c938 100644 --- a/frontend/src/api/modules/auth.ts +++ b/frontend/src/api/modules/auth.ts @@ -38,6 +38,17 @@ export const getWelcomePage = () => { export const getUserInfo = () => { return http.get('/core/auth/current'); }; +export const updateUserInfo = (params: Login.AuthInfoUpdate) => { + let request = deepCopy(params) as Login.AuthInfoUpdate; + if (request.oldPassword) { + request.oldPassword = Base64.encode(request.oldPassword); + } + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post('/core/auth/current/update', request); +}; + export const loadMFA = (params: Login.MFARequest) => { return http.post(`/core/auth/mfa`, params); }; @@ -66,13 +77,6 @@ export const passkeyDelete = (id: string) => { return http.post(`/core/auth/passkey/del`, { id }); }; -export const updateUserInfo = (params: Login.AuthInfoUpdate) => { - let request = deepCopy(params) as Login.AuthInfoUpdate; - if (request.oldPassword) { - request.oldPassword = Base64.encode(request.oldPassword); - } - if (request.password) { - request.password = Base64.encode(request.password); - } - return http.post('/core/auth/current/update', request); +export const handleExpired = (param: Login.PasswordUpdate) => { + return http.post(`/core/auth/expired/reset`, param); }; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index be6d9a978..8f6262ee7 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -148,9 +148,6 @@ export const updateProxy = (params: Setting.ProxyUpdate) => { request.proxyType = request.proxyType === 'close' ? '' : request.proxyType; return http.post(`/core/settings/proxy/update`, request); }; -export const updatePassword = (param: Setting.PasswordUpdate) => { - return http.post(`/core/settings/password/update`, param); -}; export const loadInterfaceAddr = () => { return http.get(`/core/settings/interface`); }; @@ -169,9 +166,6 @@ export const loadSSLInfo = () => { export const downloadSSL = () => { return http.download(`/core/settings/ssl/download`); }; -export const handleExpired = (param: Setting.PasswordUpdate) => { - return http.post(`/core/settings/expired/handle`, param); -}; export const getAppStoreConfig = (node?: string) => { const params = node ? `?operateNode=${node}` : ''; return http.get(`/core/settings/apps/store/config${params}`); diff --git a/frontend/src/layout/components/Sidebar/components/Collapse.vue b/frontend/src/layout/components/Sidebar/components/Collapse.vue index 4bc93ac5d..f7f35bbd6 100644 --- a/frontend/src/layout/components/Sidebar/components/Collapse.vue +++ b/frontend/src/layout/components/Sidebar/components/Collapse.vue @@ -271,9 +271,9 @@ const logout = () => { }) .then(async () => { await logOutApi(); - router.push({ name: 'entrance', params: { code: globalStore.entrance } }); globalStore.setLogStatus(false); globalStore.clearAuthInfo(); + router.push({ name: 'entrance', params: { code: globalStore.entrance } }); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); }) .catch(() => {}); diff --git a/frontend/src/layout/components/Sidebar/components/user-info/index.vue b/frontend/src/layout/components/Sidebar/components/user-info/index.vue index fa1562829..23e55ed23 100644 --- a/frontend/src/layout/components/Sidebar/components/user-info/index.vue +++ b/frontend/src/layout/components/Sidebar/components/user-info/index.vue @@ -855,9 +855,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => { loading.value = false; open.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - router.push({ name: 'entrance', params: { code: globalStore.entrance } }); globalStore.setLogStatus(false); globalStore.clearAuthInfo(); + router.push({ name: 'entrance', params: { code: globalStore.entrance } }); }) .catch(() => { loading.value = false; diff --git a/frontend/src/routers/index.ts b/frontend/src/routers/index.ts index 356a58657..30998e1b8 100644 --- a/frontend/src/routers/index.ts +++ b/frontend/src/routers/index.ts @@ -3,57 +3,36 @@ import NProgress from '@/config/nprogress'; import { GlobalStore } from '@/store'; import { AxiosCanceler } from '@/api/helper/axios-cancel'; import { hasRouteAccess } from '@/utils/rbac'; +import { loadProductProFromDB } from '@/utils/xpack'; import i18n from '@/lang'; import { MsgError } from '@/utils/message'; const axiosCanceler = new AxiosCanceler(); let isRedirecting = false; -let xpackEELoading: Promise | null = null; -let licenseStatusLoading: Promise | null = null; +const xpackEELicenseCheckWhiteList = ['XpackEELicenseRequired', 'entrance', 'login', 'Expired']; -const loadXpackEEStatus = async () => { - if (!xpackEELoading) { - xpackEELoading = fetch('/api/v2/core/auth/setting', { - credentials: 'include', - }) - .then((res) => res.json()) - .then((res) => { - const globalStore = GlobalStore(); - globalStore.isXpackEE = !!res?.data?.isXpackEE; - return globalStore.isXpackEE; - }) - .catch(() => GlobalStore().isXpackEE) - .finally(() => { - xpackEELoading = null; - }); - } - return xpackEELoading; +const clearLicenseStatus = () => { + const globalStore = GlobalStore(); + globalStore.isXpackEELicensed = false; + globalStore.isXpackEELicenseLoaded = false; }; -const loadXpackEELicenseStatus = async () => { - if (!licenseStatusLoading) { - licenseStatusLoading = fetch('/api/v2/core/xpackee/licenses/status', { - credentials: 'include', - headers: { - CurrentNode: encodeURIComponent(GlobalStore().currentNode), - }, - }) - .then((res) => res.json()) - .then((res) => res?.data?.status === 'Bound') - .catch(() => false) - .finally(() => { - licenseStatusLoading = null; - }); - } - return licenseStatusLoading; +const clearLoginStatus = () => { + const globalStore = GlobalStore(); + globalStore.setLogStatus(false); + globalStore.clearAuthInfo(); + clearLicenseStatus(); }; router.beforeEach(async (to, from, next) => { NProgress.start(); axiosCanceler.removeAllPending(); const globalStore = GlobalStore(); - if (to.name !== 'entrance' && to.name !== 'XpackEELicenseRequired' && !globalStore.isLogin) { + if (!globalStore.isLogin) { + clearLoginStatus(); + } + if (to.name !== 'entrance' && !globalStore.isLogin) { next({ name: 'entrance', params: to.params, @@ -73,19 +52,11 @@ router.beforeEach(async (to, from, next) => { NProgress.done(); return; } - if (globalStore.isLogin && to.name !== 'login') { - await loadXpackEEStatus(); - } - if ( - globalStore.isLogin && - globalStore.isXpackEE && - to.name !== 'XpackEELicenseRequired' && - to.name !== 'entrance' && - to.name !== 'login' - ) { - const licensed = globalStore.isXpackEELicensed || (await loadXpackEELicenseStatus()); - globalStore.isXpackEELicensed = licensed; - if (!licensed) { + if (globalStore.isLogin && globalStore.isXpackEE && !xpackEELicenseCheckWhiteList.includes(String(to.name))) { + if (!globalStore.isXpackEELicenseLoaded) { + await loadProductProFromDB(); + } + if (!globalStore.isXpackEELicensed) { next({ name: 'XpackEELicenseRequired', query: { code: String(to.params.code || '') } }); NProgress.done(); return; @@ -100,7 +71,7 @@ router.beforeEach(async (to, from, next) => { NProgress.done(); return; } - if (globalStore.isXpackEELicensed) { + if (!globalStore.isXpackEE || globalStore.isXpackEELicensed) { next({ name: 'home' }); NProgress.done(); return; diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts index 378df072c..ddec12801 100644 --- a/frontend/src/store/interface/index.ts +++ b/frontend/src/store/interface/index.ts @@ -68,6 +68,7 @@ export interface GlobalState { productProExpires: number; isMasterProductPro: boolean; isXpackEELicensed: boolean; + isXpackEELicenseLoaded: boolean; // multi-node masterAlias: string; currentNode: string; diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts index d8a267d97..aeb912ec3 100644 --- a/frontend/src/store/modules/global.ts +++ b/frontend/src/store/modules/global.ts @@ -65,6 +65,7 @@ const GlobalStore = defineStore({ productProExpires: 0, isMasterProductPro: false, isXpackEELicensed: false, + isXpackEELicenseLoaded: false, // multi-node masterAlias: '', currentNode: 'local', diff --git a/frontend/src/utils/xpack.ts b/frontend/src/utils/xpack.ts index d48b8c59e..fc0a7bf57 100644 --- a/frontend/src/utils/xpack.ts +++ b/frontend/src/utils/xpack.ts @@ -84,6 +84,7 @@ const loadDataFromDB = async () => { export async function loadProductProFromDB() { const globalStore = getGlobalStore(); if (!globalStore.isXpackEE) { + globalStore.isXpackEELicenseLoaded = true; const res = await getLicenseStatus(); if (!res || !res.data) { globalStore.isProductPro = false; @@ -96,6 +97,7 @@ export async function loadProductProFromDB() { return; } const res = await getXpackEELicenseStatus(); + globalStore.isXpackEELicenseLoaded = true; if (!res || !res.data) { globalStore.isXpackEELicensed = false; } else { @@ -106,6 +108,7 @@ export async function loadProductProFromDB() { export async function loadMasterProductProFromDB() { const globalStore = getGlobalStore(); if (!globalStore.isXpackEE) { + globalStore.isXpackEELicenseLoaded = true; const res = await getMasterLicenseStatus(); if (!res || !res.data) { globalStore.isMasterProductPro = false; @@ -114,6 +117,7 @@ export async function loadMasterProductProFromDB() { } } else { const res = await getXpackEELicenseStatus(); + globalStore.isXpackEELicenseLoaded = true; if (!res || !res.data) { globalStore.isXpackEELicensed = false; } else { diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue index 894f0f70f..788bce83a 100644 --- a/frontend/src/views/login/components/login-form.vue +++ b/frontend/src/views/login/components/login-form.vue @@ -630,6 +630,7 @@ const getSetting = async () => { globalStore.isFxplay = isFxplay.value; globalStore.isOffLine = res.data.isOffLine; globalStore.isXpackEE = res.data.isXpackEE; + globalStore.isXpackEELicenseLoaded = !res.data.isXpackEE; globalStore.ignoreCaptcha = !res.data.needCaptcha; passkeySetting.value = res.data.passkeySetting; if (!globalStore.ignoreCaptcha) { diff --git a/frontend/src/views/setting/expired.vue b/frontend/src/views/setting/expired.vue index 5e3867785..8582dbd52 100644 --- a/frontend/src/views/setting/expired.vue +++ b/frontend/src/views/setting/expired.vue @@ -47,7 +47,8 @@