API segura para app de mensagens com criptografia ponta‑a‑ponta por design (o servidor armazena somente ciphertext). Inclui registro com convite, senha + PIN, avatares, envio/edição/remoção de mensagens, respostas, anexos (imagens/vídeos), último ativo e limpeza de chat.
- Go (Gin)
- Postgres 16
- JWT (HS256)
- Argon2id para senha e PIN
- Armazenamento em disco para avatares e anexos (
/datavolume)
- Crie um arquivo
.env(opcional) com:JWT_SECRET=troque-isto
- Build e subir:
docker compose build --no-cache && docker compose up -d- A porta exposta será aleatória. Descubra com:
docker compose psProcure a coluna "Ports" (ex.: 0.0.0.0:49123->8081/tcp). Use a porta publicada (49123 no exemplo).
-
Dados persistentes:
- Postgres: volume
db_data - Avatares/Uploads: volume
app_datamontado em/data
- Postgres: volume
-
Admin pré-criado:
- usuário:
admin - senha:
admin - PIN:
0000 - invite code:
DEFAULT-INVITE-0001
- usuário:
- O cliente gera e guarda chaves privadas. O servidor recebe/apenas armazena
ciphertexte metadados (ex.:nonce). - Para anexos, recomenda-se criptografar o arquivo no cliente antes do upload.
- POST
/api/v1/auth/register{inviteCode, username, displayName, password, pin, publicKey} - POST
/api/v1/auth/login{username, password, pin} - GET
/api/v1/users/me(Bearer) - PUT
/api/v1/users/me/password{oldPassword, oldPin, newPassword, newPin} (Bearer) - POST
/api/v1/users/me/avatarmultipart formavatar(Bearer) - POST
/api/v1/chats{title?, isGroup, memberIds[]} (Bearer) - DELETE
/api/v1/chats/:id/clear(Bearer) - POST
/api/v1/messagesmultipart form com fieldschatId,ciphertext,nonce,replyToId?efiles[](Bearer) - PATCH
/api/v1/messages/:id{ciphertext, nonce} (Bearer) - DELETE
/api/v1/messages/:id(Bearer) - GET
/api/v1/media/avatar(Bearer) - GET
/api/v1/media/attachments/:id(Bearer)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var baseUrl = "http://SEU_HOST:PORTA"; // Porta aleatória obtida via `docker compose ps`
var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
var loginBody = new { username = "admin", password = "admin", pin = "0000" };
var loginRes = await client.PostAsync("/api/v1/auth/login", new StringContent(JsonSerializer.Serialize(loginBody), Encoding.UTF8, "application/json"));
loginRes.EnsureSuccessStatusCode();
var loginJson = await loginRes.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(loginJson);
var token = doc.RootElement.GetProperty("token").GetString();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);// criar chat
var createChatBody = new { isGroup = false, memberIds = new [] { "<outro-user-id>" } };
var chatRes = await client.PostAsync("/api/v1/chats", new StringContent(JsonSerializer.Serialize(createChatBody), Encoding.UTF8, "application/json"));
chatRes.EnsureSuccessStatusCode();
var chatJson = await chatRes.Content.ReadAsStringAsync();
var chatId = JsonDocument.Parse(chatJson).RootElement.GetProperty("chatId").GetString();
// enviar mensagem com anexo
var form = new MultipartFormDataContent();
form.Add(new StringContent(chatId!), "chatId");
form.Add(new StringContent("<ciphertext-base64>"), "ciphertext");
form.Add(new StringContent("<nonce-base64>"), "nonce");
// opcional: anexos
var fileContent = new ByteArrayContent(File.ReadAllBytes("/caminho/arquivo.png"));
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
form.Add(fileContent, "files", "arquivo.png");
var sendRes = await client.PostAsync("/api/v1/messages", form);
sendRes.EnsureSuccessStatusCode();var body = new { oldPassword = "admin", oldPin = "0000", newPassword = "novaSenha123", newPin = "1234" };
var res = await client.PutAsync("/api/v1/users/me/password", new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"));
res.EnsureSuccessStatusCode();- Senha e PIN com Argon2id.
- JWT expira em 24h.
- Autorização por chat para baixar anexos.
- CORS restrito a necessidades básicas. Coloque
ENABLE_TLS=truee monte/data/tls/server.crte/data/tls/server.keypara ativar HTTPS no container (também é possível terminar TLS no Cosmos).
- Portas: o container escuta internamente 8081 (HTTP). O compose publica porta aleatória no host.
- Invite code: apenas quem possuir um código válido consegue registrar.
- O servidor não tem acesso ao plaintext das mensagens; o app C# deve cifrar e decifrar no cliente.