feat: add master password hint functionality

- Updated user model to include masterPasswordHint.
- Modified sync handler to return masterPasswordHint.
- Implemented password hint retrieval in public API.
- Enhanced user profile management to allow updating of password hint.
- Added UI components for displaying and editing password hint.
- Updated localization files for new password hint strings.
- Improved rate limiting for sensitive public requests.
- Adjusted database schema to accommodate master password hint.
This commit is contained in:
shuaiplus
2026-03-19 00:38:56 +08:00
parent 8bc43b8f0c
commit facd0ea5f7
26 changed files with 460 additions and 26 deletions
+40
View File
@@ -13,6 +13,7 @@ interface RegisterValues {
email: string;
password: string;
password2: string;
passwordHint: string;
inviteCode: string;
}
@@ -24,6 +25,7 @@ interface AuthViewsProps {
registerValues: RegisterValues;
unlockPassword: string;
emailForLock: string;
loginHintLoading: boolean;
onChangeLogin: (next: LoginValues) => void;
onChangeRegister: (next: RegisterValues) => void;
onChangeUnlock: (password: string) => void;
@@ -33,6 +35,8 @@ interface AuthViewsProps {
onGotoLogin: () => void;
onGotoRegister: () => void;
onLogout: () => void;
onTogglePasswordHint: () => void;
onShowLockedPasswordHint: () => void;
}
function PasswordField(props: {
@@ -87,6 +91,17 @@ export default function AuthViews(props: AuthViewsProps) {
autoComplete="current-password"
onInput={props.onChangeUnlock}
/>
<div className="auth-support-row">
<span />
<button
type="button"
className="auth-link-btn"
onClick={props.onShowLockedPasswordHint}
disabled={unlockBusy}
>
{t('txt_show_password_hint')}
</button>
</div>
<button type="submit" className="btn btn-primary full" disabled={unlockBusy || !props.unlockReady}>
<Unlock size={16} className="btn-icon" />
{unlockBusy ? t('txt_unlocking') : t('txt_unlock')}
@@ -147,6 +162,18 @@ export default function AuthViews(props: AuthViewsProps) {
autoComplete="new-password"
onInput={(v) => props.onChangeRegister({ ...props.registerValues, password2: v })}
/>
<label className="field">
<span>{t('txt_password_hint_optional')}</span>
<input
className="input"
maxLength={120}
value={props.registerValues.passwordHint}
placeholder={t('txt_password_hint_register_placeholder')}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, passwordHint: (e.currentTarget as HTMLInputElement).value })
}
/>
</label>
<label className="field">
<span>{t('txt_invite_code_optional')}</span>
<input
@@ -199,6 +226,19 @@ export default function AuthViews(props: AuthViewsProps) {
onInput={(v) => props.onChangeLogin({ ...props.loginValues, password: v })}
autoFocus
/>
<div className="auth-support-row">
<span />
<button
type="button"
className="auth-link-btn"
onClick={props.onTogglePasswordHint}
disabled={loginBusy || !props.loginValues.email.trim()}
>
{props.loginHintLoading
? t('txt_loading_password_hint')
: t('txt_show_password_hint')}
</button>
</div>
<button type="submit" className="btn btn-primary full" disabled={loginBusy}>
<LogIn size={16} className="btn-icon" />
{loginBusy ? t('txt_logging_in') : t('txt_log_in')}