v.0.0.4.6 Добавлено шифрование сообщения
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
madipo2611 2025-08-21 00:50:27 +03:00
parent 261f14b451
commit f983a2f9d9
5 changed files with 83 additions and 243 deletions

View File

@ -7,6 +7,7 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/base64"
"io" "io"
) )
@ -38,7 +39,7 @@ func NewCryptoService() (*CryptoService, error) {
}, nil }, nil
} }
// Шифрование сообщения // Шифрование сообщения - возвращает зашифрованный контент, ключ и nonce
func (cs *CryptoService) EncryptMessage(content string) ([]byte, []byte, []byte, error) { func (cs *CryptoService) EncryptMessage(content string) ([]byte, []byte, []byte, error) {
// Генерация сессионного AES ключа // Генерация сессионного AES ключа
aesKey := make([]byte, 32) aesKey := make([]byte, 32)
@ -62,7 +63,7 @@ func (cs *CryptoService) EncryptMessage(content string) ([]byte, []byte, []byte,
return nil, nil, nil, err return nil, nil, nil, err
} }
encryptedContent := gcm.Seal(nonce, nonce, []byte(content), nil) encryptedContent := gcm.Seal(nil, nonce, []byte(content), nil)
// Шифрование AES ключа RSA-OAEP // Шифрование AES ключа RSA-OAEP
encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &cs.privateKey.PublicKey, aesKey, nil) encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &cs.privateKey.PublicKey, aesKey, nil)
@ -99,3 +100,13 @@ func (cs *CryptoService) DecryptMessage(encryptedContent, encryptedKey, nonce []
return string(plaintext), nil return string(plaintext), nil
} }
// Утилитарная функция для base64 кодирования (для хранения в Vault)
func (cs *CryptoService) EncodeBase64(data []byte) string {
return base64.StdEncoding.EncodeToString(data)
}
// Утилитарная функция для base64 декодирования
func (cs *CryptoService) DecodeBase64(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}

View File

@ -71,8 +71,8 @@ func (v *VaultManager) GetMasterPrivateKey() ([]byte, error) {
return nil, fmt.Errorf("invalid data format in Vault secret") return nil, fmt.Errorf("invalid data format in Vault secret")
} }
// Получаем приватный ключ в base64 - ОБРАТИТЕ ВНИМАНИЕ НА НОВОЕ ИМЯ ПОЛЯ! // Получаем приватный ключ в base64
keyInterface, ok := data["private_key_base64"] // ← Изменилось здесь! keyInterface, ok := data["private_key_base64"]
if !ok { if !ok {
return nil, fmt.Errorf("private_key_base64 not found in Vault data") return nil, fmt.Errorf("private_key_base64 not found in Vault data")
} }

View File

@ -1,3 +0,0 @@
ALTER TABLE messages
DROP COLUMN encrypted_key,
DROP COLUMN nonce;

View File

@ -1,18 +1,9 @@
-- Добавляем колонки для хранения зашифрованных данных
ALTER TABLE messages ALTER TABLE messages
ADD COLUMN encrypted_key BYTEA, ADD COLUMN encrypted_content BYTEA NOT NULL,
ADD COLUMN nonce BYTEA; ADD COLUMN encrypted_key BYTEA NOT NULL,
ADD COLUMN nonce BYTEA NOT NULL,
DROP COLUMN content;
-- Обновляем существующие сообщения (если есть) -- Обновляем индексы (если нужно)
UPDATE messages SET CREATE INDEX idx_messages_encrypted ON messages(encrypted_content);
encrypted_key = ''::bytea,
nonce = ''::bytea
WHERE encrypted_key IS NULL OR nonce IS NULL;
-- Делаем поля обязательными
ALTER TABLE messages
ALTER COLUMN encrypted_key SET NOT NULL,
ALTER COLUMN nonce SET NOT NULL;
ALTER TABLE messages
ALTER COLUMN content TYPE BYTEA USING content::bytea,
ALTER COLUMN encrypted_key TYPE BYTEA USING encrypted_key::bytea,
ALTER COLUMN nonce TYPE BYTEA USING nonce::bytea;

