fix: avoid license required before login (#12626)

This commit is contained in:
ssongliu
2026-05-21 12:35:10 +08:00
committed by zhengkunwang223
parent d55982bde0
commit eb527857f3
21 changed files with 96 additions and 102 deletions
+22
View File
@@ -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 {
-23
View File
@@ -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" {
+2 -1
View File
@@ -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),
+1 -1
View File
@@ -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
+4 -2
View File
@@ -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
}
+3
View File
@@ -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)
}
}
+1 -1
View File
@@ -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)
+9 -1
View File
@@ -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) {
+4
View File
@@ -90,4 +90,8 @@ export namespace Login {
ipWhiteList: string;
apiKeyValidityTime: string;
}
export interface PasswordUpdate {
oldPassword: string;
newPassword: string;
}
}
-4
View File
@@ -152,10 +152,6 @@ export namespace Setting {
key: string;
sslID: number;
}
export interface PasswordUpdate {
oldPassword: string;
newPassword: string;
}
export interface PortUpdate {
serverPort: number;
}
+13 -9
View File
@@ -38,6 +38,17 @@ export const getWelcomePage = () => {
export const getUserInfo = () => {
return http.get<Login.AuthInfo>('/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<any>('/core/auth/current/update', request);
};
export const loadMFA = (params: Login.MFARequest) => {
return http.post<Login.MFAInfo>(`/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<any>('/core/auth/current/update', request);
export const handleExpired = (param: Login.PasswordUpdate) => {
return http.post(`/core/auth/expired/reset`, param);
};
-6
View File
@@ -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<any>(`/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<App.AppStoreConfig>(`/core/settings/apps/store/config${params}`);
@@ -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(() => {});
@@ -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;
+21 -50
View File
@@ -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<boolean> | null = null;
let licenseStatusLoading: Promise<boolean> | 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;
+1
View File
@@ -68,6 +68,7 @@ export interface GlobalState {
productProExpires: number;
isMasterProductPro: boolean;
isXpackEELicensed: boolean;
isXpackEELicenseLoaded: boolean;
// multi-node
masterAlias: string;
currentNode: string;
+1
View File
@@ -65,6 +65,7 @@ const GlobalStore = defineStore({
productProExpires: 0,
isMasterProductPro: false,
isXpackEELicensed: false,
isXpackEELicenseLoaded: false,
// multi-node
masterAlias: '',
currentNode: 'local',
+4
View File
@@ -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 {
@@ -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) {
+2 -1
View File
@@ -47,7 +47,8 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { getSettingBaseInfo, handleExpired } from '@/api/modules/setting';
import { getSettingBaseInfo } from '@/api/modules/setting';
import { handleExpired } from '@/api/modules/auth';
import { ElForm } from 'element-plus';
import i18n from '@/lang';
import { Rules } from '@/global/form-rules';
@@ -104,6 +104,7 @@ const loadImage = (name: string) => {
const loadLoginTheme = async () => {
const res = await getLoginSetting();
globalStore.isXpackEE = res.data.isXpackEE;
globalStore.isXpackEELicenseLoaded = !res.data.isXpackEE;
globalStore.isIntl = res.data.isIntl;
globalStore.isFxplay = res.data.isFxplay;
globalStore.isOffLine = res.data.isOffLine;
@@ -125,7 +126,9 @@ const loadLoginTheme = async () => {
adjustColorToRGBA(loginBtnLinkColor.value, 30, 15),
);
if (!globalStore.isXpackEE) {
router.replace({ name: 'entrance', params: { code: globalStore.entrance } });
router.replace(
globalStore.isLogin ? { name: 'home' } : { name: 'entrance', params: { code: globalStore.entrance } },
);
}
};
@@ -155,6 +158,7 @@ const loadBackground = async () => {
const loadLicenseInfo = async () => {
const res = await getXpackEELicense();
globalStore.isXpackEELicenseLoaded = true;
licenseInfo.deviceID = res.data.deviceID;
if (res.data.status === 'Bound') {
globalStore.isXpackEELicensed = true;