first commit

This commit is contained in:
ch0ic3 2024-12-15 06:11:44 -08:00
commit 45d78dda8e
7 changed files with 422 additions and 0 deletions

6
.env Normal file
View File

@ -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==

15
package.json Normal file
View File

@ -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"
}
}

BIN
prisma/dev.db Normal file

Binary file not shown.

View File

@ -0,0 +1,39 @@
-- CreateTable
CREATE TABLE "FriendRequest" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"senderUsername" TEXT NOT NULL,
"receiverUsername" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FriendRequest_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "user" ("username") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "FriendRequest_receiverUsername_fkey" FOREIGN KEY ("receiverUsername") REFERENCES "user" ("username") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Message" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"content" TEXT NOT NULL,
"nonce" TEXT NOT NULL,
"senderUsername" TEXT NOT NULL,
"receiverUsername" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"pubKey" TEXT NOT NULL,
CONSTRAINT "Message_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "user" ("username") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Message_receiverUsername_fkey" FOREIGN KEY ("receiverUsername") REFERENCES "user" ("username") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "user" (
"username" TEXT NOT NULL PRIMARY KEY,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"pubKey" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
-- CreateIndex
CREATE UNIQUE INDEX "user_pubKey_key" ON "user"("pubKey");

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

44
prisma/schema.prisma Normal file
View File

@ -0,0 +1,44 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model FriendRequest {
id Int @id @default(autoincrement())
senderUsername String
receiverUsername String
status String @default("pending") // Allowed values: "pending", "accepted", "rejected"
createdAt DateTime @default(now())
sender user @relation("SentRequests", fields: [senderUsername], references: [username])
receiver user @relation("ReceivedRequests", fields: [receiverUsername], references: [username])
}
model Message {
id Int @id @default(autoincrement())
content String
nonce String
senderUsername String
receiverUsername String
createdAt DateTime @default(now())
pubKey String
sender user @relation("SentMessages", fields: [senderUsername], references: [username])
receiver user @relation("ReceivedMessages", fields: [receiverUsername], references: [username])
}
model user {
username String @id
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
pubKey String @unique
sentMessages Message[] @relation("SentMessages")
receivedMessages Message[] @relation("ReceivedMessages")
sentRequests FriendRequest[] @relation("SentRequests")
receivedRequests FriendRequest[] @relation("ReceivedRequests")
}

315
server.js Normal file
View File

@ -0,0 +1,315 @@
const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { PrismaClient } = require("@prisma/client");
const crypto = require("crypto");
const cors = require("cors");
require("dotenv").config();
const prisma = new PrismaClient();
const app = express();
app.use(express.json());
app.use(cors({
origin: "*", // Allow all origins
methods: ["GET", "POST", "PUT", "DELETE"], // Allow all methods
}));
const JWT_SECRET = "your_jwt_secret"; // Replace with a secure secret
// Authentication Middleware
const authenticate = async (req, res, next) => {
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");
});