feat(enterprise): add first-login rotation and receipt bridge

This commit is contained in:
冰朔 2026-08-16 20:49:13 +08:00
commit 08f514b9d1
9 changed files with 322 additions and 4 deletions

View file

@ -219,6 +219,9 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
if (value.includes('LOGIN_KEYCHAIN_FAILED')) return '本机钥匙串写入失败,凭证未能安全保存。'
if (value.includes('LOGIN_SESSION_CORRUPT')) return '登录会话已损坏,请重新登录。'
if (value.includes('LOGIN_VERIFICATION_FAILED')) return '仓库验证未通过,请稍后再试。'
if (value.includes('NEW_PASSWORD_POLICY_INVALID')) return '新密码至少 14 位,并且不能与一次性密码相同。'
if (value.includes('FIRST_LOGIN_PASSWORD_CHANGE_FAILED')) return '一次性密码未被接受,密码没有修改。'
if (value.includes('FIRST_LOGIN_PASSWORD_CHANGE_ENTERPRISE_ONLY')) return '该换密入口只用于企业四域的新账号。'
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
if (value.includes('USER_PNCC_TRUSTED_SUBJECT_REQUIRED')) return '建立人格代码频道前,需要有效的用户编号与仓库账号登录。'
@ -349,6 +352,9 @@ function HoloLakeApp() {
const [userPnccMessage, setUserPnccMessage] = useState('')
const [loginUsername, setLoginUsername] = useState('')
const [loginPassword, setLoginPassword] = useState('')
const [passwordChangeMode, setPasswordChangeMode] = useState(false)
const [newLoginPassword, setNewLoginPassword] = useState('')
const [confirmLoginPassword, setConfirmLoginPassword] = useState('')
const [loginBusy, setLoginBusy] = useState(false)
const [loginRising, setLoginRising] = useState(false)
const [loginMessage, setLoginMessage] = useState('')
@ -733,6 +739,28 @@ function HoloLakeApp() {
} catch (error) { setLoginMessage(humanError(error, 'login')) }
finally { setLoginBusy(false) }
}
const changeFirstLoginPassword = async (event: React.FormEvent) => {
event.preventDefault()
if (newLoginPassword !== confirmLoginPassword) {
setLoginMessage('两次输入的新密码不一致。')
return
}
setLoginBusy(true)
setLoginMessage('')
try {
await invoke('change_enterprise_first_login_password', {
username: loginUsername.trim(),
currentPassword: loginPassword,
newPassword: newLoginPassword,
})
setLoginPassword(newLoginPassword)
setNewLoginPassword('')
setConfirmLoginPassword('')
setPasswordChangeMode(false)
setLoginMessage('密码已更新。请用新密码验证并进入。')
} catch (error) { setLoginMessage(humanError(error, 'login')) }
finally { setLoginBusy(false) }
}
const signOutRepo = async () => {
try { await invoke('sign_out_code_repo_login') } catch { /* 登出以本机清场为准 */ }
setPersonal(previewPersonal)
@ -1038,12 +1066,17 @@ function HoloLakeApp() {
</section>
) : (
<section className={`gate-pod gate-pod-key${loginRising ? ' fade-out' : ''}`} role="dialog">
<form onSubmit={(event) => void performRepoLogin(event)}>
<form onSubmit={(event) => void (passwordChangeMode ? changeFirstLoginPassword(event) : performRepoLogin(event))}>
<input id="repo-login-username" aria-label="账号" autoFocus maxLength={40} value={loginUsername} placeholder="账号" onChange={(event) => setLoginUsername(event.target.value)}/>
<input id="repo-login-password" aria-label="密码" type="password" maxLength={512} value={loginPassword} placeholder="密码" onChange={(event) => setLoginPassword(event.target.value)}/>
<button className="gate-submit" disabled={loginBusy || !loginUsername.trim() || !loginPassword}>{loginBusy ? '正在验证…' : '验证并进入'}</button>
<input id="repo-login-password" aria-label={passwordChangeMode ? '一次性密码' : '密码'} type="password" maxLength={512} value={loginPassword} placeholder={passwordChangeMode ? '一次性密码' : '密码'} onChange={(event) => setLoginPassword(event.target.value)}/>
{passwordChangeMode && <>
<input aria-label="新密码" type="password" minLength={14} maxLength={128} value={newLoginPassword} placeholder="设置新密码(至少 14 位)" onChange={(event) => setNewLoginPassword(event.target.value)}/>
<input aria-label="确认新密码" type="password" minLength={14} maxLength={128} value={confirmLoginPassword} placeholder="再次输入新密码" onChange={(event) => setConfirmLoginPassword(event.target.value)}/>
</>}
<button className="gate-submit" disabled={loginBusy || !loginUsername.trim() || !loginPassword || (passwordChangeMode && (!newLoginPassword || !confirmLoginPassword))}>{loginBusy ? '正在处理…' : passwordChangeMode ? '修改密码' : '验证并进入'}</button>
</form>
{loginMessage && <p className="gate-hint">{loginMessage}</p>}
{zeroPoint?.resolvedDomain !== 'FIFTH_DOMAIN' && <button className="gate-back" type="button" onClick={() => { setPasswordChangeMode((value) => !value); setLoginMessage(''); setNewLoginPassword(''); setConfirmLoginPassword('') }}>{passwordChangeMode ? '返回正常登录' : '第一次使用?先修改一次性密码'}</button>}
<button className="gate-back" type="button" onClick={() => { setGateStage('number'); setGateMessage(''); setLoginMessage('') }}></button>
</section>
)}