-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat: Support mark initial password & force users to change #2003
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…irst login - Add `is_initial_password` field in database to track whether the user's password is an admin-assigned initial password - Update backend User model and business logic for the new field - Enforce password change for users logging in with the initial password, prompting a reset on first login - Update frontend login and registration logic to handle and guide initial password users, including modal/prompt for password update - Update i18n files to include relevant message strings (English, Chinese, French) - Ensure backward compatibility for existing users This enhances security by preventing long-term usage of default/admin-set passwords. closes QuantumNous#1985
WalkthroughAdds an initial-password workflow: backend introduces is_initial_password flag, updates login to require password change when true, provides a new ChangeInitialPassword API, and adjusts user creation/update semantics. Router exposes the new endpoint. Frontend login detects require_change_password, shows a modal to change password, and locales gain related strings. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User (Client)
participant FE as Login UI
participant API as Server API
participant DB as Database
U->>FE: Submit username/password
FE->>API: POST /api/user/login
API->>DB: Verify user + password
alt is_initial_password = true
API-->>FE: { require_change_password: true }
FE->>U: Show "Change Initial Password" modal
U->>FE: Enter old/new/confirm
FE->>API: POST /api/user/change_initial_password
API->>DB: Validate old pw, update hash, set is_initial_password=false
API-->>FE: { success: true }
FE->>API: POST /api/user/login (with new password)
API-->>FE: { token / session }
FE-->>U: Login success
else normal login
API-->>FE: { token / session }
FE-->>U: Login success
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
model/user.go (1)
590-600
: Clear initial-password flag on email resets to avoid forced-change loop.Users resetting via email should no longer be marked initial password.
- err = DB.Model(&User{}).Where("email = ?", email).Update("password", hashedPassword).Error + err = DB.Model(&User{}). + Where("email = ?", email). + Updates(map[string]interface{}{ + "password": hashedPassword, + "is_initial_password": false, + }).Error
🧹 Nitpick comments (5)
model/user.go (1)
20-49
: Harden schema for IsInitialPassword (add NOT NULL).Current tag lacks NOT NULL. Recommend
not null
to avoid tri-state and align with migration.- IsInitialPassword bool `json:"is_initial_password" gorm:"type:boolean;default:false;column:is_initial_password"` // 是否是初始密码 + IsInitialPassword bool `json:"is_initial_password" gorm:"type:boolean;default:false;not null;column:is_initial_password"` // 是否是初始密码bin/migration_add_initial_password.sql (2)
4-11
: Enforce NOT NULL on is_initial_password across DBs.Avoid NULL booleans; use NOT NULL defaults for all engines.
-- For SQLite -ALTER TABLE users ADD COLUMN is_initial_password INTEGER DEFAULT 0; +ALTER TABLE users ADD COLUMN is_initial_password INTEGER NOT NULL DEFAULT 0; -- For MySQL (uncomment if using MySQL) --- ALTER TABLE users ADD COLUMN is_initial_password BOOLEAN DEFAULT FALSE; +-- ALTER TABLE users ADD COLUMN is_initial_password BOOLEAN NOT NULL DEFAULT FALSE; -- For PostgreSQL (uncomment if using PostgreSQL) --- ALTER TABLE users ADD COLUMN is_initial_password BOOLEAN DEFAULT FALSE; +-- ALTER TABLE users ADD COLUMN is_initial_password BOOLEAN NOT NULL DEFAULT FALSE;
13-15
: Optional: retain UPDATE for SQLite; not needed if NOT NULL DEFAULT used.If you adopt NOT NULL DEFAULT, this UPDATE becomes redundant for new migrations but is harmless.
web/src/i18n/locales/en.json (1)
2283-2298
: Avoid duplicate i18n keys (“Cancel”, “New Password”, etc.).These keys already exist earlier; duplicates make maintenance error‑prone. Reuse existing keys or scope under a namespace.
web/src/i18n/locales/zh.json (1)
136-151
: 去重重复的翻译键(如“取消”、“新密码”、“确认密码”)。文件中已存在同名键,建议复用或按模块命名避免重复,提高可维护性。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
bin/migration_add_initial_password.sql
(1 hunks)controller/user.go
(6 hunks)model/user.go
(2 hunks)router/api-router.go
(1 hunks)web/src/components/auth/LoginForm.jsx
(7 hunks)web/src/i18n/locales/en.json
(1 hunks)web/src/i18n/locales/fr.json
(1 hunks)web/src/i18n/locales/zh.json
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
web/src/components/auth/LoginForm.jsx (2)
web/src/components/settings/PersonalSetting.jsx (1)
showChangePasswordModal
(62-62)web/src/helpers/utils.jsx (2)
showError
(122-151)showSuccess
(157-159)
router/api-router.go (1)
controller/user.go (1)
ChangeInitialPassword
(1153-1249)
controller/user.go (5)
common/constants.go (1)
RoleCommonUser
(135-135)common/gin.go (1)
ApiError
(96-101)model/main.go (1)
DB
(63-63)model/user.go (2)
User
(19-49)GetUserById
(288-300)model/log.go (1)
RecordLog
(78-94)
🔇 Additional comments (9)
model/user.go (1)
466-469
: Password update propagation looks correct.Only writes is_initial_password when password changes, preventing accidental toggles.
controller/user.go (4)
97-109
: Login gating for initial password: LGTM.Early-return with require_change_password cleanly prevents session creation.
223-229
: Registration sets non-initial password: LGTM.Self-registered users correctly start with IsInitialPassword=false.
657-660
: Admin password resets re-mark as initial: LGTM.Consistent with the intended workflow.
750-779
: Self password update clears initial flag: LGTM.Hashes server-side and sets is_initial_password=false atomically.
web/src/components/auth/LoginForm.jsx (4)
204-214
: Initial-password intercept flow on login: LGTM.Captures credentials and shows modal without creating a session.
422-432
: Cancel handler: LGTM.Preserves username, clears secrets, resets state.
900-961
: Change-password modal UX: LGTM.Clear messaging, validations, and focused inputs.
986-987
: Modal integration: LGTM.Included alongside other auth modals; ordering fine.
type ChangeInitialPasswordRequest struct { | ||
UserId int `json:"user_id"` | ||
Username string `json:"username"` | ||
OldPassword string `json:"old_password"` | ||
NewPassword string `json:"new_password"` | ||
} | ||
|
||
func ChangeInitialPassword(c *gin.Context) { | ||
var req ChangeInitialPasswordRequest | ||
err := json.NewDecoder(c.Request.Body).Decode(&req) | ||
if err != nil { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "无效的参数", | ||
}) | ||
return | ||
} | ||
|
||
if req.UserId == 0 || req.Username == "" || req.OldPassword == "" || req.NewPassword == "" { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "请提供完整的参数", | ||
}) | ||
return | ||
} | ||
|
||
// 验证新密码长度 | ||
if len(req.NewPassword) < 8 || len(req.NewPassword) > 20 { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "新密码长度必须在8-20个字符之间", | ||
}) | ||
return | ||
} | ||
|
||
// 获取用户 | ||
user, err := model.GetUserById(req.UserId, true) | ||
if err != nil { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "用户不存在", | ||
}) | ||
return | ||
} | ||
|
||
// 验证用户名匹配 | ||
if user.Username != req.Username { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "用户信息不匹配", | ||
}) | ||
return | ||
} | ||
|
||
// 验证旧密码 | ||
if !common.ValidatePasswordAndHash(req.OldPassword, user.Password) { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "原密码错误", | ||
}) | ||
return | ||
} | ||
|
||
// 验证是否确实需要修改初始密码 | ||
if !user.IsInitialPassword { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "该用户不需要修改初始密码", | ||
}) | ||
return | ||
} | ||
|
||
// 更新密码并清除初始密码标记 | ||
hashedPassword, err := common.Password2Hash(req.NewPassword) | ||
if err != nil { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "密码加密失败", | ||
}) | ||
return | ||
} | ||
|
||
// 使用 map 更新以确保 IsInitialPassword = false 能被正确写入 | ||
updates := map[string]interface{}{ | ||
"password": hashedPassword, | ||
"is_initial_password": false, | ||
} | ||
|
||
if err := model.DB.Model(&model.User{}).Where("id = ?", user.Id).Updates(updates).Error; err != nil { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, | ||
"message": "修改密码失败: " + err.Error(), | ||
}) | ||
return | ||
} | ||
|
||
// 记录日志 | ||
model.RecordLog(user.Id, model.LogTypeSystem, "修改初始密码") | ||
|
||
c.JSON(http.StatusOK, gin.H{ | ||
"success": true, | ||
"message": "密码修改成功,请重新登录", | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brute-force risk: missing Turnstile on ChangeInitialPassword.
Endpoint is unauthenticated (by design) but lacks Turnstile verification; only rate-limited. Add Turnstile middleware and require token.
Follow-ups:
- Router: add
middleware.TurnstileCheck()
to /api/user/change_initial_password. - Frontend: pass
?turnstile=${token}
and block when token missing. See suggested diffs in router and frontend comments.
🤖 Prompt for AI Agents
controller/user.go lines 1146-1249: the unauthenticated ChangeInitialPassword
handler lacks Turnstile verification; require a validated Turnstile token to
prevent brute-force. Add a check at the start of the handler to require and
validate a Turnstile token (e.g., read token from query/body/header as agreed
and call middleware/utility to verify it), return an error when missing/invalid;
do not rely on rate limit alone. Also update routing to attach
middleware.TurnstileCheck() to the /api/user/change_initial_password route and
update frontend to send the token (e.g., ?turnstile=${token}) and block submit
when token is absent.
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login) | ||
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), controller.Verify2FALogin) | ||
userRoute.POST("/change_initial_password", middleware.CriticalRateLimit(), controller.ChangeInitialPassword) | ||
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), controller.PasskeyLoginBegin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add Turnstile check to protect password-change endpoint.
Parity with login/register; mitigates brute-force attempts.
- userRoute.POST("/change_initial_password", middleware.CriticalRateLimit(), controller.ChangeInitialPassword)
+ userRoute.POST("/change_initial_password",
+ middleware.CriticalRateLimit(),
+ middleware.TurnstileCheck(),
+ controller.ChangeInitialPassword)
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In router/api-router.go around line 55, the password-change endpoint needs
Turnstile protection for parity with login/register; update the route definition
to include the Turnstile middleware (e.g. add middleware.Turnstile() in the
middleware chain before the controller handler) so requests hit the Turnstile
check prior to the password-change controller; preserve existing rate-limit or
critical middleware ordering when inserting the Turnstile middleware.
const handleChangeInitialPassword = async () => { | ||
if (!newPassword || !confirmPassword) { | ||
showError(t('请输入新密码和确认密码')); | ||
return; | ||
} | ||
|
||
if (newPassword.length < 8 || newPassword.length > 20) { | ||
showError(t('新密码长度必须在8-20个字符之间')); | ||
return; | ||
} | ||
|
||
if (newPassword !== confirmPassword) { | ||
showError(t('两次输入的密码不一致')); | ||
return; | ||
} | ||
|
||
if (newPassword === changePasswordData.old_password) { | ||
showError(t('新密码不能与原密码相同')); | ||
return; | ||
} | ||
|
||
setChangePasswordLoading(true); | ||
try { | ||
const res = await API.post('/api/user/change_initial_password', { | ||
user_id: changePasswordData.user_id, | ||
username: changePasswordData.username, | ||
old_password: changePasswordData.old_password, | ||
new_password: newPassword, | ||
}); | ||
const { success, message } = res.data; | ||
if (success) { | ||
showSuccess(t('密码修改成功,请输入新密码登录')); | ||
// 关闭修改密码对话框 | ||
setShowChangePasswordModal(false); | ||
// 保存用户名,用于重新登录 | ||
const savedUsername = changePasswordData.username; | ||
setChangePasswordData(null); | ||
// 清空修改密码表单 | ||
setNewPassword(''); | ||
setConfirmPassword(''); | ||
// 保留用户名,只清空密码字段,让用户输入新密码即可 | ||
setInputs({ username: savedUsername, password: '', wechat_verification_code: '' }); | ||
// 重置提交状态 | ||
setSubmitted(false); | ||
} else { | ||
showError(message); | ||
} | ||
} catch (error) { | ||
showError(t('密码修改失败,请重试')); | ||
} finally { | ||
setChangePasswordLoading(false); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass Turnstile token and block when missing.
Align with backend Turnstile protection; prevents automated abuse.
const handleChangeInitialPassword = async () => {
+ if (turnstileEnabled && turnstileToken === '') {
+ showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!'));
+ return;
+ }
if (!newPassword || !confirmPassword) {
showError(t('请输入新密码和确认密码'));
return;
}
@@
setChangePasswordLoading(true);
try {
- const res = await API.post('/api/user/change_initial_password', {
+ const res = await API.post(
+ `/api/user/change_initial_password?turnstile=${turnstileToken}`,
+ {
user_id: changePasswordData.user_id,
username: changePasswordData.username,
old_password: changePasswordData.old_password,
new_password: newPassword,
- });
+ }
+ );
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleChangeInitialPassword = async () => { | |
if (!newPassword || !confirmPassword) { | |
showError(t('请输入新密码和确认密码')); | |
return; | |
} | |
if (newPassword.length < 8 || newPassword.length > 20) { | |
showError(t('新密码长度必须在8-20个字符之间')); | |
return; | |
} | |
if (newPassword !== confirmPassword) { | |
showError(t('两次输入的密码不一致')); | |
return; | |
} | |
if (newPassword === changePasswordData.old_password) { | |
showError(t('新密码不能与原密码相同')); | |
return; | |
} | |
setChangePasswordLoading(true); | |
try { | |
const res = await API.post('/api/user/change_initial_password', { | |
user_id: changePasswordData.user_id, | |
username: changePasswordData.username, | |
old_password: changePasswordData.old_password, | |
new_password: newPassword, | |
}); | |
const { success, message } = res.data; | |
if (success) { | |
showSuccess(t('密码修改成功,请输入新密码登录')); | |
// 关闭修改密码对话框 | |
setShowChangePasswordModal(false); | |
// 保存用户名,用于重新登录 | |
const savedUsername = changePasswordData.username; | |
setChangePasswordData(null); | |
// 清空修改密码表单 | |
setNewPassword(''); | |
setConfirmPassword(''); | |
// 保留用户名,只清空密码字段,让用户输入新密码即可 | |
setInputs({ username: savedUsername, password: '', wechat_verification_code: '' }); | |
// 重置提交状态 | |
setSubmitted(false); | |
} else { | |
showError(message); | |
} | |
} catch (error) { | |
showError(t('密码修改失败,请重试')); | |
} finally { | |
setChangePasswordLoading(false); | |
} | |
}; | |
const handleChangeInitialPassword = async () => { | |
if (turnstileEnabled && turnstileToken === '') { | |
showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!')); | |
return; | |
} | |
if (!newPassword || !confirmPassword) { | |
showError(t('请输入新密码和确认密码')); | |
return; | |
} | |
if (newPassword.length < 8 || newPassword.length > 20) { | |
showError(t('新密码长度必须在8-20个字符之间')); | |
return; | |
} | |
if (newPassword !== confirmPassword) { | |
showError(t('两次输入的密码不一致')); | |
return; | |
} | |
if (newPassword === changePasswordData.old_password) { | |
showError(t('新密码不能与原密码相同')); | |
return; | |
} | |
setChangePasswordLoading(true); | |
try { | |
const res = await API.post( | |
`/api/user/change_initial_password?turnstile=${turnstileToken}`, | |
{ | |
user_id: changePasswordData.user_id, | |
username: changePasswordData.username, | |
old_password: changePasswordData.old_password, | |
new_password: newPassword, | |
} | |
); | |
const { success, message } = res.data; | |
if (success) { | |
showSuccess(t('密码修改成功,请输入新密码登录')); | |
// 关闭修改密码对话框 | |
setShowChangePasswordModal(false); | |
// 保存用户名,用于重新登录 | |
const savedUsername = changePasswordData.username; | |
setChangePasswordData(null); | |
// 清空修改密码表单 | |
setNewPassword(''); | |
setConfirmPassword(''); | |
// 保留用户名,只清空密码字段,让用户输入新密码即可 | |
setInputs({ username: savedUsername, password: '', wechat_verification_code: '' }); | |
// 重置提交状态 | |
setSubmitted(false); | |
} else { | |
showError(message); | |
} | |
} catch (error) { | |
showError(t('密码修改失败,请重试')); | |
} finally { | |
setChangePasswordLoading(false); | |
} | |
}; |
🤖 Prompt for AI Agents
In web/src/components/auth/LoginForm.jsx around lines 435 to 487, the
change-initial-password flow must include the Turnstile token and block
submission when it's missing: ensure you read the current Turnstile token (from
state or the Turnstile widget callback), validate it before calling the API and
show an error/return if absent, include the token in the POST payload (e.g.
turnstile_token or captcha_token) sent to /api/user/change_initial_password, and
after the request complete or on error reset/clear the token and loading states;
also preserve existing UX (success/error messages, modal close, form reset)
while adding these checks.
"访问隐私政策": "Accéder à la politique de confidentialité", | ||
"修改初始密码": "Modifier le mot de passe initial", | ||
"您使用的是管理员设置的初始密码,为了账户安全,请立即修改密码。": "Vous utilisez un mot de passe initial défini par l'administrateur. Pour la sécurité du compte, veuillez le modifier immédiatement.", | ||
"新密码": "Nouveau mot de passe", | ||
"请输入新密码(8-20个字符)": "Veuillez entrer le nouveau mot de passe (8-20 caractères)", | ||
"确认密码": "Confirmer le mot de passe", | ||
"请再次输入新密码": "Veuillez entrer à nouveau le nouveau mot de passe", | ||
"取消": "Annuler", | ||
"确认修改": "Confirmer", | ||
"请输入新密码和确认密码": "Veuillez entrer le nouveau mot de passe et la confirmation", | ||
"新密码长度必须在8-20个字符之间": "La longueur du nouveau mot de passe doit être comprise entre 8 et 20 caractères", | ||
"两次输入的密码不一致": "Les deux mots de passe ne correspondent pas", | ||
"新密码不能与原密码相同": "Le nouveau mot de passe ne peut pas être identique à l'ancien mot de passe", | ||
"密码修改成功,请输入新密码登录": "Mot de passe modifié avec succès, veuillez entrer votre nouveau mot de passe pour vous connecter", | ||
"密码修改成功,请重新登录": "Mot de passe modifié avec succès, veuillez vous reconnecter", | ||
"密码修改失败,请重试": "Échec de la modification du mot de passe, veuillez réessayer" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Critical: duplicate JSON keys; last-wins overrides earlier translations
This block redefines keys already present earlier in the file, which is invalid JSON and will silently override prior values:
- 新密码 (Line 2280) duplicates Line 730
- 确认密码 (Line 2282) duplicates Line 780
- 取消 (Line 2284) duplicates Line 522
- 两次输入的密码不一致 (Line 2288) duplicates Line 318
- 请再次输入新密码 (Line 2283) duplicates Line 1558
Remove duplicates and keep only the new, unique keys. Also minor FR tweaks for consistency.
Apply this diff within the shown range:
"访问隐私政策": "Accéder à la politique de confidentialité",
"修改初始密码": "Modifier le mot de passe initial",
"您使用的是管理员设置的初始密码,为了账户安全,请立即修改密码。": "Vous utilisez un mot de passe initial défini par l'administrateur. Pour la sécurité du compte, veuillez le modifier immédiatement.",
- "新密码": "Nouveau mot de passe",
- "请输入新密码(8-20个字符)": "Veuillez entrer le nouveau mot de passe (8-20 caractères)",
- "确认密码": "Confirmer le mot de passe",
- "请再次输入新密码": "Veuillez entrer à nouveau le nouveau mot de passe",
- "取消": "Annuler",
- "确认修改": "Confirmer",
- "请输入新密码和确认密码": "Veuillez entrer le nouveau mot de passe et la confirmation",
+ "请输入新密码(8-20个字符)": "Veuillez saisir le nouveau mot de passe (8-20 caractères)",
+ "确认修改": "Confirmer la modification",
+ "请输入新密码和确认密码": "Veuillez saisir le nouveau mot de passe et le confirmer",
"新密码长度必须在8-20个字符之间": "La longueur du nouveau mot de passe doit être comprise entre 8 et 20 caractères",
- "两次输入的密码不一致": "Les deux mots de passe ne correspondent pas",
"新密码不能与原密码相同": "Le nouveau mot de passe ne peut pas être identique à l'ancien mot de passe",
"密码修改成功,请输入新密码登录": "Mot de passe modifié avec succès, veuillez entrer votre nouveau mot de passe pour vous connecter",
"密码修改成功,请重新登录": "Mot de passe modifié avec succès, veuillez vous reconnecter",
"密码修改失败,请重试": "Échec de la modification du mot de passe, veuillez réessayer"
Please also:
- Verify these keys exist and align in en.json and zh.json for parity.
- Confirm frontend references use the existing shared keys (e.g., 新密码, 确认密码, 取消) and do not rely on the now-removed duplicates.
🏁 Script executed:
#!/bin/bash
set -e
rg -n '"新密码"' web/src/i18n/locales/fr.json
rg -n '"确认密码"' web/src/i18n/locales/fr.json
rg -n '"取消"' web/src/i18n/locales/fr.json
rg -n '"两次输入的密码不一致"' web/src/i18n/locales/fr.json
rg -n '"请再次输入新密码"' web/src/i18n/locales/fr.json
Length of output: 494
Remove duplicate JSON keys and refine FR translations
- Duplicate keys override earlier entries; remove these duplicate definitions in lines 2277–2292:
• 新密码 (2280)
• 确认密码 (2282)
• 请再次输入新密码 (2283)
• 取消 (2284)
• 两次输入的密码不一致 (2288) - Update remaining translations for consistency:
• 请输入新密码(8-20个字符) → “Veuillez saisir le nouveau mot de passe (8-20 caractères)”
• 确认修改 → “Confirmer la modification”
• 请输入新密码和确认密码 → “Veuillez saisir le nouveau mot de passe et le confirmer” - Verify en.json/zh.json parity and ensure frontend uses the original shared keys.
🤖 Prompt for AI Agents
In web/src/i18n/locales/fr.json around lines 2277 to 2292, remove the duplicate
JSON keys (新密码 at 2280, 确认密码 at 2282, 请再次输入新密码 at 2283, 取消 at 2284, 两次输入的密码不一致
at 2288) so earlier definitions are not overridden; update the remaining
translations as requested: 请输入新密码(8-20个字符) → "Veuillez saisir le nouveau mot de
passe (8-20 caractères)", 确认修改 → "Confirmer la modification", 请输入新密码和确认密码 →
"Veuillez saisir le nouveau mot de passe et le confirmer"; then verify en.json
and zh.json have matching keys and confirm the frontend uses the original shared
keys (not the removed duplicates) to preserve parity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个项目用的gorm AutoMigrate,不需要这个脚本
// 验证用户名匹配 | ||
if user.Username != req.Username { | ||
c.JSON(http.StatusOK, gin.H{ | ||
"success": false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个要用gin自带的bind和validate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
已修改为使用binding,并删除.sql
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
controller/user.go (1)
1153-1228
: Add Turnstile verification before processing unauthenticated password reset.This endpoint is intentionally unauthenticated and fully exposed on Line 1153, but it still lacks any Turnstile validation, so an attacker can brute-force
{user_id, username, old_password}
despite rate limits. Please enforce the same Turnstile check previously requested (attachmiddleware.TurnstileCheck()
on the route and reject when the token is missing/invalid) before proceeding with the reset logic.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
controller/user.go
(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controller/user.go (4)
common/gin.go (1)
ApiError
(96-101)model/main.go (1)
DB
(63-63)model/user.go (2)
User
(19-49)GetUserById
(288-300)model/log.go (1)
RecordLog
(78-94)
updates := map[string]interface{}{ | ||
"username": user.Username, | ||
"display_name": user.DisplayName, | ||
} | ||
|
||
// 如果正在更新密码,同时清除初始密码标记 | ||
if updatePassword { | ||
hashedPassword, err := common.Password2Hash(user.Password) | ||
if err != nil { | ||
common.ApiError(c, err) | ||
return | ||
} | ||
updates["password"] = hashedPassword | ||
updates["is_initial_password"] = false // 明确设置为 false | ||
} | ||
|
||
// 执行更新 | ||
if err := model.DB.Model(&model.User{}).Where("id = ?", userId).Updates(updates).Error; err != nil { | ||
common.ApiError(c, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent wiping username/display name on partial updates.
Line 762 unconditionally drops username
/display_name
into the update map. When the client submits a password-only payload (very common), those fields are omitted in JSON, unmarshal defaults them to ""
, and the DB write will blank the username/display name, breaking login. Only include keys that were actually provided in the request.
- // 构建更新字段的 map
- updates := map[string]interface{}{
- "username": user.Username,
- "display_name": user.DisplayName,
- }
+ // 构建更新字段的 map,仅在请求中包含时才更新
+ updates := map[string]interface{}{}
+ if _, ok := requestData["username"]; ok {
+ updates["username"] = user.Username
+ }
+ if _, ok := requestData["display_name"]; ok {
+ updates["display_name"] = user.DisplayName
+ }
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In controller/user.go around lines 762–780, the code unconditionally sets
"username" and "display_name" in the updates map which causes blanking when the
client omits those fields; only include these keys when they were actually
provided in the request — change the code to add username/display_name into
updates only if user.Username != "" and user.DisplayName != "" (or, if you want
to support empty-string updates, change the request DTO to use pointer fields
and check for nil pointers instead), leaving the password update logic as-is.
…irst login
is_initial_password
field in database to track whether the user's password is an admin-assigned initial passwordThis enhances security by preventing long-term usage of default/admin-set passwords.
closes #1985
Summary by CodeRabbit