Skip to content

Commit 4ee923c

Browse files
Add config to hide default passwords or force password change at first login #1355
1 parent c1d333e commit 4ee923c

18 files changed

Lines changed: 319 additions & 22 deletions

File tree

backend/apps/system/api/login.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@
22
from fastapi import APIRouter, Depends, HTTPException, Request
33
from fastapi.security import OAuth2PasswordRequestForm
44
from apps.system.schemas.logout_schema import LogoutSchema
5-
from apps.system.schemas.system_schema import BaseUserDTO
5+
from apps.system.schemas.system_schema import BaseUserDTO, LoginPwdEditor
66
from common.core.deps import SessionDep, Trans
77
from common.utils.crypto import sqlbot_decrypt
8-
from ..crud.user import authenticate
9-
from common.core.security import create_access_token
8+
from ..crud.user import authenticate, check_pwd_format, clean_user_cache, get_db_user
9+
from common.core.security import create_access_token, default_pwd, md5pwd, verify_md5pwd
1010
from datetime import timedelta
1111
from common.core.config import settings
1212
from common.core.schemas import Token
1313
from sqlbot_xpack.authentication.manage import logout as xpack_logout
14+
from sqlbot_xpack.config.arg_manage import get_group_args
1415

1516
from common.audit.models.log_model import OperationType, OperationModules
1617
from common.audit.schemas.logger_decorator import system_log, LogConfig
1718

1819
router = APIRouter(tags=["login"], prefix="/login")
1920

21+
22+
async def initial_pwd_disabled(session) -> bool:
23+
login_args = await get_group_args(session=session, flag='login')
24+
disabled_arg = next((a for a in login_args if a.pkey == 'login.initial_pwd_disabled'), None)
25+
return bool(disabled_arg and str(disabled_arg.pval).strip().lower() == 'true')
26+
27+
2028
@router.post("/access-token")
2129
@system_log(LogConfig(
2230
operation_type=OperationType.LOGIN,
@@ -39,13 +47,46 @@ async def local_login(
3947
raise HTTPException(status_code=400, detail=trans('i18n_login.user_disable', msg = trans('i18n_concat_admin')))
4048
if user.origin is not None and user.origin != 0:
4149
raise HTTPException(status_code=400, detail=trans('i18n_login.origin_error'))
50+
if (
51+
user.id != 1
52+
and await initial_pwd_disabled(session)
53+
and verify_md5pwd(default_pwd(), user.password)
54+
):
55+
return Token(access_token='', need_change_pwd=True)
4256
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
4357
user_dict = user.to_dict()
44-
return Token(access_token=create_access_token(
45-
user_dict, expires_delta=access_token_expires
46-
))
58+
return Token(
59+
access_token=create_access_token(user_dict, expires_delta=access_token_expires),
60+
need_change_pwd=False,
61+
)
62+
63+
64+
@router.post("/change-pwd")
65+
@system_log(LogConfig(
66+
operation_type=OperationType.UPDATE_PWD,
67+
module=OperationModules.USER,
68+
result_id_expr="id"
69+
))
70+
async def login_change_pwd(session: SessionDep, trans: Trans, editor: LoginPwdEditor):
71+
origin_account = await sqlbot_decrypt(editor.account)
72+
origin_pwd = await sqlbot_decrypt(editor.pwd)
73+
new_pwd = await sqlbot_decrypt(editor.new_pwd)
74+
user: BaseUserDTO = authenticate(session=session, account=origin_account, password=origin_pwd)
75+
if not user:
76+
raise HTTPException(status_code=400, detail=trans('i18n_login.account_pwd_error'))
77+
if user.origin is not None and user.origin != 0:
78+
raise HTTPException(status_code=400, detail=trans('i18n_login.origin_error'))
79+
if not check_pwd_format(new_pwd):
80+
raise HTTPException(status_code=400, detail=trans('i18n_format_invalid', key = trans('i18n_user.password')))
81+
if await initial_pwd_disabled(session) and new_pwd == default_pwd():
82+
raise HTTPException(status_code=400, detail=trans('i18n_login.new_pwd_is_initial'))
83+
db_user = get_db_user(session=session, user_id=user.id)
84+
db_user.password = md5pwd(new_pwd)
85+
session.add(db_user)
86+
await clean_user_cache(user.id)
87+
return db_user
4788

48-
@router.post("/logout")
89+
@router.post("/logout")
4990
async def logout(session: SessionDep, request: Request, dto: LogoutSchema):
5091
if dto.origin != 0:
5192
return await xpack_logout(session, request, dto)

backend/apps/system/api/user.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from common.core.sqlbot_cache import clear_cache
1919
from common.core.config import settings
2020
from apps.swagger.i18n import PLACEHOLDER_PREFIX
21+
from sqlbot_xpack.config.arg_manage import get_group_args
2122

2223
router = APIRouter(tags=["system_user"], prefix="/user")
2324

@@ -45,7 +46,11 @@ async def user_info(current_user: CurrentUser) -> UserInfoDTO:
4546

4647
@router.get("/defaultPwd", include_in_schema=False)
4748
@require_permissions(permission=SqlbotPermission(role=['admin']))
48-
async def default_pwd() -> str:
49+
async def default_pwd(session: SessionDep) -> str:
50+
login_args = await get_group_args(session=session, flag='login')
51+
hidden_arg = next((a for a in login_args if a.pkey == 'login.initial_pwd_hidden'), None)
52+
if hidden_arg and str(hidden_arg.pval).strip().lower() == 'true':
53+
return ''
4954
return settings.DEFAULT_PWD
5055

5156
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[UserGrid], summary=f"{PLACEHOLDER_PREFIX}system_user_grid", description=f"{PLACEHOLDER_PREFIX}system_user_grid")

backend/apps/system/schemas/system_schema.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ class PwdEditor(BaseModel):
8383
new_pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}new_pwd")
8484

8585

86+
class LoginPwdEditor(BaseModel):
87+
account: str = Field(description=f"{PLACEHOLDER_PREFIX}user_account")
88+
pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}origin_pwd")
89+
new_pwd: str = Field(description=f"{PLACEHOLDER_PREFIX}new_pwd")
90+
91+
8692
class UserWsBase(BaseModel):
8793
uid_list: list[int] = Field(description=f"{PLACEHOLDER_PREFIX}uid")
8894
oid: Optional[int] = Field(default=None, description=f"{PLACEHOLDER_PREFIX}oid")

