Add config to hide default passwords or force password change at first login #1355

This commit is contained in:
fit2cloud-chenyw
2026-09-02 16:41:01 +08:00
committed by fit2cloud-chenyw
parent c1d333e62d
commit 4ee923c968
18 changed files with 319 additions and 22 deletions
+8
View File
@@ -14,6 +14,14 @@ export const AuthApi = {
},
})
},
changePwd: (data: { account: string; pwd: string; new_pwd: string }) => {
const entry = {
account: LicenseGenerator.sqlbotEncrypt(data.account),
pwd: LicenseGenerator.sqlbotEncrypt(data.pwd),
new_pwd: LicenseGenerator.sqlbotEncrypt(data.new_pwd),
}
return request.post('/login/change-pwd', entry)
},
logout: (data: any) => request.post('/login/logout', data),
info: () => request.get('/user/info'),
}
+6 -1
View File
@@ -96,7 +96,9 @@
"closed_by_default": "In the Question Count window, control whether the model thinking process is expanded or closed by default.",
"and_platform_integration": "Authentication-related",
"login_settings": "Login Settings",
"default_login": "Default Login Method"
"default_login": "Default Login Method",
"disable_initial_password": "Disable Initial Password",
"hide_initial_password": "Hide Initial Password on Page"
},
"prompt": {
"default_password": "Default password:{msg}",
@@ -932,6 +934,9 @@
"default_login": "Default",
"ldap_login": "LDAP Login",
"account_login": "Account Login",
"force_change_pwd_title": "Change Initial Password",
"force_change_pwd_tips": "Your current password is the system initial password. Please change it and log in again.",
"force_change_pwd_success": "Password changed successfully",
"other_login": "Other Login Methods",
"pwd_invalid_error": "Password has expired, please contact administrator to modify or reset",
"pwd_exp_tips": "Password will expire in {0} days, please change it as soon as possible",
+6 -1
View File
@@ -96,7 +96,9 @@
"closed_by_default": "질문 수 창에서 모델 사고 프로세스를 기본적으로 확장할지 또는 닫을지 여부를 제어합니다.",
"and_platform_integration": "인증 관련",
"login_settings": "로그인 설정",
"default_login": "기본 로그인 방식"
"default_login": "기본 로그인 방식",
"disable_initial_password": "초기 비밀번호 비활성화",
"hide_initial_password": "페이지에서 초기 비밀번호 숨기기"
},
"prompt": {
"default_password:": "기본 비밀번호:{msg}",
@@ -932,6 +934,9 @@
"default_login": "기본값",
"ldap_login": "LDAP 로그인",
"account_login": "계정 로그인",
"force_change_pwd_title": "초기 비밀번호 변경",
"force_change_pwd_tips": "현재 비밀번호는 시스템 초기 비밀번호입니다. 비밀번호를 변경한 후 로그인해 주세요.",
"force_change_pwd_success": "비밀번호가 변경되었습니다",
"other_login": "기타 로그인 방식",
"pwd_invalid_error": "비밀번호가 만료되었습니다. 관리자에게 문의하여 수정 또는 재설정해 주세요",
"pwd_exp_tips": "비밀번호가 {0}일 후에 만료됩니다.尽快尽快 비밀번호를 변경해 주세요",
+6 -1
View File
@@ -96,7 +96,9 @@
"closed_by_default": "在问数窗口中,控制模型思考过程默认展开或者关闭",
"and_platform_integration": "登录认证相关",
"login_settings": "登录设置",
"default_login": "默认登录方式"
"default_login": "默认登录方式",
"disable_initial_password": "禁用初始密码",
"hide_initial_password": "页面隐藏初始密码"
},
"prompt": {
"default_password": "默认密码:{msg}",
@@ -932,6 +934,9 @@
"default_login": "默认",
"ldap_login": "LDAP 登录",
"account_login": "账号登录",
"force_change_pwd_title": "修改初始密码",
"force_change_pwd_tips": "当前密码为系统初始密码,请修改密码后再登录",
"force_change_pwd_success": "密码修改成功",
"other_login": "其他登录方式",
"pwd_invalid_error": "密码已过期请联系管理员修改或重置",
"pwd_exp_tips": "密码在 {0} 天后过期,请尽快修改密码",
+6 -1
View File
@@ -96,7 +96,9 @@
"closed_by_default": "在問數視窗中,控制模型思考過程預設展開或者關閉",
"and_platform_integration": "登入認證相關",
"login_settings": "登入設定",
"default_login": "預設登入方式"
"default_login": "預設登入方式",
"disable_initial_password": "禁用初始密碼",
"hide_initial_password": "頁面隱藏初始密碼"
},
"prompt": {
"default_password": "預設密碼:{msg}",
@@ -932,6 +934,9 @@
"default_login": "預設",
"ldap_login": "LDAP 登入",
"account_login": "帳號登入",
"force_change_pwd_title": "修改初始密碼",
"force_change_pwd_tips": "目前密碼為系統初始密碼,請修改密碼後再登入",
"force_change_pwd_success": "密碼修改成功",
"other_login": "其他登入方式",
"pwd_invalid_error": "密碼已過期請聯繫管理員修改或重設",
"pwd_exp_tips": "密碼在 {0} 天後過期,請盡快修改密碼",
+4 -1
View File
@@ -83,7 +83,10 @@ export const UserStore = defineStore('user', {
actions: {
async login(formData: { username: string; password: string }) {
const res: any = await AuthApi.login(formData)
this.setToken(res.access_token)
if (res.access_token) {
this.setToken(res.access_token)
}
return res
},
async logout() {
@@ -0,0 +1,161 @@
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { AuthApi } from '@/api/login'
const props = defineProps({
modelValue: { type: Boolean, required: true },
account: { type: String, required: true },
oldPwd: { type: String, required: true },
})
const emits = defineEmits(['update:modelValue', 'pwdSaved'])
const { t } = useI18n()
const pwdRef = ref()
const pwdForm = reactive({
pwd: '',
new_pwd: '',
confirm_pwd: '',
})
const PWD_REGEX =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+\-={}|:"<>?`\[\];',./])[A-Za-z\d~!@#$%^&*()_+\-={}|:"<>?`\[\];',./]{8,20}$/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validatePass = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error(t('common.please_input', { msg: t('user.upgrade_pwd.new_pwd') })))
} else {
if (!PWD_REGEX.test(value)) {
callback(new Error(t('user.upgrade_pwd.pwd_format_error')))
return
}
if (pwdForm.confirm_pwd !== '') {
if (!pwdRef.value) return
pwdRef.value.validateField('confirm_pwd')
}
callback()
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validatePass2 = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error(t('common.please_input', { msg: t('user.upgrade_pwd.confirm_pwd') })))
} else if (!PWD_REGEX.test(value)) {
callback(new Error(t('user.upgrade_pwd.pwd_format_error')))
} else if (value !== pwdForm.new_pwd) {
callback(new Error(t('user.upgrade_pwd.two_pwd_not_match')))
} else {
callback()
}
}
const rules = {
pwd: [
{
required: true,
message: t('common.please_input', { msg: t('user.upgrade_pwd.old_pwd') }),
trigger: 'blur',
},
],
new_pwd: [{ validator: validatePass, trigger: 'blur' }],
confirm_pwd: [{ validator: validatePass2, trigger: 'blur' }],
}
watch(
() => props.modelValue,
(val) => {
if (val) {
pwdForm.pwd = props.oldPwd
pwdForm.new_pwd = ''
pwdForm.confirm_pwd = ''
}
}
)
const closeHandler = () => {
emits('update:modelValue', false)
}
const submit = () => {
pwdRef.value.validate((res: any) => {
if (res) {
AuthApi.changePwd({
account: props.account,
pwd: pwdForm.pwd,
new_pwd: pwdForm.new_pwd,
}).then(() => {
ElMessage({
type: 'success',
message: t('login.force_change_pwd_success'),
})
emits('pwdSaved', pwdForm.new_pwd)
closeHandler()
})
}
})
}
</script>
<template>
<el-dialog
:model-value="modelValue"
:title="t('login.force_change_pwd_title')"
width="480"
append-to-body
@update:model-value="(val: boolean) => emits('update:modelValue', val)"
@close="closeHandler"
>
<div class="initial-pwd-tips">{{ t('login.force_change_pwd_tips') }}</div>
<el-form
ref="pwdRef"
:rules="rules"
label-position="top"
:model="pwdForm"
style="width: 100%"
@submit.prevent
>
<el-form-item prop="pwd" :label="t('user.upgrade_pwd.old_pwd')">
<el-input
v-model="pwdForm.pwd"
:placeholder="t('common.please_input', { msg: t('user.upgrade_pwd.old_pwd') })"
type="password"
show-password
clearable
/>
</el-form-item>
<el-form-item prop="new_pwd" :label="t('user.upgrade_pwd.new_pwd')">
<el-input
v-model="pwdForm.new_pwd"
:placeholder="t('common.please_input', { msg: t('user.upgrade_pwd.new_pwd') })"
type="password"
show-password
clearable
/>
</el-form-item>
<el-form-item prop="confirm_pwd" :label="t('user.upgrade_pwd.confirm_pwd')">
<el-input
v-model="pwdForm.confirm_pwd"
:placeholder="t('common.please_input', { msg: t('user.upgrade_pwd.confirm_pwd') })"
type="password"
show-password
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button secondary @click="closeHandler">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ t('common.save') }}</el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="less" scoped>
.initial-pwd-tips {
margin-bottom: 16px;
color: var(--ed-color-warning, #e6a23c);
font-size: 14px;
line-height: 22px;
}
</style>
+16 -1
View File
@@ -71,6 +71,12 @@
jsname="L2NvbXBvbmVudC9sb2dpbi9IYW5kbGVy"
@switch-tab="switchTab"
/>
<InitialPwdDialog
v-model="initPwdDialogVisible"
:account="loginForm.username"
:old-pwd="loginForm.password"
@pwd-saved="onInitPwdSaved"
/>
</div>
</div>
</div>
@@ -88,6 +94,7 @@ import login_image from '@/assets/embedded/login_image.png'
import { useAppearanceStoreWithOut } from '@/stores/appearance'
import loginImage from '@/assets/blue/login-image_blue.png'
import Handler from './xpack/Handler.vue'
import InitialPwdDialog from './InitialPwdDialog.vue'
import { toLoginSuccess } from '@/utils/utils'
import elementResizeDetectorMaker from 'element-resize-detector'
@@ -134,12 +141,20 @@ const loginFormRef = ref()
const submitForm = () => {
loginFormRef.value.validate((valid: boolean) => {
if (valid) {
userStore.login(loginForm.value).then(() => {
userStore.login(loginForm.value).then((res: any) => {
if (res?.need_change_pwd) {
initPwdDialogVisible.value = true
return
}
toLoginSuccess(router)
})
}
})
}
const initPwdDialogVisible = ref(false)
const onInitPwdSaved = (newPwd: string) => {
loginForm.value.password = newPwd
}
const switchTab = (name: string) => {
activeName.value = name || 'simple'
}
@@ -63,11 +63,11 @@
</div>
</div>
<div v-if="anyPlatformEnable" class="card">
<div v-if="xpackValid" class="card">
<div class="card-title">
{{ t('parameter.login_settings') }}
</div>
<div class="card-item" style="width: 100%">
<div v-if="anyPlatformEnable" class="card-item" style="width: 100%">
<div class="label">
{{ t('parameter.default_login') }}
</div>
@@ -79,6 +79,20 @@
</el-radio-group>
</div>
</div>
<div class="card-item" style="width: 100%">
<div class="label">{{ t('parameter.disable_initial_password') }}</div>
<div class="value">
<el-switch v-model="formData['login.initial_pwd_disabled']" />
</div>
</div>
<div class="card-item" style="width: 100%">
<div class="label">{{ t('parameter.hide_initial_password') }}</div>
<div class="value">
<el-switch v-model="formData['login.initial_pwd_hidden']" />
</div>
</div>
</div>
</template>
@@ -97,6 +111,8 @@ const defaultForm = reactive<Record<string, any>>({
'platform.oid': 1,
'platform.rid': 1,
'login.default_login': 0,
'login.initial_pwd_disabled': false,
'login.initial_pwd_hidden': false,
})
const loginTypeOptions = shallowRef<any[]>([{ value: 0, label: t('login.account_login') }])
+19 -2
View File
@@ -203,7 +203,7 @@
</span>
<span class="header-span">{{ t('datasource.the_original_one') }}</span>
</div>
<div class="confirm-content">
<div v-if="showInitPwd" class="confirm-content">
<span>{{ defaultPwd }}</span>
<el-button style="margin-left: 4px" text @click="copyText">{{
t('datasource.copy')
@@ -284,7 +284,7 @@
size="600px"
:before-close="onFormClose"
>
<div style="margin-bottom: 12px" class="down-template">
<div v-if="showInitPwd" style="margin-bottom: 12px" class="down-template">
<span class="icon-span">
<el-icon>
<Icon name="icon_warning_filled"><icon_warning_filled class="svg-icon" /></Icon>
@@ -584,6 +584,7 @@ import { request } from '@/utils/request'
import { workspaceList } from '@/api/workspace'
import { variablesApi } from '@/api/variables'
import { formatTimestamp } from '@/utils/date'
import { formatArg } from '@/utils/utils'
import { ClickOutside as vClickOutside } from 'element-plus-secondary'
import icon_warning_filled from '@/assets/svg/icon_warning_filled.svg'
import { useClipboard } from '@vueuse/core'
@@ -1291,6 +1292,21 @@ const loadDefaultPwd = () => {
}
})
}
const showInitPwd = ref(true)
const loadInitPwdHidden = () => {
request
.get('/system/parameter/login')
.then((res: any) => {
const argObj: Record<string, string> = {}
;(res || []).forEach((item: any) => {
argObj[item.pkey] = item.pval
})
showInitPwd.value = !formatArg(argObj['login.initial_pwd_hidden'])
})
.catch(() => {
showInitPwd.value = true
})
}
const formatUserOrigin = (origin?: number) => {
if (!origin) {
return t('user.local_creation')
@@ -1327,6 +1343,7 @@ onMounted(() => {
handleCurrentChange(1)
loadDefaultPwd()
loadInitPwdHidden()
})
const downErrorExcel = (dataKey: any) => {
userApi.errorRecord(dataKey).then((res: any) => {