diff --git a/package-lock.json b/package-lock.json index bf1ddaa..5f833f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.17", + "discreetly-interfaces": "^0.1.22", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -1905,9 +1905,9 @@ "integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.17.tgz", - "integrity": "sha512-sXS/f/oYKZMQDR4tJSmuBfyCyE4f5iO6PwEKEHAMCDH7AklQphKR9RPnpLa4DFBCIznayzKipZxmT8zBHOSk6w==", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.22.tgz", + "integrity": "sha512-p7HuKOE0Zan3Nf3Rg6auzsxNeKrtH4GMRoSMhsb+6YGO9AlF6ffnlHV4L4SSxLbJr2XInGEf0TUzsKkXwkQKoQ==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index f190e1c..9b2a0ca 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.17", + "discreetly-interfaces": "^0.1.22", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a41e42b..0c42a1b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,18 +10,27 @@ datasource db { url = env("DATABASE_URL") } +enum RoomMembershipType { + IDENTITY_LIST + RLN_CONTRACT + BANDADA +} + model Rooms { - id String @id @default(auto()) @map("_id") @db.ObjectId - roomId String @unique + id String @id @default(auto()) @map("_id") @db.ObjectId + roomId String @unique name String - rateLimit Int @default(1000) // epoch length in ms - banRateLimit Int @default(1000000) // starting number of epochs banned for - userMessageLimit Int @default(1) // per epoch - identities String[] @default([]) + rateLimit Int @default(1000) // epoch length in ms + banRateLimit Int @default(1000000) // starting number of epochs banned for + userMessageLimit Int @default(1) // per epoch + membershipType RoomMembershipType @default(IDENTITY_LIST) + identities String[] @default([]) + contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" + bandadaAddress String? // BANDADA as "url:groupID" epochs Epoch[] messages Messages[] - claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) - claimCodeIds String[] @default([]) @db.ObjectId + claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) + claimCodeIds String[] @default([]) @db.ObjectId } model ClaimCodes { @@ -33,15 +42,15 @@ model ClaimCodes { } model Messages { - id String @id @default(auto()) @map("_id") @db.ObjectId - internalNullifier String - message String - timeStamp DateTime @default(now()) - roomId String - room Rooms @relation(fields: [roomId], references: [roomId]) - proof String - epoch Epoch? @relation(fields: [epochId], references: [id]) - epochId String? @db.ObjectId + id String @id @default(auto()) @map("_id") @db.ObjectId + messageId String // Internal Nullifier + message String + timeStamp DateTime @default(now()) + roomId String + room Rooms @relation(fields: [roomId], references: [roomId]) + proof String + epoch Epoch? @relation(fields: [epochId], references: [id]) + epochId String? @db.ObjectId } model Epoch { diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 455f1a8..33c865a 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -11,7 +11,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro const timestamp = Date.now(); const rateLimit = room.rateLimit ? room.rateLimit : 1000; const currentEpoch = Math.floor(timestamp / rateLimit); - const rlnIdentifier = BigInt(msg.room); + const rlnIdentifier = BigInt(msg.roomId); const msgHash = str2BigInt(msg.message); // Check that the epoch falls within the range for the room const epoch = BigInt(msg.epoch); diff --git a/src/data/db.ts b/src/data/db.ts index c7756d2..0f2060a 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -10,15 +10,31 @@ import type { ClaimCodeT } from 'discreetly-claimcodes'; const prisma = new PrismaClient(); +interface CodeStatus { + claimed: boolean; + roomIds: string[]; +} + +interface ClaimCode { + roomIds: string[]; +} + export function getRoomByID(id: string): Promise { return prisma.rooms .findUnique({ where: { roomId: id + }, + select: { + id: true, + roomId: true, + name: true, + identities: true, + rateLimit: true, + userMessageLimit: true } }) .then((room) => { - //TODO NEED TO FILTER OUT CLAIMCODE REFERENCES return room; }) .catch((err) => { @@ -28,7 +44,10 @@ export function getRoomByID(id: string): Promise { } export function getRoomsByIdentity(identity: string): RoomI[] { - // TODO Need to create a system here where the client needs to provide a proof they know the secrets to some Identity Commitment with a unix epoch time stamp to prevent replay attacks + /* TODO Need to create a system here where the client needs to provide a + proof they know the secrets to some Identity Commitment with a unix epoch + time stamp to prevent replay attacks + */ prisma.rooms .findMany({ where: { @@ -114,3 +133,33 @@ export function createRoom( .catch((err) => console.error(err)); return false; } + +export function findClaimCode(code: string): Promise { + return prisma.claimCodes.findUnique({ + where: { claimcode: code } + }); +} + +export function updateClaimCode(code: string): Promise { + return prisma.claimCodes.update({ + where: { claimcode: code }, + data: { claimed: true } + }); +} + +export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { + return prisma.rooms.updateMany({ + where: { id: { in: roomIds } }, + data: { + identities: { + push: idc + } + } + }); +} + +export function findUpdatedRooms(roomIds: string[]): Promise { + return prisma.rooms.findMany({ + where: { id: { in: roomIds } } + }); +} diff --git a/src/data/messages.ts b/src/data/messages.ts new file mode 100644 index 0000000..82cb70b --- /dev/null +++ b/src/data/messages.ts @@ -0,0 +1,48 @@ +import { getRoomByID } from './db'; +import { PrismaClient } from '@prisma/client'; +import { MessageI } from 'discreetly-interfaces'; + +const prisma = new PrismaClient(); + +function updateRoom(roomId: string, message: MessageI): Promise { + return prisma.rooms.update({ + where: { + roomId: roomId + }, + data: { + epochs: { + create: { + epoch: +message.epoch.toString(), + messages: { + create: { + message: message.message, + messageId: message.messageId, + proof: JSON.stringify(message.proof), + roomId: roomId + } + } + } + } + } + }); +} + +export function createMessage(roomId: string, message: MessageI) { + getRoomByID(roomId) + .then((room) => { + if (room) { + updateRoom(roomId, message) + .then((roomToUpdate) => { + console.log(roomToUpdate); + }) + .catch((error) => { + console.error(`Error updating room: ${error}`); + }); + } else { + console.log('Room not found'); + } + }) + .catch((error) => { + console.error(`Error getting room: ${error}`); + }); +} diff --git a/src/data/mock.ts b/src/data/mock.ts index d9f9540..6cf64d7 100644 --- a/src/data/mock.ts +++ b/src/data/mock.ts @@ -41,7 +41,7 @@ export default function Mock(io: SocketIOServer) { setInterval(() => { const message: MessageI = { id: faker.number.bigInt().toString(), - room: BigInt('20945462742745557191488383979949684808523754877925170533224967224808050898610'), + roomId: BigInt('20945462742745557191488383979949684808523754877925170533224967224808050898610'), message: picker.pick(), timestamp: Date.now().toString(), epoch: Math.floor(Date.now() / 10000) diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 526e55d..0e98784 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -1,8 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type { Express, RequestHandler } from 'express'; import { PrismaClient } from '@prisma/client'; import { serverConfig } from '../config/serverConfig'; import { pp } from '../utils.js'; -import { createRoom, getRoomByID, getRoomsByIdentity } from '../data/db'; +import { + getRoomByID, + getRoomsByIdentity, + findClaimCode, + updateClaimCode, + updateRoomIdentities, + findUpdatedRooms, + createRoom +} from '../data/db'; import { RoomI } from 'discreetly-interfaces'; export function initEndpoints(app: Express, adminAuth: RequestHandler) { @@ -33,75 +42,27 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); app.post('/join', (req, res) => { - interface JoinRequestBody { - code: string; - idc: string; - } - console.log(req.body); - const { code, idc } = req.body as JoinRequestBody; + const { code, idc }: { code: string; idc: string } = req.body; - pp(`Express[/join]: claiming code: ${code}`); - - prisma.claimCodes - .findUnique({ - where: { - claimcode: code - } - }) - .then((codeStatus: { claimed: boolean; roomIds: string[] }) => { - console.log(codeStatus); - if (codeStatus.claimed === false) { - prisma.claimCodes - .update({ - where: { - claimcode: code - }, - data: { - claimed: true - } - }) - .then((claimCode: { roomIds: string[] }) => { - const roomIds = claimCode.roomIds.map((room) => room); - prisma.rooms - .updateMany({ - where: { - roomId: { - in: roomIds - } - }, - data: { - identities: { - push: idc - } - } - }) - .then(async () => { - // return the room name of all the rooms that were updated - const updatedRooms = await prisma.rooms.findMany({ - where: { - id: { - in: roomIds - } - } - }); - res - .status(200) - .json({ status: 'valid', roomIds: updatedRooms.map((room) => room.roomId) }); - }) - .catch((err) => { - console.error(err); - res.status(500).json({ error: 'Internal Server Error' }); + findClaimCode(code) + .then((codeStatus) => { + if (codeStatus && codeStatus.claimed === false) { + return updateClaimCode(code).then((claimCode) => { + const roomIds = claimCode.roomIds.map((room) => room); + return updateRoomIdentities(idc, roomIds).then(() => { + return findUpdatedRooms(roomIds).then((updatedRooms: RoomI[]) => { + return res.status(200).json({ + status: 'valid', + roomIds: updatedRooms.map((room) => room.roomId as string) }); - }) - .catch((err) => { - console.error(err); - res.status(500).json({ error: 'Internal Server Error' }); + }); }); + }); } else { res.status(400).json({ message: 'Claim code already used' }); } }) - .catch((err) => { + .catch((err: Error) => { console.error(err); res.status(500).json({ error: 'Internal Server Error' }); }); diff --git a/src/server.ts b/src/server.ts index 22af11d..3681381 100644 --- a/src/server.ts +++ b/src/server.ts @@ -17,7 +17,6 @@ import { listEndpoints } from './endpoints/utils'; const app = express(); const socket_server = new Server(app); - shim(); app.use(express.json()); diff --git a/src/websockets/index.ts b/src/websockets/index.ts index 26f9f2f..2500cf9 100644 --- a/src/websockets/index.ts +++ b/src/websockets/index.ts @@ -3,6 +3,7 @@ import { Socket, Server as SocketIOServer } from 'socket.io'; import verifyProof from '../crypto/verifier'; import { getRoomByID } from '../data/db'; import { pp } from '../utils'; +import { createMessage } from '../data/messages'; const userCount: { [key: string]: number; @@ -14,25 +15,26 @@ export function websocketSetup(io: SocketIOServer) { socket.on('validateMessage', (msg: MessageI) => { pp({ 'VALIDATING MESSAGE ID': msg.id.slice(0, 11), 'MSG:': msg.message }); - getRoomByID(msg.room.toString()) + let valid: boolean; + getRoomByID(String(msg.roomId)) .then((room: RoomI) => { if (!room) { pp('INVALID ROOM', 'warn'); return; } verifyProof(msg, room) - .then((valid) => { - if (valid) { - // TODO STORE MESSAGE HERE - io.emit('messageBroadcast', msg); - } else { - pp('INVALID MESSAGE', 'warn'); - return; - } + .then((v) => { + valid = v; + createMessage(String(msg.roomId), msg); + io.emit('messageBroadcast', msg); }) .catch((err) => { err; }); + if (!valid) { + pp('INVALID MESSAGE', 'warn'); + return; + } }) .catch((err) => pp(err, 'error')); });