Add end-to-end encryption for chat messages (X25519 / NaCl box)

Messages between matched users are now encrypted client-side with each
user's own key pair before they ever reach the server; the backend only
ever stores/relays opaque ciphertext + nonce and cannot read message
content, including for the last-message preview on the matches list.

- User gains a PublicKey field, registered/rotated via PUT /profile/public-key
- Message gains CipherText/Nonce; legacy plaintext Text is kept as-is for
  messages sent before this change
- MatchView exposes the partner's public key and the last message's
  ciphertext/nonce so the client can decrypt previews itself
- Removed the now-dead plaintext SanitizeMessageText utility — the server
  can no longer sanitize message content since it's opaque ciphertext

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 18:57:46 +03:30
parent 2c6d4bfbd2
commit 1e5ef02bdf
11 changed files with 154 additions and 180 deletions
+24
View File
@@ -201,3 +201,27 @@ func (h *ProfileHandler) DeleteAccount(c *gin.Context) {
}
utils.Success(c, http.StatusOK, "حساب کاربری غیرفعال شد", nil)
}
// UpdatePublicKey PUT /profile/public-key — ثبت/جایگزینی کلید عمومی X25519
// برای رمزنگاری سرتاسری پیام‌ها. کلاینت این کلید رو خودش تولید می‌کنه؛ کلید
// خصوصی متناظرش هیچ‌وقت به این endpoint (یا هیچ endpoint دیگه‌ای) فرستاده نمی‌شه.
func (h *ProfileHandler) UpdatePublicKey(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
return
}
var req dto.UpdatePublicKeyRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.Fail(c, http.StatusBadRequest, "کلید عمومی نامعتبر است", err.Error())
return
}
if err := h.profileService.UpdatePublicKey(user, req.PublicKey); err != nil {
if errors.Is(err, service.ErrInvalidPublicKey) {
utils.Fail(c, http.StatusBadRequest, err.Error(), nil)
return
}
utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی کلید عمومی", nil)
return
}
utils.Success(c, http.StatusOK, "کلید عمومی ذخیره شد", nil)
}