first commit
This commit is contained in:
commit
45d78dda8e
6
.env
Normal file
6
.env
Normal 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
15
package.json
Normal 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
BIN
prisma/dev.db
Normal file
Binary file not shown.
39
prisma/migrations/20241214175430_/migration.sql
Normal file
39
prisma/migrations/20241214175430_/migration.sql
Normal 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");
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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
44
prisma/schema.prisma
Normal 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
315
server.js
Normal 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");
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user