diff --git a/backend/apps/system/api/login.py b/backend/apps/system/api/login.py
index 3697cf58c..41a87a5e7 100644
--- a/backend/apps/system/api/login.py
+++ b/backend/apps/system/api/login.py
@@ -2,21 +2,29 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.security import OAuth2PasswordRequestForm
from apps.system.schemas.logout_schema import LogoutSchema
-from apps.system.schemas.system_schema import BaseUserDTO
+from apps.system.schemas.system_schema import BaseUserDTO, LoginPwdEditor
from common.core.deps import SessionDep, Trans
from common.utils.crypto import sqlbot_decrypt
-from ..crud.user import authenticate
-from common.core.security import create_access_token
+from ..crud.user import authenticate, check_pwd_format, clean_user_cache, get_db_user
+from common.core.security import create_access_token, default_pwd, md5pwd, verify_md5pwd
from datetime import timedelta
from common.core.config import settings
from common.core.schemas import Token
from sqlbot_xpack.authentication.manage import logout as xpack_logout
+from sqlbot_xpack.config.arg_manage import get_group_args
from common.audit.models.log_model import OperationType, OperationModules
from common.audit.schemas.logger_decorator import system_log, LogConfig
router = APIRouter(tags=["login"], prefix="/login")
+
+async def initial_pwd_disabled(session) -> bool:
+ login_args = await get_group_args(session=session, flag='login')
+ disabled_arg = next((a for a in login_args if a.pkey == 'login.initial_pwd_disabled'), None)
+ return bool(disabled_arg and str(disabled_arg.pval).strip().lower() == 'true')
+
+
@router.post("/access-token")
@system_log(LogConfig(
operation_type=OperationType.LOGIN,
@@ -39,13 +47,46 @@ async def local_login(
raise HTTPException(status_code=400, detail=trans('i18n_login.user_disable', msg = trans('i18n_concat_admin')))
if user.origin is not None and user.origin != 0:
raise HTTPException(status_code=400, detail=trans('i18n_login.origin_error'))
+ if (
+ user.id != 1
+ and await initial_pwd_disabled(session)
+ and verify_md5pwd(default_pwd(), user.password)
+ ):
+ return Token(access_token='', need_change_pwd=True)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
user_dict = user.to_dict()
- return Token(access_token=create_access_token(
- user_dict, expires_delta=access_token_expires
- ))
+ return Token(
+ access_token=create_access_token(user_dict, expires_delta=access_token_expires),
+ need_change_pwd=False,
+ )
+
+
+@router.post("/change-pwd")
+@system_log(LogConfig(
+ operation_type=OperationType.UPDATE_PWD,
+ module=OperationModules.USER,
+ result_id_expr="id"
+))
+async def login_change_pwd(session: SessionDep, trans: Trans, editor: LoginPwdEditor):
+ origin_account = await sqlbot_decrypt(editor.account)
+ origin_pwd = await sqlbot_decrypt(editor.pwd)
+ new_pwd = await sqlbot_decrypt(editor.new_pwd)
+ user: BaseUserDTO = authenticate(session=session, account=origin_account, password=origin_pwd)
+ if not user:
+ raise HTTPException(status_code=400, detail=trans('i18n_login.account_pwd_error'))
+ if user.origin is not None and user.origin != 0:
+ raise HTTPException(status_code=400, detail=trans('i18n_login.origin_error'))
+ if not check_pwd_format(new_pwd):
+ raise HTTPException(status_code=400, detail=trans('i18n_format_invalid', key = trans('i18n_user.password')))
+ if await initial_pwd_disabled(session) and new_pwd == default_pwd():
+ raise HTTPException(status_code=400, detail=trans('i18n_login.new_pwd_is_initial'))
+ db_user = get_db_user(session=session, user_id=user.id)
+ db_user.password = md5pwd(new_pwd)
+ session.add(db_user)
+ await clean_user_cache(user.id)
+ return db_user
-@router.post("/logout")
+@router.post("/logout")
async def logout(session: SessionDep, request: Request, dto: LogoutSchema):
if dto.origin != 0:
return await xpack_logout(session, request, dto)
diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py
index 0386f3a1c..d383fe597 100644
--- a/backend/apps/system/api/user.py
+++ b/backend/apps/system/api/user.py
@@ -18,6 +18,7 @@
from common.core.sqlbot_cache import clear_cache
from common.core.config import settings
from apps.swagger.i18n import PLACEHOLDER_PREFIX
+from sqlbot_xpack.config.arg_manage import get_group_args
router = APIRouter(tags=["system_user"], prefix="/user")
@@ -45,7 +46,11 @@ async def user_info(current_user: CurrentUser) -> UserInfoDTO:
@router.get("/defaultPwd", include_in_schema=False)
@require_permissions(permission=SqlbotPermission(role=['admin']))
-async def default_pwd() -> str:
+async def default_pwd(session: SessionDep) -> str:
+ login_args = await get_group_args(session=session, flag='login')
+ hidden_arg = next((a for a in login_args if a.pkey == 'login.initial_pwd_hidden'), None)
+ if hidden_arg and str(hidden_arg.pval).strip().lower() == 'true':
+ return ''
return settings.DEFAULT_PWD
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[UserGrid], summary=f"{PLACEHOLDER_PREFIX}system_user_grid", description=f"{PLACEHOLDER_PREFIX}system_user_grid")
diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py
index db2f255b4..1e75bdfbc 100644
--- a/backend/apps/system/schemas/system_schema.py
+++ b/backend/apps/system/schemas/system_schema.py
@@ -83,6 +83,12 @@ class PwdEditor(BaseModel):
new_pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}new_pwd")
+class LoginPwdEditor(BaseModel):
+ account: str = Field(description=f"{PLACEHOLDER_PREFIX}user_account")
+ pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}origin_pwd")
+ new_pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}new_pwd")
+
+
class UserWsBase(BaseModel):
uid_list: list[int] = Field(description=f"{PLACEHOLDER_PREFIX}uid")
oid: Optional[int] = Field(default=None, description=f"{PLACEHOLDER_PREFIX}oid")
diff --git a/backend/common/core/schemas.py b/backend/common/core/schemas.py
index 04c3d5eaa..2c60fa0e9 100644
--- a/backend/common/core/schemas.py
+++ b/backend/common/core/schemas.py
@@ -16,6 +16,7 @@ class Token(SQLModel):
access_token: str
token_type: str = "bearer"
platform_info: Optional[dict] = None
+ need_change_pwd: Optional[bool] = None
class XOAuth2PasswordBearer(OAuth2PasswordBearer):
async def __call__(self, request: Request) -> Optional[str]:
diff --git a/backend/locales/en.json b/backend/locales/en.json
index 43db335b8..08ab4bcca 100644
--- a/backend/locales/en.json
+++ b/backend/locales/en.json
@@ -14,7 +14,8 @@
"user_disable": "Account is disabled, {msg}",
"origin_error": "Invalid login method",
"prohibit_auto_create": "Automatically creating users is prohibited. Please synchronize users first",
- "no_platform_user": "Account does not exist. Please sync the user first."
+ "no_platform_user": "Account does not exist. Please sync the user first.",
+ "new_pwd_is_initial": "The new password cannot be the initial password"
},
"i18n_user": {
"account": "Account",
diff --git a/backend/locales/ko-KR.json b/backend/locales/ko-KR.json
index 186c2a4d6..d12b70d31 100644
--- a/backend/locales/ko-KR.json
+++ b/backend/locales/ko-KR.json
@@ -14,7 +14,8 @@
"user_disable": "계정이 비활성화되었습니다, {msg}",
"origin_error": "잘못된 로그인 방식입니다",
"prohibit_auto_create": "사용자 자동 생성이 금지되어 있습니다. 먼저 사용자를 동기화해 주세요",
- "no_platform_user": "계정이 존재하지 않습니다. 먼저 사용자를 동기화하세요."
+ "no_platform_user": "계정이 존재하지 않습니다. 먼저 사용자를 동기화하세요.",
+ "new_pwd_is_initial": "새 비밀번호는 초기 비밀번호로 설정할 수 없습니다"
},
"i18n_user": {
"account": "계정",
diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json
index 788e911e7..2fa8744cf 100644
--- a/backend/locales/zh-CN.json
+++ b/backend/locales/zh-CN.json
@@ -14,7 +14,8 @@
"user_disable": "账号已禁用,{msg}",
"origin_error": "登录方式错误",
"prohibit_auto_create": "禁止自动创建用户,请先同步用户",
- "no_platform_user": "账号不存在,请先同步用户"
+ "no_platform_user": "账号不存在,请先同步用户",
+ "new_pwd_is_initial": "新密码不能为初始密码"
},
"i18n_user": {
"account": "账号",
diff --git a/backend/locales/zh-TW.json b/backend/locales/zh-TW.json
index 6b37aca9f..3a5358ab5 100644
--- a/backend/locales/zh-TW.json
+++ b/backend/locales/zh-TW.json
@@ -14,7 +14,8 @@
"user_disable": "帳號已禁用,{msg}",
"origin_error": "登入方式錯誤",
"prohibit_auto_create": "禁止自動建立用戶,請先同步用戶",
- "no_platform_user": "帳號不存在,請先同步用戶"
+ "no_platform_user": "帳號不存在,請先同步用戶",
+ "new_pwd_is_initial": "新密碼不能為初始密碼"
},
"i18n_user": {
"account": "帳號",
diff --git a/frontend/src/api/login.ts b/frontend/src/api/login.ts
index 7dfdb0ec4..72ae72c3d 100644
--- a/frontend/src/api/login.ts
+++ b/frontend/src/api/login.ts
@@ -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'),
}
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
index 3680a773d..430fa369c 100644
--- a/frontend/src/i18n/en.json
+++ b/frontend/src/i18n/en.json
@@ -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",
diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json
index 332e303f6..850102f9b 100644
--- a/frontend/src/i18n/ko-KR.json
+++ b/frontend/src/i18n/ko-KR.json
@@ -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}일 후에 만료됩니다.尽快尽快 비밀번호를 변경해 주세요",
diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json
index 06697bd26..30cea8f2d 100644
--- a/frontend/src/i18n/zh-CN.json
+++ b/frontend/src/i18n/zh-CN.json
@@ -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} 天后过期,请尽快修改密码",
diff --git a/frontend/src/i18n/zh-TW.json b/frontend/src/i18n/zh-TW.json
index 86991ae44..295d2d016 100644
--- a/frontend/src/i18n/zh-TW.json
+++ b/frontend/src/i18n/zh-TW.json
@@ -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} 天後過期,請盡快修改密碼",
diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts
index b72e061d7..30ba4f3a8 100644
--- a/frontend/src/stores/user.ts
+++ b/frontend/src/stores/user.ts
@@ -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() {
diff --git a/frontend/src/views/login/InitialPwdDialog.vue b/frontend/src/views/login/InitialPwdDialog.vue
new file mode 100644
index 000000000..af0b95360
--- /dev/null
+++ b/frontend/src/views/login/InitialPwdDialog.vue
@@ -0,0 +1,161 @@
+
+
+
+