From 45d78dda8eede1228b28a943a6d70fc7154da4e8 Mon Sep 17 00:00:00 2001 From: ch0ic3 Date: Sun, 15 Dec 2024 06:11:44 -0800 Subject: [PATCH] first commit --- .env | 6 + package.json | 15 + prisma/dev.db | Bin 0 -> 49152 bytes .../migrations/20241214175430_/migration.sql | 39 +++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 44 +++ server.js | 315 ++++++++++++++++++ 7 files changed, 422 insertions(+) create mode 100644 .env create mode 100644 package.json create mode 100644 prisma/dev.db create mode 100644 prisma/migrations/20241214175430_/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 server.js diff --git a/.env b/.env new file mode 100644 index 0000000..23313f9 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +DATABASE_URL="file:./dev.db" +SERVER_PRIVATE_KEY=BKnARIgeuGXwc9hDWtRliYDlO8kYpko3tQIjUlJ1V5bp+so= +SERVER_PUBLIC_KEY=pGJakB2GylbfI8mPYu8KK2p/jRPG20iylNReVX82z2hVkQknHoymVU3/LDzj0t+FCpj1qxDBJZHk2qX0RXx9FdwSzCaQ86ZnjPzlqAn7jITVknNve28113fKb0/TbgLZ3QK8k7C1rXbS13WtAcnssyMvvNeeha0l+mx4Bv/jt+AZrgTT2Cg1SqbwZhY1T+p6ULjvlbgoz7X+mncIctE5WC2al4+Syo5PZxnPLacMs5HsKhKeQwH40Nzx26aP4AiU6kFPAcBD8hJB0Q5GZK71EUZmghZCEdHMfWfYy6rebON8hGWT4VHnHUfO5HTydJJfIoDHrdqdzLGuffD1vyUURcS2zyYvRx4e2v5Da1oi2qaFdRTllIcr1/LAMxTfYfrsjtHGQQ5AcPqtNmlldrPStw7CkMxlgKTG8xBeUhndoLakyLU9Enj9HDT1QYh92OxLaSTI6BlxUjC7YYhYJ179yFfswXnvm8nBZlc+Gvr6XZG33nEgYb8BmwcfvhnirRTI +prime=2IGKThw2+H+X0Mc5ZxIfOGX1b3lLc/mDB2Ne73FdoEiJRRfWaETlLUg8dAFUWn4Jxg/QSbP7+f/Q0dZbPCShAXrqWsqxScNk+XvFRTUAhqq1h82Bh4Puok2P1ke6sPR/k+LCt9uHMlt/X0TodDonFIMr87KjB9bQ+zysfPXU2G9chMjqPpY2AkInPHfaMtKtIfBslXKbhGwtaK6t0h0GGJ7W3GU8e1dzo/JgwVDnqoTryAKdoFmrpjg41naQOC5Hl59i+Ik5yEL+NCvSis7IYo55sM6cbu8B4N4wNwDKkefgElADvhSKQJirbmSpPXs5Lr7GXgRBR6t/AGrYTgahPw== +generator=Ag== + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2e0a483 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "dependencies": { + "@prisma/client": "^5.22.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "express-ws": "^5.0.2", + "js-chacha20": "^1.1.0", + "js-sha256": "^0.11.0", + "jsonwebtoken": "^9.0.2", + "prisma": "^5.22.0" + } +} diff --git a/prisma/dev.db b/prisma/dev.db new file mode 100644 index 0000000000000000000000000000000000000000..caa77dd768d16263d4759a8f6b07803a18a436f4 GIT binary patch literal 49152 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 { + const token = req.header("Authorization")?.replace("Bearer ", ""); + if (!token) return res.status(401).send("Access denied"); + + try { + const decoded = jwt.verify(token, JWT_SECRET); + req.user = decoded; + next(); + } catch (err) { + return res.status(401).send("Invalid token"); + } +}; + +// Register User +app.post("/register", async (req, res) => { + const { email, username, password, pubKey } = req.body; + + const userExists = await prisma.user.findUnique({ where: { username } }); + if (userExists) return res.status(400).send("User already exists"); + + const hashedPassword = await bcrypt.hash(password, 10); + const user = await prisma.user.create({ + data: { + email, + username, + password: hashedPassword, + pubKey, + }, + }); + + const token = jwt.sign({ username: user.username }, JWT_SECRET, { expiresIn: "1h" }); + res.send({ token }); +}); + +// Update Public Key +app.post("/user/updatePublicKey", authenticate, async (req, res) => { + const { pubKey } = req.body; + const username = req.user.username; + + if (!pubKey) return res.status(400).send("Public key is required"); + + try { + await prisma.user.update({ + where: { username }, + data: { pubKey }, + }); + + res.send({ message: "Public key updated successfully" }); + } catch (error) { + console.error("Error updating public key:", error); + res.status(500).send({ error: "Failed to update public key" }); + } +}); + + +// Login User +// Login User +app.post("/login", async (req, res) => { + const { username, password } = req.body; + + const user = await prisma.user.findUnique({ + where: { username }, + select: { password: true, pubKey: true }, + }); + if (!user) return res.status(400).send("Invalid credentials"); + + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) return res.status(400).send("Invalid credentials"); + + const token = jwt.sign({ username }, JWT_SECRET, { expiresIn: "1h" }); + res.send({ token, pubKey: user.pubKey }); +}); + + +// Fetch pending friend requests sent to the authenticated user +app.get("/friend-requests/pending", authenticate, async (req, res) => { + const username = req.user.username; + + try { + const pendingRequests = await prisma.friendRequest.findMany({ + where: { + receiverUsername: username, + status: "pending", + }, + include: { + sender: { + select: { username: true }, + }, + }, + }); + + res.send(pendingRequests); + } catch (err) { + res.status(500).send({ error: "Failed to fetch pending friend requests" }); + } +}); + +// Send Friend Request +app.post("/friend-request", authenticate, async (req, res) => { + const { receiverUsername } = req.body; + const senderUsername = req.user.username; + + const receiver = await prisma.user.findUnique({ where: { username: receiverUsername } }); + if (!receiver) return res.status(404).send("User not found"); + + const existingRequest = await prisma.friendRequest.findFirst({ + where: { + OR: [ + { senderUsername, receiverUsername }, + { senderUsername: receiverUsername, receiverUsername: senderUsername }, + ], + }, + }); + + if (existingRequest) return res.status(400).send("Friend request already exists"); + + const request = await prisma.friendRequest.create({ + data: { + senderUsername, + receiverUsername, + status: "pending", + }, + }); + + res.send(request); +}); + +// Respond to Friend Request +app.post("/friend-request/respond", authenticate, async (req, res) => { + const { requestId, response } = req.body; + const username = req.user.username; + + const request = await prisma.friendRequest.findUnique({ where: { id: requestId } }); + if (!request || request.receiverUsername !== username) + return res.status(403).send("You cannot respond to this request"); + + const status = response === "accepted" ? "accepted" : "rejected"; + + await prisma.friendRequest.update({ + where: { id: requestId }, + data: { status }, + }); + + res.send("Response recorded"); +}); + +// Fetch Friends +app.get("/friends", authenticate, async (req, res) => { + const username = req.user.username; + + try { + const friends = await prisma.friendRequest.findMany({ + where: { + OR: [ + { senderUsername: username, status: "accepted" }, + { receiverUsername: username, status: "accepted" }, + ], + }, + include: { + sender: { select: { username: true, email: true } }, + receiver: { select: { username: true, email: true } }, + }, + }); + + const friendList = friends.map((f) => ({ + username: f.senderUsername === username ? f.receiver.username : f.sender.username, + email: f.senderUsername === username ? f.receiver.email : f.sender.email, + })); + + res.send(friendList); + } catch (err) { + res.status(500).send({ error: "Failed to fetch friends" }); + } +}); + +// Fetch Messages Between Two Users +app.get("/messages", authenticate, async (req, res) => { + const { receiverUsername } = req.query; + const username = req.user.username; + + if (!receiverUsername) { + return res.status(400).send("Receiver username is required"); + } + + const messages = await prisma.message.findMany({ + where: { + OR: [ + { senderUsername: username, receiverUsername }, + { senderUsername: receiverUsername, receiverUsername: username }, + ], + }, + orderBy: { createdAt: "asc" }, + }); + + res.send(messages); +}); + + +// Send a Message +app.post("/messages", authenticate, async (req, res) => { + const senderUsername = req.user.username; + const { encrypted, nonce, pubKey, receiverUsername } = req.body; + console.log(encrypted) + if (!encrypted || !nonce || !pubKey || !receiverUsername) { + return res.status(400).send("Missing required fields (encrypted, nonce, pubKey, receiverUsername)"); + } + + const receiver = await prisma.user.findUnique({ where: { username: receiverUsername } }); + if (!receiver) return res.status(404).send("Receiver not found"); + + const isFriend = await prisma.friendRequest.findFirst({ + where: { + OR: [ + { senderUsername, receiverUsername, status: "accepted" }, + { senderUsername: receiverUsername, receiverUsername: senderUsername, status: "accepted" }, + ], + }, + }); + + if (!isFriend) return res.status(403).send("You can only message friends"); + + const message = await prisma.message.create({ + data: { + content: encrypted, + senderUsername, + receiverUsername, + nonce: nonce, + pubKey: pubKey, + }, + }); + + res.send(message); +}); + +app.get("/test" , async(req,res) => { + const messages = await prisma.message.findMany() + res.send(messages) +}) + + +// Get User Public Key if they are friends +app.get("/user/pubkey", authenticate, async (req, res) => { + const { username: friendUsername } = req.query; // Username of the requested friend + const username = req.user.username; // Authenticated user's username + + try { + // Check if the user exists + const user = await prisma.user.findUnique({ + where: { username: friendUsername }, + select: { pubKey: true }, // Only select the public key + }); + + if (!user) { + return res.status(404).send("User not found"); + } + + // Check if the authenticated user is friends with the requested user + const isFriend = await prisma.friendRequest.findFirst({ + where: { + OR: [ + { senderUsername: username, receiverUsername: friendUsername, status: "accepted" }, + { senderUsername: friendUsername, receiverUsername: username, status: "accepted" }, + ], + }, + }); + + if (!isFriend) { + return res.status(403).send("You can only fetch the public key of your friends"); + } + + // Return the public key + res.send({ pubKey: user.pubKey }); + } catch (err) { + res.status(500).send({ error: "Failed to fetch the public key" }); + } +}); + + +// Delete Message +app.delete("/messages/:id", authenticate, async (req, res) => { + const { id } = req.params; + const username = req.user.username; + + const message = await prisma.message.findUnique({ where: { id: parseInt(id) } }); + if (!message || message.senderUsername !== username) + return res.status(403).send("You can only delete your own messages"); + + await prisma.message.delete({ where: { id: parseInt(id) } }); + res.send("Message deleted successfully"); +}); + +app.listen(4000, () => { + console.log("Server running on http://localhost:4000"); +});