统一修复未知收件人超管限定与全部控制器Unicode转义
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-10 22:27:27 +08:00
parent 2331916c28
commit 6de4a1cae3
30 changed files with 111 additions and 108 deletions

View File

@ -60,10 +60,10 @@ func Download(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
func writeAttachmentError(ctx *context.Context, w http.ResponseWriter, err error) {
log.WithContext(ctx).WithError(err).Error("load attachment failed")
if errors.Is(err, attachments.ErrAttachmentUnavailable) {
response.NewErrorResponse(response.ParamsError, "\u9644\u4ef6\u4e0d\u53ef\u7528", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "附件不可用", "").FPrint(w)
return
}
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u9644\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取附件失败", "").FPrint(w)
}
func safeDownloadName(name string) string {

View File

@ -24,7 +24,7 @@ func TestSafeDownloadNameLength(t *testing.T) {
if got := safeDownloadName(input); len([]rune(got)) != 180 {
t.Fatalf("unexpected filename length: %d", len([]rune(got)))
}
if got := safeDownloadName("\u4e2d\u6587-report.txt"); got != "\u4e2d\u6587-report.txt" {
if got := safeDownloadName("中文-report.txt"); got != "中文-report.txt" {
t.Fatalf("unicode filename changed: %q", got)
}
}

View File

@ -17,7 +17,7 @@ func EmailDelete(ctx *context.Context, w http.ResponseWriter, req *http.Request)
var reqData emailDeleteRequest
if err := decodeEmailJSONRequest(w, req, &reqData, maxEmailJSONRequestBytes); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid email delete request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -29,7 +29,7 @@ func EmailDelete(ctx *context.Context, w http.ResponseWriter, req *http.Request)
err := del_email.DelEmail(ctx, reqData.IDs, reqData.ForcedDel)
if err != nil {
log.WithContext(ctx).WithError(err).Error("delete email failed")
response.NewErrorResponse(response.ServerError, "\u5220\u9664\u90ae\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "删除邮件失败", "").FPrint(w)
return
}
response.NewSuccessResponse("success").FPrint(w)

View File

@ -16,7 +16,7 @@ func EmailDetail(ctx *context.Context, w http.ResponseWriter, req *http.Request)
var retData emailDetailRequest
if err := decodeEmailJSONRequest(w, req, &retData, maxEmailJSONRequestBytes); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid email detail request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -28,7 +28,7 @@ func EmailDetail(ctx *context.Context, w http.ResponseWriter, req *http.Request)
email, err := detail.GetEmailDetail(ctx, retData.ID, true)
if err != nil {
log.WithContext(ctx).WithError(err).Error("load email detail failed")
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u90ae\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取邮件失败", "").FPrint(w)
return
}

View File

@ -56,19 +56,19 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var retData emailRequest
if err := decodeEmailJSONRequest(w, req, &retData, maxEmailJSONRequestBytes); err != nil {
log.WithContext(ctx).Info("email list request validation failed")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
offset, err := normalizeEmailListRequest(&retData)
if err != nil {
response.NewErrorResponse(response.ParamsError, "\u5206\u9875\u53c2\u6570\u8d85\u51fa\u9650\u5236", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "分页参数超出限制", "").FPrint(w)
return
}
tagInfo := dto.SearchTag{Type: -1, Status: -1, GroupId: -1}
if strings.TrimSpace(retData.Tag) != "" {
if err := json.Unmarshal([]byte(retData.Tag), &tagInfo); err != nil {
response.NewErrorResponse(response.ParamsError, "\u90ae\u4ef6\u7b5b\u9009\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "邮件筛选参数错误", "").FPrint(w)
return
}
}
@ -76,7 +76,7 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
emailList, total, err := list.GetEmailList(ctx, tagInfo, retData.Keyword, false, offset, retData.PageSize)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query email list failed")
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u90ae\u4ef6\u5217\u8868\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取邮件列表失败", "").FPrint(w)
return
}
items := make([]*emilItem, 0, len(emailList))
@ -84,7 +84,7 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var sender User
if err := decodeStoredJSON(email.Sender, &sender); err != nil {
log.WithContext(ctx).WithField("email_id", email.Id).WithError(err).Error("decode stored sender failed")
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u90ae\u4ef6\u5217\u8868\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取邮件列表失败", "").FPrint(w)
return
}
if sender.EmailAddress == "" {
@ -95,7 +95,7 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var recipients []User
if err := decodeStoredJSON(email.To, &recipients); err != nil {
log.WithContext(ctx).WithField("email_id", email.Id).WithError(err).Error("decode stored recipients failed")
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u90ae\u4ef6\u5217\u8868\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取邮件列表失败", "").FPrint(w)
return
}
authentication := parsemail.NewEmailAuthentication(email.SPFCheck == 1, email.DKIMCheck == 1)

View File

@ -19,7 +19,7 @@ func Move(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData moveRequest
if err := decodeEmailJSONRequest(w, req, &reqData, maxEmailJSONRequestBytes); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid email move request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -32,12 +32,12 @@ func Move(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
err := group.Move2DefaultBox(ctx, reqData.IDs, name)
if err != nil {
log.WithContext(ctx).WithError(err).Error("move email failed")
response.NewErrorResponse(response.ServerError, "\u79fb\u52a8\u90ae\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "移动邮件失败", "").FPrint(w)
return
}
} else if err := group.MoveMailToGroup(ctx, reqData.IDs, reqData.GroupId); err != nil {
log.WithContext(ctx).WithError(err).Error("move email to user mailbox failed")
response.NewErrorResponse(response.ServerError, "\u79fb\u52a8\u90ae\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "移动邮件失败", "").FPrint(w)
return
}
response.NewSuccessResponse("success").FPrint(w)

View File

@ -16,7 +16,7 @@ func MarkRead(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData markReadRequest
if err := decodeEmailJSONRequest(w, req, &reqData, maxEmailJSONRequestBytes); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid email read request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -28,7 +28,7 @@ func MarkRead(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
for _, id := range reqData.IDs {
if _, err := detail.GetEmailDetail(ctx, id, true); err != nil {
log.WithContext(ctx).WithError(err).Error("mark email read failed")
response.NewErrorResponse(response.ServerError, "\u66f4\u65b0\u90ae\u4ef6\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "更新邮件失败", "").FPrint(w)
return
}
}

View File

@ -71,18 +71,18 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData sendRequest
if err := decodeEmailJSONRequest(w, req, &reqData, maxSendRequestBytes); err != nil {
log.WithContext(ctx).Errorf("%+v", err)
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
log.WithContext(ctx).Infof("发送邮件")
allowLocalAlias := ctx.EffectiveRole() == access.RoleSuperAdmin
if reqData.From.Email != "" && !validation.IsAuthorizedSender(reqData.From.Email, ctx.UserAccount, allowLocalAlias, domains.Accepted) {
response.NewErrorResponse(response.ParamsError, "\u53d1\u4ef6\u4eba\u5730\u5740\u4e0d\u5141\u8bb8", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "发件人地址不允许", "").FPrint(w)
return
}
if reqData.Sender.Email != "" && !validation.IsAuthorizedSender(reqData.Sender.Email, ctx.UserAccount, allowLocalAlias, domains.Accepted) {
response.NewErrorResponse(response.ParamsError, "\u53d1\u9001\u8005\u5730\u5740\u4e0d\u5141\u8bb8", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "发送者地址不允许", "").FPrint(w)
return
}
@ -197,7 +197,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
log.WithContext(ctx).Debugf("插件执行--SendBefore End")
if e.From == nil || e.Sender == nil || !validation.IsAuthorizedSender(e.From.EmailAddress, ctx.UserAccount, allowLocalAlias, domains.Accepted) || !validation.IsAuthorizedSender(e.Sender.EmailAddress, ctx.UserAccount, allowLocalAlias, domains.Accepted) {
response.NewErrorResponse(response.ParamsError, "\u63d2\u4ef6\u4fee\u6539\u4e86\u672a\u6388\u6743\u7684\u53d1\u4ef6\u4eba", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "插件修改了未授权的发件人", "").FPrint(w)
return
}
if err := validation.ValidateEmail(e, validation.EmailLimits{
@ -208,7 +208,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
MaxBodyBytes: maxSendBodyBytes,
MaxEstimatedBytes: maxSendRequestBytes,
}); err != nil {
response.NewErrorResponse(response.ParamsError, "\u63d2\u4ef6\u4fee\u6539\u540e\u7684\u90ae\u4ef6\u8d85\u51fa\u5b89\u5168\u9650\u5236", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "插件修改后的邮件超出安全限制", "").FPrint(w)
return
}

View File

@ -1,4 +1,4 @@
package email
package email
import (
"testing"

View File

@ -22,7 +22,7 @@ func GetUserGroupList(ctx *context.Context, w http.ResponseWriter, req *http.Req
infos, err := group.GetGroupList(ctx)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query group list failed")
response.NewErrorResponse(response.ServerError, "\u83b7\u53d6\u5206\u7ec4\u5217\u8868\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "获取分组列表失败", "").FPrint(w)
return
}
@ -57,7 +57,7 @@ func GetUserGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request
infos, err := group.GetGroupInfoList(ctx)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query group tree failed")
response.NewErrorResponse(response.ServerError, "\u83b7\u53d6\u5206\u7ec4\u5217\u8868\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "获取分组列表失败", "").FPrint(w)
return
}
retData = array.Merge(retData, infos)
@ -74,14 +74,14 @@ func AddGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData addGroupRequest
if err := decodeJSONRequest(w, req, &reqData); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid group create request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
newGroup, err := group.CreateGroup(ctx, reqData.Name, reqData.ParentId)
if err != nil {
log.WithContext(ctx).WithError(err).Error("create group failed")
response.NewErrorResponse(response.ServerError, "\u521b\u5efa\u5206\u7ec4\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "创建分组失败", "").FPrint(w)
return
}
@ -96,13 +96,13 @@ func DelGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData delGroupRequest
if err := decodeJSONRequest(w, req, &reqData); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid group delete request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
succ, err := group.DelGroup(ctx, reqData.Id)
if err != nil {
log.WithContext(ctx).WithError(err).Error("delete group failed")
response.NewErrorResponse(response.ServerError, "\u5220\u9664\u5206\u7ec4\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "删除分组失败", "").FPrint(w)
return
}
response.NewSuccessResponse(succ).FPrint(w)

View File

@ -20,7 +20,7 @@ const (
func GetPluginList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if !authenticatedPluginUser(ctx) || authorizeCapability(ctx, access.CapabilityPluginReadGlobal, "", false) != nil {
response.NewErrorResponse(response.NoAccessPrivileges, "\u65e0\u6743\u8bbf\u95ee", "").FPrint(w)
response.NewErrorResponse(response.NoAccessPrivileges, "无权访问", "").FPrint(w)
return
}
ret := []string{}
@ -33,7 +33,7 @@ func GetPluginList(ctx *context.Context, w http.ResponseWriter, req *http.Reques
func SettingsHtml(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if !authenticatedPluginUser(ctx) || authorizeCapability(ctx, access.CapabilityPluginReadGlobal, "", false) != nil {
response.NewErrorResponse(response.NoAccessPrivileges, "\u65e0\u6743\u8bbf\u95ee", "").FPrint(w)
response.NewErrorResponse(response.NoAccessPrivileges, "无权访问", "").FPrint(w)
return
}
rawPath := strings.TrimPrefix(req.URL.Path, "/api/plugin/settings/")

View File

@ -93,7 +93,7 @@ func TestSettingsHtmlRejectsNonAdminSave(t *testing.T) {
recorder := httptest.NewRecorder()
SettingsHtml(&context.Context{}, recorder, httptest.NewRequest(http.MethodPost, "/api/plugin/settings/PluginTest/save", strings.NewReader(`{"url":"https://model.example.com"}`)))
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "\u65e0\u6743\u8bbf\u95ee") {
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "无权访问") {
t.Fatalf("non-admin plugin save was not rejected: status=%d body=%s", recorder.Code, recorder.Body.String())
}
if hook.resource != "" || hook.body != "" {
@ -109,7 +109,7 @@ func TestPluginConfigurationRejectsNonAdminAccess(t *testing.T) {
listRecorder := httptest.NewRecorder()
GetPluginList(&context.Context{}, listRecorder, httptest.NewRequest(http.MethodGet, "/api/plugin/list", nil))
if !strings.Contains(listRecorder.Body.String(), "\u65e0\u6743\u8bbf\u95ee") {
if !strings.Contains(listRecorder.Body.String(), "无权访问") {
t.Fatalf("unauthenticated plugin list was not rejected: body=%s", listRecorder.Body.String())
}
@ -121,7 +121,7 @@ func TestPluginConfigurationRejectsNonAdminAccess(t *testing.T) {
pageRecorder := httptest.NewRecorder()
SettingsHtml(&context.Context{UserID: 2, Role: access.RoleUser}, pageRecorder, httptest.NewRequest(http.MethodGet, "/api/plugin/settings/PluginTest/index.html", nil))
if !strings.Contains(pageRecorder.Body.String(), "\u65e0\u6743\u8bbf\u95ee") {
if !strings.Contains(pageRecorder.Body.String(), "无权访问") {
t.Fatalf("non-admin plugin page was not rejected: body=%s", pageRecorder.Body.String())
}
if hook.resource != "" || hook.body != "" {

View File

@ -34,7 +34,7 @@ func GetRule(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
res, err := rule.GetAllRules(ctx, ctx.UserID)
if err != nil {
log.WithContext(ctx).WithError(err).Error("load rules failed")
response.NewErrorResponse(response.ServerError, "\u8bfb\u53d6\u89c4\u5219\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "读取规则失败", "").FPrint(w)
return
}
response.NewSuccessResponse(res).FPrint(w)
@ -45,14 +45,14 @@ func UpsertRule(ctx *context.Context, w http.ResponseWriter, req *http.Request)
var data dto.Rule
if err := decodeRuleBody(w, req, &data); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid rule request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
data.Name = strings.TrimSpace(data.Name)
data.Params = strings.TrimSpace(data.Params)
if data.Id < 0 || data.UserId != 0 || data.Name == "" || len(data.Name) > maxRuleNameBytes || len(data.Params) > maxRuleParamsBytes || data.Sort < -maxRuleSort || data.Sort > maxRuleSort {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -63,23 +63,23 @@ func UpsertRule(ctx *context.Context, w http.ResponseWriter, req *http.Request)
}
if (data.Action == dto.READ || data.Action == dto.DELETE) && data.Params != "" {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if len(data.Rules) == 0 || len(data.Rules) > maxRuleConditions || !array.InArray(data.Action, []dto.RuleType{dto.READ, dto.FORWARD, dto.DELETE, dto.MOVE}) {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
for _, r := range data.Rules {
if r == nil || r.Rule == "" || len(r.Rule) > maxRuleValueBytes || !array.InArray(r.Field, []string{"From", "Subject", "To", "Cc", "Text", "Html", "Content"}) || !array.InArray(r.Type, []string{match.RuleTypeRegex, match.RuleTypeContains, match.RuleTypeEq}) {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if r.Type == match.RuleTypeRegex {
if err := match.ValidateRegex(r.Rule); err != nil {
response.NewErrorResponse(response.ParamsError, "\u6b63\u5219\u8868\u8fbe\u5f0f\u65e0\u6548", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "正则表达式无效", "").FPrint(w)
return
}
}
@ -88,12 +88,12 @@ func UpsertRule(ctx *context.Context, w http.ResponseWriter, req *http.Request)
encoded, err := data.Encode()
if err != nil {
log.WithContext(ctx).WithError(err).Error("encode rule failed")
response.NewErrorResponse(response.ServerError, "\u4fdd\u5b58\u89c4\u5219\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "保存规则失败", "").FPrint(w)
return
}
if err := save(ctx, encoded); err != nil {
log.WithContext(ctx).WithError(err).Error("save rule failed")
response.NewErrorResponse(response.ServerError, "\u4fdd\u5b58\u89c4\u5219\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "保存规则失败", "").FPrint(w)
return
}
response.NewSuccessResponse("succ").FPrint(w)
@ -149,19 +149,19 @@ func DelRule(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var data delRuleReq
if err := decodeRuleBody(w, req, &data); err != nil {
log.WithContext(ctx).WithError(err).Info("invalid delete rule request")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if data.Id <= 0 {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
_, err := db.Instance.Exec(db.WithContext(ctx, "delete from rule where id =? and user_id =?"), data.Id, ctx.UserID)
if err != nil {
log.WithContext(ctx).WithError(err).Error("delete rule failed")
response.NewErrorResponse(response.ServerError, "\u5220\u9664\u89c4\u5219\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "删除规则失败", "").FPrint(w)
return
}

View File

@ -501,7 +501,7 @@ func BeginPasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *htt
if emailRecoveryBeginIPAttempts.blocked(beginKey) {
id, err := newOpaqueRecoveryChallengeID()
if err != nil {
writeSecurityError(w, response.ParamsError, "\u8bf7\u6c42\u5df2\u53d7\u7406")
writeSecurityError(w, response.ParamsError, "请求已受理")
return
}
response.NewSuccessResponse(map[string]any{"id": id}).FPrint(w)
@ -512,7 +512,7 @@ func BeginPasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *htt
}
browserContext, ok := recoveryBrowserContext(req, w, true)
if !ok {
writeSecurityError(w, response.ParamsError, "\u8bf7\u6c42\u5df2\u53d7\u7406")
writeSecurityError(w, response.ParamsError, "请求已受理")
return
}
id, err := runtime.recovery.Begin(recovery.BeginRequest{Identifier: input.Account, Email: input.Email, BrowserContext: browserContext}, remoteIP(req))
@ -532,7 +532,7 @@ func VerifyPasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *ht
}
failureKey := emailRecoveryFailureIPAttempts.ipKey(req)
if emailRecoveryFailureIPAttempts.blocked(failureKey) {
writeSecurityError(w, response.ParamsError, "\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "验证失败")
return
}
var input verificationCheckRequest
@ -550,7 +550,7 @@ func VerifyPasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *ht
browserContext, ok := recoveryBrowserContext(req, w, methodOffline)
if !ok {
emailRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "验证失败")
return
}
if methodOffline {
@ -573,7 +573,7 @@ func CompletePasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *
}
failureKey := emailRecoveryFailureIPAttempts.ipKey(req)
if emailRecoveryFailureIPAttempts.blocked(failureKey) {
writeSecurityError(w, response.ParamsError, "\u91cd\u7f6e\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "重置失败")
return
}
var input recoveryCompleteRequest
@ -585,7 +585,7 @@ func CompletePasswordRecovery(ctx *context.Context, w http.ResponseWriter, req *
browserContext, ok := recoveryBrowserContext(req, w, false)
if !ok {
emailRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "验证失败")
return
}
if err := runtime.recovery.CompleteWithContext(input.Ticket, input.NewPassword, browserContext); err != nil {

View File

@ -30,7 +30,7 @@ func ModifyPassword(ctx *context.Context, w http.ResponseWriter, req *http.Reque
return
}
if ctx.EffectiveRole() == access.RoleSuperAdmin {
response.NewErrorResponse(response.NoAccessPrivileges, "\u8d85\u7ea7\u7ba1\u7406\u5458\u5fc5\u987b\u901a\u8fc7\u90e8\u7f72\u7aef\u4e13\u7528\u901a\u9053\u91cd\u7f6e\u5bc6\u7801", "").FPrint(w)
response.NewErrorResponse(response.NoAccessPrivileges, "超级管理员必须通过部署端专用通道重置密码", "").FPrint(w)
return
}
var retData modifyPasswordRequest
@ -76,7 +76,7 @@ func ModifyPassword(ctx *context.Context, w http.ResponseWriter, req *http.Reque
}
if err := auth.SetPassword(&user, retData.Password); err != nil {
if errors.Is(err, password.ErrInvalidLength) {
response.NewErrorResponse(response.ParamsError, "\u5bc6\u7801\u957f\u5ea6\u5fc5\u987b\u4e3a 8-12 \u5b57\u8282", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "密码长度必须为 8-12 字节", "").FPrint(w)
return
}
log.WithContext(ctx).WithError(err).Error("password hash failed")

View File

@ -84,7 +84,7 @@ func writeSetupError(ctx *context.Context, w http.ResponseWriter, err error) {
message = ssl.UserCertificateErrorMessage(err)
}
if message == "" {
message = "\u521d\u59cb\u5316\u64cd\u4f5c\u5931\u8d25"
message = "初始化操作失败"
}
response.NewErrorResponse(response.ServerError, message, "").FPrint(w)
}
@ -94,7 +94,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
reqBytes, err := io.ReadAll(req.Body)
if err != nil {
log.WithContext(ctx).WithError(err).Info("setup request body rejected")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -102,7 +102,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
err = json.Unmarshal(reqBytes, &reqData)
if err != nil {
log.WithContext(ctx).WithError(err).Info("setup request json rejected")
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}

View File

@ -65,29 +65,29 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request)
return
}
if reqData.Username == "" || reqData.Password == "" || reqData.Account == "" {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if reqData.Disabled != 0 && reqData.Disabled != 1 {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
account, err := validation.NormalizeAccount(reqData.Account)
if err != nil {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
username, err := validation.NormalizeDisplayName(reqData.Username)
if err != nil {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
user := models.User{Name: username, Account: account, Disabled: reqData.Disabled, SessionVersion: 1, SecurityVersion: 1}
user.ApplyRole(targetRole)
if err := auth.SetPassword(&user, reqData.Password); err != nil {
if errors.Is(err, password.ErrInvalidLength) {
response.NewErrorResponse(response.ParamsError, "\u5bc6\u7801\u957f\u5ea6\u5fc5\u987b\u4e3a 8-12 \u5b57\u8282", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "密码长度必须为 8-12 字节", "").FPrint(w)
return
}
log.WithContext(ctx).WithError(err).Error("password hash failed")
@ -96,7 +96,7 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request)
}
if _, err := db.Instance.Insert(&user); err != nil {
log.WithContext(ctx).WithError(err).Error("create user failed")
response.NewErrorResponse(response.ServerError, "\u521b\u5efa\u7528\u6237\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "创建用户失败", "").FPrint(w)
return
}
response.NewSuccessResponse(user).FPrint(w)
@ -153,12 +153,12 @@ func loadUserSecurityStatus(users []models.User) (map[int]bool, map[int]bool, er
func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if err := authorizeCapability(ctx, access.CapabilityUserRead, "", false); err != nil {
response.NewErrorResponse(response.NoAccessPrivileges, "\u65e0\u6743\u8bbf\u95ee", "").FPrint(w)
response.NewErrorResponse(response.NoAccessPrivileges, "无权访问", "").FPrint(w)
return
}
var reqData userListRequest
if err := decodeUserRequest(w, req, &reqData); err != nil {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if reqData.PageSize <= 0 || reqData.PageSize > 100 {
@ -251,11 +251,11 @@ func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
return
}
if reqData.Id == 0 && reqData.Account == "" {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
if reqData.Disabled != 0 && reqData.Disabled != 1 {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
@ -267,18 +267,18 @@ func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
} else {
account, validationErr := validation.NormalizeAccount(reqData.Account)
if validationErr != nil {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
found, err = db.Instance.Where("account=?", account).Get(&user)
}
if err != nil {
log.WithContext(ctx).Errorf("SQL Error: %+v", err)
response.NewErrorResponse(response.ServerError, "\u67e5\u8be2\u7528\u6237\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "查询用户失败", "").FPrint(w)
return
}
if !found || user.ID == 0 {
response.NewErrorResponse(response.ParamsError, "\u7528\u6237\u4e0d\u5b58\u5728", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "用户不存在", "").FPrint(w)
return
}
targetRole := user.EffectiveRole()
@ -319,17 +319,17 @@ func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
user.ApplyRole(newRole)
}
if user.IsAdmin == 1 && reqData.Disabled != 0 {
response.NewErrorResponse(response.ParamsError, "\u4e0d\u5141\u8bb8\u7981\u7528\u7ba1\u7406\u5458\u8d26\u6237", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "不允许禁用管理员账户", "").FPrint(w)
return
}
if user.ID == ctx.UserID && reqData.Password != "" {
response.NewErrorResponse(response.ParamsError, "\u8bf7\u901a\u8fc7\u5b89\u5168\u8bbe\u7f6e\u4fee\u6539\u5f53\u524d\u8d26\u6237\u5bc6\u7801", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "请通过安全设置修改当前账户密码", "").FPrint(w)
return
}
if reqData.Username != "" {
username, validationErr := validation.NormalizeDisplayName(reqData.Username)
if validationErr != nil {
response.NewErrorResponse(response.ParamsError, "\u53c2\u6570\u9519\u8bef", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
user.Name = username
@ -338,7 +338,7 @@ func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if reqData.Password != "" {
if err := auth.SetPassword(&user, reqData.Password); err != nil {
if errors.Is(err, password.ErrInvalidLength) {
response.NewErrorResponse(response.ParamsError, "\u5bc6\u7801\u957f\u5ea6\u5fc5\u987b\u4e3a 8-12 \u5b57\u8282", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "密码长度必须为 8-12 字节", "").FPrint(w)
return
}
log.WithContext(ctx).WithError(err).Error("password hash failed")

View File

@ -180,19 +180,19 @@ func RevokeWebAuthnCredential(ctx *context.Context, w http.ResponseWriter, req *
func BeginWebAuthnLogin(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
service := webAuthnRuntime()
if service == nil {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u4e0d\u53ef\u7528")
writeSecurityError(w, response.ParamsError, "安全密钥登录不可用")
return
}
beginKey := webAuthnLoginBeginIPAttempts.ipKey(req)
failureKey := webAuthnLoginFailureIPAttempts.ipKey(req)
if webAuthnLoginBeginIPAttempts.blocked(beginKey) || webAuthnLoginFailureIPAttempts.blocked(failureKey) || loginIPAttempts.blocked(loginIPAttempts.ipKey(req)) {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥登录失败")
return
}
var input webAuthnLoginBeginRequest
if err := decodeJSONRequest(w, req, &input); err != nil || strings.TrimSpace(input.ChallengeID) == "" || strings.TrimSpace(input.Account) == "" {
webAuthnLoginFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u8bf7\u6c42\u65e0\u6548")
writeSecurityError(w, response.ParamsError, "安全密钥登录请求无效")
return
}
item, ok := pendingLoginVerification(input.ChallengeID, input.Account)
@ -204,7 +204,7 @@ func BeginWebAuthnLogin(ctx *context.Context, w http.ResponseWriter, req *http.R
options, sessionID, err := service.BeginLogin(item.UserID)
if err != nil {
webAuthnLoginFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥登录失败")
return
}
response.NewSuccessResponse(map[string]any{
@ -221,19 +221,19 @@ func FinishWebAuthnLogin(ctx *context.Context, w http.ResponseWriter, req *http.
runtime := currentSecurityRuntime()
service := runtime.webauthn
if service == nil {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u4e0d\u53ef\u7528")
writeSecurityError(w, response.ParamsError, "安全密钥登录不可用")
return
}
failureKey := webAuthnLoginFailureIPAttempts.ipKey(req)
if webAuthnLoginFailureIPAttempts.blocked(failureKey) || loginIPAttempts.blocked(loginIPAttempts.ipKey(req)) {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥登录失败")
return
}
var input webAuthnFinishRequest
if err := decodeJSONRequest(w, req, &input); err != nil || strings.TrimSpace(input.ChallengeID) == "" || strings.TrimSpace(input.SessionID) == "" || len(input.Credential) == 0 || !json.Valid(input.Credential) {
webAuthnLoginFailureIPAttempts.recordFailure(failureKey)
loginIPAttempts.recordFailure(loginIPAttempts.ipKey(req))
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u8bf7\u6c42\u65e0\u6548")
writeSecurityError(w, response.ParamsError, "安全密钥登录请求无效")
return
}
account := strings.TrimSpace(input.Account)
@ -251,7 +251,7 @@ func FinishWebAuthnLogin(ctx *context.Context, w http.ResponseWriter, req *http.
if _, err := service.FinishLogin(item.UserID, input.SessionID, credentialRequest); err != nil {
webAuthnLoginFailureIPAttempts.recordFailure(failureKey)
loginIPAttempts.recordFailure(loginIPAttempts.ipKey(req))
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u767b\u5f55\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥登录失败")
return
}
var user models.User
@ -273,28 +273,28 @@ func BeginWebAuthnRecovery(ctx *context.Context, w http.ResponseWriter, req *htt
}
service := currentSecurityRuntime().recovery
if service == nil {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u6062\u590d\u4e0d\u53ef\u7528")
writeSecurityError(w, response.ParamsError, "安全密钥恢复不可用")
return
}
beginKey := webAuthnRecoveryBeginIPAttempts.ipKey(req)
failureKey := webAuthnRecoveryFailureIPAttempts.ipKey(req)
if webAuthnRecoveryBeginIPAttempts.blocked(beginKey) || webAuthnRecoveryFailureIPAttempts.blocked(failureKey) {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥验证失败")
return
}
var input webAuthnRecoveryBeginRequest
if err := decodeJSONRequest(w, req, &input); err != nil || strings.TrimSpace(input.Identifier) == "" {
webAuthnRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u6062\u590d\u8bf7\u6c42\u65e0\u6548")
writeSecurityError(w, response.ParamsError, "安全密钥恢复请求无效")
return
}
if _, ok := recoveryBrowserContext(req, w, true); !ok {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u6062\u590d\u8bf7\u6c42\u65e0\u6548")
writeSecurityError(w, response.ParamsError, "安全密钥恢复请求无效")
return
}
options, sessionID, err := service.BeginWebAuthn(input.Identifier)
if err != nil {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥验证失败")
return
}
webAuthnRecoveryBeginIPAttempts.recordFailure(beginKey)
@ -307,31 +307,31 @@ func VerifyWebAuthnRecovery(ctx *context.Context, w http.ResponseWriter, req *ht
}
service := currentSecurityRuntime().recovery
if service == nil {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u6062\u590d\u4e0d\u53ef\u7528")
writeSecurityError(w, response.ParamsError, "安全密钥恢复不可用")
return
}
failureKey := webAuthnRecoveryFailureIPAttempts.ipKey(req)
if webAuthnRecoveryFailureIPAttempts.blocked(failureKey) {
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥验证失败")
return
}
var input webAuthnRecoveryVerifyRequest
if err := decodeJSONRequest(w, req, &input); err != nil || strings.TrimSpace(input.Identifier) == "" || strings.TrimSpace(input.SessionID) == "" || len(input.Credential) == 0 || !json.Valid(input.Credential) {
webAuthnRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u6062\u590d\u8bf7\u6c42\u65e0\u6548")
writeSecurityError(w, response.ParamsError, "安全密钥恢复请求无效")
return
}
browserContext, ok := recoveryBrowserContext(req, w, false)
if !ok {
webAuthnRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥验证失败")
return
}
credentialRequest := cloneWebAuthnRequest(req, input.Credential)
ticket, err := service.VerifyWebAuthnWithContext(input.Identifier, input.SessionID, credentialRequest, browserContext)
if err != nil {
webAuthnRecoveryFailureIPAttempts.recordFailure(failureKey)
writeSecurityError(w, response.ParamsError, "\u5b89\u5168\u5bc6\u94a5\u9a8c\u8bc1\u5931\u8d25")
writeSecurityError(w, response.ParamsError, "安全密钥验证失败")
return
}
response.NewSuccessResponse(map[string]any{"ticket": ticket}).FPrint(w)

View File

@ -78,7 +78,7 @@ func Init(version string) error {
if err != nil {
return errors.Wrap(err)
}
log.Infof("\u5f53\u524d\u6570\u636e\u5e93\u7ed3\u6784\u7248\u672c: %d", report.CurrentVersion)
log.Infof("当前数据库结构版本: %d", report.CurrentVersion)
// 更新历史数据
if err := fixHistoryData(); err != nil {
return errors.Wrap(err)
@ -125,7 +125,7 @@ func legacySyncEnabled(version string) bool {
return false
}
if version != "test" && !config.DevelopmentMode {
log.Warn("\u751f\u4ea7\u73af\u5883\u5df2\u62d2\u7edd\u542f\u7528\u65e7 Sync2 \u517c\u5bb9\u6a21\u5f0f")
log.Warn("生产环境已拒绝启用旧 Sync2 兼容模式")
return false
}
return true

View File

@ -25,7 +25,7 @@ func (p *Response) FPrint(w http.ResponseWriter) {
bytesData, err := json.Marshal(p)
if err != nil {
log.WithError(err).Error("serialize HTTP response failed")
http.Error(w, "\u54cd\u5e94\u5e8f\u5217\u5316\u5931\u8d25", http.StatusInternalServerError)
http.Error(w, "响应序列化失败", http.StatusInternalServerError)
return
}
if observer, ok := w.(interface{ ObserveResult(int) }); ok {

View File

@ -50,7 +50,7 @@ func main() {
fmt.Println("文件第一列是分类,0表示正常邮件,1表示垃圾邮件,2表示诈骗邮件")
if err := exportEmails(id); err != nil {
fmt.Fprintf(os.Stderr, "\u5bfc\u51fa\u5931\u8d25: %v\n", err)
fmt.Fprintf(os.Stderr, "导出失败: %v\n", err)
os.Exit(1)
}
}

View File

@ -201,7 +201,7 @@ func TestSettingsHtmlEscapesConfiguredURL(t *testing.T) {
func TestBuildSpamBlockModelTextLimitsInput(t *testing.T) {
email := &parsemail.Email{
Subject: strings.Repeat("\u754c", maxSpamBlockModelTextBytes),
Subject: strings.Repeat("界", maxSpamBlockModelTextBytes),
HTML: []byte(strings.Repeat("<p>body</p>", maxSpamBlockSourceBytes)),
}
text := buildSpamBlockModelText(email)

View File

@ -115,7 +115,7 @@ func TestWeChatRuntimeConfigRejectsTrailingJSON(t *testing.T) {
func TestBuildWeChatMessageLimitsContent(t *testing.T) {
message := buildWeChatMessage(&parsemail.Email{
Subject: strings.Repeat("\u754c", maxWeChatMessageBytes),
Subject: strings.Repeat("界", maxWeChatMessageBytes),
Text: []byte(strings.Repeat("body", maxWeChatMessageBytes)),
})
if len(message) > maxWeChatMessageBytes {

View File

@ -115,7 +115,7 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
r.Body = http.MaxBytesReader(w, r.Body, 64<<20)
if normalizedAPIPath(r.URL.Path) == "/api/login" && !loginRequestAllowed(r) {
response.NewErrorResponse(response.ParamsError, "\u8bf7\u6c42\u6765\u6e90\u6821\u9a8c\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ParamsError, "请求来源校验失败", "").FPrint(w)
return
}
@ -146,7 +146,7 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
found, err := db.Instance.ID(identity.ID).Get(&currentUser)
if err != nil {
log.WithContext(ctx).WithError(err).Error("validate session user failed")
response.NewErrorResponse(response.ServerError, "\u4f1a\u8bdd\u6821\u9a8c\u5931\u8d25", "").FPrint(w)
response.NewErrorResponse(response.ServerError, "会话校验失败", "").FPrint(w)
return
}
if !found || currentUser.Disabled != 0 || currentUser.Account != identity.Account || currentUser.SessionVersion != identity.SessionVersion || (identity.SecurityVersion > 0 && currentUser.SecurityVersion != identity.SecurityVersion) {

View File

@ -175,7 +175,7 @@ func resolveIncomingUsers(ctx *context.Context, email *parsemail.Email, emailTyp
}
log.WithContext(ctx).Infof("收件人不存在但DKIM验证通过,转交管理员: recipient_count=%d", len(accounts))
if err := db.Instance.Table(&models.User{}).Where("is_admin=1 AND disabled=0").Find(&users); err != nil {
if err := db.Instance.Table(&models.User{}).Where(models.SuperAdminPredicate + " AND disabled=0").Find(&users); err != nil {
return nil, false, err
}
if len(users) == 0 {

View File

@ -2,3 +2,6 @@ package models
// OrdinaryUserPredicate 与 EffectiveRole 的兼容规则一致,旧管理员不能混入普通用户范围。
const OrdinaryUserPredicate = "(role = 'user' OR ((role IS NULL OR role NOT IN ('user', 'admin', 'super_admin')) AND is_admin <> 1))"
// SuperAdminPredicate 与 EffectiveRole 的兼容规则一致,确保未知收件人等系统邮件严格仅流向超级管理员。
const SuperAdminPredicate = "(role = 'super_admin' OR ((role IS NULL OR role NOT IN ('user', 'admin', 'super_admin')) AND is_admin = 1))"

View File

@ -46,7 +46,7 @@ func readWindowsPassword(prompt string) (string, error) {
value[i] = 0
}
if password.ValidateUserPassword(normalized) != nil {
return "", errors.New("\u5bc6\u7801\u957f\u5ea6\u5fc5\u987b\u4e3a 8 \u5230 12 \u4e2a\u5b57\u7b26")
return "", errors.New("密码长度必须为 8 到 12 个字符")
}
return normalized, nil
}

View File

@ -82,7 +82,7 @@ func TestProtocolEnforcesUnifiedPasswordLength(t *testing.T) {
valid := []string{
`{"version":1,"nonce":"n","new_password":"12345678"}`,
`{"version":1,"nonce":"n","new_password":"123456789012"}`,
`{"version":1,"nonce":"n","new_password":"\u5bc6\u7801aa"}`,
`{"version":1,"nonce":"n","new_password":"密码aa"}`,
}
for _, input := range valid {
if _, err := DecodeRequest(strings.NewReader(input)); err != nil {
@ -93,7 +93,7 @@ func TestProtocolEnforcesUnifiedPasswordLength(t *testing.T) {
invalid := []string{
`{"version":1,"nonce":"n","new_password":"1234567"}`,
`{"version":1,"nonce":"n","new_password":"1234567890123"}`,
`{"version":1,"nonce":"n","new_password":"\u5bc6\u7801\u5bc6\u7801a"}`,
`{"version":1,"nonce":"n","new_password":"密码密码a"}`,
}
for _, input := range invalid {
if _, err := DecodeRequest(strings.NewReader(input)); !errors.Is(err, ErrInvalidRequest) {

View File

@ -144,7 +144,7 @@ func GetDatabaseSettingsDetail(ctx *context.Context) (map[string]string, error)
func GetAdminPassword(ctx *context.Context) (string, error) {
if db.Instance == nil {
return "", databaseError("\u6570\u636e\u5e93\u5c1a\u672a\u521d\u59cb\u5316\uff0c\u8bf7\u5148\u5b8c\u6210\u6570\u636e\u5e93\u8bbe\u7f6e", nil)
return "", databaseError("数据库尚未初始化,请先完成数据库设置", nil)
}
users := []*models.User{}
if err := db.Instance.Find(&users); err != nil {
@ -162,7 +162,7 @@ func SetAdminPassword(ctx *context.Context, account, pwd string) error {
adminSetupMutex.Lock()
defer adminSetupMutex.Unlock()
if db.Instance == nil {
return databaseError("\u6570\u636e\u5e93\u5c1a\u672a\u521d\u59cb\u5316\uff0c\u8bf7\u5148\u5b8c\u6210\u6570\u636e\u5e93\u8bbe\u7f6e", nil)
return databaseError("数据库尚未初始化,请先完成数据库设置", nil)
}
if err := validateAdminPassword(pwd); err != nil {

View File

@ -58,7 +58,7 @@ func buildDNSItems(configData *config.Config, domain, dkimPublicKey, ipv4, ipv6,
func buildDNSItemsWithWebIPv4(configData *config.Config, domain, dkimPublicKey, ipv4, ipv6, ipTips string, webIPv4 []string) []*DNSItem {
address := normalizeDNSAddress(ipv4)
if address == "" {
address = "[\u672a\u68c0\u6d4b\u5230\u516c\u7f51 IPv4]"
address = "[未检测到公网 IPv4]"
}
items := make([]*DNSItem, 0, 13)