backend/common/core/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Token(SQLModel):
1616
access_token: str
1717
token_type: str = "bearer"
1818
platform_info: Optional[dict] = None
19+
need_change_pwd: Optional[bool] = None
1920

2021
class XOAuth2PasswordBearer(OAuth2PasswordBearer):
2122
async def __call__(self, request: Request) -> Optional[str]:

backend/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"user_disable": "Account is disabled, {msg}",
1515
"origin_error": "Invalid login method",
1616
"prohibit_auto_create": "Automatically creating users is prohibited. Please synchronize users first",
17-
"no_platform_user": "Account does not exist. Please sync the user first."
17+
"no_platform_user": "Account does not exist. Please sync the user first.",
18+
"new_pwd_is_initial": "The new password cannot be the initial password"
1819
},
1920
"i18n_user": {
2021
"account": "Account",

backend/locales/ko-KR.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"user_disable": "계정이 비활성화되었습니다, {msg}",
1515
"origin_error": "잘못된 로그인 방식입니다",
1616
"prohibit_auto_create": "사용자 자동 생성이 금지되어 있습니다. 먼저 사용자를 동기화해 주세요",
17-
"no_platform_user": "계정이 존재하지 않습니다. 먼저 사용자를 동기화하세요."
17+
"no_platform_user": "계정이 존재하지 않습니다. 먼저 사용자를 동기화하세요.",
18+
"new_pwd_is_initial": "새 비밀번호는 초기 비밀번호로 설정할 수 없습니다"
1819
},
1920
"i18n_user": {
2021
"account": "계정",

backend/locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"user_disable": "账号已禁用,{msg}",
1515
"origin_error": "登录方式错误",
1616
"prohibit_auto_create": "禁止自动创建用户,请先同步用户",
17-
"no_platform_user": "账号不存在,请先同步用户"
17+
"no_platform_user": "账号不存在,请先同步用户",
18+
"new_pwd_is_initial": "新密码不能为初始密码"
1819
},
1920
"i18n_user": {
2021
"account": "账号",

backend/locales/zh-TW.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"user_disable": "帳號已禁用,{msg}",
1515
"origin_error": "登入方式錯誤",
1616
"prohibit_auto_create": "禁止自動建立用戶,請先同步用戶",
17-
"no_platform_user": "帳號不存在,請先同步用戶"
17+
"no_platform_user": "帳號不存在,請先同步用戶",
18+
"new_pwd_is_initial": "新密碼不能為初始密碼"
1819
},
1920
"i18n_user": {
2021
"account": "帳號",

frontend/src/api/login.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export const AuthApi = {
1414
},
1515
})
1616
},
17+
changePwd: (data: { account: string; pwd: string; new_pwd: string }) => {
18+
const entry = {
19+
account: LicenseGenerator.sqlbotEncrypt(data.account),
20+
pwd: LicenseGenerator.sqlbotEncrypt(data.pwd),
21+
new_pwd: LicenseGenerator.sqlbotEncrypt(data.new_pwd),
22+
}
23+
return request.post('/login/change-pwd', entry)
24+
},
1725
logout: (data: any) => request.post('/login/logout', data),
1826
info: () => request.get('/user/info'),
1927
}

frontend/src/i18n/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@
9696
"closed_by_default": "In the Question Count window, control whether the model thinking process is expanded or closed by default.",
9797
"and_platform_integration": "Authentication-related",
9898
"login_settings": "Login Settings",
99-
"default_login": "Default Login Method"
99+
"default_login": "Default Login Method",
100+
"disable_initial_password": "Disable Initial Password",
101+
"hide_initial_password": "Hide Initial Password on Page"
100102
},
101103
"prompt": {
102104
"default_password": "Default password:{msg}",
@@ -932,6 +934,9 @@
932934
"default_login": "Default",
933935
"ldap_login": "LDAP Login",
934936
"account_login": "Account Login",
937+
"force_change_pwd_title": "Change Initial Password",
938+
"force_change_pwd_tips": "Your current password is the system initial password. Please change it and log in again.",
939+
"force_change_pwd_success": "Password changed successfully",
935940
"other_login": "Other Login Methods",
936941
"pwd_invalid_error": "Password has expired, please contact administrator to modify or reset",
937942
"pwd_exp_tips": "Password will expire in {0} days, please change it as soon as possible",

0 commit comments

Comments
 (0)