fix: resolve P1 backend consistency issues
This commit is contained in:
+119
-10
@@ -62,7 +62,7 @@ const (
|
||||
imVerificationResendAfterSeconds = 60
|
||||
imVerificationMaxAttempts = 5
|
||||
imVerificationCreateRateWindow = time.Hour
|
||||
imSensitiveChallengeRateLimitEnabled = false
|
||||
imSensitiveChallengeRateLimitEnabled = true
|
||||
imActionTokenExpiresInSeconds = 300
|
||||
imIndependentPasswordMaxAttempts = 5
|
||||
imIndependentPasswordLockSeconds = 300
|
||||
@@ -1503,7 +1503,7 @@ func serveIMSecurityChallenges(wrt http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
action := strings.TrimSpace(body.Scene)
|
||||
if action == "" || len(action) > 64 || strings.TrimSpace(body.Channel) != "sms" {
|
||||
if !validIMSecurityChallengeScene(action) || strings.TrimSpace(body.Channel) != "sms" {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
@@ -2530,7 +2530,8 @@ func serveIMMeetingReminders(wrt http.ResponseWriter, req *http.Request, meeting
|
||||
meetingParticipantUserIDs(participants), "会议提醒", meetingID, map[string]any{"meeting_id": meetingID, "reminded_count": len(participants)})
|
||||
writeIMSuccess(wrt, req, map[string]any{
|
||||
"meeting_id": meetingID,
|
||||
"reminded": meetingParticipantPayloads(participants),
|
||||
"reminded": meetingParticipantPayloadsWithSummaries(participants,
|
||||
meetingUserSummaries(principal.TenantID, []types.Meeting{{Participants: participants}})),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3706,9 +3707,10 @@ func serveIMFavoriteForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
results := make([]map[string]any, 0, len(body.TargetTopics)*len(body.FavoriteIDs))
|
||||
comment := strings.TrimSpace(body.Comment)
|
||||
for _, target := range body.TargetTopics {
|
||||
target = strings.TrimSpace(target)
|
||||
if target == "" {
|
||||
results = append(results, map[string]any{"target_topic": target, "status": "failed", "reason": imReasonMalformedRequest})
|
||||
rawTarget := strings.TrimSpace(target)
|
||||
target, targetOK := normalizeIMForwardTopic(principal.Uid, rawTarget)
|
||||
if target == "" || !targetOK {
|
||||
results = append(results, map[string]any{"target_topic": rawTarget, "status": "failed", "reason": imReasonMalformedRequest})
|
||||
continue
|
||||
}
|
||||
if err := requireTopicWriter(principal, target); err != nil {
|
||||
@@ -3733,6 +3735,10 @@ func serveIMFavoriteForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
},
|
||||
Content: favoriteForwardContent(item, comment),
|
||||
}
|
||||
if err := prepareHTTPForwardMessage(principal.TenantID, msg); err != nil {
|
||||
results = append(results, map[string]any{"target_topic": target, "favorite_id": favoriteID, "status": "failed", "reason": relationErrorReason(err)})
|
||||
continue
|
||||
}
|
||||
saved, err := saveMessageMaybeKafka(req.Context(), principal.TenantID, msg, nil, true)
|
||||
if err != nil {
|
||||
results = append(results, map[string]any{"target_topic": target, "favorite_id": favoriteID, "status": "failed", "reason": relationErrorReason(err)})
|
||||
@@ -4402,6 +4408,60 @@ func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
relation, err := store.Relations.GetRelationshipContext(principal.TenantID, principal.Uid, target, principal.Uid.P2PName(target))
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if relation == nil || relation.Relationship != types.RelationshipNone {
|
||||
reason := "FRIEND_REQUEST_CONFLICT"
|
||||
if relation != nil && (relation.Relationship == types.RelationshipBlockedByMe || relation.Relationship == types.RelationshipBlockedByPeer) {
|
||||
reason = imReasonPermissionDenied
|
||||
}
|
||||
writeIMError(wrt, req, http.StatusConflict, 30006, reason, "friend request is not allowed for current relationship", nil)
|
||||
return
|
||||
}
|
||||
source := strings.TrimSpace(body.Source)
|
||||
sourceTopic := strings.TrimSpace(body.SourceTopic)
|
||||
switch source {
|
||||
case "search", "qr":
|
||||
if sourceTopic != "" {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "source_topic is only valid for group source", nil)
|
||||
return
|
||||
}
|
||||
case "group":
|
||||
if types.GetTopicCat(sourceTopic) != types.TopicCatGrp {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "valid group source_topic is required", nil)
|
||||
return
|
||||
}
|
||||
requesterSub, requesterErr := store.Subs.Get(principal.TenantID, sourceTopic, principal.Uid, false)
|
||||
targetSub, targetErr := store.Subs.Get(principal.TenantID, sourceTopic, target, false)
|
||||
if requesterErr != nil || targetErr != nil {
|
||||
if requesterErr != nil {
|
||||
writeIMRelationError(wrt, req, requesterErr)
|
||||
} else {
|
||||
writeIMRelationError(wrt, req, targetErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if requesterSub == nil || targetSub == nil ||
|
||||
!(requesterSub.ModeGiven & requesterSub.ModeWant).IsJoiner() || !(targetSub.ModeGiven & targetSub.ModeWant).IsJoiner() {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "group membership required", nil)
|
||||
return
|
||||
}
|
||||
settings, settingsErr := store.GroupBusiness.GetSettings(principal.TenantID, sourceTopic)
|
||||
if settingsErr != nil {
|
||||
writeIMRelationError(wrt, req, settingsErr)
|
||||
return
|
||||
}
|
||||
if settings == nil || !settings.MemberPrivateChat {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "member private chat is disabled", nil)
|
||||
return
|
||||
}
|
||||
default:
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "invalid friend request source", nil)
|
||||
return
|
||||
}
|
||||
privacy, err := store.Profiles.GetPrivacy(principal.TenantID, target)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
@@ -4424,8 +4484,8 @@ func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
ApplicantID: principal.Uid,
|
||||
TargetID: target,
|
||||
Message: message,
|
||||
Source: strings.TrimSpace(body.Source),
|
||||
SourceTopic: strings.TrimSpace(body.SourceTopic),
|
||||
Source: source,
|
||||
SourceTopic: sourceTopic,
|
||||
Status: types.FriendRequestPending,
|
||||
IdempotencyKey: idempotencyKey,
|
||||
ExpiresAt: now.Add(7 * 24 * time.Hour),
|
||||
@@ -5038,7 +5098,7 @@ func serveIMGroupMyProfile(wrt http.ResponseWriter, req *http.Request, topic str
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if _, err := store.GroupBusiness.UpdateMember(principal.TenantID, topic, principal.Uid, types.GroupMemberPatch{Nickname: &nickname, Version: body.Version}); err != nil {
|
||||
if _, err := store.GroupBusiness.UpdateMember(principal.TenantID, topic, principal.Uid, types.GroupMemberPatch{Nickname: &nickname}); err != nil {
|
||||
if errors.Is(err, types.ErrNotFound) {
|
||||
logs.Warn.Printf("group[%s]: member profile saved to subscription only; business member row missing for user %s", topic, principal.Uid.UserId())
|
||||
dispatchIMGroupEvents(principal.TenantID)
|
||||
@@ -5129,6 +5189,17 @@ func serveIMGroupInvitations(wrt http.ResponseWriter, req *http.Request, topic s
|
||||
return
|
||||
}
|
||||
access := selfSub.ModeGiven & selfSub.ModeWant
|
||||
if !access.IsAdmin() {
|
||||
settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if settings == nil || !settings.MemberInvite {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "member invitation is disabled", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !access.IsSharer() && !access.IsAdmin() {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "permission denied", nil)
|
||||
return
|
||||
@@ -5232,6 +5303,17 @@ func serveIMGroupFiles(wrt http.ResponseWriter, req *http.Request, topic, tail s
|
||||
"has_more": nextCursor != "",
|
||||
})
|
||||
case tail == "files" && req.Method == http.MethodPost:
|
||||
if !access.IsAdmin() {
|
||||
settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if settings == nil || !settings.MemberUploadFiles {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "member file upload is disabled", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
idempotencyKey, ok := requireIMIdempotencyKey(wrt, req)
|
||||
if !ok {
|
||||
return
|
||||
@@ -5344,6 +5426,18 @@ func serveIMGroupJoinRequests(wrt http.ResponseWriter, req *http.Request, topic
|
||||
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonMalformedRequest, "group not found", nil)
|
||||
return
|
||||
}
|
||||
if !settings.JoinApproval {
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
joined, joinErr := store.GroupBusiness.InviteMember(principal.TenantID, topic, principal.Uid, principal.Uid,
|
||||
"", strings.TrimSpace(req.Header.Get("Idempotency-Key")), groupMemberPrivate("", false, false, 1, now), now)
|
||||
if joinErr != nil {
|
||||
writeIMRelationError(wrt, req, joinErr)
|
||||
return
|
||||
}
|
||||
dispatchIMGroupEvents(principal.TenantID)
|
||||
writeIMSuccess(wrt, req, map[string]any{"topic": topic, "status": "joined", "joined": joined, "source": "open_group"})
|
||||
return
|
||||
}
|
||||
sub, err := store.Subs.Get(principal.TenantID, topic, principal.Uid, false)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
@@ -7353,7 +7447,9 @@ func mergeGroupSettingsPayload(payload map[string]any, settings *types.GroupSett
|
||||
payload["description"] = settings.Description
|
||||
payload["owner"] = settings.OwnerUserID.UserId()
|
||||
payload["member_count"] = settings.MemberCount
|
||||
payload["version"] = settings.Version
|
||||
if _, exists := payload["version"]; !exists {
|
||||
payload["version"] = settings.Version
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
@@ -8682,6 +8778,7 @@ func userProfilePayload10(profile *types.UserProfile) map[string]any {
|
||||
"field_rules": map[string]any{
|
||||
"nickname": map[string]any{"min_length": 1, "max_length": 32},
|
||||
"signature": map[string]any{"max_length": 120},
|
||||
"region": map[string]any{"max_length": 128},
|
||||
"birth_date": map[string]any{"format": "YYYY-MM-DD"},
|
||||
"gender": []string{"male", "female", "other", ""},
|
||||
"email": map[string]any{"max_length": 128},
|
||||
@@ -8690,6 +8787,15 @@ func userProfilePayload10(profile *types.UserProfile) map[string]any {
|
||||
return data
|
||||
}
|
||||
|
||||
func validIMSecurityChallengeScene(scene string) bool {
|
||||
switch strings.TrimSpace(scene) {
|
||||
case "set_independent_password", "enable_youth_mode", "close_youth_mode", "reset_independent_password":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func userPrivacyPayload(settings *types.UserPrivacySettings) map[string]any {
|
||||
return map[string]any{
|
||||
"add_me_methods": settings.AddMeMethods,
|
||||
@@ -9437,6 +9543,9 @@ func normalizeIMUserVisibleTopic(viewer types.Uid, topic string) (string, bool)
|
||||
if topic == "" {
|
||||
return "", false
|
||||
}
|
||||
if topic == "slf" {
|
||||
return viewer.SlfName(), !viewer.IsZero()
|
||||
}
|
||||
if len(topic) < 3 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user