A full-stack real-time chat application with AES-256 end-to-end encryption, anonymous authentication, and transient encrypted messaging.
π Live Demo: https://downpourchat.netlify.app
π₯οΈ Server: https://downpourchat-server.onrender.com
π¦ Repo: https://github.com/Ankit-2039/DownpourChat
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, React Router v6 |
| Backend | Node.js, Express.js |
| Database | MongoDB Atlas, Mongoose |
| Real-time | Socket.IO v4 |
| Encryption | Web Crypto API (AES-256-CBC, PBKDF2) |
| Sessions | express-session + connect-mongo |
| Hosting | Netlify (client), Render (server) |
- π AES-256 E2E Encryption β server never sees plaintext
- π€ Anonymous authentication β no login, no registration
- β‘ Real-time messaging via Socket.IO WebSockets
- βοΈ Typing indicators with multi-user support
- π Chat transcripts β persistent encrypted history
- πΎ Export chat β download decrypted transcript as JSON on leave
- β³ Transient rooms β auto-expire after 24 hours
root/
βββ start.bat # Windows: double-click to launch everything
βββ netlify.toml # Netlify SPA routing config
βββ package.json # Root: concurrently runs server + client
βββ server/
β βββ config/db.js
β βββ models/ # Room.js, Message.js
β βββ routes/ # roomRoutes.js, transcriptRoutes.js
β βββ socket/ # socketHandler.js
β βββ middleware/ # sessionMiddleware.js
β βββ app.js
β βββ server.js
β βββ .env.example
βββ client/
βββ src/
β βββ crypto/ # cryptoUtils.js (PBKDF2 + AES-256)
β βββ socket/ # socketClient.js
β βββ context/ # ChatContext.jsx
β βββ hooks/ # useSocket.js, useEncryption.js
β βββ components/ # JoinRoom, ChatWindow, MessageList, MessageInput, TypingIndicator
β βββ pages/ # Home.jsx, Chat.jsx
β βββ App.jsx
β βββ index.css
βββ index.html
βββ vite.config.js
βββ .env.example
- Node.js >= 18
- MongoDB running locally, or a MongoDB Atlas URI
git clone https://github.com/Ankit-2039/DownpourChat.git
cd DownpourChatServer β server/.env:
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/encrypted-chat
SESSION_SECRET=replace_with_a_strong_random_string
CLIENT_ORIGIN=http://localhost:5173
NODE_ENV=developmentClient β client/.env:
VITE_SERVER_URL=http://localhost:5000Windows β double-click start.bat
Or from terminal:
npm install # installs concurrently at root
npm run dev # starts both server + client| Service | URL |
|---|---|
| Client | http://localhost:5173 |
| Server | http://localhost:5000 |
| Service | Platform | URL |
|---|---|---|
| Client | Netlify | https://downpourchat.netlify.app |
| Server | Render | https://downpourchat-server.onrender.com |
| Database | MongoDB Atlas | Managed cloud |
Render (server):
MONGO_URI=<your Atlas URI>
SESSION_SECRET=<strong random string>
CLIENT_ORIGIN=https://downpourchat.netlify.app
NODE_ENV=productionNetlify (client):
VITE_SERVER_URL=https://downpourchat-server.onrender.comNote: Render free tier spins down after 15 min inactivity. First request may take ~30s to cold start.
User A Server User B
| | |
| passphrase + roomId | |
|βββΊ PBKDF2 βββΊ AES-256 Key | passphrase + roomId |
| (in memory only) | AES-256 Key βββ PBKDF2 βββ|
| | (in memory only) |
| encrypt(plaintext) βββββββββββΊ store ciphertext βββββββββββββΊ|
| | (server sees NO plaintext) |
| | decrypt() |
| | plaintext β |
- Key Derivation β Client derives AES-256 key via
PBKDF2(passphrase, roomId, 100000 iterations, SHA-256). Key never leaves the browser. - Encrypt before send β Messages are encrypted client-side before emitting via Socket.IO. Server only receives
{ ciphertext, iv }. - Server is blind β Server stores and relays only ciphertext. No plaintext field exists in the database.
- Decrypt on receive β Incoming ciphertext is decrypted immediately using the in-memory key. Wrong passphrase renders
[decryption failed]. - Transcript β Chat history is fetched from the REST API and decrypted client-side on mount.
- On first request,
attachAnonIdmiddleware assigns auuid v4asreq.session.anonId - Stored in MongoDB via
connect-mongo, shared with Socket.IO - No login, no registration, no PII stored
- Rooms use UUID v4 identifiers with a 24-hour TTL β auto-expire in MongoDB
- Joining validates room existence before key derivation proceeds
- On leave, users are prompted to download chat as JSON
- File is decrypted client-side before export β server never involved
- System messages excluded from export
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/rooms/create |
Create a new room, returns roomId |
| POST | /api/rooms/join |
Validate room exists |
| GET | /api/transcript/:roomId |
Fetch last 100 ciphertexts |
| GET | /health |
Health check |
Client β Server
| Event | Payload | Description |
|---|---|---|
message:send |
{ ciphertext, iv } |
Send encrypted message |
typing:start |
β | User started typing |
typing:stop |
β | User stopped typing |
Server β Client
| Event | Payload | Description |
|---|---|---|
message:receive |
{ _id, username, ciphertext, iv, createdAt } |
Broadcast encrypted msg |
typing:update |
{ typingUsers: string[] } |
Current typers in room |
user:joined |
{ username } |
User joined notification |
user:left |
{ username } |
User left notification |
- Passphrase strength directly determines encryption strength
- Page refresh clears the in-memory
CryptoKeyβ users must re-enter passphrase (intentional) - Usernames stored as plaintext metadata only β not message content
- Sessions use
httpOnlycookies withsecureflag in production - Room IDs are UUID v4 β share only via trusted channels
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 5000) |
MONGO_URI |
Yes | MongoDB connection string |
SESSION_SECRET |
Yes | Secret for signing session cookies |
CLIENT_ORIGIN |
No | CORS origin (default: localhost:5173) |
NODE_ENV |
No | development or production |
| Variable | Required | Description |
|---|---|---|
VITE_SERVER_URL |
No | Backend URL (default: localhost:5000) |
| Package | Purpose |
|---|---|
| concurrently | Run server + client together |
| Package | Purpose |
|---|---|
| express | HTTP server & routing |
| mongoose | MongoDB ODM |
| socket.io | WebSocket server |
| express-session | Session management |
| connect-mongo | MongoDB session store |
| cors | Cross-origin requests |
| uuid | Room ID + anonId generation |
| dotenv | Environment variable loading |
| Package | Purpose |
|---|---|
| react | UI framework |
| react-router-dom | Client-side routing |
| socket.io-client | WebSocket client |
| uuid | Client-side anonId generation |
No external crypto library β encryption uses the native Web Crypto API built into all modern browsers.