From 704453794c21118a92244dc8bfc8ff9215b2401e Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 1 Aug 2023 14:22:50 -0400 Subject: [PATCH 01/35] schema change --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0c42a1b..d679364 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,6 +27,7 @@ model Rooms { identities String[] @default([]) contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" bandadaAddress String? // BANDADA as "url:groupID" + bandadaAPIKey String? // Bandada API Key epochs Epoch[] messages Messages[] claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) From 1ba4bb521f61619565ef81102e63d0ef43a6954f Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Fri, 4 Aug 2023 17:59:34 -0500 Subject: [PATCH 02/35] refactor(jest) changed all tests to properly expect responses --- package-lock.json | 8 +- package.json | 2 +- src/data/db.ts | 9 +- src/endpoints/index.ts | 6 +- tests/express.test.ts | 240 +++++++++++++++++++++++++++++------------ tests/utils.ts | 9 ++ 6 files changed, 198 insertions(+), 76 deletions(-) create mode 100644 tests/utils.ts diff --git a/package-lock.json b/package-lock.json index 367e671..8bc20ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@prisma/client": "^5.0.0", "body-parser": "^1.20.2", "cors": "^2.8.5", - "discreetly-claimcodes": "^1.1.3", + "discreetly-claimcodes": "^1.1.5", "discreetly-interfaces": "^0.1.23", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -3465,9 +3465,9 @@ } }, "node_modules/discreetly-claimcodes": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.3.tgz", - "integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.5.tgz", + "integrity": "sha512-pQueoGtBJk/FrTfGzepjqYfTLaymS+4t11byI4OcfjWQOagRsD7dtavGcowTVQ7Ib/vjKna5T+71WcgWZaAWuA==" }, "node_modules/discreetly-interfaces": { "version": "0.1.23", diff --git a/package.json b/package.json index 304857e..8bd2e8c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@prisma/client": "^5.0.0", "body-parser": "^1.20.2", "cors": "^2.8.5", - "discreetly-claimcodes": "^1.1.3", + "discreetly-claimcodes": "^1.1.5", "discreetly-interfaces": "^0.1.23", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/src/data/db.ts b/src/data/db.ts index 61eb7bc..7e54d65 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -142,11 +142,14 @@ export async function createRoom( } }; - await prisma.rooms + return prisma.rooms .upsert(roomData) .then(() => { return true; }) - .catch((err) => console.error(err)); - return false; + .catch((err) => { + console.error(err); + return false; + }); + } diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 2d12630..287e76d 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -54,9 +54,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } }); - app.get(['/rooms/:idc', '/api/rooms/:idc'], (req, res) => { + app.get(['/rooms/:idc', '/api/rooms/:idc'], async (req, res) => { pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc)); - res.json(getRoomsByIdentity(req.params.idc)); + res.status(200).json(await getRoomsByIdentity(req.params.idc)); }); interface JoinData { @@ -109,6 +109,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { /* ~~~~ ADMIN ENDPOINTS ~~~~ */ 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; @@ -117,6 +118,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { const numClaimCodes = roomMetadata.numClaimCodes || 0; createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes) .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' }); diff --git a/tests/express.test.ts b/tests/express.test.ts index 50c96a4..c9a1c33 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -1,103 +1,211 @@ -const request = require('supertest'); -import _app from '../src/server' -import { genId } from 'discreetly-interfaces'; -import { serverConfig } from '../src/config/serverConfig'; -import { describe } from 'node:test'; -import expressBasicAuth from 'express-basic-auth'; -import { transferableAbortController } from 'node:util'; +const request = require("supertest"); +import _app from "../src/server"; +import { genId } from "discreetly-interfaces"; +import { serverConfig } from "../src/config/serverConfig"; +import { PrismaClient } from "@prisma/client"; +import { beforeAll, beforeEach, describe, expect, test } from "@jest/globals"; +import { pp } from "../src/utils"; +import { randBigint, randomRoomName } from "./utils"; -process.env.DATABASE_URL = process.env.DATABASE_URL_TEST +process.env.DATABASE_URL = process.env.DATABASE_URL_TEST; +process.env.PORT = "3001"; +beforeAll(async () => { + const prismaTest = new PrismaClient(); + await prismaTest.rooms.deleteMany(); + await prismaTest.claimCodes.deleteMany(); +}); const room = { - roomName: 'Test-room', + roomName: randomRoomName(), rateLimit: 1000, userMessageLimit: 1, numClaimCodes: 5, - approxNumMockUsers: 20, -} +}; const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); -const joinTest = { - code: "coast-filter-noise-feature", //needs to be changed to a valid code - idc: "12345678901234567890" -} -describe('GET /', () => { - test('It should respond with server info', () => { - request(_app).get('/').expect('Content-Type', 'application/json; charset=utf-8').then(res => { - }) - }) -}) +let testCode = ""; + +const testIdentity = randBigint(); +console.log(testIdentity) + + +describe("Endpoints should all work hopefully", () => { + test("It should respond with server info", async () => { + await request(_app) + .get("/") + .then((res) => { + expect(res.status).toBe(200); + expect(res.header["content-type"]).toBe( + "application/json; charset=utf-8" + ); + expect(res.body.id).toBe(serverConfig.id); + }) + .catch((error) => pp("GET '/' - " + error, "error")); + }); -describe("POST /room/add", () => { test("It should add a new room to the database", async () => { - const username = 'admin'; + const username = "admin"; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); - + const base64Credentials = Buffer.from(`${username}:${password}`).toString( + "base64" + ); await request(_app) .post("/room/add") - .set('Authorization', `Basic ${base64Credentials}`) + .set("Authorization", `Basic ${base64Credentials}`) .send(room) - .then(res => { - expect(res.json).toBe('{message :"Room created successfully"}') - }); - }); -}); -describe("GET /api/room/:id", () => { + .then((res) => { + try { + expect(res.body).toEqual({ message: "Room created successfully" }); + } catch (error) { + console.warn("POST /room/add - " + error); + } + }) + .catch((error) => console.warn("POST /room/add - " + error)); + }); + + + test("It should return the room with the given id", async () => { await request(_app) .get(`/api/room/${roomByIdTest}`) - .then(res => { - console.log(res.body); - expect(res.body.roomName).toBe(room.roomName) - }); + .then((res) => { + try { + expect(res.status).toEqual(200); + expect(res.body.name).toEqual(room.roomName); + } catch (error) { + pp("GET /api/room/:roomId - " + error, "error"); + } + }) + .catch((error) => pp("GET /api/room/:roomId - " + error, "error")); }); -}); -describe("GET /api/rooms", () => { + + test("It should return all rooms", async () => { - const username = 'admin'; + const username = "admin"; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); + const base64Credentials = Buffer.from(`${username}:${password}`).toString( + "base64" + ); await request(_app) .get("/api/rooms") - .set('Authorization', `Basic ${base64Credentials}`) - .then(res => { - expect(res.status).toBe(200) - expect(res.bodyname).toBe(room.roomName) - }); - }); -}) -describe("GET /logclaimcodes", () => { - test("It should return all claim codes", async () => { - const username = 'admin'; + .set("Authorization", `Basic ${base64Credentials}`) + .then((res) => { + try { + expect(res.status).toEqual(200); + expect(typeof res.body).toEqual("object") + expect(res.body[0].name).toEqual(room.roomName); + } catch (error) { + pp("GET /api/rooms - " + error, "error"); + } + }) + .catch((error) => pp("GET /api/rooms - " + error, "error")); + }); + + + + // test("It should return all claim codes", async () => { + // const username = "admin"; + // const password = process.env.PASSWORD; + // const base64Credentials = Buffer.from(`${username}:${password}`).toString( + // "base64" + // ); + // await request(_app) + // .get("/logclaimcodes") + // .set("Authorization", `Basic ${base64Credentials}`) + // .then((res) => { + // try { + // // TODO check an array is 4 words - with a seperator between - use split + // // push claim codes to an empty array and use one of those for the /join instead + // testCode = res.body[0].claimcode + // expect(res.body[0].claimcode.split('-').length).toEqual(4) + // expect(res.status).toEqual(401); + // expect(res.body.length).toBeGreaterThan(0); + // } catch (error) { + // pp("GET /logclaimcodes - " + error, "error"); + // } + // }) + // .catch((error) => pp("GET /logclaimcodes - " + error, "error")); + // }); + + // const joinTest = { + // code: testCode, + // idc: testIdentity, + // }; + + // test("It should add a users identity to the rooms the claim code is associated with", async () => { + // await request(_app) + // .post("/join") + // .send(joinTest) + // .then((res) => { + // try { + // expect(res.statusCode).toEqual(200); + // expect(res.body.status).toEqual("valid"); + // } catch (error) { + // pp("POST /join - " + error, "error"); + // } + // }) + // .catch((error) => pp("POST /join - " + error, "error")); + // }); + + + test("It should return all claim codes and add a user's identity to the rooms the claim code is associated with", async () => { + const username = "admin"; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); + const base64Credentials = Buffer.from(`${username}:${password}`).toString("base64"); + await request(_app) .get("/logclaimcodes") - .set('Authorization', `Basic ${base64Credentials}`) - .then(res => { - expect(res.status).toBe(401) - expect(res.body.length).toBeGreaterThan(0) + + .set("Authorization", `Basic ${base64Credentials}`) + .then(async (res) => { + try { + testCode = res.body[0].claimcode; + expect(testCode.split('-').length).toEqual(4); + expect(res.status).toEqual(401); + expect(res.body.length).toBeGreaterThan(0); + + + const joinTest = { + code: testCode, + idc: testIdentity + }; + + await request(_app) + .post("/join") + .send(joinTest) + .then((res) => { + expect(res.statusCode).toEqual(200); + expect(res.body.status).toEqual("valid"); + }); + } catch (error) { + console.error('Error in test: ', error); + } + }) + .catch((error) => { + console.error('Error in request: ', error); }); }); -}); + console.log(testIdentity); -describe("POST /join", () => { - test("It should add a users identity to the rooms the claim code is associated with", async () => { + test("It should return all rooms associated with the given identity", async () => { await request(_app) - .post("/join") - .send(joinTest) - .then(res => { - expect(res.statusCode).toBe(200) - expect(res.body.status).toBe('valid') - }) - }) -}) + .get(`/api/rooms/${testIdentity}`) + .then((res) => { + try { + console.log(res.body); + expect(res.statusCode).toEqual(200); + } catch (error) { + pp("GET /api/rooms/:idc - " + error, "error"); + } + }) + .catch((error) => pp("GET /api/rooms/:idc - " + error, "error")); + }); +}); diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 0000000..931a8ce --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,9 @@ +import { faker } from '@faker-js/faker'; + +export function randBigint(): bigint { + return faker.number.bigInt(); +} + +export function randomRoomName(min = 5, max = 20) { + return faker.string.alphanumeric({ length: { min: min, max: max } }); +} From 43a16865b0f16e85918f720ad8f2420548ba6562 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Fri, 4 Aug 2023 18:01:08 -0500 Subject: [PATCH 03/35] chore(tests) setEnvVars for jest branch --- tests/setEnvVars.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/setEnvVars.ts diff --git a/tests/setEnvVars.ts b/tests/setEnvVars.ts new file mode 100644 index 0000000..6f8c7c5 --- /dev/null +++ b/tests/setEnvVars.ts @@ -0,0 +1,6 @@ +process.env.DATABASE_URL = process.env.DATABASE_URL_TEST; +process.env.NODE_ENV = 'test'; +process.env.PASSWORD = 'TEST_PASSWORD'; +process.env.PORT = '3001'; +process.env.SERVER_ID = '1234'; +process.env.SERVER_NAME = 'JEST_TEST_SERVER'; From 933a208357a2a20ec89128c088ca21cc8dd3fcc0 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Mon, 7 Aug 2023 10:44:29 -0400 Subject: [PATCH 04/35] feat: updated random Big Int to be roughly the same length as posiedon hash --- tests/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/utils.ts b/tests/utils.ts index 5f93125..eef395b 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,7 +1,9 @@ import { faker } from '@faker-js/faker'; export function randBigint(): bigint { - return faker.number.bigInt(); + const min = 1000000000000000000000000000000000000000000000000000000000000000000000000000n; + const max = 9999999999999999999999999999999999999999999999999999999999999999999999999999n; + return faker.number.bigInt({ min: min, max: max }); } export function randomRoomName(min = 5, max = 20): string { From 83c594b9fb1bb794839842064b7e632b3b4eff31 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 8 Aug 2023 11:37:38 -0500 Subject: [PATCH 05/35] feature(express) endpoint for sending system messages to all rooms feature(jest) tests for system messages endpoint --- src/data/db.ts | 19 +++++ src/endpoints/index.ts | 96 ++++++++++++++---------- tests/express.test.ts | 162 +++++++++++++++++++++++------------------ 3 files changed, 169 insertions(+), 108 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 7e54d65..27014ff 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -107,6 +107,25 @@ export function findUpdatedRooms(roomIds: string[]): Promise { }); } +export function createSystemMessages(message: string): Promise { + return prisma.rooms.findMany() + .then(rooms => { + const createMessages = rooms.map(room => { + return prisma.messages.create({ + data: { + message, + roomId: room.roomId, + messageId: "0", + proof: JSON.stringify({}), + }, + }); + }); + + return Promise.all(createMessages); + }); +} + + /** * Creates a new room with the given name and optional parameters. * @param {string} name - The name of the room. diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 287e76d..5476513 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, @@ -9,9 +9,10 @@ import { updateClaimCode, updateRoomIdentities, findUpdatedRooms, - createRoom -} from '../data/db'; -import { RoomI } from 'discreetly-interfaces'; + createRoom, + createSystemMessages, +} from "../data/db"; +import { RoomI } from "discreetly-interfaces"; const prisma = new PrismaClient(); @@ -28,22 +29,25 @@ 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 { // Add null check before accessing properties of room object const { roomId, name, rateLimit, userMessageLimit } = room || {}; @@ -54,8 +58,10 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } }); - app.get(['/rooms/:idc', '/api/rooms/:idc'], async (req, res) => { - pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc)); + 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)); }); @@ -65,20 +71,22 @@ 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; } @@ -94,8 +102,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), }); }) ); @@ -108,8 +116,8 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } /* ~~~~ ADMIN ENDPOINTS ~~~~ */ - app.post(['/room/add', '/api/room/add'], adminAuth, (req, res) => { - console.log(req.body) + 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; @@ -121,9 +129,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { 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) => { @@ -132,26 +140,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) => { @@ -159,12 +167,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) => { @@ -172,7 +180,19 @@ 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 } = req.body; + 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" }); + } + }); } diff --git a/tests/express.test.ts b/tests/express.test.ts index c9a1c33..b86e758 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -1,4 +1,3 @@ - const request = require("supertest"); import _app from "../src/server"; import { genId } from "discreetly-interfaces"; @@ -13,11 +12,11 @@ process.env.PORT = "3001"; beforeAll(async () => { const prismaTest = new PrismaClient(); + await prismaTest.messages.deleteMany(); await prismaTest.rooms.deleteMany(); await prismaTest.claimCodes.deleteMany(); }); - const room = { roomName: randomRoomName(), rateLimit: 1000, @@ -27,12 +26,12 @@ const room = { const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); - let testCode = ""; const testIdentity = randBigint(); -console.log(testIdentity) +const username = "admin"; +const password = process.env.PASSWORD; describe("Endpoints should all work hopefully", () => { test("It should respond with server info", async () => { @@ -69,8 +68,6 @@ describe("Endpoints should all work hopefully", () => { .catch((error) => console.warn("POST /room/add - " + error)); }); - - test("It should return the room with the given id", async () => { await request(_app) .get(`/api/room/${roomByIdTest}`) @@ -85,11 +82,7 @@ describe("Endpoints should all work hopefully", () => { .catch((error) => pp("GET /api/room/:roomId - " + error, "error")); }); - - test("It should return all rooms", async () => { - const username = "admin"; - const password = process.env.PASSWORD; const base64Credentials = Buffer.from(`${username}:${password}`).toString( "base64" ); @@ -100,7 +93,7 @@ describe("Endpoints should all work hopefully", () => { .then((res) => { try { expect(res.status).toEqual(200); - expect(typeof res.body).toEqual("object") + expect(typeof res.body).toEqual("object"); expect(res.body[0].name).toEqual(room.roomName); } catch (error) { pp("GET /api/rooms - " + error, "error"); @@ -109,57 +102,10 @@ describe("Endpoints should all work hopefully", () => { .catch((error) => pp("GET /api/rooms - " + error, "error")); }); - - - // test("It should return all claim codes", async () => { - // const username = "admin"; - // const password = process.env.PASSWORD; - // const base64Credentials = Buffer.from(`${username}:${password}`).toString( - // "base64" - // ); - // await request(_app) - // .get("/logclaimcodes") - // .set("Authorization", `Basic ${base64Credentials}`) - // .then((res) => { - // try { - // // TODO check an array is 4 words - with a seperator between - use split - // // push claim codes to an empty array and use one of those for the /join instead - // testCode = res.body[0].claimcode - // expect(res.body[0].claimcode.split('-').length).toEqual(4) - // expect(res.status).toEqual(401); - // expect(res.body.length).toBeGreaterThan(0); - // } catch (error) { - // pp("GET /logclaimcodes - " + error, "error"); - // } - // }) - // .catch((error) => pp("GET /logclaimcodes - " + error, "error")); - // }); - - // const joinTest = { - // code: testCode, - // idc: testIdentity, - // }; - - // test("It should add a users identity to the rooms the claim code is associated with", async () => { - // await request(_app) - // .post("/join") - // .send(joinTest) - // .then((res) => { - // try { - // expect(res.statusCode).toEqual(200); - // expect(res.body.status).toEqual("valid"); - // } catch (error) { - // pp("POST /join - " + error, "error"); - // } - // }) - // .catch((error) => pp("POST /join - " + error, "error")); - // }); - - test("It should return all claim codes and add a user's identity to the rooms the claim code is associated with", async () => { - const username = "admin"; - const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString("base64"); + const base64Credentials = Buffer.from(`${username}:${password}`).toString( + "base64" + ); await request(_app) .get("/logclaimcodes") @@ -168,14 +114,13 @@ describe("Endpoints should all work hopefully", () => { .then(async (res) => { try { testCode = res.body[0].claimcode; - expect(testCode.split('-').length).toEqual(4); + expect(testCode.split("-").length).toEqual(4); expect(res.status).toEqual(401); expect(res.body.length).toBeGreaterThan(0); - const joinTest = { code: testCode, - idc: testIdentity + idc: testIdentity, }; await request(_app) @@ -186,21 +131,19 @@ describe("Endpoints should all work hopefully", () => { expect(res.body.status).toEqual("valid"); }); } catch (error) { - console.error('Error in test: ', error); + console.error("Error in test: ", error); } }) .catch((error) => { - console.error('Error in request: ', error); + console.error("Error in request: ", error); }); }); - console.log(testIdentity); test("It should return all rooms associated with the given identity", async () => { await request(_app) - .get(`/api/rooms/${testIdentity}`) - .then((res) => { + .get(`/api/rooms/${testIdentity}`) + .then((res) => { try { - console.log(res.body); expect(res.statusCode).toEqual(200); } catch (error) { pp("GET /api/rooms/:idc - " + error, "error"); @@ -208,4 +151,83 @@ describe("Endpoints should all work hopefully", () => { }) .catch((error) => pp("GET /api/rooms/:idc - " + error, "error")); }); + + test("It should send a message to all rooms", async () => { + const message = { + message: "Test message", + }; + const base64Credentials = Buffer.from(`${username}:${password}`).toString( + "base64" + ); + await request(_app) + .post("/admin/message") + .set("Authorization", `Basic ${base64Credentials}`) + .send(message) + .then((res) => { + try { + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ message: "Messages sent to all rooms" }); + } catch (error) { + pp("POST /admin/message - " + error, "error"); + } + }); + }); + + test("It should return the messages for a given room", async () => { + await request(_app) + .get(`/api/room/${roomByIdTest}/messages`) + .then((res) => { + try { + expect(res.statusCode).toEqual(200); + expect(res.body.length).toBeGreaterThan(0); + } catch (error) { + pp("GET /api/messages/:roomId - " + error, "error"); + } + }) + .catch((error) => pp("GET /api/messages/:roomId - " + error, "error")); + }); }); + +// test("It should return all claim codes", async () => { +// const username = "admin"; +// const password = process.env.PASSWORD; +// const base64Credentials = Buffer.from(`${username}:${password}`).toString( +// "base64" +// ); +// await request(_app) +// .get("/logclaimcodes") +// .set("Authorization", `Basic ${base64Credentials}`) +// .then((res) => { +// try { +// // TODO check an array is 4 words - with a seperator between - use split +// // push claim codes to an empty array and use one of those for the /join instead +// testCode = res.body[0].claimcode +// expect(res.body[0].claimcode.split('-').length).toEqual(4) +// expect(res.status).toEqual(401); +// expect(res.body.length).toBeGreaterThan(0); +// } catch (error) { +// pp("GET /logclaimcodes - " + error, "error"); +// } +// }) +// .catch((error) => pp("GET /logclaimcodes - " + error, "error")); +// }); + +// const joinTest = { +// code: testCode, +// idc: testIdentity, +// }; + +// test("It should add a users identity to the rooms the claim code is associated with", async () => { +// await request(_app) +// .post("/join") +// .send(joinTest) +// .then((res) => { +// try { +// expect(res.statusCode).toEqual(200); +// expect(res.body.status).toEqual("valid"); +// } catch (error) { +// pp("POST /join - " + error, "error"); +// } +// }) +// .catch((error) => pp("POST /join - " + error, "error")); +// }); From f528650d4f218fad23744988f4065c8db4e0647a Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 8 Aug 2023 14:38:04 -0500 Subject: [PATCH 06/35] feature(express) express endpoint for sending system messages to a specific room --- src/data/db.ts | 8 ++++++-- src/endpoints/index.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 27014ff..665e998 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -107,9 +107,13 @@ export function findUpdatedRooms(roomIds: string[]): Promise { }); } -export function createSystemMessages(message: string): Promise { - return prisma.rooms.findMany() +export function createSystemMessages(message: string, roomId?: string): Promise { + const query = roomId ? { where: { roomId } } : undefined; + return prisma.rooms.findMany(query) .then(rooms => { + if (roomId && rooms.length === 0) { + Promise.reject('Room not found') + } const createMessages = rooms.map(room => { return prisma.messages.create({ data: { diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 5476513..5bf14a9 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -195,4 +195,17 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { res.status(500).json({ error: "Internal Server Error" }); } }); + + app.post("/admin/message/:roomId", adminAuth, async (req, res) => { + const { roomId } = req.params; + const { message } = req.body; + 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" }); + } + }); } From 14101019aabde0130532850326730eadd1a705e1 Mon Sep 17 00:00:00 2001 From: Blake Duncan Date: Tue, 8 Aug 2023 17:02:24 -0400 Subject: [PATCH 07/35] Cors config for socket io --- src/server.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/server.ts b/src/server.ts index 219934f..f1b3223 100644 --- a/src/server.ts +++ b/src/server.ts @@ -70,7 +70,11 @@ if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { initEndpoints(app, adminAuth); _app = initAppListeners(PORT); listEndpoints(app); - io = new SocketIOServer(_app, {}); + io = new SocketIOServer(_app, { + cors: { + origin: '*' + } + }); initWebsockets(io); mock(io); } else { @@ -78,7 +82,11 @@ if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { serverConfigStartup.port = PORT; initEndpoints(app, adminAuth); _app = initAppListeners(PORT); - io = new SocketIOServer(_app, {}); + io = new SocketIOServer(_app, { + cors: { + origin: '*' + } + }); initWebsockets(io); } From 840dfb861b52839a3bf8e58abcda8acd6b537c57 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Thu, 10 Aug 2023 16:10:30 -0400 Subject: [PATCH 08/35] RoomI schema updates --- package-lock.json | 8 ++++---- package.json | 4 ++-- prisma/schema.prisma | 7 +++++++ src/crypto/verifier.ts | 8 +++++--- src/data/db.ts | 43 ++++++++++++++++++++++++------------------ 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 367e671..85d0f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.23", + "discreetly-interfaces": "^0.1.25", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -3470,9 +3470,9 @@ "integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.23.tgz", - "integrity": "sha512-C1mqzLZY52aW83XHUBr3lxe8F7HFagx4V+MzigxPf5GTjDGhelIbnmihhV3RUtWb1uddo1Gm1CImD+meygf1bg==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.25.tgz", + "integrity": "sha512-yWKAycHE6mwJbROT+9rQNE5fwYrYmL+VH9ZJfHl2nqt3L++31djQjVgV2xG5/rYBTrkUPnEbFXpzRF1i2pqO7g==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index 62d550d..6c69be6 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.23", + "discreetly-interfaces": "^0.1.25", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -64,4 +64,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 4eebff6..5254f70 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,6 +16,12 @@ enum RoomMembershipType { BANDADA } +enum RoomType { + PUBLIC + PRIVATE + PIXEL +} + model Rooms { id String @id @default(auto()) @map("_id") @db.ObjectId roomId String @unique @@ -31,6 +37,7 @@ model Rooms { messages Messages[] claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) claimCodeIds String[] @default([]) @db.ObjectId + type RoomType @default(PUBLIC) } model ClaimCodes { diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index e8b5352..1da92b3 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -40,9 +40,11 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro } // Check that the merkle root is correct - const group = new Group(room.id, 20, room.membership?.identityCommitments); - if (group.root !== msg.proof.snarkProof.publicSignals.root) { - return false; + if (room.identities && Array.isArray(room.identities)) { + const group = new Group(room.id, 20, room.identities as bigint[] | undefined); + if (group.root !== msg.proof.snarkProof.publicSignals.root) { + return false; + } } // Check that the proof is correct diff --git a/src/data/db.ts b/src/data/db.ts index 61eb7bc..cb6055a 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -2,7 +2,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ import { PrismaClient } from '@prisma/client'; -import { RoomI, genId } from 'discreetly-interfaces'; +import { genId } from 'discreetly-interfaces'; +import type { RoomI } from 'discreetly-interfaces'; import { serverConfig } from '../config/serverConfig'; import { genMockUsers, genClaimCodeArray, pp } from '../utils'; @@ -29,7 +30,11 @@ export function getRoomByID(id: string): Promise { name: true, identities: true, rateLimit: true, - userMessageLimit: true + userMessageLimit: true, + membershipType: true, + contractAddress: true, + bandadaAddress: true, + type: true } }) .then((room) => { @@ -82,23 +87,25 @@ export function updateClaimCode(code: string): Promise { } export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { - return prisma.rooms.findMany({ - where: { id: { in: roomIds } }, - }) - .then((rooms) => { - const roomsToUpdate = rooms - .filter(room => !room.identities.includes(idc)) - .map(room => room.id); + 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 } } - }); - } - }).catch(err => { - pp(err, 'error') - }) + if (roomsToUpdate) { + return prisma.rooms.updateMany({ + where: { id: { in: roomsToUpdate } }, + data: { identities: { push: idc } } + }); + } + }) + .catch((err) => { + pp(err, 'error'); + }); } export function findUpdatedRooms(roomIds: string[]): Promise { From a9e4d29e87f11d236bc9ab3cfe3da1ac3ea42dcf Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Thu, 10 Aug 2023 16:58:54 -0400 Subject: [PATCH 09/35] fixed room lookups but createRooms is still broken --- package-lock.json | 8 ++++---- package.json | 2 +- prisma/schema.prisma | 15 ++------------- src/data/db.ts | 20 ++++++++++++++++---- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85d0f4e..2932987 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.25", + "discreetly-interfaces": "^0.1.29", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -3470,9 +3470,9 @@ "integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.25.tgz", - "integrity": "sha512-yWKAycHE6mwJbROT+9rQNE5fwYrYmL+VH9ZJfHl2nqt3L++31djQjVgV2xG5/rYBTrkUPnEbFXpzRF1i2pqO7g==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.29.tgz", + "integrity": "sha512-3R63KkmB+wFKFFzD9DixX3VDoLCYkDuMQZucAItmXbjE+0tFgmrK683a1/WBI9VkBhARip6HsVNrjzaGqdR1Aw==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index 6c69be6..9191050 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.3", - "discreetly-interfaces": "^0.1.25", + "discreetly-interfaces": "^0.1.29", "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 5254f70..2625dde 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,17 +10,6 @@ datasource db { url = env("DATABASE_URL") } -enum RoomMembershipType { - IDENTITY_LIST - RLN_CONTRACT - BANDADA -} - -enum RoomType { - PUBLIC - PRIVATE - PIXEL -} model Rooms { id String @id @default(auto()) @map("_id") @db.ObjectId @@ -29,7 +18,7 @@ model Rooms { 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) + membershipType String @default("IDENTITY_LIST") identities String[] @default([]) contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" bandadaAddress String? // BANDADA as "url:groupID" @@ -37,7 +26,7 @@ model Rooms { messages Messages[] claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) claimCodeIds String[] @default([]) @db.ObjectId - type RoomType @default(PUBLIC) + type String } model ClaimCodes { diff --git a/src/data/db.ts b/src/data/db.ts index cb6055a..32d63bd 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -18,8 +18,8 @@ interface RoomsFromClaimCode { roomIds: string[]; } -export function getRoomByID(id: string): Promise { - return prisma.rooms +export async function getRoomByID(id: string): Promise { + const room = await prisma.rooms .findUnique({ where: { roomId: id @@ -44,6 +44,12 @@ export function getRoomByID(id: string): Promise { console.error(err); throw err; // Add this line to throw the error }); + return new Promise((resolve, reject) => { + if (room) { + resolve(room as RoomI); + } + reject('Room not found'); + }); } export async function getRoomsByIdentity(identity: string): Promise { @@ -108,10 +114,16 @@ export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { - return prisma.rooms.findMany({ +export async function findUpdatedRooms(roomIds: string[]): Promise { + const rooms = await prisma.rooms.findMany({ where: { id: { in: roomIds } } }); + return new Promise((resolve, reject) => { + if (rooms) { + resolve(rooms as RoomI[]); + } + reject('No rooms found'); + }); } /** From 1b2512a73c3b28d0a37478fe7ee569ce13e1c79f Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Mon, 14 Aug 2023 11:47:08 -0500 Subject: [PATCH 10/35] refactor(express) fixing types in createRoom --- src/data/db.ts | 6 ++++-- src/endpoints/index.ts | 6 +++++- tests/express.test.ts | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 0182930..c624c0e 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -139,7 +139,8 @@ export async function createRoom( rateLimit = 1000, userMessageLimit = 1, numClaimCodes = 0, - approxNumMockUsers = 20 + approxNumMockUsers = 20, + type: string = 'PUBLIC' ): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); @@ -155,11 +156,12 @@ export async function createRoom( rateLimit: rateLimit, userMessageLimit: userMessageLimit, identities: mockUsers, + type, claimCodes: { create: claimCodes } } - }; + }; return await prisma.rooms .upsert(roomData) diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 63f2e69..7d48838 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -105,6 +105,8 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { rateLimit: number; userMessageLimit: number; numClaimCodes?: number; + approxNumMockUsers?: number; + roomType?: string; } /* ~~~~ ADMIN ENDPOINTS ~~~~ */ @@ -115,7 +117,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { const rateLimit = roomMetadata.rateLimit; const userMessageLimit = roomMetadata.userMessageLimit; const numClaimCodes = roomMetadata.numClaimCodes || 0; - createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes) + const approxNumMockUsers = roomMetadata.approxNumMockUsers; + const type = roomMetadata.roomType; + createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes, approxNumMockUsers, type) .then((result) => { if (result) { // TODO should return roomID and claim codes if they are generated diff --git a/tests/express.test.ts b/tests/express.test.ts index 2f09150..bfb8f3e 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -12,6 +12,7 @@ process.env.PORT = "3001"; beforeAll(async () => { const prismaTest = new PrismaClient(); + await prismaTest.messages.deleteMany(); await prismaTest.rooms.deleteMany(); await prismaTest.claimCodes.deleteMany(); }); @@ -22,6 +23,8 @@ const room = { rateLimit: 1000, userMessageLimit: 1, numClaimCodes: 5, + approxNumMockUsers: 10, + type: "PUBLIC" }; const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); From 46996ecbc6f859f11ec8c31317b30ee5a84b1cb5 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Mon, 14 Aug 2023 12:48:07 -0500 Subject: [PATCH 11/35] chore(utils) added curly brace :) --- tests/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils.ts b/tests/utils.ts index 349046d..eef395b 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -4,6 +4,7 @@ export function randBigint(): bigint { const min = 1000000000000000000000000000000000000000000000000000000000000000000000000000n; const max = 9999999999999999999999999999999999999999999999999999999999999999999999999999n; return faker.number.bigInt({ min: min, max: max }); +} export function randomRoomName(min = 5, max = 20): string { return faker.string.alphanumeric({ length: { min: min, max: max } }); From 934699cc412e62e5ea28ad70d5f2f7a297334276 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 15 Aug 2023 11:21:30 -0500 Subject: [PATCH 12/35] refactor(express) refactored system messages into single route --- prisma/schema.prisma | 21 ++++++++++----------- src/data/db.ts | 2 +- src/endpoints/index.ts | 33 +++++++++++++++------------------ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2625dde..16c6431 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,23 +10,22 @@ datasource db { url = env("DATABASE_URL") } - 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 - membershipType String @default("IDENTITY_LIST") - 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 String @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 - type String + claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) + claimCodeIds String[] @default([]) @db.ObjectId + type String @default("PUBLIC") } model ClaimCodes { diff --git a/src/data/db.ts b/src/data/db.ts index 9414e97..81db126 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -184,7 +184,7 @@ export async function createRoom( create: claimCodes } } - }; + }; return await prisma.rooms .upsert(roomData) diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 3b93f50..8da8875 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -58,8 +58,10 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } }); - app.get(['/rooms/:idc', '/api/rooms/:idc'], async (req, res) => { - pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc)); + 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)); }); @@ -126,7 +128,14 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { const numClaimCodes = roomMetadata.numClaimCodes || 0; const approxNumMockUsers = roomMetadata.approxNumMockUsers; const type = roomMetadata.roomType; - createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes, approxNumMockUsers, type) + createRoom( + roomName, + rateLimit, + userMessageLimit, + numClaimCodes, + approxNumMockUsers, + type + ) .then((result) => { console.log(result); if (result) { @@ -187,24 +196,12 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); app.post("/admin/message", adminAuth, async (req, res) => { - const { message } = req.body; + const { message, roomId } = req.body; 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, async (req, res) => { - const { roomId } = req.params; - const { message } = req.body; - pp(String("Express: sending system message: " + message + " to " + roomId)); - try { + const successMessage = roomId ? "Message sent to room " + roomId : "Messages sent to all rooms"; await createSystemMessages(message, roomId); - res.status(200).json({ message: "Message sent to room " + roomId }); + res.status(200).json({ message: successMessage }); } catch (err) { console.error(err); res.status(500).json({ error: "Internal Server Error" }); From eba1eb60ffc6df8fe351dd4519a5ee698f23d6f1 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 13:13:57 -0400 Subject: [PATCH 13/35] feat added Shamir Reocvery --- package-lock.json | 73 ++++++++++++++++++++---------------- package.json | 3 +- src/crypto/shamirRecovery.ts | 31 +++++++++++++++ src/crypto/verifier.ts | 3 -- 4 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 src/crypto/shamirRecovery.ts diff --git a/package-lock.json b/package-lock.json index 48c2bf0..3108087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", + "ffjavascript": "^0.2.60", "helmet": "^7.0.0", "mongodb": "^5.7.0", "poseidon-lite": "^0.2.0", @@ -3130,11 +3131,6 @@ "web-worker": "^1.2.0" } }, - "node_modules/circom_runtime/node_modules/wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "node_modules/circom_runtime/node_modules/wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -4164,13 +4160,12 @@ } }, "node_modules/ffjavascript": { - "version": "0.2.55", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.55.tgz", - "integrity": "sha512-8X0FCIPOWiK6DTWh3pnE3O6D6nIQsirStAXpWMzRDnoDX7SEnDX4I28aVhwjL7L35XS1vy2AU7zc0UCGYxdLjw==", + "version": "0.2.60", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", + "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", "dependencies": { - "big-integer": "^1.6.48", - "wasmbuilder": "^0.0.12", - "wasmcurves": "0.1.0", + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", "web-worker": "^1.2.0" } }, @@ -6578,11 +6573,6 @@ "web-worker": "^1.2.0" } }, - "node_modules/r1csfile/node_modules/wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "node_modules/r1csfile/node_modules/wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -6762,11 +6752,39 @@ "snarkjs": "^0.7.0" } }, + "node_modules/rlnjs/node_modules/ffjavascript": { + "version": "0.2.55", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.55.tgz", + "integrity": "sha512-8X0FCIPOWiK6DTWh3pnE3O6D6nIQsirStAXpWMzRDnoDX7SEnDX4I28aVhwjL7L35XS1vy2AU7zc0UCGYxdLjw==", + "dependencies": { + "big-integer": "^1.6.48", + "wasmbuilder": "^0.0.12", + "wasmcurves": "0.1.0", + "web-worker": "^1.2.0" + } + }, "node_modules/rlnjs/node_modules/poseidon-lite": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/poseidon-lite/-/poseidon-lite-0.0.2.tgz", "integrity": "sha512-bGdDPTOQkJbBjbtSEWc3gY+YhqlGTxGlZ8041F8TGGg5QyGGp1Cfs4b8AEnFFjHbkPg6WdWXUgEjU1GKOKWAPw==" }, + "node_modules/rlnjs/node_modules/wasmbuilder": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.12.tgz", + "integrity": "sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w==", + "dependencies": { + "big-integer": "^1.6.48" + } + }, + "node_modules/rlnjs/node_modules/wasmcurves": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.1.0.tgz", + "integrity": "sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q==", + "dependencies": { + "big-integer": "^1.6.42", + "blakejs": "^1.1.0" + } + }, "node_modules/rollup": { "version": "3.26.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", @@ -7105,11 +7123,6 @@ "web-worker": "^1.2.0" } }, - "node_modules/snarkjs/node_modules/wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "node_modules/snarkjs/node_modules/wasmcurves": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz", @@ -7857,20 +7870,16 @@ } }, "node_modules/wasmbuilder": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.12.tgz", - "integrity": "sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w==", - "dependencies": { - "big-integer": "^1.6.48" - } + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", + "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" }, "node_modules/wasmcurves": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.1.0.tgz", - "integrity": "sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz", + "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==", "dependencies": { - "big-integer": "^1.6.42", - "blakejs": "^1.1.0" + "wasmbuilder": "0.0.16" } }, "node_modules/web-worker": { diff --git a/package.json b/package.json index 3881dc3..b830f82 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", + "ffjavascript": "^0.2.60", "helmet": "^7.0.0", "mongodb": "^5.7.0", "poseidon-lite": "^0.2.0", @@ -64,4 +65,4 @@ "ts-node": "^10.9.1", "typescript": "^5.1.6" } -} \ No newline at end of file +} diff --git a/src/crypto/shamirRecovery.ts b/src/crypto/shamirRecovery.ts new file mode 100644 index 0000000..ab11072 --- /dev/null +++ b/src/crypto/shamirRecovery.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { ZqField } from 'ffjavascript'; + +/* + This is the "Baby Jubjub" curve described here: + https://iden3-docs.readthedocs.io/en/latest/_downloads/33717d75ab84e11313cc0d8a090b636f/Baby-Jubjub.pdf +*/ +const SNARK_FIELD_SIZE = BigInt( + '21888242871839275222246405745257275088548364400416034343698204186575808495617' +); + +// Creates the finite field +const Fq = new ZqField(SNARK_FIELD_SIZE); + +/** + * Recovers secret from two shares + * @param x1 signal hash of first message + * @param x2 signal hash of second message + * @param y1 yshare of first message + * @param y2 yshare of second message + * @returns identity secret + */ +export function shamirRecovery(x1: bigint, x2: bigint, y1: bigint, y2: bigint): bigint { + const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)); + const privateKey = Fq.sub(y1, Fq.mul(slope, x1)); + + return Fq.normalize(privateKey); +} diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 1da92b3..8a37430 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -25,9 +25,6 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro return false; } - // Check that the internal nullifier doesn't have collisions - // TODO! INTERNAL NULLIFIER (RLNjs cache) - // Check that the message hash is correct if (msgHash !== msg.proof.snarkProof.publicSignals.x) { console.warn( From 9787ad5fc6c34a037399a4918e07ed22465daffd Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 14:46:29 -0400 Subject: [PATCH 14/35] feat identity commitment recovery --- src/crypto/shamirRecovery.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/crypto/shamirRecovery.ts b/src/crypto/shamirRecovery.ts index ab11072..961e9b4 100644 --- a/src/crypto/shamirRecovery.ts +++ b/src/crypto/shamirRecovery.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ZqField } from 'ffjavascript'; +import { poseidon1 } from 'poseidon-lite'; /* This is the "Baby Jubjub" curve described here: @@ -29,3 +30,7 @@ export function shamirRecovery(x1: bigint, x2: bigint, y1: bigint, y2: bigint): return Fq.normalize(privateKey); } + +export function getIdentityCommitmentFromSecret(secret: bigint): bigint { + return poseidon1([secret]); +} From e66619fbe15ffdf3fa27e96d382c4e3c593f3a01 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 15 Aug 2023 14:27:29 -0500 Subject: [PATCH 15/35] feature(messages) Checking internal nullifer collisions on messages --- package-lock.json | 8 +-- package.json | 2 +- src/data/db.ts | 4 +- src/data/messages.ts | 125 +++++++++++++++++++++++++++++++++++++------ 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3108087..deace5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.29", + "discreetly-interfaces": "^0.1.31", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -3466,9 +3466,9 @@ "integrity": "sha512-pQueoGtBJk/FrTfGzepjqYfTLaymS+4t11byI4OcfjWQOagRsD7dtavGcowTVQ7Ib/vjKna5T+71WcgWZaAWuA==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.29.tgz", - "integrity": "sha512-3R63KkmB+wFKFFzD9DixX3VDoLCYkDuMQZucAItmXbjE+0tFgmrK683a1/WBI9VkBhARip6HsVNrjzaGqdR1Aw==", + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.31.tgz", + "integrity": "sha512-XRuQee/hmX0I3NkXkNZEzZ+6ZLLVyjjyv4HzcN2H1HJWnhmBsb0BozHifYd22Xk83LuXp7rojnK9qrAHwy1k9g==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index b830f82..d7c5d24 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.29", + "discreetly-interfaces": "^0.1.31", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", diff --git a/src/data/db.ts b/src/data/db.ts index 81db126..91f6f91 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -170,11 +170,11 @@ export async function createRoom( const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id, name).toString() + roomId: genId(serverConfig.id as bigint, name).toString() }, update: {}, create: { - roomId: genId(serverConfig.id, name).toString(), + roomId: genId(serverConfig.id as bigint, name).toString(), name: name, rateLimit: rateLimit, userMessageLimit: userMessageLimit, diff --git a/src/data/messages.ts b/src/data/messages.ts index cf517db..7487365 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -1,16 +1,108 @@ -import { getRoomByID } from './db'; -import { PrismaClient } from '@prisma/client'; -import { MessageI } from 'discreetly-interfaces'; +import { getRoomByID } from "./db"; +import { PrismaClient } from "@prisma/client"; +import { MessageI } from "discreetly-interfaces"; +import { shamirRecovery } from "../crypto/shamirRecovery"; +import { RLNFullProof } from "rlnjs"; const prisma = new PrismaClient(); +export type StrBigInt = string | bigint; + +export type Proof = { + pi_a: StrBigInt[]; + pi_b: StrBigInt[][]; + pi_c: StrBigInt[]; + protocol: string; + curve: string; +}; + +export type RLNPublicSignals = { + x: StrBigInt; + externalNullifier: StrBigInt; + y: StrBigInt; + root: StrBigInt; + nullifier: StrBigInt; +}; + +export type RLNSNARKProof = { + proof: Proof; + publicSignals: RLNPublicSignals; +}; + +interface CollisionCheckResult { + collision: boolean; + secret?: bigint; + oldMessage?: MessageI; +} + +export async function checkRLNCollision( + roomId: string, + message: MessageI +): Promise { + return new Promise((res, rej) => { + prisma.rooms + .findFirst({ + where: { roomId }, + include: { + epochs: { + where: { epoch: String(message.epoch) }, + include: { + messages: { + where: { messageId: message.messageId }, + }, + }, + }, + }, + }) + .then((oldMessage) => { + if (!message.proof) { + throw new Error("Proof not provided"); + } + if (!oldMessage) { + res({ collision: false } as CollisionCheckResult); + } else { + const oldMessageProof = JSON.parse( + oldMessage.epochs[0].messages[0].proof + ) as RLNFullProof; + const oldMessagex2 = BigInt( + oldMessageProof.snarkProof.publicSignals.x + ); + const oldMessagey2 = BigInt( + oldMessageProof.snarkProof.publicSignals.y + ); + + let proof: RLNFullProof; + + if (typeof message.proof === "string") { + proof = JSON.parse(message.proof) as RLNFullProof; + } else { + proof = message.proof as RLNFullProof; + } + const [x1, y1] = [ + BigInt(proof.snarkProof.publicSignals.x), + BigInt(proof.snarkProof.publicSignals.y), + ]; + const [x2, y2] = [oldMessagex2, oldMessagey2]; + + const secret = shamirRecovery(x1, x2, y1, y2); + + res({ + collision: true, + secret, + oldMessage: oldMessage.epochs[0].messages[0] as MessageI, + } as CollisionCheckResult); + } + }); + }); +} + function addMessageToRoom(roomId: string, message: MessageI): Promise { if (!message.epoch) { - throw new Error('Epoch not provided'); + throw new Error("Epoch not provided"); } return prisma.rooms.update({ where: { - roomId: roomId + roomId: roomId, }, data: { epochs: { @@ -18,15 +110,15 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { epoch: String(message.epoch), messages: { create: { - message: message.message ? message.message.toString() : '', - messageId: message.messageId ? message.messageId.toString() : '', + message: message.message ? message.message.toString() : "", + messageId: message.messageId ? message.messageId.toString() : "", proof: JSON.stringify(message.proof), - roomId: roomId - } - } - } - } - } + roomId: roomId, + }, + }, + }, + }, + }, }); } @@ -34,7 +126,10 @@ export function createMessage(roomId: string, message: MessageI): boolean { getRoomByID(roomId) .then((room) => { if (room) { - // Todo This should check that there is no duplicate messageId with in this room and epoch, if there is, we need to return an error and reconstruct the secret from both messages, and ban the user + // Todo This should check that there is no duplicate messageId with in this room and epoch, + // if there is, we need to return an error and + // reconstruct the secret from both messages, and ban the user + addMessageToRoom(roomId, message) .then((roomToUpdate) => { console.log(roomToUpdate); @@ -45,7 +140,7 @@ export function createMessage(roomId: string, message: MessageI): boolean { return false; }); } else { - console.log('Room not found'); + console.log("Room not found"); return false; } }) From bddd96a46c706acc799f10df89b43566aaf63e55 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 15 Aug 2023 16:17:24 -0500 Subject: [PATCH 16/35] feature(RLN) implemeting RLN collision checking for messages --- src/data/messages.ts | 53 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index 7487365..08c9193 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -72,7 +72,7 @@ export async function checkRLNCollision( ); let proof: RLNFullProof; - + if (typeof message.proof === "string") { proof = JSON.parse(message.proof) as RLNFullProof; } else { @@ -123,30 +123,33 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { } export function createMessage(roomId: string, message: MessageI): boolean { - getRoomByID(roomId) - .then((room) => { - if (room) { - // Todo This should check that there is no duplicate messageId with in this room and epoch, - // if there is, we need to return an error and - // reconstruct the secret from both messages, and ban the user - - addMessageToRoom(roomId, message) - .then((roomToUpdate) => { - console.log(roomToUpdate); - return true; - }) - .catch((error) => { - console.error(`Error updating room: ${error}`); + getRoomByID(roomId).then((room) => { + if (room) { + // Todo This should check that there is no duplicate messageId with in this room and epoch, + // if there is, we need to return an error and + // reconstruct the secret from both messages, and ban the user + checkRLNCollision(roomId, message) + .then((collisionResult) => { + if (!collisionResult.collision) { + addMessageToRoom(roomId, message) + .then((roomToUpdate) => { + console.log(roomToUpdate); + return true; + }) + .catch((error) => { + console.error(`Error updating room: ${error}`); + return false; + }); + } else { + console.log("Collision found"); return false; - }); - } else { - console.log("Room not found"); - return false; - } - }) - .catch((error) => { - console.error(`Error getting room: ${error}`); - return false; - }); + } + }) + .catch((error) => { + console.error(`Error getting room: ${error}`); + return false; + }); + } + }); return false; } From 9475263a8c3fb556c3278cd21a95e3675e52e222 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer <1675654+AtHeartEngineer@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:20:32 -0400 Subject: [PATCH 17/35] Update src/data/messages.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/data/messages.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index 08c9193..39aa5c2 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -8,13 +8,13 @@ const prisma = new PrismaClient(); export type StrBigInt = string | bigint; -export type Proof = { +export interface Proof { pi_a: StrBigInt[]; pi_b: StrBigInt[][]; pi_c: StrBigInt[]; protocol: string; curve: string; -}; +} export type RLNPublicSignals = { x: StrBigInt; From fe9ef4d7f79453ebee5e58bd6477308a72867488 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer <1675654+AtHeartEngineer@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:20:37 -0400 Subject: [PATCH 18/35] Update src/data/messages.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/data/messages.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index 39aa5c2..e09f887 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -16,13 +16,13 @@ export interface Proof { curve: string; } -export type RLNPublicSignals = { +export interface RLNPublicSignals { x: StrBigInt; externalNullifier: StrBigInt; y: StrBigInt; root: StrBigInt; nullifier: StrBigInt; -}; +} export type RLNSNARKProof = { proof: Proof; From 33b2e042dcc72ee7e221e9e36106eaec8273c512 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer <1675654+AtHeartEngineer@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:20:43 -0400 Subject: [PATCH 19/35] Update src/data/messages.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/data/messages.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index e09f887..84a2144 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -24,10 +24,10 @@ export interface RLNPublicSignals { nullifier: StrBigInt; } -export type RLNSNARKProof = { +export interface RLNSNARKProof { proof: Proof; publicSignals: RLNPublicSignals; -}; +} interface CollisionCheckResult { collision: boolean; From e371b1ef340a006cab7c6d4a6829a2cbc0509d6e Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 17:43:07 -0400 Subject: [PATCH 20/35] fix .catch + style --- README.md | 6 +++ src/data/messages.ts | 122 ++++++++++++++++++++++--------------------- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 8686ab3..8430da5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - [Run the server](#run-the-server) - [Running tests](#running-tests) - [🔩 Usage](#-usage) + - [Style Guide](#style-guide)
@@ -133,3 +134,8 @@ npm run test + + +#### Style Guide + +* Single Quotes \ No newline at end of file diff --git a/src/data/messages.ts b/src/data/messages.ts index 84a2144..31e7721 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -1,8 +1,8 @@ -import { getRoomByID } from "./db"; -import { PrismaClient } from "@prisma/client"; -import { MessageI } from "discreetly-interfaces"; -import { shamirRecovery } from "../crypto/shamirRecovery"; -import { RLNFullProof } from "rlnjs"; +import { getRoomByID } from './db'; +import { PrismaClient } from '@prisma/client'; +import { MessageI } from 'discreetly-interfaces'; +import { shamirRecovery } from '../crypto/shamirRecovery'; +import { RLNFullProof } from 'rlnjs'; const prisma = new PrismaClient(); @@ -39,7 +39,7 @@ export async function checkRLNCollision( roomId: string, message: MessageI ): Promise { - return new Promise((res, rej) => { + return new Promise((res) => { prisma.rooms .findFirst({ where: { roomId }, @@ -48,15 +48,15 @@ export async function checkRLNCollision( where: { epoch: String(message.epoch) }, include: { messages: { - where: { messageId: message.messageId }, - }, - }, - }, - }, + where: { messageId: message.messageId } + } + } + } + } }) .then((oldMessage) => { if (!message.proof) { - throw new Error("Proof not provided"); + throw new Error('Proof not provided'); } if (!oldMessage) { res({ collision: false } as CollisionCheckResult); @@ -64,23 +64,19 @@ export async function checkRLNCollision( const oldMessageProof = JSON.parse( oldMessage.epochs[0].messages[0].proof ) as RLNFullProof; - const oldMessagex2 = BigInt( - oldMessageProof.snarkProof.publicSignals.x - ); - const oldMessagey2 = BigInt( - oldMessageProof.snarkProof.publicSignals.y - ); + const oldMessagex2 = BigInt(oldMessageProof.snarkProof.publicSignals.x); + const oldMessagey2 = BigInt(oldMessageProof.snarkProof.publicSignals.y); let proof: RLNFullProof; - if (typeof message.proof === "string") { + if (typeof message.proof === 'string') { proof = JSON.parse(message.proof) as RLNFullProof; } else { - proof = message.proof as RLNFullProof; + proof = message.proof; } const [x1, y1] = [ BigInt(proof.snarkProof.publicSignals.x), - BigInt(proof.snarkProof.publicSignals.y), + BigInt(proof.snarkProof.publicSignals.y) ]; const [x2, y2] = [oldMessagex2, oldMessagey2]; @@ -89,20 +85,21 @@ export async function checkRLNCollision( res({ collision: true, secret, - oldMessage: oldMessage.epochs[0].messages[0] as MessageI, + oldMessage: oldMessage.epochs[0].messages[0] as unknown as MessageI } as CollisionCheckResult); } - }); + }) + .catch((err) => console.error(err)); }); } function addMessageToRoom(roomId: string, message: MessageI): Promise { if (!message.epoch) { - throw new Error("Epoch not provided"); + throw new Error('Epoch not provided'); } return prisma.rooms.update({ where: { - roomId: roomId, + roomId: roomId }, data: { epochs: { @@ -110,46 +107,51 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { epoch: String(message.epoch), messages: { create: { - message: message.message ? message.message.toString() : "", - messageId: message.messageId ? message.messageId.toString() : "", + message: message.message ? message.message.toString() : '', + messageId: message.messageId ? message.messageId.toString() : '', proof: JSON.stringify(message.proof), - roomId: roomId, - }, - }, - }, - }, - }, + roomId: roomId + } + } + } + } + } }); } export function createMessage(roomId: string, message: MessageI): boolean { - getRoomByID(roomId).then((room) => { - if (room) { - // Todo This should check that there is no duplicate messageId with in this room and epoch, - // if there is, we need to return an error and - // reconstruct the secret from both messages, and ban the user - checkRLNCollision(roomId, message) - .then((collisionResult) => { - if (!collisionResult.collision) { - addMessageToRoom(roomId, message) - .then((roomToUpdate) => { - console.log(roomToUpdate); - return true; - }) - .catch((error) => { - console.error(`Error updating room: ${error}`); - return false; - }); - } else { - console.log("Collision found"); + getRoomByID(roomId) + .then((room) => { + if (room) { + // Todo This should check that there is no duplicate messageId with in this room and epoch, + // if there is, we need to return an error and + // reconstruct the secret from both messages, and ban the user + checkRLNCollision(roomId, message) + .then((collisionResult) => { + if (!collisionResult.collision) { + addMessageToRoom(roomId, message) + .then((roomToUpdate) => { + console.log(roomToUpdate); + return true; + }) + .catch((error) => { + console.error(`Error updating room: ${error}`); + return false; + }); + } else { + console.log('Collision found'); + return false; + } + }) + .catch((error) => { + console.error(`Error getting room: ${error}`); return false; - } - }) - .catch((error) => { - console.error(`Error getting room: ${error}`); - return false; - }); - } - }); + }); + } + }) + .catch((error) => { + console.error(`Error getting room: ${error}`); + return false; + }); return false; } From 4a47c0a99d7862b9f61f265b3ef7865fa3b4955c Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 17:48:03 -0400 Subject: [PATCH 21/35] feat createMessageResult interface --- src/data/messages.ts | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index 31e7721..c5d3d66 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -6,39 +6,13 @@ import { RLNFullProof } from 'rlnjs'; const prisma = new PrismaClient(); -export type StrBigInt = string | bigint; - -export interface Proof { - pi_a: StrBigInt[]; - pi_b: StrBigInt[][]; - pi_c: StrBigInt[]; - protocol: string; - curve: string; -} - -export interface RLNPublicSignals { - x: StrBigInt; - externalNullifier: StrBigInt; - y: StrBigInt; - root: StrBigInt; - nullifier: StrBigInt; -} - -export interface RLNSNARKProof { - proof: Proof; - publicSignals: RLNPublicSignals; -} - interface CollisionCheckResult { collision: boolean; secret?: bigint; oldMessage?: MessageI; } -export async function checkRLNCollision( - roomId: string, - message: MessageI -): Promise { +async function checkRLNCollision(roomId: string, message: MessageI): Promise { return new Promise((res) => { prisma.rooms .findFirst({ @@ -93,6 +67,12 @@ export async function checkRLNCollision( }); } +interface createMessageResult { + success: boolean; + message?: MessageI; + idc: string | bigint; +} + function addMessageToRoom(roomId: string, message: MessageI): Promise { if (!message.epoch) { throw new Error('Epoch not provided'); @@ -119,7 +99,7 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { }); } -export function createMessage(roomId: string, message: MessageI): boolean { +export function createMessage(roomId: string, message: MessageI): createMessageResult { getRoomByID(roomId) .then((room) => { if (room) { @@ -132,10 +112,10 @@ export function createMessage(roomId: string, message: MessageI): boolean { addMessageToRoom(roomId, message) .then((roomToUpdate) => { console.log(roomToUpdate); - return true; + return { success: true }; }) .catch((error) => { - console.error(`Error updating room: ${error}`); + console.error(`Couldn't add message room ${error}`); return false; }); } else { From 9e0584f61f02534d09e3a39224144d9a6b7c077f Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 15 Aug 2023 17:28:17 -0500 Subject: [PATCH 22/35] feature(db util) function for removing identity from a room --- src/data/db.ts | 104 ++++++++++++++++++++++++++----------------- src/data/messages.ts | 99 ++++++++++++++++++++++++---------------- 2 files changed, 122 insertions(+), 81 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 91f6f91..709577d 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { PrismaClient } from '@prisma/client'; -import { genId } from 'discreetly-interfaces'; -import type { RoomI } from 'discreetly-interfaces'; -import { serverConfig } from '../config/serverConfig'; -import { genMockUsers, genClaimCodeArray, pp } from '../utils'; +import { PrismaClient } from "@prisma/client"; +import { genId } from "discreetly-interfaces"; +import type { RoomI } from "discreetly-interfaces"; +import { serverConfig } from "../config/serverConfig"; +import { genMockUsers, genClaimCodeArray, pp } from "../utils"; const prisma = new PrismaClient(); @@ -22,7 +22,7 @@ export async function getRoomByID(id: string): Promise { const room = await prisma.rooms .findUnique({ where: { - roomId: id + roomId: id, }, select: { id: true, @@ -34,8 +34,8 @@ export async function getRoomByID(id: string): Promise { membershipType: true, contractAddress: true, bandadaAddress: true, - type: true - } + type: true, + }, }) .then((room) => { return room; @@ -48,7 +48,7 @@ export async function getRoomByID(id: string): Promise { if (room) { resolve(room as RoomI); } - reject('Room not found'); + reject("Room not found"); }); } @@ -64,9 +64,9 @@ export async function getRoomsByIdentity(identity: string): Promise { const rooms = await prisma.rooms.findMany({ where: { identities: { - has: identity - } - } + has: identity, + }, + }, }); rooms.forEach((room) => { r.push(room.roomId); @@ -81,21 +81,24 @@ export async function getRoomsByIdentity(identity: string): Promise { export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ - where: { claimcode: code } + where: { claimcode: code }, }); } export function updateClaimCode(code: string): Promise { return prisma.claimCodes.update({ where: { claimcode: code }, - data: { claimed: true } + data: { claimed: true }, }); } -export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { +export function updateRoomIdentities( + idc: string, + roomIds: string[] +): Promise { return prisma.rooms .findMany({ - where: { id: { in: roomIds } } + where: { id: { in: roomIds } }, }) .then((rooms) => { const roomsToUpdate = rooms @@ -105,49 +108,66 @@ export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { - pp(err, 'error'); + pp(err, "error"); }); } export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ - where: { id: { in: roomIds } } + where: { id: { in: roomIds } }, }); return new Promise((resolve, reject) => { if (rooms) { resolve(rooms as RoomI[]); } - reject('No rooms found'); + reject("No rooms found"); }); } -export function createSystemMessages(message: string, roomId?: string): Promise { +export function createSystemMessages( + message: string, + roomId?: string +): Promise { const query = roomId ? { where: { roomId } } : undefined; - return prisma.rooms.findMany(query) - .then(rooms => { - if (roomId && rooms.length === 0) { - Promise.reject('Room not found') - } - const createMessages = rooms.map(room => { - return prisma.messages.create({ - data: { - message, - roomId: room.roomId, - messageId: "0", - proof: JSON.stringify({}), - }, - }); + return prisma.rooms.findMany(query).then((rooms) => { + if (roomId && rooms.length === 0) { + return Promise.reject("Room not found"); + } + const createMessages = rooms.map((room) => { + return prisma.messages.create({ + data: { + message, + roomId: room.roomId, + messageId: "0", + proof: JSON.stringify({}), + }, }); - - return Promise.all(createMessages); }); + + return Promise.all(createMessages); + }).catch(err => { + console.error(err); + }) } +export function removeIdentityFromRoom(idc: string, room: RoomI): Promise { + const updateIdentities = room.identities?.map((identity) => + identity === idc ? "0n" : identity + ) as string[]; + return prisma.rooms.update({ + where: { id: room.id }, + data: { identities: updateIdentities }, + }).then((room) => { + return room as RoomI; + }).catch(err => { + console.error(err); + }) +} /** * Creates a new room with the given name and optional parameters. @@ -163,14 +183,14 @@ export async function createRoom( userMessageLimit = 1, numClaimCodes = 0, approxNumMockUsers = 20, - type: string = 'PUBLIC' + type = "PUBLIC" ): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as bigint, name).toString() + roomId: genId(serverConfig.id as bigint, name).toString(), }, update: {}, create: { @@ -181,9 +201,9 @@ export async function createRoom( identities: mockUsers, type, claimCodes: { - create: claimCodes - } - } + create: claimCodes, + }, + }, }; return await prisma.rooms diff --git a/src/data/messages.ts b/src/data/messages.ts index c5d3d66..362082c 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -1,8 +1,11 @@ -import { getRoomByID } from './db'; -import { PrismaClient } from '@prisma/client'; -import { MessageI } from 'discreetly-interfaces'; -import { shamirRecovery } from '../crypto/shamirRecovery'; -import { RLNFullProof } from 'rlnjs'; +import { getRoomByID, removeIdentityFromRoom } from "./db"; +import { PrismaClient } from "@prisma/client"; +import { MessageI } from "discreetly-interfaces"; +import { + shamirRecovery, + getIdentityCommitmentFromSecret, +} from "../crypto/shamirRecovery"; +import { RLNFullProof } from "rlnjs"; const prisma = new PrismaClient(); @@ -12,7 +15,10 @@ interface CollisionCheckResult { oldMessage?: MessageI; } -async function checkRLNCollision(roomId: string, message: MessageI): Promise { +async function checkRLNCollision( + roomId: string, + message: MessageI +): Promise { return new Promise((res) => { prisma.rooms .findFirst({ @@ -22,15 +28,15 @@ async function checkRLNCollision(roomId: string, message: MessageI): Promise { if (!message.proof) { - throw new Error('Proof not provided'); + throw new Error("Proof not provided"); } if (!oldMessage) { res({ collision: false } as CollisionCheckResult); @@ -38,19 +44,23 @@ async function checkRLNCollision(roomId: string, message: MessageI): Promise { if (!message.epoch) { - throw new Error('Epoch not provided'); + throw new Error("Epoch not provided"); } return prisma.rooms.update({ where: { - roomId: roomId + roomId: roomId, }, data: { epochs: { @@ -87,19 +91,27 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { epoch: String(message.epoch), messages: { create: { - message: message.message ? message.message.toString() : '', - messageId: message.messageId ? message.messageId.toString() : '', + message: message.message ? message.message.toString() : "", + messageId: message.messageId ? message.messageId.toString() : "", proof: JSON.stringify(message.proof), - roomId: roomId - } - } - } - } - } + roomId: roomId, + }, + }, + }, + }, + }, }); } +interface createMessageResult { + success: boolean; + message?: MessageI; + idc?: string | bigint; +} -export function createMessage(roomId: string, message: MessageI): createMessageResult { +export function createMessage( + roomId: string, + message: MessageI +): createMessageResult { getRoomByID(roomId) .then((room) => { if (room) { @@ -119,19 +131,28 @@ export function createMessage(roomId: string, message: MessageI): createMessageR return false; }); } else { - console.log('Collision found'); - return false; + console.log("Collision found"); + const identityCommitment = getIdentityCommitmentFromSecret( + collisionResult.secret! + ); + removeIdentityFromRoom(identityCommitment.toString(), room) + .then(() => { + return { success: false }; + }) + .catch((error) => { + console.error(`Couldn't remove identity from room ${error}`); + }); } }) .catch((error) => { console.error(`Error getting room: ${error}`); - return false; + return { success: false }; }); } }) .catch((error) => { console.error(`Error getting room: ${error}`); - return false; + return { success: false }; }); - return false; + return { success: false }; } From 20486a55307b7322b5b824ce141aa6654f7710d8 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 15 Aug 2023 17:47:26 -0500 Subject: [PATCH 23/35] exported createMessageResult interface --- src/data/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/messages.ts b/src/data/messages.ts index 362082c..1e5f685 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -102,7 +102,7 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { }, }); } -interface createMessageResult { +export interface createMessageResult { success: boolean; message?: MessageI; idc?: string | bigint; From 8efefe9ea44cfb7c57e480022a0fc9726202dd39 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 22:14:40 -0400 Subject: [PATCH 24/35] feat(data/db): adjust string conversions and field additions feat(websockets): emit member count to room on join/leave refactor(messages): improve typings and method calls refactor(utils): adjust bigint conversions refactor(data/db): update import syntax and object formations refactor(crypto/verifier): adjust proof checks and conditionals chore(dependecies): upgrade discreetly-interfaces version --- package-lock.json | 8 +-- package.json | 4 +- prisma/schema.prisma | 23 ++++---- prisma/seed.ts | 6 +- src/crypto/verifier.ts | 21 +++++-- src/data/db.ts | 119 ++++++++++++++++++++-------------------- src/data/messages.ts | 75 ++++++++++--------------- src/utils.ts | 2 +- src/websockets/index.ts | 10 +++- 9 files changed, 135 insertions(+), 133 deletions(-) diff --git a/package-lock.json b/package-lock.json index deace5c..bd4937f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.31", + "discreetly-interfaces": "^0.1.34", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -3466,9 +3466,9 @@ "integrity": "sha512-pQueoGtBJk/FrTfGzepjqYfTLaymS+4t11byI4OcfjWQOagRsD7dtavGcowTVQ7Ib/vjKna5T+71WcgWZaAWuA==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.31.tgz", - "integrity": "sha512-XRuQee/hmX0I3NkXkNZEzZ+6ZLLVyjjyv4HzcN2H1HJWnhmBsb0BozHifYd22Xk83LuXp7rojnK9qrAHwy1k9g==", + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.34.tgz", + "integrity": "sha512-7purPOWOowVH44ebdweBdZ4z2RsBQy5/H7xi6PdsHkaw1xwg8u3Ev2US5EdavP1igZ+SzebJdK8jT0ZTjzX8Kg==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index d7c5d24..726c257 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.31", + "discreetly-interfaces": "^0.1.34", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -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 16c6431..1f83c66 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,21 +11,22 @@ datasource db { } 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 - membershipType String @default("IDENTITY_LIST") - 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 String @default("IDENTITY_LIST") + identities String[] @default([]) contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" - bandadaAddress String? // BANDADA as "url:groupID" + bandadaAddress String? + bandadaGroupId String? epochs Epoch[] messages Messages[] - claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) - claimCodeIds String[] @default([]) @db.ObjectId - type String @default("PUBLIC") + claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) + claimCodeIds String[] @default([]) @db.ObjectId + type String @default("PUBLIC") } model ClaimCodes { diff --git a/prisma/seed.ts b/prisma/seed.ts index b29a0e9..bac58de 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); - createRoom('10 Second Room', 10000, 2, 10); - createRoom('100 Second Room', 100000, 10, 10); + 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)); } main(); diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 8a37430..98b8515 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -1,6 +1,6 @@ import type { MessageI, RoomI } from 'discreetly-interfaces'; import { str2BigInt } from 'discreetly-interfaces'; -import { RLNVerifier } from 'rlnjs'; +import { RLNFullProof, RLNVerifier } from 'rlnjs'; import vkey from './verification_key'; import { Group } from '@semaphore-protocol/group'; @@ -17,6 +17,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro const currentEpoch = Math.floor(timestamp / rateLimit); const rlnIdentifier = BigInt(msg.roomId); const msgHash = str2BigInt(msg.message); + let proof: RLNFullProof | undefined; // Check that the epoch falls within the range for the room const epoch = BigInt(msg.epoch); if (epoch < currentEpoch - epochErrorRange || epoch > currentEpoch + epochErrorRange) { @@ -24,14 +25,24 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro console.warn('Epoch out of range:', epoch, 'currentEpoch:', currentEpoch); return false; } + if (typeof msg.proof === 'string') { + proof = JSON.parse(msg.proof) as RLNFullProof; + } else { + console.warn('Invalid proof format:', msg.proof); + return false; + } + if (!proof) { + console.warn('Proof is undefined:', msg.proof); + return false; + } // Check that the message hash is correct - if (msgHash !== msg.proof.snarkProof.publicSignals.x) { + if (msgHash !== proof.snarkProof.publicSignals.x) { console.warn( 'Message hash incorrect:', msgHash, 'Hash in proof:', - msg.proof.snarkProof.publicSignals.x + proof.snarkProof.publicSignals.x ); return false; } @@ -39,13 +50,13 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro // Check that the merkle root is correct if (room.identities && Array.isArray(room.identities)) { const group = new Group(room.id, 20, room.identities as bigint[] | undefined); - if (group.root !== msg.proof.snarkProof.publicSignals.root) { + if (group.root !== proof.snarkProof.publicSignals.root) { return false; } } // Check that the proof is correct - return v.verifyProof(rlnIdentifier, msg.proof); + return v.verifyProof(rlnIdentifier, proof); } export default verifyProof; diff --git a/src/data/db.ts b/src/data/db.ts index 709577d..7a18eb6 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { PrismaClient } from "@prisma/client"; -import { genId } from "discreetly-interfaces"; -import type { RoomI } from "discreetly-interfaces"; -import { serverConfig } from "../config/serverConfig"; -import { genMockUsers, genClaimCodeArray, pp } from "../utils"; +import { PrismaClient } from '@prisma/client'; +import { genId } from 'discreetly-interfaces'; +import type { RoomI } from 'discreetly-interfaces'; +import { serverConfig } from '../config/serverConfig'; +import { genMockUsers, genClaimCodeArray, pp } from '../utils'; const prisma = new PrismaClient(); @@ -22,7 +22,7 @@ export async function getRoomByID(id: string): Promise { const room = await prisma.rooms .findUnique({ where: { - roomId: id, + roomId: id }, select: { id: true, @@ -34,8 +34,9 @@ export async function getRoomByID(id: string): Promise { membershipType: true, contractAddress: true, bandadaAddress: true, - type: true, - }, + bandadaGroupId: true, + type: true + } }) .then((room) => { return room; @@ -48,7 +49,7 @@ export async function getRoomByID(id: string): Promise { if (room) { resolve(room as RoomI); } - reject("Room not found"); + reject('Room not found'); }); } @@ -64,9 +65,9 @@ export async function getRoomsByIdentity(identity: string): Promise { const rooms = await prisma.rooms.findMany({ where: { identities: { - has: identity, - }, - }, + has: identity + } + } }); rooms.forEach((room) => { r.push(room.roomId); @@ -81,24 +82,21 @@ export async function getRoomsByIdentity(identity: string): Promise { export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ - where: { claimcode: code }, + where: { claimcode: code } }); } export function updateClaimCode(code: string): Promise { return prisma.claimCodes.update({ where: { claimcode: code }, - data: { claimed: true }, + data: { claimed: true } }); } -export function updateRoomIdentities( - idc: string, - roomIds: string[] -): Promise { +export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { return prisma.rooms .findMany({ - where: { id: { in: roomIds } }, + where: { id: { in: roomIds } } }) .then((rooms) => { const roomsToUpdate = rooms @@ -108,65 +106,68 @@ export function updateRoomIdentities( if (roomsToUpdate) { return prisma.rooms.updateMany({ where: { id: { in: roomsToUpdate } }, - data: { identities: { push: idc } }, + data: { identities: { push: idc } } }); } }) .catch((err) => { - pp(err, "error"); + pp(err, 'error'); }); } export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ - where: { id: { in: roomIds } }, + where: { id: { in: roomIds } } }); return new Promise((resolve, reject) => { if (rooms) { resolve(rooms as RoomI[]); } - reject("No rooms found"); + reject('No rooms found'); }); } -export function createSystemMessages( - message: string, - roomId?: string -): Promise { +export function createSystemMessages(message: string, roomId?: string): Promise { const query = roomId ? { where: { roomId } } : undefined; - return prisma.rooms.findMany(query).then((rooms) => { - if (roomId && rooms.length === 0) { - return Promise.reject("Room not found"); - } - const createMessages = rooms.map((room) => { - return prisma.messages.create({ - data: { - message, - roomId: room.roomId, - messageId: "0", - proof: JSON.stringify({}), - }, + return prisma.rooms + .findMany(query) + .then((rooms) => { + if (roomId && rooms.length === 0) { + return Promise.reject('Room not found'); + } + const createMessages = rooms.map((room) => { + return prisma.messages.create({ + data: { + message, + roomId: room.roomId, + messageId: '0', + proof: JSON.stringify({}) + } + }); }); - }); - return Promise.all(createMessages); - }).catch(err => { - console.error(err); - }) + return Promise.all(createMessages); + }) + .catch((err) => { + console.error(err); + }); } export function removeIdentityFromRoom(idc: string, room: RoomI): Promise { const updateIdentities = room.identities?.map((identity) => - identity === idc ? "0n" : identity + identity === idc ? '0n' : identity ) as string[]; - return prisma.rooms.update({ - where: { id: room.id }, - data: { identities: updateIdentities }, - }).then((room) => { - return room as RoomI; - }).catch(err => { - console.error(err); - }) + return prisma.rooms + .update({ + where: { id: room.id }, + data: { identities: updateIdentities } + }) + .then((room) => { + return room as RoomI; + }) + .catch((err) => { + console.error(err); + }); } /** @@ -183,27 +184,27 @@ export async function createRoom( userMessageLimit = 1, numClaimCodes = 0, approxNumMockUsers = 20, - type = "PUBLIC" + type = 'PUBLIC' ): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as bigint, name).toString(), + roomId: genId(serverConfig.id as string, name).toString() }, update: {}, create: { - roomId: genId(serverConfig.id as bigint, name).toString(), + roomId: genId(serverConfig.id as string, name).toString(), name: name, rateLimit: rateLimit, userMessageLimit: userMessageLimit, identities: mockUsers, type, claimCodes: { - create: claimCodes, - }, - }, + create: claimCodes + } + } }; return await prisma.rooms diff --git a/src/data/messages.ts b/src/data/messages.ts index 1e5f685..e0a6788 100644 --- a/src/data/messages.ts +++ b/src/data/messages.ts @@ -1,11 +1,8 @@ -import { getRoomByID, removeIdentityFromRoom } from "./db"; -import { PrismaClient } from "@prisma/client"; -import { MessageI } from "discreetly-interfaces"; -import { - shamirRecovery, - getIdentityCommitmentFromSecret, -} from "../crypto/shamirRecovery"; -import { RLNFullProof } from "rlnjs"; +import { getRoomByID, removeIdentityFromRoom } from './db'; +import { PrismaClient } from '@prisma/client'; +import { MessageI } from 'discreetly-interfaces'; +import { shamirRecovery, getIdentityCommitmentFromSecret } from '../crypto/shamirRecovery'; +import { RLNFullProof } from 'rlnjs'; const prisma = new PrismaClient(); @@ -15,10 +12,7 @@ interface CollisionCheckResult { oldMessage?: MessageI; } -async function checkRLNCollision( - roomId: string, - message: MessageI -): Promise { +async function checkRLNCollision(roomId: string, message: MessageI): Promise { return new Promise((res) => { prisma.rooms .findFirst({ @@ -28,15 +22,15 @@ async function checkRLNCollision( where: { epoch: String(message.epoch) }, include: { messages: { - where: { messageId: message.messageId }, - }, - }, - }, - }, + where: { messageId: message.messageId } + } + } + } + } }) .then((oldMessage) => { if (!message.proof) { - throw new Error("Proof not provided"); + throw new Error('Proof not provided'); } if (!oldMessage) { res({ collision: false } as CollisionCheckResult); @@ -44,23 +38,19 @@ async function checkRLNCollision( const oldMessageProof = JSON.parse( oldMessage.epochs[0].messages[0].proof ) as RLNFullProof; - const oldMessagex2 = BigInt( - oldMessageProof.snarkProof.publicSignals.x - ); - const oldMessagey2 = BigInt( - oldMessageProof.snarkProof.publicSignals.y - ); + const oldMessagex2 = BigInt(oldMessageProof.snarkProof.publicSignals.x); + const oldMessagey2 = BigInt(oldMessageProof.snarkProof.publicSignals.y); let proof: RLNFullProof; - if (typeof message.proof === "string") { + if (typeof message.proof === 'string') { proof = JSON.parse(message.proof) as RLNFullProof; } else { proof = message.proof; } const [x1, y1] = [ BigInt(proof.snarkProof.publicSignals.x), - BigInt(proof.snarkProof.publicSignals.y), + BigInt(proof.snarkProof.publicSignals.y) ]; const [x2, y2] = [oldMessagex2, oldMessagey2]; @@ -69,7 +59,7 @@ async function checkRLNCollision( res({ collision: true, secret, - oldMessage: oldMessage.epochs[0].messages[0] as unknown as MessageI, + oldMessage: oldMessage.epochs[0].messages[0] as unknown as MessageI } as CollisionCheckResult); } }) @@ -79,11 +69,11 @@ async function checkRLNCollision( function addMessageToRoom(roomId: string, message: MessageI): Promise { if (!message.epoch) { - throw new Error("Epoch not provided"); + throw new Error('Epoch not provided'); } return prisma.rooms.update({ where: { - roomId: roomId, + roomId: roomId }, data: { epochs: { @@ -91,15 +81,15 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise { epoch: String(message.epoch), messages: { create: { - message: message.message ? message.message.toString() : "", - messageId: message.messageId ? message.messageId.toString() : "", + message: message.message ? message.message.toString() : '', + messageId: message.messageId ? message.messageId.toString() : '', proof: JSON.stringify(message.proof), - roomId: roomId, - }, - }, - }, - }, - }, + roomId: roomId + } + } + } + } + } }); } export interface createMessageResult { @@ -108,10 +98,7 @@ export interface createMessageResult { idc?: string | bigint; } -export function createMessage( - roomId: string, - message: MessageI -): createMessageResult { +export function createMessage(roomId: string, message: MessageI): createMessageResult { getRoomByID(roomId) .then((room) => { if (room) { @@ -131,10 +118,8 @@ export function createMessage( return false; }); } else { - console.log("Collision found"); - const identityCommitment = getIdentityCommitmentFromSecret( - collisionResult.secret! - ); + console.log('Collision found'); + const identityCommitment = getIdentityCommitmentFromSecret(collisionResult.secret!); removeIdentityFromRoom(identityCommitment.toString(), room) .then(() => { return { success: false }; diff --git a/src/utils.ts b/src/utils.ts index 661cd8c..cb35916 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, + serverConfig.id as string, // Generates a random string of length 10 Math.random() .toString(36) diff --git a/src/websockets/index.ts b/src/websockets/index.ts index bd07ce4..748aadc 100644 --- a/src/websockets/index.ts +++ b/src/websockets/index.ts @@ -3,7 +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'; +import { createMessage, createMessageResult } from '../data/messages'; const userCount: Record = {}; @@ -23,8 +23,9 @@ export function websocketSetup(io: SocketIOServer) { verifyProof(msg, room) .then((v) => { validProof = v; - const validMessage: boolean = createMessage(String(msg.roomId), msg); - if (!validProof || !validMessage) { + // TODO import createMessageResult, and broadcast the idc and message ID that were removed to those room users + const validMessage: createMessageResult = createMessage(String(msg.roomId), msg); + if (!validProof || !validMessage.success) { pp('INVALID MESSAGE', 'warn'); return; } @@ -44,11 +45,14 @@ export function websocketSetup(io: SocketIOServer) { socket.on('joinRoom', (roomID: bigint) => { const id = roomID.toString(); userCount[id] = userCount[id] ? userCount[id] + 1 : 1; + void socket.join(id); + io.to(id).emit('Members', userCount[id] ? userCount[id] : 0); }); socket.on('leaveRoom', (roomID: bigint) => { const id = roomID.toString(); userCount[id] = userCount[id] ? userCount[id] - 1 : 0; + io.to(id).emit('Members', userCount[id] ? userCount[id] : 0); }); }); } From c810b310c0b0d5221956833eaa72d8630467b716 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 22:21:35 -0400 Subject: [PATCH 25/35] refactor(tests) update tests and close app on afterAll; added coverage --- .gitignore | 1 + package.json | 2 +- tests/express.test.ts | 133 +++++++++++++++++++----------------------- 3 files changed, 62 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 621b233..0c3aa98 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist/ *.log .env thunder-tests/ +coverage/ diff --git a/package.json b/package.json index 726c257..901460e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "watch": "rollup --config rollup.config.mjs --watch", "serve": "nodemon -q dist/server.jcs", "dev": "concurrently \"npm run watch\" \"npm run serve\"", - "test": "jest --detectOpenHandles", + "test": "jest --detectOpenHandles --coverage", "lint": "eslint ." }, "engines": { diff --git a/tests/express.test.ts b/tests/express.test.ts index 932d43e..c142027 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -1,14 +1,14 @@ -const request = require("supertest"); -import _app from "../src/server"; -import { genId } from "discreetly-interfaces"; -import { serverConfig } from "../src/config/serverConfig"; -import { PrismaClient } from "@prisma/client"; -import { beforeAll, beforeEach, describe, expect, test } from "@jest/globals"; -import { pp } from "../src/utils"; -import { randBigint, randomRoomName } from "./utils"; +const request = require('supertest'); +import _app from '../src/server'; +import { genId } from 'discreetly-interfaces'; +import { serverConfig } from '../src/config/serverConfig'; +import { PrismaClient } from '@prisma/client'; +import { beforeAll, beforeEach, describe, expect, test } from '@jest/globals'; +import { pp } from '../src/utils'; +import { randBigint, randomRoomName } from './utils'; process.env.DATABASE_URL = process.env.DATABASE_URL_TEST; -process.env.PORT = "3001"; +process.env.PORT = '3001'; beforeAll(async () => { const prismaTest = new PrismaClient(); @@ -17,11 +17,8 @@ beforeAll(async () => { await prismaTest.claimCodes.deleteMany(); }); -beforeAll(async () => { - const prismaTest = new PrismaClient(); - await prismaTest.messages.deleteMany(); - await prismaTest.rooms.deleteMany(); - await prismaTest.claimCodes.deleteMany(); +afterAll(async () => { + _app.close(); }); const room = { @@ -30,52 +27,47 @@ const room = { userMessageLimit: 1, numClaimCodes: 5, approxNumMockUsers: 10, - type: "PUBLIC" + type: 'PUBLIC' }; -const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); -let testCode = ""; +const roomByIdTest = genId(serverConfig.id as string, room.roomName).toString(); +let testCode = ''; const testIdentity = randBigint(); -const username = "admin"; +const username = 'admin'; const password = process.env.PASSWORD; - -describe("Endpoints should all work hopefully", () => { - test("It should respond with server info", async () => { +describe('Endpoints should all work hopefully', () => { + test('It should respond with server info', async () => { await request(_app) - .get("/") + .get('/') .then((res) => { expect(res.status).toBe(200); - expect(res.header["content-type"]).toBe( - "application/json; charset=utf-8" - ); + expect(res.header['content-type']).toBe('application/json; charset=utf-8'); expect(res.body.id).toBe(serverConfig.id); }) - .catch((error) => pp("GET '/' - " + error, "error")); + .catch((error) => pp("GET '/' - " + error, 'error')); }); - test("It should add a new room to the database", async () => { - const username = "admin"; + test('It should add a new room to the database', async () => { + const username = 'admin'; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString( - "base64" - ); + const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); await request(_app) - .post("/room/add") - .set("Authorization", `Basic ${base64Credentials}`) + .post('/room/add') + .set('Authorization', `Basic ${base64Credentials}`) .send(room) .then((res) => { try { - expect(res.body).toEqual({ message: "Room created successfully" }); + expect(res.body).toEqual({ message: 'Room created successfully' }); } catch (error) { - console.warn("POST /room/add - " + error); + console.warn('POST /room/add - ' + error); } }) - .catch((error) => console.warn("POST /room/add - " + error)); + .catch((error) => console.warn('POST /room/add - ' + error)); }); - test("It should return the room with the given id", async () => { + test('It should return the room with the given id', async () => { await request(_app) .get(`/api/room/${roomByIdTest}`) .then((res) => { @@ -83,43 +75,40 @@ describe("Endpoints should all work hopefully", () => { expect(res.status).toEqual(200); expect(res.body.name).toEqual(room.roomName); } catch (error) { - pp("GET /api/room/:roomId - " + error, "error"); + pp('GET /api/room/:roomId - ' + error, 'error'); } }) - .catch((error) => pp("GET /api/room/:roomId - " + error, "error")); + .catch((error) => pp('GET /api/room/:roomId - ' + error, 'error')); }); - test("It should return all rooms", async () => { - const username = "admin"; + test('It should return all rooms', async () => { + const username = 'admin'; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString( - "base64" - ); + const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); await request(_app) - .get("/api/rooms") + .get('/api/rooms') - .set("Authorization", `Basic ${base64Credentials}`) + .set('Authorization', `Basic ${base64Credentials}`) .then((res) => { try { expect(res.status).toEqual(200); - expect(typeof res.body).toEqual("object"); + expect(typeof res.body).toEqual('object'); expect(res.body[0].name).toEqual(room.roomName); } catch (error) { - pp("GET /api/rooms - " + error, "error"); + pp('GET /api/rooms - ' + error, 'error'); } }) - .catch((error) => pp("GET /api/rooms - " + error, "error")); + .catch((error) => pp('GET /api/rooms - ' + error, 'error')); }); - test("It should return all claim codes and add a user's identity to the rooms the claim code is associated with", async () => { - const username = "admin"; + const username = 'admin'; const password = process.env.PASSWORD; - const base64Credentials = Buffer.from(`${username}:${password}`).toString("base64"); + const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); await request(_app) - .get("/logclaimcodes") + .get('/logclaimcodes') - .set("Authorization", `Basic ${base64Credentials}`) + .set('Authorization', `Basic ${base64Credentials}`) .then(async (res) => { try { testCode = res.body[0].claimcode; @@ -133,11 +122,11 @@ describe("Endpoints should all work hopefully", () => { }; await request(_app) - .post("/join") + .post('/join') .send(joinTest) .then((res) => { expect(res.statusCode).toEqual(200); - expect(res.body.status).toEqual("valid"); + expect(res.body.status).toEqual('valid'); }); } catch (error) { console.error('Error in test: ', error); @@ -149,42 +138,40 @@ describe("Endpoints should all work hopefully", () => { }); console.log(testIdentity); - test("It should return all rooms associated with the given identity", async () => { + test('It should return all rooms associated with the given identity', async () => { await request(_app) - .get(`/api/rooms/${testIdentity}`) - .then((res) => { + .get(`/api/rooms/${testIdentity}`) + .then((res) => { try { console.log(res.body); expect(res.statusCode).toEqual(200); } catch (error) { - pp("GET /api/rooms/:idc - " + error, "error"); + pp('GET /api/rooms/:idc - ' + error, 'error'); } }) - .catch((error) => pp("GET /api/rooms/:idc - " + error, "error")); + .catch((error) => pp('GET /api/rooms/:idc - ' + error, 'error')); }); - test("It should send a message to all rooms", async () => { + test('It should send a message to all rooms', async () => { const message = { - message: "Test message", + message: 'Test message' }; - const base64Credentials = Buffer.from(`${username}:${password}`).toString( - "base64" - ); + const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64'); await request(_app) - .post("/admin/message") - .set("Authorization", `Basic ${base64Credentials}`) + .post('/admin/message') + .set('Authorization', `Basic ${base64Credentials}`) .send(message) .then((res) => { try { expect(res.statusCode).toEqual(200); - expect(res.body).toEqual({ message: "Messages sent to all rooms" }); + expect(res.body).toEqual({ message: 'Messages sent to all rooms' }); } catch (error) { - pp("POST /admin/message - " + error, "error"); + pp('POST /admin/message - ' + error, 'error'); } }); }); - test("It should return the messages for a given room", async () => { + test('It should return the messages for a given room', async () => { await request(_app) .get(`/api/room/${roomByIdTest}/messages`) .then((res) => { @@ -192,9 +179,9 @@ describe("Endpoints should all work hopefully", () => { expect(res.statusCode).toEqual(200); expect(res.body.length).toBeGreaterThan(0); } catch (error) { - pp("GET /api/messages/:roomId - " + error, "error"); + pp('GET /api/messages/:roomId - ' + error, 'error'); } }) - .catch((error) => pp("GET /api/messages/:roomId - " + error, "error")); + .catch((error) => pp('GET /api/messages/:roomId - ' + error, 'error')); }); }); From 0fd0a02969edef208a50271a6a2c64f95410edb7 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Fri, 18 Aug 2023 11:58:48 -0500 Subject: [PATCH 26/35] chore(express) fixing eslint errors on routes --- src/crypto/verifier.ts | 4 +- src/data/db.ts | 15 ++++--- src/endpoints/index.ts | 90 ++++++++++++++++++++++++++++-------------- src/utils.ts | 2 +- tests/express.test.ts | 2 +- 5 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 1da92b3..82995f3 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -1,6 +1,6 @@ import type { MessageI, RoomI } from 'discreetly-interfaces'; import { str2BigInt } from 'discreetly-interfaces'; -import { RLNVerifier } from 'rlnjs'; +import { RLNFullProof, RLNVerifier } from 'rlnjs'; import vkey from './verification_key'; import { Group } from '@semaphore-protocol/group'; @@ -48,7 +48,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro } // Check that the proof is correct - return v.verifyProof(rlnIdentifier, msg.proof); + return v.verifyProof(rlnIdentifier, msg.proof as RLNFullProof); } export default verifyProof; diff --git a/src/data/db.ts b/src/data/db.ts index 9414e97..abf06cb 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -131,7 +131,7 @@ export function createSystemMessages(message: string, roomId?: string): Promise< return prisma.rooms.findMany(query) .then(rooms => { if (roomId && rooms.length === 0) { - Promise.reject('Room not found') + return Promise.reject('Room not found') } const createMessages = rooms.map(room => { return prisma.messages.create({ @@ -145,7 +145,10 @@ export function createSystemMessages(message: string, roomId?: string): Promise< }); return Promise.all(createMessages); - }); + }).catch(err => { + console.error(err); + return Promise.reject(err); + }) } @@ -163,18 +166,18 @@ export async function createRoom( userMessageLimit = 1, numClaimCodes = 0, approxNumMockUsers = 20, - type: string = 'PUBLIC' + type: string ): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id, name).toString() + roomId: genId(serverConfig.id as bigint, name).toString() }, update: {}, create: { - roomId: genId(serverConfig.id, name).toString(), + roomId: genId(serverConfig.id as bigint, name).toString(), name: name, rateLimit: rateLimit, userMessageLimit: userMessageLimit, @@ -184,7 +187,7 @@ export async function createRoom( create: claimCodes } } - }; + }; return await prisma.rooms .upsert(roomData) diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 3b93f50..3f2a717 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -58,10 +58,22 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } }); - 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; @@ -123,10 +135,17 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { 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; - createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes, approxNumMockUsers, type) + const type = roomMetadata.roomType as unknown as string; + createRoom( + roomName, + rateLimit, + userMessageLimit, + numClaimCodes, + approxNumMockUsers, + type + ) .then((result) => { console.log(result); if (result) { @@ -186,28 +205,39 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); }); - app.post("/admin/message", adminAuth, async (req, res) => { - const { message } = req.body; - 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", + 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" }); + } + }) + ); - app.post("/admin/message/:roomId", adminAuth, async (req, res) => { - const { roomId } = req.params; - const { message } = req.body; - 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 661cd8c..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, + 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 932d43e..0aac0b7 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -33,7 +33,7 @@ const room = { type: "PUBLIC" }; -const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); +const roomByIdTest = genId(serverConfig.id as bigint, room.roomName).toString(); let testCode = ""; const testIdentity = randBigint(); const username = "admin"; From 4f6543b3649d537c87178e31723b310d267401bf Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Mon, 21 Aug 2023 17:15:54 -0500 Subject: [PATCH 27/35] feature(bandada) /join route now checks for membership types chore(crypto) changed some logic in the verifier that was throwing eslint errors --- package-lock.json | 8 +++--- package.json | 4 +-- prisma/seed.ts | 10 ++++---- src/crypto/verifier.ts | 14 ++++++++--- src/data/db.ts | 57 ++++++++++++++++++++++++++++++++++-------- src/endpoints/index.ts | 11 +++++++- 6 files changed, 79 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48c2bf0..5b39a28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.29", + "discreetly-interfaces": "^0.1.34", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -3470,9 +3470,9 @@ "integrity": "sha512-pQueoGtBJk/FrTfGzepjqYfTLaymS+4t11byI4OcfjWQOagRsD7dtavGcowTVQ7Ib/vjKna5T+71WcgWZaAWuA==" }, "node_modules/discreetly-interfaces": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.29.tgz", - "integrity": "sha512-3R63KkmB+wFKFFzD9DixX3VDoLCYkDuMQZucAItmXbjE+0tFgmrK683a1/WBI9VkBhARip6HsVNrjzaGqdR1Aw==", + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.34.tgz", + "integrity": "sha512-7purPOWOowVH44ebdweBdZ4z2RsBQy5/H7xi6PdsHkaw1xwg8u3Ev2US5EdavP1igZ+SzebJdK8jT0ZTjzX8Kg==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index 3881dc3..67848d3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "body-parser": "^1.20.2", "cors": "^2.8.5", "discreetly-claimcodes": "^1.1.5", - "discreetly-interfaces": "^0.1.29", + "discreetly-interfaces": "^0.1.34", "dotenv": "^16.3.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -64,4 +64,4 @@ "ts-node": "^10.9.1", "typescript": "^5.1.6" } -} \ No newline at end of file +} diff --git a/prisma/seed.ts b/prisma/seed.ts index b29a0e9..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); - createRoom('10 Second Room', 10000, 2, 10); - createRoom('100 Second Room', 100000, 10, 10); +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 82995f3..db3b917 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -29,12 +29,20 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro // TODO! INTERNAL NULLIFIER (RLNjs cache) // Check that the message hash is correct - if (msgHash !== msg.proof.snarkProof.publicSignals.x) { + let proof: RLNFullProof; + + if (typeof msg.proof === 'string') { + proof = JSON.parse(msg.proof) as RLNFullProof; + } else { + proof = msg.proof; + } + + if (msgHash !== proof.snarkProof.publicSignals.x) { console.warn( 'Message hash incorrect:', msgHash, 'Hash in proof:', - msg.proof.snarkProof.publicSignals.x + proof.snarkProof.publicSignals.x ); return false; } @@ -42,7 +50,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro // Check that the merkle root is correct if (room.identities && Array.isArray(room.identities)) { const group = new Group(room.id, 20, room.identities as bigint[] | undefined); - if (group.root !== msg.proof.snarkProof.publicSignals.root) { + if (group.root !== proof.snarkProof.publicSignals.root) { return false; } } diff --git a/src/data/db.ts b/src/data/db.ts index abf06cb..1a67d43 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -92,28 +92,59 @@ export function updateClaimCode(code: string): Promise { }); } -export function updateRoomIdentities(idc: string, roomIds: string[]): Promise { +export async function updateRoomIdentities(idc: string, roomIds: string[]): Promise { return prisma.rooms .findMany({ where: { id: { in: roomIds } } }) .then((rooms) => { - const roomsToUpdate = rooms - .filter((room) => !room.identities.includes(idc)) + const identityListRooms = rooms + .filter((room) => room.membershipType === "IDENTITY_LIST" && !room.identities.includes(idc)) .map((room) => room.id); - if (roomsToUpdate) { + if (identityListRooms.length > 0) { return prisma.rooms.updateMany({ - where: { id: { in: roomsToUpdate } }, + where: { id: { in: identityListRooms } }, data: { identities: { push: idc } } }); } + const bandadaGroupRooms = rooms + .filter((room) => room.membershipType === "BANDADA_GROUP" && !room.identities.includes(idc)) + .map((room) => room); + + if (bandadaGroupRooms.length > 0) { + bandadaGroupRooms.forEach((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, + }, + }; + + const url = `https://api.bandada.pse.dev/groups/${room.bandadaAddress}/members/${idc}`; + + fetch(url, requestOptions) + .then((res) => { + if (res.status == 200) { + console.log(`Successfully added user to Bandada group ${room.bandadaAddress}`); + } + }) + .catch(console.error); + }); + } }) .catch((err) => { pp(err, 'error'); }); } + export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ where: { id: { in: roomIds } } @@ -161,28 +192,34 @@ export function createSystemMessages(message: string, roomId?: string): Promise< * @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room. */ export async function createRoom( - name: string, + roomName: string, rateLimit = 1000, userMessageLimit = 1, numClaimCodes = 0, approxNumMockUsers = 20, - type: string + type: string, + bandadaAddress?: string, + bandadaAPIKey?: string, + membershipType?: string ): Promise { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); console.log(claimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as bigint, name).toString() + roomId: genId(serverConfig.id as bigint, roomName).toString() }, update: {}, create: { - roomId: genId(serverConfig.id as bigint, name).toString(), - name: name, + roomId: genId(serverConfig.id as bigint, roomName).toString(), + name: roomName, rateLimit: rateLimit, userMessageLimit: userMessageLimit, identities: mockUsers, type, + bandadaAddress, + bandadaAPIKey, + membershipType, claimCodes: { create: claimCodes } diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 3f2a717..33a3de7 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -125,6 +125,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { numClaimCodes?: number; approxNumMockUsers?: number; roomType?: string; + bandadaAddress?: string; + bandadaAPIKey?: string; + membershipType?: string; } /* ~~~~ ADMIN ENDPOINTS ~~~~ */ @@ -138,13 +141,19 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { const numClaimCodes = roomMetadata.numClaimCodes ?? 0; const approxNumMockUsers = roomMetadata.approxNumMockUsers; const type = roomMetadata.roomType as unknown as string; + const bandadaAddress = roomMetadata.bandadaAddress; + const bandadaAPIKey = roomMetadata.bandadaAPIKey; + const membershipType = roomMetadata.membershipType; createRoom( roomName, rateLimit, userMessageLimit, numClaimCodes, approxNumMockUsers, - type + type, + bandadaAddress, + bandadaAPIKey, + membershipType ) .then((result) => { console.log(result); From 1c5b90a21c37efaa66c0807bc332f912d0a8c786 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Mon, 21 Aug 2023 18:20:21 -0500 Subject: [PATCH 28/35] refactor(bandada) refactored updateRoomIdentities into seperate functions for readability feature(db utils) added function to ensure an identityCommitment is a valid identityCommitment --- src/data/db.ts | 180 +++++++++++++++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 72 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 1a67d43..4ce31cd 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { PrismaClient } from '@prisma/client'; -import { genId } from 'discreetly-interfaces'; -import type { RoomI } from 'discreetly-interfaces'; -import { serverConfig } from '../config/serverConfig'; -import { genMockUsers, genClaimCodeArray, pp } from '../utils'; +import { PrismaClient } from "@prisma/client"; +import { genId } from "discreetly-interfaces"; +import type { RoomI } from "discreetly-interfaces"; +import { serverConfig } from "../config/serverConfig"; +import { genMockUsers, genClaimCodeArray, pp } from "../utils"; const prisma = new PrismaClient(); @@ -22,7 +22,7 @@ export async function getRoomByID(id: string): Promise { const room = await prisma.rooms .findUnique({ where: { - roomId: id + roomId: id, }, select: { id: true, @@ -34,8 +34,8 @@ export async function getRoomByID(id: string): Promise { membershipType: true, contractAddress: true, bandadaAddress: true, - type: true - } + type: true, + }, }) .then((room) => { return room; @@ -48,7 +48,7 @@ export async function getRoomByID(id: string): Promise { if (room) { resolve(room as RoomI); } - reject('Room not found'); + reject("Room not found"); }); } @@ -64,9 +64,9 @@ export async function getRoomsByIdentity(identity: string): Promise { const rooms = await prisma.rooms.findMany({ where: { identities: { - has: identity - } - } + has: identity, + }, + }, }); rooms.forEach((room) => { r.push(room.roomId); @@ -81,90 +81,126 @@ export async function getRoomsByIdentity(identity: string): Promise { export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ - where: { claimcode: code } + where: { claimcode: code }, }); } export function updateClaimCode(code: string): Promise { return prisma.claimCodes.update({ where: { claimcode: code }, - data: { claimed: true } + data: { claimed: true }, }); } -export async 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 } } + where: { id: { in: roomIds } }, }) - .then((rooms) => { - const identityListRooms = rooms - .filter((room) => room.membershipType === "IDENTITY_LIST" && !room.identities.includes(idc)) - .map((room) => room.id); - - if (identityListRooms.length > 0) { - return prisma.rooms.updateMany({ - where: { id: { in: identityListRooms } }, - data: { identities: { push: idc } } - }); - } - const bandadaGroupRooms = rooms - .filter((room) => room.membershipType === "BANDADA_GROUP" && !room.identities.includes(idc)) - .map((room) => room); - - if (bandadaGroupRooms.length > 0) { - bandadaGroupRooms.forEach((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, - }, - }; - - const url = `https://api.bandada.pse.dev/groups/${room.bandadaAddress}/members/${idc}`; - - fetch(url, requestOptions) - .then((res) => { - if (res.status == 200) { - console.log(`Successfully added user to Bandada group ${room.bandadaAddress}`); - } - }) - .catch(console.error); - }); - } + .then(async (rooms) => { + await handleIdentityListRooms(rooms, identityCommitment); + await handleBandadaGroups(rooms, identityCommitment); }) .catch((err) => { - pp(err, 'error'); + pp(err, "error"); }); } +function handleIdentityListRooms(rooms, identityCommitment: string): any { + 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 handleBandadaGroups(rooms, identityCommitment: string): any { + const bandadaGroupRooms = rooms + .filter( + (room) => + room.membershipType === "BANDADA_GROUP" && + !room.identities.includes(identityCommitment) + ) + .map((room) => room as RoomI); + + if (bandadaGroupRooms.length > 0) { + bandadaGroupRooms.forEach((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, + }, + }; + const url = `https://api.bandada.pse.dev/groups/${room.bandadaAddress}/members/${identityCommitment}`; + fetch(url, requestOptions) + .then((res) => { + if (res.status == 200) { + console.log( + `Successfully added user to Bandada group ${room.bandadaAddress}` + ); + } + }) + .catch(console.error); + }); + } +} export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ - where: { id: { in: roomIds } } + where: { id: { in: roomIds } }, }); return new Promise((resolve, reject) => { if (rooms) { resolve(rooms as RoomI[]); } - reject('No rooms found'); + reject("No rooms found"); }); } -export function createSystemMessages(message: string, roomId?: string): Promise { +export function createSystemMessages( + message: string, + roomId?: string +): Promise { const query = roomId ? { where: { roomId } } : undefined; - return prisma.rooms.findMany(query) - .then(rooms => { + return prisma.rooms + .findMany(query) + .then((rooms) => { if (roomId && rooms.length === 0) { - return Promise.reject('Room not found') + return Promise.reject("Room not found"); } - const createMessages = rooms.map(room => { + const createMessages = rooms.map((room) => { return prisma.messages.create({ data: { message, @@ -176,13 +212,13 @@ export function createSystemMessages(message: string, roomId?: string): Promise< }); return Promise.all(createMessages); - }).catch(err => { + }) + .catch((err) => { console.error(err); return Promise.reject(err); - }) + }); } - /** * Creates a new room with the given name and optional parameters. * @param {string} name - The name of the room. @@ -207,7 +243,7 @@ export async function createRoom( const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as bigint, roomName).toString() + roomId: genId(serverConfig.id as bigint, roomName).toString(), }, update: {}, create: { @@ -221,9 +257,9 @@ export async function createRoom( bandadaAPIKey, membershipType, claimCodes: { - create: claimCodes - } - } + create: claimCodes, + }, + }, }; return await prisma.rooms From 4634bcce7f106e710dde685ee1e52efbedbb6e8b Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Mon, 21 Aug 2023 19:05:13 -0500 Subject: [PATCH 29/35] refactor(bandada) identityCommitments are now also stored in room.identities --- src/data/db.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data/db.ts b/src/data/db.ts index 4ce31cd..f2dfc5d 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -151,7 +151,7 @@ function handleBandadaGroups(rooms, identityCommitment: string): any { .map((room) => room as RoomI); if (bandadaGroupRooms.length > 0) { - bandadaGroupRooms.forEach((room) => { + bandadaGroupRooms.forEach(async (room) => { if (!room.bandadaAPIKey) { console.error("API key is missing for room:", room); return; @@ -163,6 +163,10 @@ function handleBandadaGroups(rooms, identityCommitment: string): any { "x-api-key": room.bandadaAPIKey, }, }; + await prisma.rooms.updateMany({ + where: { id: room.id }, + data: { identities: { push: identityCommitment } }, + }); const url = `https://api.bandada.pse.dev/groups/${room.bandadaAddress}/members/${identityCommitment}`; fetch(url, requestOptions) .then((res) => { From 52c1ebc9c767f751ea135dff0fcdd70e332c8f1f Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 22 Aug 2023 10:19:46 -0500 Subject: [PATCH 30/35] feature(bandada) fetch bandada group by identity commitment --- src/data/db.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index f2dfc5d..f6a8f14 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -52,6 +52,7 @@ export async function getRoomByID(id: string): Promise { }); } + export async function getRoomsByIdentity(identity: string): Promise { /* 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 @@ -68,9 +69,18 @@ export async function getRoomsByIdentity(identity: string): Promise { }, }, }); - rooms.forEach((room) => { - r.push(room.roomId); - }); + for (const room of rooms) { + if (room.membershipType === "IDENTITY_LIST") { + r.push(room.roomId); + } + if (room.membershipType === "BANDADA_GROUP") { + const rooms = await fetch( + `https://api.bandada.pse.dev/groups/${room.bandadaAddress}` + ); + const roomData = await rooms.json() + r.push(roomData.id as string); + } + } console.log(r); return r; } catch (err) { From 4ddd6e3d7ffd637c369923d8542550f429d202a6 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 22 Aug 2023 11:39:00 -0400 Subject: [PATCH 31/35] feat return more room info depending on membershiptype --- src/endpoints/index.ts | 138 +++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 33a3de7..20fada0 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,29 +29,56 @@ 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)); @@ -59,18 +86,14 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); app.get( - ["/rooms/:idc", "/api/rooms/:idc"], + ['/rooms/:idc', '/api/rooms/:idc'], asyncHandler(async (req: Request, res: Response) => { try { - pp( - String( - "Express: fetching rooms by identityCommitment " + req.params.idc - ) - ); + 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" }); + res.status(500).json({ error: 'Internal Server Error' }); } }) ); @@ -81,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; } @@ -112,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) }); }) ); @@ -131,7 +152,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { } /* ~~~~ 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); @@ -159,9 +180,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { 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) => { @@ -170,26 +191,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) => { @@ -197,12 +218,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) => { @@ -210,43 +231,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", + '/admin/message', adminAuth, asyncHandler(async (req: Request, res: Response) => { const { message } = req.body as { message: string }; - pp(String("Express: sending system message: " + message)); + pp(String('Express: sending system message: ' + message)); try { await createSystemMessages(message); - res.status(200).json({ message: "Messages sent to all rooms" }); + res.status(200).json({ message: 'Messages sent to all rooms' }); } 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/:roomId", + '/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) - ); + pp(String('Express: sending system message: ' + message + ' to ' + roomId)); try { await createSystemMessages(message, roomId); - res.status(200).json({ message: "Message sent to room " + roomId }); + res.status(200).json({ message: 'Message sent to room ' + roomId }); } catch (err) { console.error(err); - res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: 'Internal Server Error' }); } }) ); - } From 452151b990cbab5bc1e536cf3fbf91d5ce7b5a16 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 22 Aug 2023 11:16:17 -0500 Subject: [PATCH 32/35] refactor(bandada) refactored prisma schema to match RoomI interface and routes to match --- prisma/schema.prisma | 1 + src/data/db.ts | 87 ++++++++++++++++++++------------------ src/endpoints/index.ts | 96 +++++++++++++++++++++--------------------- 3 files changed, 96 insertions(+), 88 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 76e96da..dbd70ec 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,6 +22,7 @@ model Rooms { identities String[] @default([]) contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" bandadaAddress String? // BANDADA as "url:groupID" + bandadaGroupId String? // Bandada Group ID bandadaAPIKey String? // Bandada API Key epochs Epoch[] messages Messages[] diff --git a/src/data/db.ts b/src/data/db.ts index f6a8f14..4a5808d 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { PrismaClient } from "@prisma/client"; -import { genId } from "discreetly-interfaces"; -import type { RoomI } from "discreetly-interfaces"; -import { serverConfig } from "../config/serverConfig"; -import { genMockUsers, genClaimCodeArray, pp } from "../utils"; +import { PrismaClient } from '@prisma/client'; +import { genId } from 'discreetly-interfaces'; +import type { RoomI } from 'discreetly-interfaces'; +import { serverConfig } from '../config/serverConfig'; +import { genMockUsers, genClaimCodeArray, pp } from '../utils'; const prisma = new PrismaClient(); @@ -48,47 +48,50 @@ export async function getRoomByID(id: string): Promise { if (room) { resolve(room as RoomI); } - reject("Room not found"); + reject('Room not found'); }); } +/* 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 +https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this +*/ export async function getRoomsByIdentity(identity: string): Promise { - /* 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 - - https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this - */ - const r: string[] = []; try { - const rooms = await prisma.rooms.findMany({ + const roomsFromDB = await prisma.rooms.findMany({ where: { identities: { has: identity, }, }, }); - for (const room of rooms) { - if (room.membershipType === "IDENTITY_LIST") { - r.push(room.roomId); - } - if (room.membershipType === "BANDADA_GROUP") { - const rooms = await fetch( - `https://api.bandada.pse.dev/groups/${room.bandadaAddress}` - ); - const roomData = await rooms.json() - r.push(roomData.id as string); - } - } - console.log(r); - return r; + + // Fetch all room IDs asynchronously + const roomIds = await Promise.all( + roomsFromDB.map(room => getRoomIdFromRoomData(room)) + ); + + return roomIds; } catch (err) { console.error(err); return []; } } +// Helper function to get the room ID based on its data +async function getRoomIdFromRoomData(room: any): Promise { + if (room.membershipType === 'IDENTITY_LIST') { + return room.roomId as string; + } else if (room.membershipType === 'BANDADA_GROUP') { + const response = await fetch(`https://${room.bandadaAddress}/groups/${room.bandadaGroupId}`); + const roomData = await response.json(); + return roomData.id as string; + } + return ''; +} + export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ where: { claimcode: code }, @@ -109,10 +112,10 @@ function sanitizeIDC(idc: string): string { if (idc === tempString) { return idc; } else { - throw new Error("Invalid IDC provided."); + throw new Error('Invalid IDC provided.'); } } catch (error) { - throw new Error("Invalid IDC provided."); + throw new Error('Invalid IDC provided.'); } } @@ -130,7 +133,7 @@ export async function updateRoomIdentities( await handleBandadaGroups(rooms, identityCommitment); }) .catch((err) => { - pp(err, "error"); + pp(err, 'error'); }); } @@ -138,7 +141,7 @@ function handleIdentityListRooms(rooms, identityCommitment: string): any { const identityListRooms = rooms .filter( (room) => - room.membershipType === "IDENTITY_LIST" && + room.membershipType === 'IDENTITY_LIST' && !room.identities.includes(identityCommitment) ) .map((room) => room.id as string); @@ -155,7 +158,7 @@ function handleBandadaGroups(rooms, identityCommitment: string): any { const bandadaGroupRooms = rooms .filter( (room) => - room.membershipType === "BANDADA_GROUP" && + room.membershipType === 'BANDADA_GROUP' && !room.identities.includes(identityCommitment) ) .map((room) => room as RoomI); @@ -163,21 +166,21 @@ function handleBandadaGroups(rooms, identityCommitment: string): any { if (bandadaGroupRooms.length > 0) { bandadaGroupRooms.forEach(async (room) => { if (!room.bandadaAPIKey) { - console.error("API key is missing for room:", room); + console.error('API key is missing for room:', room); return; } const requestOptions = { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", - "x-api-key": room.bandadaAPIKey, + 'Content-Type': 'application/json', + 'x-api-key': room.bandadaAPIKey, }, }; await prisma.rooms.updateMany({ where: { id: room.id }, data: { identities: { push: identityCommitment } }, }); - const url = `https://api.bandada.pse.dev/groups/${room.bandadaAddress}/members/${identityCommitment}`; + const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${identityCommitment}`; fetch(url, requestOptions) .then((res) => { if (res.status == 200) { @@ -199,7 +202,7 @@ export async function findUpdatedRooms(roomIds: string[]): Promise { if (rooms) { resolve(rooms as RoomI[]); } - reject("No rooms found"); + reject('No rooms found'); }); } @@ -212,14 +215,14 @@ export function createSystemMessages( .findMany(query) .then((rooms) => { if (roomId && rooms.length === 0) { - return Promise.reject("Room not found"); + return Promise.reject('Room not found'); } const createMessages = rooms.map((room) => { return prisma.messages.create({ data: { message, roomId: room.roomId, - messageId: "0", + messageId: '0', proof: JSON.stringify({}), }, }); @@ -249,6 +252,7 @@ export async function createRoom( approxNumMockUsers = 20, type: string, bandadaAddress?: string, + bandadaGroupId?: string, bandadaAPIKey?: string, membershipType?: string ): Promise { @@ -268,6 +272,7 @@ export async function createRoom( identities: mockUsers, type, bandadaAddress, + bandadaGroupId, bandadaAPIKey, membershipType, claimCodes: { diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index 33a3de7..1546797 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,23 +29,23 @@ 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" }), + () => res.status(500).json({ error: 'Internal Server Error' }), 1000 ); } else { @@ -59,18 +59,18 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { }); app.get( - ["/rooms/:idc", "/api/rooms/:idc"], + ['/rooms/:idc', '/api/rooms/:idc'], asyncHandler(async (req: Request, res: Response) => { try { pp( String( - "Express: fetching rooms by identityCommitment " + req.params.idc + '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" }); + res.status(500).json({ error: 'Internal Server Error' }); } }) ); @@ -81,22 +81,22 @@ 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" }); + .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; } @@ -112,8 +112,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) }); }) ); @@ -127,11 +127,12 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { 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); @@ -142,6 +143,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { const approxNumMockUsers = roomMetadata.approxNumMockUsers; 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( @@ -152,6 +154,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { approxNumMockUsers, type, bandadaAddress, + bandadaGroupId, bandadaAPIKey, membershipType ) @@ -159,9 +162,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { 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) => { @@ -170,26 +173,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) => { @@ -197,12 +200,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) => { @@ -210,43 +213,42 @@ 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", + '/admin/message', adminAuth, asyncHandler(async (req: Request, res: Response) => { const { message } = req.body as { message: string }; - pp(String("Express: sending system message: " + message)); + pp(String('Express: sending system message: ' + message)); try { await createSystemMessages(message); - res.status(200).json({ message: "Messages sent to all rooms" }); + res.status(200).json({ message: 'Messages sent to all rooms' }); } 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/:roomId", + '/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) + String('Express: sending system message: ' + message + ' to ' + roomId) ); try { await createSystemMessages(message, roomId); - res.status(200).json({ message: "Message sent to room " + roomId }); + res.status(200).json({ message: 'Message sent to room ' + roomId }); } catch (err) { console.error(err); - res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: 'Internal Server Error' }); } }) ); - } From e7a0754975bcb5541deea034c03cbb2a1b147204 Mon Sep 17 00:00:00 2001 From: Tanner Shaw Date: Tue, 22 Aug 2023 12:08:37 -0500 Subject: [PATCH 33/35] refactor(bandada) removed unnessecary functionality --- src/data/db.ts | 45 ++++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 4a5808d..b21b30b 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -51,47 +51,34 @@ export async function getRoomByID(id: string): Promise { reject('Room not found'); }); } -/* 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 - -https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this -*/ export async function getRoomsByIdentity(identity: string): Promise { + /* 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 + + https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this + */ + const r: string[] = []; try { - const roomsFromDB = await prisma.rooms.findMany({ + const rooms = await prisma.rooms.findMany({ where: { identities: { - has: identity, - }, - }, + has: identity + } + } }); - - // Fetch all room IDs asynchronously - const roomIds = await Promise.all( - roomsFromDB.map(room => getRoomIdFromRoomData(room)) - ); - - return roomIds; + rooms.forEach((room) => { + r.push(room.roomId); + }); + console.log(r); + return r; } catch (err) { console.error(err); return []; } } -// Helper function to get the room ID based on its data -async function getRoomIdFromRoomData(room: any): Promise { - if (room.membershipType === 'IDENTITY_LIST') { - return room.roomId as string; - } else if (room.membershipType === 'BANDADA_GROUP') { - const response = await fetch(`https://${room.bandadaAddress}/groups/${room.bandadaGroupId}`); - const roomData = await response.json(); - return roomData.id as string; - } - return ''; -} - export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ where: { claimcode: code }, From 9407b2cd9a183aaee22a7761768a7c56a8c0837c Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 22 Aug 2023 13:16:08 -0400 Subject: [PATCH 34/35] fix types and linting --- src/data/db.ts | 55 ++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/data/db.ts b/src/data/db.ts index 819dd3c..3b1ebd4 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -22,7 +22,7 @@ export async function getRoomByID(id: string): Promise { const room = await prisma.rooms .findUnique({ where: { - roomId: id, + roomId: id }, select: { id: true, @@ -82,14 +82,14 @@ export async function getRoomsByIdentity(identity: string): Promise { export function findClaimCode(code: string): Promise { return prisma.claimCodes.findUnique({ - where: { claimcode: code }, + where: { claimcode: code } }); } export function updateClaimCode(code: string): Promise { return prisma.claimCodes.update({ where: { claimcode: code }, - data: { claimed: true }, + data: { claimed: true } }); } @@ -107,47 +107,42 @@ function sanitizeIDC(idc: string): string { } } -export async function updateRoomIdentities( - idc: string, - roomIds: string[] -): Promise { +export async function updateRoomIdentities(idc: string, roomIds: string[]): Promise { const identityCommitment = sanitizeIDC(idc); return prisma.rooms .findMany({ - where: { id: { in: roomIds } }, + where: { id: { in: roomIds } } }) - .then(async (rooms) => { - await handleIdentityListRooms(rooms, identityCommitment); - await handleBandadaGroups(rooms, identityCommitment); + .then((rooms) => { + addIdentityToIdentityListRooms(rooms, identityCommitment); + addIdentityToBandadaRooms(rooms, identityCommitment); }) .catch((err) => { pp(err, 'error'); }); } -function handleIdentityListRooms(rooms, identityCommitment: string): any { +function addIdentityToIdentityListRooms(rooms, identityCommitment: string): unknown { const identityListRooms = rooms .filter( (room) => - room.membershipType === 'IDENTITY_LIST' && - !room.identities.includes(identityCommitment) + 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 } }, + data: { identities: { push: identityCommitment } } }); } } -function handleBandadaGroups(rooms, identityCommitment: string): any { +function addIdentityToBandadaRooms(rooms, identityCommitment: string): void { const bandadaGroupRooms = rooms .filter( (room) => - room.membershipType === 'BANDADA_GROUP' && - !room.identities.includes(identityCommitment) + room.membershipType === 'BANDADA_GROUP' && !room.identities.includes(identityCommitment) ) .map((room) => room as RoomI); @@ -161,30 +156,30 @@ function handleBandadaGroups(rooms, identityCommitment: string): any { method: 'POST', headers: { 'Content-Type': 'application/json', - 'x-api-key': room.bandadaAPIKey, - }, + 'x-api-key': room.bandadaAPIKey + } }; await prisma.rooms.updateMany({ where: { id: room.id }, - data: { identities: { push: identityCommitment } }, + data: { identities: { push: identityCommitment } } }); const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${identityCommitment}`; fetch(url, requestOptions) .then((res) => { if (res.status == 200) { - console.log( - `Successfully added user to Bandada group ${room.bandadaAddress}` - ); + console.debug(`Successfully added user to Bandada group ${room.bandadaAddress}`); } }) - .catch(console.error); + .catch((err) => { + console.error(err); + }); }); } } export async function findUpdatedRooms(roomIds: string[]): Promise { const rooms = await prisma.rooms.findMany({ - where: { id: { in: roomIds } }, + where: { id: { in: roomIds } } }); return new Promise((resolve, reject) => { if (rooms) { @@ -194,10 +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) @@ -266,7 +259,7 @@ export async function createRoom( const mockUsers: string[] = genMockUsers(approxNumMockUsers); const roomData = { where: { - roomId: genId(serverConfig.id as bigint, roomName).toString(), + roomId: genId(serverConfig.id as bigint, roomName).toString() }, update: {}, create: { From 0ce3ceb8f9e4a52f1e70fdf556e2adfad8440d5a Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 22 Aug 2023 13:18:34 -0400 Subject: [PATCH 35/35] fixed verifier --- src/crypto/verifier.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 3135994..c3f633c 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -38,14 +38,6 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro // Check that the message hash is correct - let proof: RLNFullProof; - - if (typeof msg.proof === 'string') { - proof = JSON.parse(msg.proof) as RLNFullProof; - } else { - proof = msg.proof; - } - if (msgHash !== proof.snarkProof.publicSignals.x) { console.warn( 'Message hash incorrect:', @@ -65,7 +57,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro } // Check that the proof is correct - return v.verifyProof(rlnIdentifier, msg.proof as RLNFullProof); + return v.verifyProof(rlnIdentifier, proof); } export default verifyProof;