diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 83f513d..cc3edce 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,6 +10,8 @@ datasource db { url = env("DATABASE_URL") } +// Room Models +//----------------------------------------------------------------------------------------- model Rooms { id String @id @default(auto()) @map("_id") @db.ObjectId roomId String @unique @@ -31,12 +33,38 @@ model Rooms { type String @default("PUBLIC") ephemeral String @default("PERSISTENT") encrypted String @default("PLAINTEXT") + sessionIds Boolean @default(true) gatewayIds String[] @default([]) @db.ObjectId gateways GateWayIdentity[] @relation(fields: [gatewayIds], references: [id]) ethereumGroups EthereumGroup[] @relation(fields: [ethereumGroupIds], references: [id]) ethereumGroupIds String[] @db.ObjectId + jubmojiGroups JubmojiGroup[] @relation(fields: [jubmojiGroupIds], references: [id]) + jubmojiGroupIds String[] @db.ObjectId } +model Epoch { + id String @id @default(auto()) @map("_id") @db.ObjectId + epoch String + messages Messages[] + rooms Rooms? @relation(fields: [roomsId], references: [id]) + roomsId String? @db.ObjectId +} + +model Messages { + id String @id @default(auto()) @map("_id") @db.ObjectId + messageId String // Internal Nullifier + message String + timeStamp DateTime @default(now()) + roomId String + messageType String? @default("TEXT") + room Rooms @relation(fields: [roomId], references: [roomId]) + proof String + epoch Epoch? @relation(fields: [epochId], references: [id]) + epochId String? @db.ObjectId +} + +// Gateway Models +//----------------------------------------------------------------------------------------- model GateWayIdentity { id String @id @default(auto()) @map("_id") @db.ObjectId semaphoreIdentity String @unique @@ -48,6 +76,7 @@ model GateWayIdentity { usedClaimCodes String[] @default([]) @db.ObjectId claimCodes ClaimCodes[] @relation(fields: [usedClaimCodes], references: [id]) ethereumAddress EthereumAddress[] + jubmojiAddress JubmojiAddress[] } model EthereumGroup { @@ -65,6 +94,21 @@ model EthereumAddress { gatewayId String @db.ObjectId } +model JubmojiGroup { + id String @id @default(auto()) @map("_id") @db.ObjectId + name String @unique + jubmojiAddresses String[] @default([]) + rooms Rooms[] @relation(fields: [roomIds], references: [id]) + roomIds String[] @default([]) @db.ObjectId +} + +model JubmojiAddress { + id String @id @default(auto()) @map("_id") @db.ObjectId + jubmojiAddress String @unique + gateways GateWayIdentity @relation(fields: [gatewayId], references: [id]) + gatewayId String @db.ObjectId +} + model ClaimCodes { id String @id @default(auto()) @map("_id") @db.ObjectId claimcode String @unique @@ -77,27 +121,8 @@ model ClaimCodes { gateways GateWayIdentity[] @relation(fields: [gatewayIds], references: [id]) } -model Messages { - id String @id @default(auto()) @map("_id") @db.ObjectId - messageId String // Internal Nullifier - message String - timeStamp DateTime @default(now()) - roomId String - messageType String? @default("TEXT") - room Rooms @relation(fields: [roomId], references: [roomId]) - proof String - epoch Epoch? @relation(fields: [epochId], references: [id]) - epochId String? @db.ObjectId -} - -model Epoch { - id String @id @default(auto()) @map("_id") @db.ObjectId - epoch String - messages Messages[] - rooms Rooms? @relation(fields: [roomsId], references: [id]) - roomsId String? @db.ObjectId -} - +// Discord Bot Models +//----------------------------------------------------------------------------------------- model Discord { id String @id @default(auto()) @map("_id") @db.ObjectId discordServerId String @unique diff --git a/prisma/seed.ts b/prisma/seed.ts index 6154574..f7f8e5b 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,5 +1,5 @@ import { createRoom } from '../src/data/db'; -import { createEthGroup } from '../src/data/db'; +import { createEthGroupForRoom } from '../src/data/db'; import addresses from './addresses'; async function main() { // @param name — The name of the room. @@ -17,8 +17,8 @@ async function main() { await createRoom('The Word', 10000, 12, 0, 0, 'PUBLIC', [], '007001'); const bcgd = await createRoom('Beacon Chain Genesis Depositors', 10000, 12, 0 ,20, 'PUBLIC'); const sgf = await createRoom('Stateful Genesis Funders', 10000, 12, 0, 20, 'PUBLIC'); - await createEthGroup('Beacon Chain Genesis Depositors', bcgd!.roomId, addresses.bcgd); - await createEthGroup('Stateful Genesis Funders', sgf!.roomId, addresses.sgf); + await createEthGroupForRoom('Beacon Chain Genesis Depositors', bcgd!.roomId, addresses.bcgd); + await createEthGroupForRoom('Stateful Genesis Funders', sgf!.roomId, addresses.sgf); } await main(); diff --git a/src/data/db/create.ts b/src/data/db/create.ts index 7252451..45f983e 100644 --- a/src/data/db/create.ts +++ b/src/data/db/create.ts @@ -1,7 +1,12 @@ import { PrismaClient } from '@prisma/client'; -import { getRateCommitmentHash, MessageI, randomBigInt } from 'discreetly-interfaces'; +import { + getRateCommitmentHash, + MessageI, + randomBigInt +} from 'discreetly-interfaces'; import { genClaimCodeArray, genMockUsers } from '../../utils'; import type { Server as SocketIOServer } from 'socket.io'; +import { EthGroupI } from '../../types'; const prisma = new PrismaClient(); @@ -34,7 +39,9 @@ export async function createRoom( bandadaAPIKey?: string, membershipType?: string, roomId?: string -): Promise<{ roomId: string ; claimCodes: { claimcode: string }[] } | undefined | null> { +): Promise< + { roomId: string; claimCodes: { claimcode: string }[] } | undefined | null +> { const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); const mockUsers: string[] = genMockUsers(approxNumMockUsers); const identityCommitments: string[] = mockUsers.map((user) => @@ -42,7 +49,7 @@ export async function createRoom( ); const _roomId = roomId ? roomId : randomBigInt().toString(); - const room = await prisma.rooms.findUnique({where: {roomId: _roomId}}) + const room = await prisma.rooms.findUnique({ where: { roomId: _roomId } }); if (room) return null; const roomData = { @@ -67,7 +74,7 @@ export async function createRoom( }, gateways: { create: mockUsers.map((user) => ({ - semaphoreIdentity: user, + semaphoreIdentity: user })) } } @@ -76,7 +83,7 @@ export async function createRoom( return await prisma.rooms .upsert(roomData) .then(() => { - return {roomId: _roomId, claimCodes}; + return { roomId: _roomId, claimCodes }; }) .catch((err) => { console.error(err); @@ -132,7 +139,10 @@ export function createSystemMessages( * @param {MessageI} message - The message to add to the room. * @returns {Promise} - A promise that resolves when the message has been added to the room. */ -export function createMessageInRoom(roomId: string, message: MessageI): Promise { +export function createMessageInRoom( + roomId: string, + message: MessageI +): Promise { if (!message.epoch) { throw new Error('Epoch not provided'); } @@ -158,3 +168,94 @@ export function createMessageInRoom(roomId: string, message: MessageI): Promise< } }); } + +export function createEthGroup( + name: string, + roomIds: string[] +): Promise { + return prisma.ethereumGroup.create({ + data: { + name: name, + rooms: { + connect: roomIds.map((roomId) => ({ roomId })) + } + } + }); +} + +export function createClaimCode( + claimCode: string, + roomIds: string[], + expiresAt: number, + usesLeft: number, + discordId: string, + roomId?: string + ) { + if (!roomId) { + return prisma.claimCodes.create({ + data: { + claimcode: claimCode, + roomIds: roomIds, + expiresAt: expiresAt, + usesLeft: usesLeft, + discordId: discordId + } + }); + } else { + return prisma.claimCodes.create({ + data: { + claimcode: claimCode, + roomIds: roomIds, + expiresAt: expiresAt, + usesLeft: usesLeft, + discordId: discordId, + rooms: { + connect: { + roomId: roomId + } + } + } + }); + } +} + + +export async function joinRoomsFromEthAddress( + recoveredAddress: string, + message: string +) { + const gatewayIdentity = await prisma.gateWayIdentity.upsert({ + where: { semaphoreIdentity: message }, + update: {}, + create: { + semaphoreIdentity: message + } + }); + await prisma.ethereumAddress.upsert({ + where: { ethereumAddress: recoveredAddress }, + update: {}, + create: { + ethereumAddress: recoveredAddress, + gatewayId: gatewayIdentity.id + } + }); + const roomsToJoin = await prisma.ethereumGroup.findMany({ + where: { + ethereumAddresses: { + has: recoveredAddress + } + }, + select: { + roomIds: true + } + }); + const roomIdsSet = new Set(roomsToJoin.map((room) => room.roomIds).flat()); + const roomIds = Array.from(roomIdsSet); + + await prisma.gateWayIdentity.update({ + where: { id: gatewayIdentity.id }, + data: { roomIds: { set: roomIds } } + }); + + return roomIds; +} diff --git a/src/data/db/find.ts b/src/data/db/find.ts index 6ba9cad..d3f8fb2 100644 --- a/src/data/db/find.ts +++ b/src/data/db/find.ts @@ -46,15 +46,39 @@ export async function findRoomById(id: string): Promise { }); } +export function findRoomClaimCodes(roomId: string) { + return prisma.rooms + .findUnique({ + where: { roomId: roomId }, + include: { claimCodes: true } + }) +} + +// Fetches all claim codes from the database +export function findAllClaimCodes() { + return prisma.claimCodes.findMany() +} + +/** + * This function is used to find all rooms in the database + * @param {boolean} all - True or false to query all rooms + * @param {string[]} rooms - An array of roomIds to query + * @returns + */ +export async function findAllRooms(all?: boolean, rooms?: string[] | undefined) { + const query = all ? undefined : { where: { roomId: { in: rooms } } } + return await prisma.rooms.findMany(query); +} /* 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 */ + /** * This function takes in an identity and returns the rooms the identity is in. - * @param identity - the identity of a user + * @param {string[]} identity - the identity of a user * @returns an array of roomIds */ export async function findRoomsByIdentity(identity: string): Promise { @@ -93,7 +117,14 @@ export async function findClaimCode(code: string): Promise { }); } -export async function findGatewayByIdentity(identity: string): Promise { +/** + * Finds a gateway identity in the database. + * @param {string} identity - The identity to find + * @returns + */ +export async function findGatewayByIdentity( + identity: string +): Promise { return await prisma.gateWayIdentity.findFirst({ where: { semaphoreIdentity: identity @@ -120,6 +151,12 @@ export async function findUpdatedRooms(roomIds: string[]): Promise { }); } +/** + * This function is used to find a room in the database using a message and the roomId and returns the room + * @param {string} roomId - The id of the room to find + * @param {MessageI} message - The id of message to find + * @returns + */ export async function findRoomWithMessageId( roomId: string, message: MessageI @@ -153,3 +190,101 @@ export async function findRoomWithMessageId( throw err; } } + +/** + * This function is used to find groups in the database that the ethereum address is in + * @param {string} group - The type of the group to find + * @param {string} group - The address of the group to find + * @returns + */ +export function findManyGroups( + group: string, + address?: string +) { + switch (group) { + case 'ethereum': + if (address) { + return prisma.ethereumGroup.findMany({ + where: { + ethereumAddresses: { + has: address + } + }, + select: { + name: true + } + }); + } else { + return prisma.ethereumGroup.findMany({ + select: { + name: true + } + }); + } + case 'jubmoji': + if (address) { + return prisma.jubmojiGroup.findMany({ + where: { + jubmojiAddresses: { + has: address + } + }, + select: { + name: true + } + }); + } else { + return prisma.jubmojiGroup.findMany({ + select: { + name: true + } + }); + } + default: + throw new Error('Invalid group'); + } +} + +/** + * This function is used to find a group in the database and returns the groups ethereum addresses + * @param {string} name - The name of the group to find + * @returns {Promise<{ ethereumAddresses: string[] } | null>} - A promise that resolves to the group + */ +export function findUniqueEthGroup( + name: string +): Promise<{ ethereumAddresses: string[] } | null> { + return prisma.ethereumGroup.findUnique({ + where: { + name: name + }, + select: { + ethereumAddresses: true + } + }); +} + +/** + * This function is used to find messages in a room + * @param {string} roomId - The id of the room to find messages in + * @returns {void} + */ +export function findMessages(roomId : string) { + return prisma.messages + .findMany({ + take: 500, + orderBy: { + timeStamp: 'desc' + }, + where: { + roomId: roomId + }, + select: { + id: false, + message: true, + messageId: true, + proof: true, + roomId: true, + timeStamp: true + } + }) +} diff --git a/src/data/db/remove.ts b/src/data/db/remove.ts index 8aff204..13ecdb2 100644 --- a/src/data/db/remove.ts +++ b/src/data/db/remove.ts @@ -49,7 +49,6 @@ export function removeIdentityFromRoom( * @param {string} roomId - The id of the room to remove * @returns {Promise} - A promise that resolves to true if the room was removed and false otherwise * */ - export function removeRoom(roomId: string): Promise { return prisma.messages .deleteMany({ @@ -82,7 +81,6 @@ export function removeRoom(roomId: string): Promise { * @param {string} messageId - The id of the message to remove * @returns {Promise} - A promise that resolves to true if the message was removed and false otherwise */ - export function removeMessage(roomId: string, messageId: string) { return prisma.messages .deleteMany({ @@ -99,3 +97,43 @@ export function removeMessage(roomId: string, messageId: string) { return false; }); } + +/** + * This function deletes an ethereum group from the database. + * @param name - The name of the Ethereum group to delete + * @returns {void} + */ +export function removeEthGroup(group: string, name: string) { + switch(group) { + case 'ethereum': + return prisma.ethereumGroup + .delete({ + where: { + name: name + } + }) + case 'jubmoji': + return prisma.jubmojiGroup + .delete({ + where: { + name: name + } + }) + default: + throw new Error('No group found') + } +} + +/** + * This function removes a claim code from the database. + * @param {string} code - The claim code to find + * @returns {void} + */ +export function removeClaimCode(code: string) { + return prisma.claimCodes + .delete({ + where: { + claimcode: code + } + }) +} diff --git a/src/data/db/update.ts b/src/data/db/update.ts index f192793..12cdee5 100644 --- a/src/data/db/update.ts +++ b/src/data/db/update.ts @@ -5,6 +5,7 @@ import { RoomI } from 'discreetly-interfaces'; import { getRateCommitmentHash } from '../../crypto'; import { pp } from '../../utils'; import { RoomWithSecretsI, ClaimCodeI } from '../../types/'; +import { IDCProof } from 'idc-nullifier'; const prisma = new PrismaClient(); @@ -299,8 +300,7 @@ export async function addIdentityToBandadaRooms( * @param {string} roomId - The ID of the room * @param {string[]} ethAddresses - The list of Ethereum addresses to add to the group */ - -export async function createEthGroup( +export async function createEthGroupForRoom( name: string, roomId: string, ethAddresses: string[] @@ -321,3 +321,102 @@ export async function createEthGroup( } }); } + + +/** + * + * @param {string[]} names - The names of the ethereum groups to add the addresses to + * @param {string[]} ethAddresses - An array of ethereum addresses to add to the groups + * @returns + */ +export function addAddressesToEthGroup(names: string[], ethAddresses: string[]) { + return prisma.ethereumGroup.updateMany({ + where: { + name: { + in: names + } + }, + data: { + ethereumAddresses: { + push: ethAddresses + } + } + }); +} + +/** + * This function updates an ethereum group in the database. + * @param {string} name - The name of the ethereum group to update + * @param {string[]} ethAddresses - An array of ethereum addresses to add to the group + * @param {string[]} roomIds - The ids of the rooms to connect to the group + * @returns + */ +export function updateEthGroup(name: string, ethAddresses: string[], roomIds: string[]) { + return prisma.ethereumGroup.update({ + where: { + name: name + }, + data: { + ethereumAddresses: { + push: ethAddresses + }, + rooms: { + connect: roomIds.map((roomId) => ({ roomId })) + } + } + }); +} + +/** + * Updates a room's claim code list. + * @param {string} roomId - The id of the room to update + * @param {string} claimCodeId - The id of the claim code to add to the room + * @returns + */ +export function updateRoomClaimCodes(roomId: string, claimCodeId: string) { + return prisma.rooms.update({ + where: { + roomId: roomId + }, + data: { + claimCodeIds: { + push: claimCodeId + } + } + }); +} + +/** + * Updates a users identity commitment in the database. + * @param {IDCProof} generatedProof - Proof generated by the user + * @returns + */ +export async function updateIdentites(generatedProof: IDCProof) { + return await prisma.gateWayIdentity.update({ + where: { + semaphoreIdentity: String(generatedProof.publicSignals.identityCommitment) + }, + data: { + semaphoreIdentity: String(generatedProof.publicSignals.externalNullifier), + } + }) +} + +/** + * Adds an admin to a room. + * @param {string} roomId - The id of the room to add the admin to + * @param {string} idc - The identity of the admin + * @returns + */ +export async function addAdminToRoom(roomId: string, idc: string) { + return await prisma.rooms.update({ + where: { + roomId: roomId + }, + data: { + adminIdentities: { + push: idc + } + } + }); +} diff --git a/src/data/utils.ts b/src/data/utils.ts index 3080b8e..aa2463b 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -1,3 +1,12 @@ +import { + ecrecover, + pubToAddress, + bufferToHex, + fromRpcSig, + toBuffer, + hashPersonalMessage +} from 'ethereumjs-util'; + /** * The sanitizeIDC function takes a string and returns a string. * The string is converted to a BigInt and then back to a string. @@ -21,3 +30,17 @@ export function sanitizeIDC(idc: string): string { throw new Error('Invalid IDC provided.'); } } + +export function recoverPublicKey(message: string, signature: string): string { + const msgHex = bufferToHex(Buffer.from(message)); + const msgBuffer = toBuffer(msgHex); + const msgHash = hashPersonalMessage(msgBuffer); + + const { v, r, s } = fromRpcSig(signature); + const publicKey = ecrecover(msgHash, v, r, s); + const address = pubToAddress(publicKey); + + const recoveredAddress = bufferToHex(address); + + return recoveredAddress +} diff --git a/src/endpoints/admin/admin.ts b/src/endpoints/admin/admin.ts index 020387a..6c1915a 100644 --- a/src/endpoints/admin/admin.ts +++ b/src/endpoints/admin/admin.ts @@ -2,14 +2,12 @@ import express from 'express'; import type { Request, Response } from 'express'; import asyncHandler from 'express-async-handler'; import basicAuth from 'express-basic-auth'; -import { PrismaClient } from '@prisma/client'; import { genClaimCodeArray, pp } from '../../utils'; import { IDCProof } from 'idc-nullifier/dist/types/types'; import { verifyIdentityProof } from '../../crypto/idcVerifier/verifier'; import { limiter } from '../middleware'; -import { createSystemMessages } from '../../data/db'; +import { addAdminToRoom, createClaimCode, createSystemMessages, findAllClaimCodes, findAllRooms, findRoomClaimCodes, updateIdentites, updateRoomClaimCodes } from '../../data/db'; -const prisma = new PrismaClient(); const router = express.Router(); const adminPassword = process.env.PASSWORD ? process.env.PASSWORD : 'password'; @@ -61,34 +59,17 @@ router.post( const threeMonthsLater = new Date(currentDate).setMonth(currentDate.getMonth() + 3); const codeExpires = expiresAt ? expiresAt : threeMonthsLater; - const query = all ? undefined : { where: { roomId: { in: rooms } } }; + // const query = all ? undefined : { where: { roomId: { in: rooms } } }; const codes = genClaimCodeArray(numCodes); - return await prisma.rooms.findMany(query).then((rooms) => { + return findAllRooms(all, rooms).then((rooms) => { + console.log(rooms); const roomIds = rooms.map((room) => room.id); const createCodes = codes.map((code) => { - return prisma.claimCodes - .create({ - data: { - claimcode: code.claimcode, - roomIds: roomIds, - expiresAt: codeExpires, - usesLeft: usesLeft, - discordId: discordId - } - }) + return createClaimCode(code.claimcode, roomIds, codeExpires, usesLeft, discordId) .then((newCode) => { const updatePromises = rooms.map((room) => { - return prisma.rooms.update({ - where: { - roomId: room.roomId - }, - data: { - claimCodeIds: { - push: newCode.id - } - } - }); + return updateRoomClaimCodes(room.roomId, newCode.id) }); return Promise.all(updatePromises); }) @@ -136,11 +117,7 @@ router.post('/:roomId/addcode', adminAuth, (req, res) => { const codeExpires = expires ? expires : threeMonthsLater; - prisma.rooms - .findUnique({ - where: { roomId: roomId }, - include: { claimCodes: true } - }) + findRoomClaimCodes(roomId) .then((room) => { if (!room) { res.status(404).json({ error: 'Room not found' }); @@ -148,20 +125,8 @@ router.post('/:roomId/addcode', adminAuth, (req, res) => { } // Map over the codes array and create a claim code for each code const createCodes = codes.map((code) => { - return prisma.claimCodes.create({ - data: { - claimcode: code.claimcode, - expiresAt: codeExpires, - usesLeft: usesLeft, - rooms: { - connect: { - roomId: roomId - } - } - } - }); + return createClaimCode(code.claimcode, [], codeExpires, usesLeft, '', roomId) }); - return Promise.all(createCodes); }) .then(() => { @@ -176,8 +141,7 @@ router.post('/:roomId/addcode', adminAuth, (req, res) => { // This fetches the claim/invite codes from the database and returns them as JSON router.get('/logclaimcodes', adminAuth, (req, res) => { pp('Express: fetching claim codes'); - prisma.claimCodes - .findMany() + findAllClaimCodes() .then((claimCodes) => { res.status(401).json(claimCodes); }) @@ -190,8 +154,7 @@ router.get('/logclaimcodes', adminAuth, (req, res) => { // GET all rooms from the database and return them as JSON router.get('/rooms', adminAuth, (req, res) => { pp(String('Express: fetching all rooms')); - prisma.rooms - .findMany() + findAllRooms() .then((rooms) => { res.status(200).json(rooms); }) @@ -215,14 +178,7 @@ router.post( const isValid = await verifyIdentityProof(generatedProof); if (isValid) { - const updatedIdentity = await prisma.gateWayIdentity.update({ - where: { - semaphoreIdentity: String(generatedProof.publicSignals.identityCommitment) - }, - data: { - semaphoreIdentity: String(generatedProof.publicSignals.externalNullifier) - } - }); + const updatedIdentity = await updateIdentites(generatedProof) res.status(200).json({ message: 'Identity updated successfully', updatedIdentity }); } else { res.status(500).json({ error: 'Internal Server Error' }); @@ -286,16 +242,7 @@ router.post( const { roomId } = req.params; const { idc } = req.body as { idc: string }; try { - await prisma.rooms.update({ - where: { - roomId: roomId - }, - data: { - adminIdentities: { - push: idc - } - } - }); + await addAdminToRoom(roomId, idc); res.status(200).json({ message: `Admin added to room ${roomId}` }); } catch (err) { res.status(500).json({ error: 'Internal Server Error' }); diff --git a/src/endpoints/gateways/ethereumGroup.ts b/src/endpoints/gateways/ethereumGroup.ts index 85c5f96..ef6951b 100644 --- a/src/endpoints/gateways/ethereumGroup.ts +++ b/src/endpoints/gateways/ethereumGroup.ts @@ -1,18 +1,11 @@ import asyncHandler from 'express-async-handler'; import type { Request, Response } from 'express'; import express from 'express'; -import { PrismaClient } from '@prisma/client'; import { limiter } from '../middleware'; import { generateRandomClaimCode } from 'discreetly-claimcodes'; -import { - ecrecover, - pubToAddress, - bufferToHex, - fromRpcSig, - toBuffer, - hashPersonalMessage -} from 'ethereumjs-util'; import basicAuth from 'express-basic-auth'; +import { addAddressesToEthGroup, updateEthGroup, createEthGroup, findManyGroups, findUniqueEthGroup, removeEthGroup, joinRoomsFromEthAddress } from '../../data/db'; +import { recoverPublicKey } from '../../data/utils'; const adminPassword = process.env.PASSWORD ? process.env.PASSWORD @@ -26,16 +19,10 @@ const adminAuth = basicAuth({ }); const router = express.Router(); -const prisma = new PrismaClient(); // Fetches all ethereum groups that exist in the database router.get('/groups/all', adminAuth, (req: Request, res: Response) => { - prisma.ethereumGroup - .findMany({ - select: { - name: true - } - }) + findManyGroups('ethereum') .then((groups) => { res.status(200).json(groups); }) @@ -55,17 +42,7 @@ router.get('/groups/all', adminAuth, (req: Request, res: Response) => { */ router.get('/group/:address', limiter, (req, res) => { const { address } = req.params as { address: string }; - prisma.ethereumGroup - .findMany({ - where: { - ethereumAddresses: { - has: address - } - }, - select: { - name: true - } - }) + findManyGroups('ethereum', address) .then((groups) => { res.status(200).json(groups); }) @@ -95,14 +72,8 @@ router.post( name: string; roomIds: string[]; }; - const ethereumGroup = await prisma.ethereumGroup.create({ - data: { - name: name, - rooms: { - connect: roomIds.map((roomId) => ({ roomId })) - } - } - }); + if (!name) res.status(500).json({ error: 'No name provided' }) + const ethereumGroup = await createEthGroup(name, roomIds) res.json({ success: true, ethereumGroup }); }) ); @@ -119,20 +90,17 @@ router.post( names: string[]; ethAddresses: string[]; }; - if (!names) return; - const groups = await prisma.ethereumGroup.updateMany({ - where: { - name: { - in: names - } - }, - data: { - ethereumAddresses: { - push: ethAddresses - } + if (!names) res.status(500).json({ error: 'No names provided' }); + try { + const groups = await addAddressesToEthGroup(names, ethAddresses); + if (groups.count === 0) { + res.status(500).json({ error: 'No groups found' }); + return; } - }); - res.json({ success: true, groups }); + res.json({ success: true, groups }); + } catch (err) { + res.status(500).json({ error: 'Internal Server Error' }); + } }) ); @@ -159,33 +127,16 @@ router.post( roomIds: []; }; try { - const foundGroup = await prisma.ethereumGroup.findUnique({ - where: { - name: name - }, - select: { - ethereumAddresses: true - } - }); + const foundGroup = await findUniqueEthGroup(name) let addresses: string[] = []; + if (foundGroup?.ethereumAddresses) { addresses = ethAddresses.filter((address) => { return !foundGroup.ethereumAddresses.includes(address); }); } - const updatedGroup = await prisma.ethereumGroup.update({ - where: { - name: name - }, - data: { - ethereumAddresses: { - push: addresses - }, - rooms: { - connect: roomIds.map((roomId) => ({ roomId })) - } - } - }); + + const updatedGroup = await updateEthGroup(name, addresses, roomIds); res.json({ success: true, updatedGroup }); } catch (err) { console.error(err); @@ -203,12 +154,7 @@ router.post( */ router.post('/group/delete', adminAuth, (req, res) => { const { name } = req.body as { name: string }; - prisma.ethereumGroup - .delete({ - where: { - name: name - } - }) + removeEthGroup('ethereum', name) .then((group) => { res.status(200).json(group); }) @@ -240,51 +186,9 @@ router.post( }; try { - const msgHex = bufferToHex(Buffer.from(message)); - const msgBuffer = toBuffer(msgHex); - const msgHash = hashPersonalMessage(msgBuffer); - - const { v, r, s } = fromRpcSig(signature); - const publicKey = ecrecover(msgHash, v, r, s); - const address = pubToAddress(publicKey); - - const recoveredAddress = bufferToHex(address); - const gatewayIdentity = await prisma.gateWayIdentity.upsert({ - where: { semaphoreIdentity: message }, - update: {}, - create: { - semaphoreIdentity: message - } - }); - - await prisma.ethereumAddress.upsert({ - where: { ethereumAddress: recoveredAddress }, - update: {}, - create: { - ethereumAddress: recoveredAddress, - gatewayId: gatewayIdentity.id - } - }); - - const roomsToJoin = await prisma.ethereumGroup.findMany({ - where: { - ethereumAddresses: { - has: recoveredAddress - } - }, - select: { - roomIds: true - } - }); - - const roomIdsSet = new Set(roomsToJoin.map((room) => room.roomIds).flat()); - const roomIds = Array.from(roomIdsSet); - - await prisma.gateWayIdentity.update({ - where: { id: gatewayIdentity.id }, - data: { roomIds: { set: roomIds } } - }); + const recoveredAddress = recoverPublicKey(message, signature); + const roomIds = await joinRoomsFromEthAddress(recoveredAddress, message); res.json({ status: 'valid', roomIds: roomIds }); } catch (err) { console.error(err); diff --git a/src/endpoints/gateways/inviteCode.ts b/src/endpoints/gateways/inviteCode.ts index abd672e..20685ec 100644 --- a/src/endpoints/gateways/inviteCode.ts +++ b/src/endpoints/gateways/inviteCode.ts @@ -7,7 +7,8 @@ import { findClaimCode, updateClaimCode, updateRoomIdentities, - findUpdatedRooms + findUpdatedRooms, + removeClaimCode } from '../../data/db/'; import { GatewayInviteDataI } from '../../types'; import { RoomI } from 'discreetly-interfaces'; @@ -52,11 +53,7 @@ router.post( if (foundCode && (foundCode.usesLeft >= 0 || foundCode.usesLeft === -1)) { const updatedCode = await updateClaimCode(code, idc); if (updatedCode && updatedCode.usesLeft === 0) { - await prisma.claimCodes.delete({ - where: { - claimcode: code - } - }); + await removeClaimCode(code); } } else { res.status(400).json({ message: 'Invalid Claim Code' }); diff --git a/src/endpoints/gateways/jubmoji.ts b/src/endpoints/gateways/jubmoji.ts new file mode 100644 index 0000000..392a5df --- /dev/null +++ b/src/endpoints/gateways/jubmoji.ts @@ -0,0 +1,162 @@ +import express from 'express'; +import { Request, Response } from 'express'; +import { PrismaClient } from '@prisma/client'; +import { limiter } from '../middleware'; +import { generateRandomClaimCode } from 'discreetly-claimcodes'; +import basicAuth from 'express-basic-auth'; +import { findManyGroups, removeEthGroup } from '../../data/db'; + +const router = express.Router(); +const prisma = new PrismaClient(); + +const adminPassword = process.env.PASSWORD + ? process.env.PASSWORD + : // eslint-disable-next-line @typescript-eslint/no-unsafe-call + (generateRandomClaimCode(4) as string); + +const adminAuth = basicAuth({ + users: { + admin: adminPassword + } +}); + +// Fetches all jubmoji groups that exist in the database +router.get('/groups/all', adminAuth, (req: Request, res: Response) => { + findManyGroups('jubmoji') + .then((groups) => { + res.status(200).json(groups); + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }); +}); + +/** + * This code gets the Jubmoji group with the given address. + * @param {string} address - The address of the Jubmoji group to get + * @returns {void} + * @example { + * "address": "string" + * } + */ +router.get('/group/:address', limiter, (req: Request, res: Response) => { + const { address } = req.params as { address: string }; + findManyGroups('jubmoji', address) + .then((groups) => { + res.status(200).json(groups); + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }); +}) + + +/** + * This code creates a new Jubmoji group with the given name, and + * connects the group to the given rooms. It then sends back a JSON + * response with the newly created Jubmoji group. + * @param {string} name - The name of the Jubmoji group to create + * @param {string[]} roomIds - The ids of the rooms to connect to the group + * @returns {void} + * @example { + * "name": "string", + * "roomIds": string[] + * } + */ +router.post('/group/create', adminAuth, (req: Request, res: Response) => { + const { name, roomIds } = req.body as { name: string; roomIds: string[] }; + if (!name) res.status(500).json({ error: 'No name provided' }); + prisma.jubmojiGroup + .create({ + data: { + name, + rooms: { + connect: roomIds.map((roomId) => ({ roomId })) + } + } + }) + .then((group) => { + res.status(200).json(group); + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }); +}) + +/** Adds a list of jubmoji addresses to a list of groups in the database. + * @param {string[]} names - The names of the Jubmoji groups to add the address to + * @param {string[]} addresses - The addresses to add to the Jubmoji groups + */ +router.post('/group/add', adminAuth, (req: Request, res: Response) => { + const { names, addresses } = req.body as { names: string[]; addresses: string[] }; + + if (!names) res.status(500).json({ error: 'No name provided' }); + + prisma.jubmojiGroup + .updateMany({ + where: { + name: { + in: names + } + }, + data: { + jubmojiAddresses: { + push: addresses + } + } + }) + .then((group) => { + res.status(200).json(group); + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }); +}) + +/** Edits a jubmoji group in the database + * The body of the request contains the name of the group to edit and the room IDs to associate with the group. + * @param {string} name - The name of the Jubmoji group to edit + * @param {string[]} roomIds - The ids of the rooms to connect to the group + * @returns {void} + * @example { + * "name": "string", + * "roomIds": string[] + * } + */ +router.post('/group/edit', adminAuth, (req: Request, res: Response) => { + const { name, roomIds } = req.body as { name: string; roomIds: string[] }; + + if (!name) res.status(500).json({ error: 'No name provided' }); + prisma.jubmojiGroup.update({ + where: { + name: name + }, + data: { + rooms: { + connect: roomIds.map((id) => ({ id })) + } + } + }).then((group) => { + res.status(200).json(group); + }).catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }) +}) + +router.post('/group/delete', adminAuth, (req: Request, res: Response) => { + const { name } = req.body as { name: string }; + removeEthGroup('jubmoji', name) + .then((group) => { + res.status(200).json(group); + }).catch((err) => { + console.error(err); + res.status(500).json({ error: 'Internal Server Error' }); + }) +}) + +export default router; diff --git a/src/endpoints/gateways/theWord.ts b/src/endpoints/gateways/theWord.ts index db5e842..63c9c0e 100644 --- a/src/endpoints/gateways/theWord.ts +++ b/src/endpoints/gateways/theWord.ts @@ -27,11 +27,11 @@ router.post( const isValid = await verifyTheWordProof(proof); if (isValid) { - const room = (await prisma.rooms.findUnique({ + const room = await prisma.rooms.findUnique({ where: { roomId: '007' + process.env.THEWORD_ITERATION } - })) as RoomI; + }) as RoomI; const addedRoom = await addIdentityToIdentityListRooms([room], idc); if (addedRoom.length === 0) { res.status(500).json({ diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index c48b34c..bb48e56 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -6,10 +6,12 @@ import discordRouter from './gateways/discord'; import ethRouter from './gateways/ethereumGroup'; import theWordRouter from './gateways/theWord'; import codeRouter from './gateways/inviteCode'; +import jubmojiRouter from './gateways/jubmoji'; import roomRouter from './rooms/rooms'; import identityRouter from './identity/idc'; import adminRouter from './admin/admin'; + export function initEndpoints(app: Express) { // This code is used to fetch the server info from the api // This is used to display the server info on the client side @@ -17,6 +19,7 @@ export function initEndpoints(app: Express) { app.use('/gateway/eth', ethRouter); app.use('/gateway/theword', theWordRouter); app.use('/gateway/code', codeRouter); + app.use('/gateway/jubmoji', jubmojiRouter) app.use('/room', roomRouter); app.use('/identity', identityRouter); app.use('/admin', adminRouter); diff --git a/src/endpoints/rooms/rooms.ts b/src/endpoints/rooms/rooms.ts index aba8b2e..27c2a34 100644 --- a/src/endpoints/rooms/rooms.ts +++ b/src/endpoints/rooms/rooms.ts @@ -1,25 +1,20 @@ import express from 'express'; import type { Request, Response } from 'express'; import { limiter } from '../middleware'; -import asyncHandler from 'express-async-handler'; -import { PrismaClient } from '@prisma/client'; -import { verifyIdentityProof } from '../../crypto/idcVerifier/verifier'; import { pp } from '../../utils'; -import { IDCProof } from 'idc-nullifier/dist/types/types'; import { addRoomData } from '../../types'; import { findRoomById, - findRoomsByIdentity, createRoom, removeRoom, - removeMessage + removeMessage, + findMessages } from '../../data/db/'; import { MessageI, RoomI } from 'discreetly-interfaces'; import { RLNFullProof } from 'rlnjs'; import basicAuth from 'express-basic-auth'; import { generateRandomClaimCode } from 'discreetly-claimcodes'; const router = express.Router(); -const prisma = new PrismaClient(); const adminPassword = process.env.PASSWORD ? process.env.PASSWORD @@ -224,24 +219,7 @@ router.post('/:roomId/message/delete', adminAuth, (req, res) => { */ router.get('/:id/messages', limiter, (req, res) => { const { id } = req.params; - prisma.messages - .findMany({ - take: 500, - orderBy: { - timeStamp: 'desc' - }, - where: { - roomId: id - }, - select: { - id: false, - message: true, - messageId: true, - proof: true, - roomId: true, - timeStamp: true - } - }) + findMessages(id) .then((messages) => { messages.map((message: MessageI) => { message.timeStamp = new Date(message.timeStamp as Date).getTime(); diff --git a/src/types/index.ts b/src/types/index.ts index 7c096c3..3379917 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -21,6 +21,7 @@ export interface ClaimCodeI { discordId: string | null; } + export interface GateWayIdentityI { semaphoreIdentity: string | null; roomIds: string[]; @@ -67,3 +68,9 @@ export interface addRoomData { admin?: boolean; discordIds?: string[]; } + +export interface EthGroupI { + name: string; + roomIds: string[]; + ethereumAddresses: string[]; +}