diff --git a/package.json b/package.json index 901460e..4728e73 100644 --- a/package.json +++ b/package.json @@ -65,4 +65,4 @@ "ts-node": "^10.9.1", "typescript": "^5.1.6" } -} \ No newline at end of file +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1f83c66..878e689 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,8 +20,9 @@ model Rooms { membershipType String @default("IDENTITY_LIST") identities String[] @default([]) contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" - bandadaAddress String? - bandadaGroupId String? + bandadaAddress String? // BANDADA as "url:groupID" + bandadaGroupId String? // Bandada Group ID + bandadaAPIKey String? // Bandada API Key epochs Epoch[] messages Messages[] claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) diff --git a/prisma/seed.ts b/prisma/seed.ts index bac58de..90bbd47 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,9 +1,9 @@ import { createRoom } from '../src/data/db'; -function main() { - createRoom('1 Second Room', 1000, 1, 10).catch((err) => console.warn(err)); - createRoom('10 Second Room', 10000, 2, 10).catch((err) => console.warn(err)); - createRoom('100 Second Room', 100000, 10, 10).catch((err) => console.warn(err)); +async function main(){ + await createRoom('1 Second Room', 1000, 1, 10, 20, 'PUBLIC'); + await createRoom('10 Second Room', 10000, 2, 10, 20, 'PUBLIC'); + await createRoom('100 Second Room', 100000, 10, 10, 20, 'PUBLIC'); } -main(); +await main(); diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 98b8515..c3f633c 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -37,6 +37,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro } // Check that the message hash is correct + if (msgHash !== proof.snarkProof.publicSignals.x) { console.warn( 'Message hash incorrect:', diff --git a/src/data/db.ts b/src/data/db.ts index 7a18eb6..3b1ebd4 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -93,28 +93,90 @@ export function updateClaimCode(code: string): Promise { }); } -export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { +function sanitizeIDC(idc: string): string { + try { + const tempBigInt = BigInt(idc); + const tempString = tempBigInt.toString(); + if (idc === tempString) { + return idc; + } else { + throw new Error('Invalid IDC provided.'); + } + } catch (error) { + throw new Error('Invalid IDC provided.'); + } +} + +export async function updateRoomIdentities(idc: string, roomIds: string[]): Promise { + const identityCommitment = sanitizeIDC(idc); return prisma.rooms .findMany({ where: { id: { in: roomIds } } }) .then((rooms) => { - const roomsToUpdate = rooms - .filter((room) => !room.identities.includes(idc)) - .map((room) => room.id); - - if (roomsToUpdate) { - return prisma.rooms.updateMany({ - where: { id: { in: roomsToUpdate } }, - data: { identities: { push: idc } } - }); - } + addIdentityToIdentityListRooms(rooms, identityCommitment); + addIdentityToBandadaRooms(rooms, identityCommitment); }) .catch((err) => { pp(err, 'error'); }); } +function addIdentityToIdentityListRooms(rooms, identityCommitment: string): unknown { + const identityListRooms = rooms + .filter( + (room) => + room.membershipType === 'IDENTITY_LIST' && !room.identities.includes(identityCommitment) + ) + .map((room) => room.id as string); + + if (identityListRooms.length > 0) { + return prisma.rooms.updateMany({ + where: { id: { in: identityListRooms } }, + data: { identities: { push: identityCommitment } } + }); + } +} + +function addIdentityToBandadaRooms(rooms, identityCommitment: string): void { + const bandadaGroupRooms = rooms + .filter( + (room) => + room.membershipType === 'BANDADA_GROUP' && !room.identities.includes(identityCommitment) + ) + .map((room) => room as RoomI); + + if (bandadaGroupRooms.length > 0) { + bandadaGroupRooms.forEach(async (room) => { + if (!room.bandadaAPIKey) { + console.error('API key is missing for room:', room); + return; + } + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': room.bandadaAPIKey + } + }; + await prisma.rooms.updateMany({ + where: { id: room.id }, + data: { identities: { push: identityCommitment } } + }); + const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${identityCommitment}`; + fetch(url, requestOptions) + .then((res) => { + if (res.status == 200) { + console.debug(`Successfully added user to Bandada group ${room.bandadaAddress}`); + } + }) + .catch((err) => { + console.error(err); + }); + }); + } +} + export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ where: { id: { in: roomIds } } @@ -127,7 +189,8 @@ export async function findUpdatedRooms(roomIds: string[]): Promise { }); } -export function createSystemMessages(message: string, roomId?: string): Promise { +// TODO: Make interface for this return type; which is like a MessageI +export function createSystemMessages(message: string, roomId?: string): Promise { const query = roomId ? { where: { roomId } } : undefined; return prisma.rooms .findMany(query) @@ -150,6 +213,7 @@ export function createSystemMessages(message: string, roomId?: string): Promise< }) .catch((err) => { console.error(err); + return Promise.reject(err); }); } @@ -179,28 +243,36 @@ export function removeIdentityFromRoom(idc: string, room: RoomI): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as string, name).toString() + roomId: genId(serverConfig.id as bigint, roomName).toString() }, update: {}, create: { - roomId: genId(serverConfig.id as string, name).toString(), - name: name, + roomId: genId(serverConfig.id as bigint, roomName).toString(), + name: roomName, rateLimit: rateLimit, userMessageLimit: userMessageLimit, identities: mockUsers, type, + bandadaAddress, + bandadaGroupId, + bandadaAPIKey, + membershipType, claimCodes: { create: claimCodes } diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 8da8875..1bd4885 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -1,7 +1,7 @@ -import type { Express, RequestHandler, Request, Response } from "express"; -import { PrismaClient } from "@prisma/client"; -import { serverConfig } from "../config/serverConfig"; -import { pp } from "../utils"; +import type { Express, RequestHandler, Request, Response } from 'express'; +import { PrismaClient } from '@prisma/client'; +import { serverConfig } from '../config/serverConfig'; +import { pp } from '../utils'; import { getRoomByID, getRoomsByIdentity, @@ -10,9 +10,9 @@ import { updateRoomIdentities, findUpdatedRooms, createRoom, - createSystemMessages, -} from "../data/db"; -import { RoomI } from "discreetly-interfaces"; + createSystemMessages +} from '../data/db'; +import { RoomI } from 'discreetly-interfaces'; const prisma = new PrismaClient(); @@ -29,41 +29,74 @@ function asyncHandler(fn: { } export function initEndpoints(app: Express, adminAuth: RequestHandler) { - app.get(["/", "/api"], (req, res) => { - pp("Express: fetching server info"); + app.get(['/', '/api'], (req, res) => { + pp('Express: fetching server info'); res.status(200).json(serverConfig); }); - app.get(["/room/:id", "/api/room/:id"], (req, res) => { + app.get(['/room/:id', '/api/room/:id'], (req, res) => { if (!req.params.id) { - res.status(400).json({ error: "Bad Request" }); + res.status(400).json({ error: 'Bad Request' }); } else { - const requestRoomId = req.params.id ?? "0"; - pp(String("Express: fetching room info for " + req.params.id)); + const requestRoomId = req.params.id ?? '0'; + pp(String('Express: fetching room info for ' + req.params.id)); getRoomByID(requestRoomId) .then((room: RoomI) => { if (!room) { // This is set as a timeout to prevent someone from trying to brute force room ids - setTimeout( - () => res.status(500).json({ error: "Internal Server Error" }), - 1000 - ); + setTimeout(() => res.status(500).json({ error: 'Internal Server Error' }), 1000); } else { + const { + roomId, + name, + rateLimit, + userMessageLimit, + membershipType, + identities, + contractAddress, + bandadaAddress, + bandadaGroupId, + type + } = room || {}; + const id = String(roomId); + const roomResult: RoomI = { + id, + roomId, + name, + rateLimit, + userMessageLimit, + membershipType + }; // Add null check before accessing properties of room object - const { roomId, name, rateLimit, userMessageLimit } = room || {}; - res.status(200).json({ roomId, name, rateLimit, userMessageLimit }); + if (membershipType === 'BANDADA_GROUP') { + roomResult.bandadaAddress = bandadaAddress; + roomResult.bandadaGroupId = bandadaGroupId; + } + if (membershipType === 'IDENTITY_LIST') { + roomResult.identities = identities; + } + if (type === 'CONTRACT') { + roomResult.contractAddress = contractAddress; + } + res.status(200).json(roomResult); } }) .catch((err) => console.error(err)); } }); - app.get(["/rooms/:idc", "/api/rooms/:idc"], async (req, res) => { - pp( - String("Express: fetching rooms by identityCommitment " + req.params.idc) - ); - res.status(200).json(await getRoomsByIdentity(req.params.idc)); - }); + app.get( + ['/rooms/:idc', '/api/rooms/:idc'], + asyncHandler(async (req: Request, res: Response) => { + try { + pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc)); + res.status(200).json(await getRoomsByIdentity(req.params.idc)); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Internal Server Error' }); + } + }) + ); interface JoinData { code: string; @@ -71,22 +104,20 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } app.post( - ["/join", "/api/join"], + ['/join', '/api/join'], asyncHandler(async (req: Request, res: Response) => { const parsedBody: JoinData = req.body as JoinData; if (!parsedBody.code || !parsedBody.idc) { - res - .status(400) - .json({ message: "{code: string, idc: string} expected" }); + res.status(400).json({ message: '{code: string, idc: string} expected' }); } const { code, idc } = parsedBody; - console.log("Invite Code:", code); + console.log('Invite Code:', code); // Check if claim code is valid and not used before const codeStatus = await findClaimCode(code); if (!codeStatus || codeStatus.claimed) { - res.status(400).json({ message: "Claim code already used" }); + res.status(400).json({ message: 'Claim code already used' }); return; } @@ -102,8 +133,8 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { // Return the room ids of the updated rooms res.status(200).json({ - status: "valid", - roomIds: updatedRooms.map((room: RoomI) => room.roomId), + status: 'valid', + roomIds: updatedRooms.map((room: RoomI) => room.roomId) }); }) ); @@ -115,34 +146,46 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { numClaimCodes?: number; approxNumMockUsers?: number; roomType?: string; + bandadaAddress?: string; + bandadaAPIKey?: string; + bandadaGroupId?: string; + membershipType?: string; } /* ~~~~ ADMIN ENDPOINTS ~~~~ */ - app.post(["/room/add", "/api/room/add"], adminAuth, (req, res) => { + app.post(['/room/add', '/api/room/add'], adminAuth, (req, res) => { console.log(req.body); const roomMetadata = req.body as addRoomData; console.log(roomMetadata); const roomName = roomMetadata.roomName; const rateLimit = roomMetadata.rateLimit; const userMessageLimit = roomMetadata.userMessageLimit; - const numClaimCodes = roomMetadata.numClaimCodes || 0; + const numClaimCodes = roomMetadata.numClaimCodes ?? 0; const approxNumMockUsers = roomMetadata.approxNumMockUsers; - const type = roomMetadata.roomType; + const type = roomMetadata.roomType as unknown as string; + const bandadaAddress = roomMetadata.bandadaAddress; + const bandadaGroupId = roomMetadata.bandadaGroupId; + const bandadaAPIKey = roomMetadata.bandadaAPIKey; + const membershipType = roomMetadata.membershipType; createRoom( roomName, rateLimit, userMessageLimit, numClaimCodes, approxNumMockUsers, - type + type, + bandadaAddress, + bandadaGroupId, + bandadaAPIKey, + membershipType ) .then((result) => { console.log(result); if (result) { // TODO should return roomID and claim codes if they are generated - res.status(200).json({ message: "Room created successfully" }); + res.status(200).json({ message: 'Room created successfully' }); } else { - res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: 'Internal Server Error' }); } }) .catch((err) => { @@ -151,26 +194,26 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); }); - app.get("/api/room/:id/messages", (req, res) => { + app.get('/api/room/:id/messages', (req, res) => { const { id } = req.params; prisma.messages .findMany({ where: { - roomId: id, - }, + roomId: id + } }) .then((messages) => { - pp("Express: fetching messages for room " + id); + pp('Express: fetching messages for room ' + id); res.status(200).json(messages); }) .catch((error: Error) => { - pp(error, "error"); - res.status(500).send("Error fetching messages"); + pp(error, 'error'); + res.status(500).send('Error fetching messages'); }); }); - app.get(["/logclaimcodes", "/api/logclaimcodes"], adminAuth, (req, res) => { - pp("Express: fetching claim codes"); + app.get(['/logclaimcodes', '/api/logclaimcodes'], adminAuth, (req, res) => { + pp('Express: fetching claim codes'); prisma.claimCodes .findMany() .then((claimCodes) => { @@ -178,12 +221,12 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }) .catch((err) => { console.error(err); - res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: 'Internal Server Error' }); }); }); - app.get(["/rooms", "/api/rooms"], adminAuth, (req, res) => { - pp(String("Express: fetching all rooms")); + app.get(['/rooms', '/api/rooms'], adminAuth, (req, res) => { + pp(String('Express: fetching all rooms')); prisma.rooms .findMany() .then((rooms) => { @@ -191,20 +234,40 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }) .catch((err) => { console.error(err); - res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: 'Internal Server Error' }); }); }); - app.post("/admin/message", adminAuth, async (req, res) => { - const { message, roomId } = req.body; - pp(String("Express: sending system message: " + message)); - try { - const successMessage = roomId ? "Message sent to room " + roomId : "Messages sent to all rooms"; - await createSystemMessages(message, roomId); - res.status(200).json({ message: successMessage }); - } catch (err) { - console.error(err); - res.status(500).json({ error: "Internal Server Error" }); - } - }); + app.post( + '/admin/message', + adminAuth, + asyncHandler(async (req: Request, res: Response) => { + const { message } = req.body as { message: string }; + pp(String('Express: sending system message: ' + message)); + try { + await createSystemMessages(message); + res.status(200).json({ message: 'Messages sent to all rooms' }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + } + }) + ); + + app.post( + '/admin/message/:roomId', + adminAuth, + asyncHandler(async (req: Request, res: Response) => { + const { roomId } = req.params; + const { message } = req.body as { message: string }; + pp(String('Express: sending system message: ' + message + ' to ' + roomId)); + try { + await createSystemMessages(message, roomId); + res.status(200).json({ message: 'Message sent to room ' + roomId }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + } + }) + ); } diff --git a/src/utils.ts b/src/utils.ts index cb35916..1a52dc7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,7 +20,7 @@ export function genMockUsers(numMockUsers: number): string[] { for (let i = 0; i < newNumMockUsers; i++) { mockUsers.push( genId( - serverConfig.id as string, + serverConfig.id as bigint, // Generates a random string of length 10 Math.random() .toString(36) diff --git a/tests/express.test.ts b/tests/express.test.ts index c142027..00c587d 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -30,8 +30,8 @@ const room = { type: 'PUBLIC' }; -const roomByIdTest = genId(serverConfig.id as string, room.roomName).toString(); -let testCode = ''; +const roomByIdTest = genId(serverConfig.id as bigint, room.roomName).toString(); +let testCode = ""; const testIdentity = randBigint(); const username = 'admin'; const password = process.env.PASSWORD;