281
server.go
View File

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -18,7 +17,7 @@ import (
"net" "net"
"os" "os"
"sync" "sync"
crypto2 "tailly_messages/crypto" "tailly_messages/crypto"
"tailly_messages/proto" "tailly_messages/proto"
"time" "time"
) )
@ -27,21 +26,15 @@ type server struct {
proto.UnimplementedMessageServiceServer proto.UnimplementedMessageServiceServer
db *pgxpool.Pool db *pgxpool.Pool
rabbitConn *amqp.Connection rabbitConn *amqp.Connection
crypto *crypto2.CryptoService
mu sync.Mutex mu sync.Mutex
logger *log.Logger logger *log.Logger
crypto *crypto.CryptoService
} }
func NewServer(db *pgxpool.Pool, rabbitConn *amqp.Connection) *server { func NewServer(db *pgxpool.Pool, rabbitConn *amqp.Connection) *server {
cryptoService, err := crypto2.NewCryptoService()
if err != nil {
log.Fatalf("Failed to initialize crypto service: %v", err)
}
return &server{ return &server{
db: db, db: db,
rabbitConn: rabbitConn, rabbitConn: rabbitConn,
crypto: cryptoService,
logger: log.New(os.Stdout, "MESSAGE_SERVICE: ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile), logger: log.New(os.Stdout, "MESSAGE_SERVICE: ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile),
} }
} }
@ -86,7 +79,7 @@ func (s *server) CreateChat(ctx context.Context, req *proto.CreateChatRequest) (
errMsg := fmt.Sprintf("One or both users don't exist: user1=%d (%t), user2=%d (%t)", errMsg := fmt.Sprintf("One or both users don't exist: user1=%d (%t), user2=%d (%t)",
user1, user1Exists, user2, user2Exists) user1, user1Exists, user2, user2Exists)
s.logger.Println(errMsg) s.logger.Println(errMsg)
return nil, fmt.Errorf("%w", errors.New(errMsg)) return nil, fmt.Errorf("%w", errors.New(errMsg)) // Оборачиваем ошибку
} }
var chat proto.Chat var chat proto.Chat
@ -139,44 +132,13 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest)
s.logger.Printf("SendMessage execution time: %v", time.Since(start)) s.logger.Printf("SendMessage execution time: %v", time.Since(start))
}(time.Now()) }(time.Now())
// Шифрование сообщения // Шифруем сообщение
s.logger.Printf("Encrypting message for chat_id=%d", req.ChatId)
encryptedContent, encryptedKey, nonce, err := s.crypto.EncryptMessage(req.Content) encryptedContent, encryptedKey, nonce, err := s.crypto.EncryptMessage(req.Content)
if err != nil { if err != nil {
s.logger.Printf("Failed to encrypt message: %v", err) s.logger.Printf("Failed to encrypt message: %v", err)
return nil, fmt.Errorf("failed to encrypt message: %v", err) return nil, fmt.Errorf("failed to encrypt message: %v", err)
} }
// Кодируем в base64 для хранения в TEXT поле
encodedContent := base64.StdEncoding.EncodeToString(encryptedContent)
encodedKey := base64.StdEncoding.EncodeToString(encryptedKey)
encodedNonce := base64.StdEncoding.EncodeToString(nonce)
s.logger.Printf("Base64 encoded: content_len=%d, key_len=%d, nonce_len=%d",
len(encodedContent), len(encodedKey), len(encodedNonce))
// Тестовая расшифровка сразу после шифрования
testDecrypted, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil {
s.logger.Printf("IMMEDIATE DECRYPTION TEST FAILED: %v", err)
return nil, fmt.Errorf("encryption/decryption test failed: %v", err)
}
if testDecrypted != req.Content {
s.logger.Printf("IMMEDIATE DECRYPTION CONTENT MISMATCH")
return nil, fmt.Errorf("encryption/decryption content mismatch")
}
s.logger.Printf("Immediate decryption test passed")
// Сохранение сессионного ключа в Vault
s.logger.Printf("Storing session key in Vault for chat_id=%d", req.ChatId)
err = s.crypto.Vault.StoreSessionKey(int(req.ChatId), 0, encryptedKey)
if err != nil {
s.logger.Printf("Failed to store session key: %v", err)
return nil, fmt.Errorf("failed to store session key: %v", err)
}
s.logger.Printf("Getting chat info for chat_id=%d", req.ChatId) s.logger.Printf("Getting chat info for chat_id=%d", req.ChatId)
var user1Id, user2Id int32 var user1Id, user2Id int32
err = s.db.QueryRow(ctx, "SELECT user1_id, user2_id FROM chats WHERE id = $1", req.ChatId).Scan(&user1Id, &user2Id) err = s.db.QueryRow(ctx, "SELECT user1_id, user2_id FROM chats WHERE id = $1", req.ChatId).Scan(&user1Id, &user2Id)
@ -201,27 +163,28 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest)
var message proto.Message var message proto.Message
var createdAt time.Time var createdAt time.Time
// Используем закодированные base64 строки
err = s.db.QueryRow(ctx, ` err = s.db.QueryRow(ctx, `
INSERT INTO messages (chat_id, sender_id, receiver_id, content, encrypted_key, nonce) INSERT INTO messages (chat_id, sender_id, receiver_id, encrypted_content, encrypted_key, nonce)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, chat_id, sender_id, receiver_id, status, created_at RETURNING id, chat_id, sender_id, receiver_id, status, created_at
`, req.ChatId, req.SenderId, receiverId, encodedContent, encodedKey, encodedNonce).Scan( `, req.ChatId, req.SenderId, receiverId, encryptedContent, encryptedKey, nonce).Scan(
&message.Id, &message.ChatId, &message.SenderId, &message.ReceiverId, &message.Status, &createdAt, &message.Id, &message.ChatId, &message.SenderId, &message.ReceiverId, &message.Status, &createdAt,
) )
if err != nil { if err != nil {
s.logger.Printf("Failed to insert message: %v", err) s.logger.Printf("Failed to insert encrypted message: %v", err)
return nil, err return nil, err
} }
message.CreatedAt = timestamppb.New(createdAt) message.CreatedAt = timestamppb.New(createdAt)
// Обновление сессионного ключа в Vault с правильным messageID // Для RabbitMQ отправляем расшифрованное сообщение (или зашифрованное, в зависимости от требований)
s.logger.Printf("Updating session key in Vault with actual message_id=%d", message.Id) // Здесь отправляем расшифрованное для совместимости с существующими клиентами
err = s.crypto.Vault.StoreSessionKey(int(req.ChatId), int(message.Id), encryptedKey) decryptedContent, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil { if err != nil {
s.logger.Printf("Failed to update session key with message ID: %v", err) s.logger.Printf("Failed to decrypt for RabbitMQ: %v", err)
return nil, err
} }
message.Content = decryptedContent
s.logger.Printf("Updating chat updated_at for chat_id=%d", req.ChatId) s.logger.Printf("Updating chat updated_at for chat_id=%d", req.ChatId)
_, err = s.db.Exec(ctx, `UPDATE chats SET updated_at = NOW() WHERE id = $1`, req.ChatId) _, err = s.db.Exec(ctx, `UPDATE chats SET updated_at = NOW() WHERE id = $1`, req.ChatId)
@ -230,24 +193,7 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest)
return nil, err return nil, err
} }
// Расшифровка для отправки через RabbitMQ s.logger.Printf("Publishing message to RabbitMQ for user_id=%d", receiverId)
decryptedContent, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil {
s.logger.Printf("Failed to decrypt message for RabbitMQ: %v", err)
return nil, fmt.Errorf("failed to decrypt message: %v", err)
}
rabbitMsg := proto.Message{
Id: message.Id,
ChatId: message.ChatId,
SenderId: message.SenderId,
ReceiverId: message.ReceiverId,
Content: decryptedContent,
Status: message.Status,
CreatedAt: message.CreatedAt,
}
s.logger.Printf("Publishing decrypted message to RabbitMQ for user_id=%d", receiverId)
var lastErr error var lastErr error
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ch, err := s.rabbitConn.Channel() ch, err := s.rabbitConn.Channel()
@ -259,7 +205,7 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest)
} }
queueName := fmt.Sprintf("user_%d_messages", receiverId) queueName := fmt.Sprintf("user_%d_messages", receiverId)
msgBytes, _ := json.Marshal(rabbitMsg) msgBytes, _ := json.Marshal(message)
s.logger.Printf("Publishing to queue %s: %s", queueName, string(msgBytes)) s.logger.Printf("Publishing to queue %s: %s", queueName, string(msgBytes))
err = ch.PublishWithContext(ctx, err = ch.PublishWithContext(ctx,
@ -298,7 +244,7 @@ func (s *server) GetChat(ctx context.Context, req *proto.GetChatRequest) (*proto
var chat proto.Chat var chat proto.Chat
var createdAt, updatedAt time.Time var createdAt, updatedAt time.Time
var lastMessageID sql.NullInt32 var lastMessageID sql.NullInt32
var lastMessageContentBase64, lastMessageEncryptedKeyBase64, lastMessageNonceBase64 sql.NullString var lastMessageEncryptedContent, lastMessageEncryptedKey, lastMessageNonce []byte
var lastMessageStatus sql.NullString var lastMessageStatus sql.NullString
var lastMessageCreatedAt sql.NullTime var lastMessageCreatedAt sql.NullTime
@ -309,18 +255,18 @@ func (s *server) GetChat(ctx context.Context, req *proto.GetChatRequest) (*proto
s.logger.Printf("Querying chat between users %d and %d", user1, user2) s.logger.Printf("Querying chat between users %d and %d", user1, user2)
err := s.db.QueryRow(ctx, ` err := s.db.QueryRow(ctx, `
SELECT c.id, c.user1_id, c.user2_id, c.created_at, c.updated_at, SELECT c.id, c.user1_id, c.user2_id, c.created_at, c.updated_at,
m.id, m.content, m.encrypted_key, m.nonce, m.status, m.created_at m.id, m.encrypted_content, m.encrypted_key, m.nonce, m.status, m.created_at
FROM chats c FROM chats c
LEFT JOIN messages m ON m.id = ( LEFT JOIN messages m ON m.id = (
SELECT id FROM messages WHERE chat_id = c.id SELECT id FROM messages WHERE chat_id = c.id
ORDER BY created_at DESC LIMIT 1 ORDER BY created_at DESC LIMIT 1
) )
WHERE c.user1_id = $1 AND c.user2_id = $2 WHERE c.user1_id = $1 AND c.user2_id = $2
`, user1, user2).Scan( `, user1, user2).Scan(
&chat.Id, &chat.User1Id, &chat.User2Id, &createdAt, &updatedAt, &chat.Id, &chat.User1Id, &chat.User2Id, &createdAt, &updatedAt,
&lastMessageID, &lastMessageContentBase64, &lastMessageEncryptedKeyBase64, &lastMessageID, &lastMessageEncryptedContent, &lastMessageEncryptedKey,
&lastMessageNonceBase64, &lastMessageStatus, &lastMessageCreatedAt, &lastMessageNonce, &lastMessageStatus, &lastMessageCreatedAt,
) )
if err != nil { if err != nil {
s.logger.Printf("Failed to get chat: %v", err) s.logger.Printf("Failed to get chat: %v", err)
@ -332,39 +278,15 @@ func (s *server) GetChat(ctx context.Context, req *proto.GetChatRequest) (*proto
if lastMessageID.Valid { if lastMessageID.Valid {
// Расшифровываем последнее сообщение // Расшифровываем последнее сообщение
var decryptedContent string decryptedContent, err := s.crypto.DecryptMessage(
lastMessageEncryptedContent,
if lastMessageContentBase64.Valid && lastMessageEncryptedKeyBase64.Valid && lastMessageNonceBase64.Valid { lastMessageEncryptedKey,
// Декодируем из base64 lastMessageNonce,
encryptedContent, err := base64.StdEncoding.DecodeString(lastMessageContentBase64.String) )
if err != nil { if err != nil {
s.logger.Printf("Failed to decode content for last message %d: %v", lastMessageID.Int32, err) s.logger.Printf("Failed to decrypt last message: %v", err)
decryptedContent = "[decode error: content]" // Можно вернуть ошибку или пустое сообщение
} else { decryptedContent = "[не удалось расшифровать]"
encryptedKey, err := base64.StdEncoding.DecodeString(lastMessageEncryptedKeyBase64.String)
if err != nil {
s.logger.Printf("Failed to decode key for last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[decode error: key]"
} else {
nonce, err := base64.StdEncoding.DecodeString(lastMessageNonceBase64.String)
if err != nil {
s.logger.Printf("Failed to decode nonce for last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[decode error: nonce]"
} else {
// Расшифровка сообщения
decrypted, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil {
s.logger.Printf("Failed to decrypt last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[encrypted message]"
} else {
decryptedContent = decrypted
s.logger.Printf("Successfully decrypted last message for chat %d", chat.Id)
}
}
}
}
} else {
decryptedContent = "[no message content]"
} }
chat.LastMessage = &proto.Message{ chat.LastMessage = &proto.Message{
@ -390,12 +312,12 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages
s.logger.Printf("Querying messages for chat_id=%d, limit=%d, offset=%d", req.ChatId, req.Limit, req.Offset) s.logger.Printf("Querying messages for chat_id=%d, limit=%d, offset=%d", req.ChatId, req.Limit, req.Offset)
rows, err := s.db.Query(ctx, ` rows, err := s.db.Query(ctx, `
SELECT id, chat_id, sender_id, receiver_id, content, encrypted_key, nonce, status, created_at SELECT id, chat_id, sender_id, receiver_id, encrypted_content, encrypted_key, nonce, status, created_at
FROM messages FROM messages
WHERE chat_id = $1 WHERE chat_id = $1
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT $2 OFFSET $3 LIMIT $2 OFFSET $3
`, req.ChatId, req.Limit, req.Offset) `, req.ChatId, req.Limit, req.Offset)
if err != nil { if err != nil {
s.logger.Printf("Failed to query messages: %v", err) s.logger.Printf("Failed to query messages: %v", err)
return nil, err return nil, err
@ -406,69 +328,25 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages
for rows.Next() { for rows.Next() {
var msg proto.Message var msg proto.Message
var createdAt time.Time var createdAt time.Time
var encryptedKeyBase64, nonceBase64, encryptedContentBase64 string var encryptedContent, encryptedKey, nonce []byte
err := rows.Scan( err := rows.Scan(
&msg.Id, &msg.ChatId, &msg.SenderId, &msg.ReceiverId, &msg.Id, &msg.ChatId, &msg.SenderId, &msg.ReceiverId,
&encryptedContentBase64, &encryptedKeyBase64, &nonceBase64, &msg.Status, &createdAt, &encryptedContent, &encryptedKey, &nonce,
&msg.Status, &createdAt,
) )
if err != nil { if err != nil {
s.logger.Printf("Failed to scan message row: %v", err) s.logger.Printf("Failed to scan message row: %v", err)
return nil, err return nil, err
} }
s.logger.Printf("Processing message %d: content_len=%d, key_len=%d, nonce_len=%d", // Расшифровываем сообщение
msg.Id, len(encryptedContentBase64), len(encryptedKeyBase64), len(nonceBase64))
// Декодируем из base64
encryptedContent, err := base64.StdEncoding.DecodeString(encryptedContentBase64)
if err != nil {
s.logger.Printf("Failed to decode content for message %d: %v", msg.Id, err)
msg.Content = "[base64 decode error: content]"
continue
}
encryptedKey, err := base64.StdEncoding.DecodeString(encryptedKeyBase64)
if err != nil {
s.logger.Printf("Failed to decode key for message %d: %v", msg.Id, err)
msg.Content = "[base64 decode error: key]"
continue
}
nonce, err := base64.StdEncoding.DecodeString(nonceBase64)
if err != nil {
s.logger.Printf("Failed to decode nonce for message %d: %v", msg.Id, err)
msg.Content = "[base64 decode error: nonce]"
continue
}
// Проверяем что данные не пустые после декодирования
if len(encryptedContent) == 0 {
s.logger.Printf("Empty content after base64 decode for message %d", msg.Id)
msg.Content = "[empty content after decode]"
continue
}
if len(encryptedKey) == 0 {
s.logger.Printf("Empty key after base64 decode for message %d", msg.Id)
msg.Content = "[empty key after decode]"
continue
}
if len(nonce) == 0 {
s.logger.Printf("Empty nonce after base64 decode for message %d", msg.Id)
msg.Content = "[empty nonce after decode]"
continue
}
// Расшифровка сообщения
decryptedContent, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce) decryptedContent, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil { if err != nil {
s.logger.Printf("Failed to decrypt message %d: %v", msg.Id, err) s.logger.Printf("Failed to decrypt message %d: %v", msg.Id, err)
s.logger.Printf("Decryption failed details: content_len=%d, key_len=%d, nonce_len=%d", msg.Content = "[не удалось расшифровать]"
len(encryptedContent), len(encryptedKey), len(nonce))
msg.Content = "[decryption failed: " + err.Error() + "]"
} else { } else {
msg.Content = decryptedContent msg.Content = decryptedContent
s.logger.Printf("Successfully decrypted message %d", msg.Id)
} }
msg.CreatedAt = timestamppb.New(createdAt) msg.CreatedAt = timestamppb.New(createdAt)
@ -480,7 +358,7 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages
return nil, err return nil, err
} }
s.logger.Printf("Retrieved and decrypted %d messages for chat_id=%d", len(messages), req.ChatId) s.logger.Printf("Retrieved %d messages for chat_id=%d", len(messages), req.ChatId)
resp := &proto.MessagesResponse{Messages: messages} resp := &proto.MessagesResponse{Messages: messages}
s.logResponse("GetChatMessages", resp, nil) s.logResponse("GetChatMessages", resp, nil)
return resp, nil return resp, nil
@ -494,16 +372,16 @@ func (s *server) GetUserChats(ctx context.Context, req *proto.GetUserChatsReques
s.logger.Printf("Querying chats for user_id=%d", req.UserId) s.logger.Printf("Querying chats for user_id=%d", req.UserId)
rows, err := s.db.Query(ctx, ` rows, err := s.db.Query(ctx, `
SELECT c.id, c.user1_id, c.user2_id, c.created_at, c.updated_at, SELECT c.id, c.user1_id, c.user2_id, c.created_at, c.updated_at,
m.id, m.content, m.encrypted_key, m.nonce, m.status, m.created_at m.id, m.content, m.status, m.created_at
FROM chats c FROM chats c
LEFT JOIN messages m ON m.id = ( LEFT JOIN messages m ON m.id = (
SELECT id FROM messages WHERE chat_id = c.id SELECT id FROM messages WHERE chat_id = c.id
ORDER BY created_at DESC LIMIT 1 ORDER BY created_at DESC LIMIT 1
) )
WHERE c.user1_id = $1 OR c.user2_id = $1 WHERE c.user1_id = $1 OR c.user2_id = $1
ORDER BY c.updated_at DESC ORDER BY c.updated_at DESC
`, req.UserId) `, req.UserId)
if err != nil { if err != nil {
s.logger.Printf("Failed to query user chats: %v", err) s.logger.Printf("Failed to query user chats: %v", err)
return nil, err return nil, err
@ -515,14 +393,13 @@ func (s *server) GetUserChats(ctx context.Context, req *proto.GetUserChatsReques
var chat proto.Chat var chat proto.Chat
var createdAt, updatedAt time.Time var createdAt, updatedAt time.Time
var lastMessageID sql.NullInt32 var lastMessageID sql.NullInt32
var lastMessageContentBase64, lastMessageEncryptedKeyBase64, lastMessageNonceBase64 sql.NullString var lastMessageContent sql.NullString
var lastMessageStatus sql.NullString var lastMessageStatus sql.NullString
var lastMessageCreatedAt sql.NullTime var lastMessageCreatedAt sql.NullTime
err := rows.Scan( err := rows.Scan(
&chat.Id, &chat.User1Id, &chat.User2Id, &createdAt, &updatedAt, &chat.Id, &chat.User1Id, &chat.User2Id, &createdAt, &updatedAt,
&lastMessageID, &lastMessageContentBase64, &lastMessageEncryptedKeyBase64, &lastMessageID, &lastMessageContent, &lastMessageStatus, &lastMessageCreatedAt,
&lastMessageNonceBase64, &lastMessageStatus, &lastMessageCreatedAt,
) )
if err != nil { if err != nil {
s.logger.Printf("Failed to scan chat row: %v", err) s.logger.Printf("Failed to scan chat row: %v", err)
@ -533,46 +410,10 @@ func (s *server) GetUserChats(ctx context.Context, req *proto.GetUserChatsReques
chat.UpdatedAt = timestamppb.New(updatedAt) chat.UpdatedAt = timestamppb.New(updatedAt)
if lastMessageID.Valid { if lastMessageID.Valid {
// Расшифровываем последнее сообщение
var decryptedContent string
if lastMessageContentBase64.Valid && lastMessageEncryptedKeyBase64.Valid && lastMessageNonceBase64.Valid {
// Декодируем из base64
encryptedContent, err := base64.StdEncoding.DecodeString(lastMessageContentBase64.String)
if err != nil {
s.logger.Printf("Failed to decode content for last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[decode error: content]"
} else {
encryptedKey, err := base64.StdEncoding.DecodeString(lastMessageEncryptedKeyBase64.String)
if err != nil {
s.logger.Printf("Failed to decode key for last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[decode error: key]"
} else {
nonce, err := base64.StdEncoding.DecodeString(lastMessageNonceBase64.String)
if err != nil {
s.logger.Printf("Failed to decode nonce for last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[decode error: nonce]"
} else {
// Расшифровка сообщения
decrypted, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce)
if err != nil {
s.logger.Printf("Failed to decrypt last message %d: %v", lastMessageID.Int32, err)
decryptedContent = "[encrypted message]"
} else {
decryptedContent = decrypted
s.logger.Printf("Successfully decrypted last message for chat %d", chat.Id)
}
}
}
}
} else {
decryptedContent = "[no message content]"
}
chat.LastMessage = &proto.Message{ chat.LastMessage = &proto.Message{
Id: lastMessageID.Int32, Id: lastMessageID.Int32,
ChatId: chat.Id, ChatId: chat.Id,
Content: decryptedContent, Content: lastMessageContent.String,
Status: lastMessageStatus.String, Status: lastMessageStatus.String,
CreatedAt: timestamppb.New(lastMessageCreatedAt.Time), CreatedAt: timestamppb.New(lastMessageCreatedAt.Time),
} }