A tiny distributed chat built with Node.js and Express for a Distributed Systems (SD) assignment. Each instance runs an HTTP server and a simple CLI so peers can register each other and exchange encrypted messages.
This app runs a local HTTP server and an interactive CLI menu:
- You start the server on a machine/port.
- You “Add IP” to register a remote peer. A mutual registration happens automatically: the remote stores your identity, and you store the remote’s identity.
- You can then send messages to any registered peer. Messages are encrypted end-to-end with a shared key derived from both peers’ identity objects.
- You can list messages received, filter by user, and manage the local peer list.
Key files:
src/server.js: HTTP server, routes, CLI loop bootstrapsrc/client.js: CLI flow and actions for each menu optionsrc/utils.js: utilities (I/O, HTTP calls, cryptography, formatting)
- Node.js 14+ (CommonJS project)
npm installStart the server and open the CLI:
npm startFor auto-reload during development:
npm run devBy default, the server listens on port 3000 (see src/server.js).
When the server starts, you’ll see a menu with options:
- List IPs — show the known peer list
- Add IP — register a new peer (format:
127.0.0.1:3001) - Remove IP — remove a peer by index
- List messages — show all messages or filter by user name
- Send message — pick a peer and send an encrypted message
- Exit — terminate the app
Notes:
- “Add IP” performs a two-way handshake using the
/cadastroendpoint: the remote stores your identity; you store the remote’s identity (from the response). - “Send message” uses the
/mensagemendpoint on the selected peer.
Each instance defines its identity in src/server.js as meusDados:
var meusDados = {
ip: '127.0.0.1',
nome: 'Jhonatan',
porta: '3000',
chave: 'chaveUltraSecreta'
}ip: your host/IP address reachable by peersnome: a human-friendly nameporta: the port your server listens on (string)chave: a shared identifier used to look up the sender on message receipt
Important: The chave value is used for sender lookup (basic authentication), not as the actual encryption key.
All endpoints accept/return plain text or JSON as noted.
- POST
/cadastro
- Request headers:
ip,nome,porta,chave(describing the caller) - Behavior: the receiver stores the caller’s identity in its local list and responds with its own identity (
meusDados) as JSON - Response:
200 OKand JSON body{ ip, nome, porta, chave }
- POST
/mensagem
- Request headers:
chave(sender’schave) - Request body: AES-encrypted message (text)
- Behavior: the receiver verifies the sender by matching
chavewith a known peer; if found, it decrypts and stores both the plaintext and ciphertext - Responses:
200 OKwithOkon success500withUsuário não cadastradoif the sender is unknown
Messages are encrypted symmetrically using crypto-js AES. The encryption key is derived from the two peers’ identity objects:
- Encryption:
AES.encrypt(message, JSON.stringify([sender, receiver])) - Decryption:
AES.decrypt(ciphertext, JSON.stringify([sender, receiver]))
Where sender and receiver are full identity objects as exchanged via /cadastro.
Security note: This is an academic/demo scheme. Deriving a key from JSON-serialized identity objects is not secure for production. There is no integrity/authentication of headers beyond the simple chave lookup.
-
Identity (
meusDados/ peer entry){ ip: string; nome: string; porta: string; chave: string }
-
Message entry (stored on receiver)
{ ip: string; nome: string; porta: string; mensagem: string; mensagemCriptografada: string }
-
Peer list:
Array<Identity> -
Message list:
Array<MessageEntry>
-
Prepare two instances:
- Peer A: in
src/server.js, keepporta = 3000, setmeusDados.nome,meusDados.chaveas you like. - Peer B: duplicate the project (or run on another machine) and change
portaandmeusDados.porta(e.g.,3001), updatemeusDados.nomeandmeusDados.chave.
- Peer A: in
-
Start both:
- On each, run
npm start.
- On each, run
-
On Peer A, choose “Add IP” and enter
127.0.0.1:3001(or the Peer B host:port).- Peer B will log the registration.
- Peer A now has Peer B in its list.
-
Send messages:
- On Peer A, choose “Send message”, type the content, select Peer B by index.
- Peer B will receive, decrypt, and store the message.
-
Inspect messages on the receiver via “List messages”. Filter by user or show all.
- To change the listening port, edit
portainsrc/server.jsand keepmeusDados.portain sync. - Ensure
meusDados.ipis reachable by the remote peer (e.g., LAN IP, not just127.0.0.1across machines). - The menu uses standard input; run each instance in its own terminal.
-
“Usuário não cadastrado” on
/mensagem:- Register the sender on the receiver using “Add IP” (only one side needs to add; the handshake stores identities on both sides).
- Verify
chavematches the stored identity.
-
“Falha ao enviar a mensagem”:
- Check the IP/port are correct and reachable from the sender.
- Confirm the remote server is running and not blocked by a firewall.
-
No messages shown:
- Ensure you sent to the correct peer index.
- Use “List messages” and enter
0to see all.
👤 Augusto Amaral, Matheus Dias, Leonardo Nascimento, Bruno Palmeira de Oliveira
- GitHub: @AugustoAmaral
- GitHub: @Math5oul
- GitHub: @Leonardondm
- GitHub: @Brunopalm
ISC — see the badge above. If you add a LICENSE file, reference it here.
If this project helped you, give it a ⭐️!