diff --git a/src/endpoints/gateways/discord.ts b/src/endpoints/gateways/discord.ts index be2db23..abe9fd7 100644 --- a/src/endpoints/gateways/discord.ts +++ b/src/endpoints/gateways/discord.ts @@ -2,11 +2,21 @@ import express from 'express'; import type { Request, Response } from 'express'; import { limiter } from '../middleware'; import { PrismaClient } from '@prisma/client'; -import { adminAuth } from '../../server'; + import asyncHandler from 'express-async-handler'; +import basicAuth from 'express-basic-auth'; + + const router = express.Router(); const prisma = new PrismaClient(); +const discordPassword = process.env.DISCORD_PASSWORD ? process.env.DISCORD_PASSWORD : 'password'; + +const adminAuth = basicAuth({ + users: { + admin: discordPassword + } +}) /** * Creates a new guild in the database when the bot is added to a discord server. * @param {string} guildId - The id of the guild to be added diff --git a/src/endpoints/gateways/ethereumGroup.ts b/src/endpoints/gateways/ethereumGroup.ts index 21e9008..b344a29 100644 --- a/src/endpoints/gateways/ethereumGroup.ts +++ b/src/endpoints/gateways/ethereumGroup.ts @@ -1,7 +1,6 @@ import asyncHandler from 'express-async-handler'; import type { Request, Response } from 'express'; import express from 'express'; -import { adminAuth } from '../../server'; import { PrismaClient } from '@prisma/client'; import { limiter } from '../middleware'; import { @@ -12,11 +11,21 @@ import { toBuffer, hashPersonalMessage } from 'ethereumjs-util'; +import basicAuth from 'express-basic-auth'; + +const adminPassword = process.env.ADMIN_PASSWORD ? process.env.ADMIN_PASSWORD : 'password'; + +const adminAuth = basicAuth({ + users: { + admin: adminPassword + } +}); + const router = express.Router(); const prisma = new PrismaClient(); -router.get(['/eth/groups/all', '/api/eth/groups/all'], adminAuth, (req: Request, res: Response) => { +router.get('/groups/all', adminAuth, (req: Request, res: Response) => { prisma.ethereumGroup .findMany({ select: { diff --git a/src/endpoints/gateways/index.ts b/src/endpoints/gateways/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/endpoints/index.ts b/src/endpoints/index.ts index eba7ae3..c10607f 100644 --- a/src/endpoints/index.ts +++ b/src/endpoints/index.ts @@ -2,16 +2,6 @@ import type { Express, RequestHandler, Request, Response } from 'express'; import { PrismaClient } from '@prisma/client'; import { serverConfig } from '../config/serverConfig'; import { genClaimCodeArray, pp } from '../utils'; -import { - findRoomById, - findRoomsByIdentity, - createRoom, - createSystemMessages, - removeRoom, - removeMessage, -} from '../data/db/'; -import { MessageI, RoomI } from 'discreetly-interfaces'; -import { RLNFullProof } from 'rlnjs'; import { SNARKProof as idcProof } from 'idc-nullifier/dist/types/types'; import { verifyIdentityProof } from '../crypto/idcVerifier/verifier'; import { limiter } from './middleware'; @@ -20,6 +10,7 @@ import discordRouter from './gateways/discord'; import ethRouter from './gateways/ethereumGroup'; import theWordRouter from './gateways/theWord'; import codeRouter from './gateways/inviteCode' +import roomRouter from './rooms/rooms' // import expressBasicAuth from 'express-basic-auth'; const prisma = new PrismaClient(); @@ -31,307 +22,13 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) { app.use('/gateway/eth', ethRouter) app.use('/gateway/theword', theWordRouter) app.use('/gateway/code', codeRouter) - + app.use('/room', roomRouter) app.get(['/'], (req, res) => { pp('Express: fetching server info'); res.status(200).json(serverConfig); }); - - // This code gets a room by its ID, and then checks if room is null. - // If room is null, it returns a 500 error. - // Otherwise, it returns a 200 status code and the room object. - - app.get(['/room/:id'], limiter, (req, res) => { - if (!req.params.id) { - res.status(400).json({ error: 'Bad Request' }); - } else { - const requestRoomId = req.params.id ?? '0'; - pp(String('Express: fetching room info for ' + req.params.id)); - findRoomById(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); - } else { - const { - roomId, - name, - rateLimit, - userMessageLimit, - membershipType, - identities, - bandadaAddress, - bandadaGroupId - } = room || {}; - const id = String(roomId); - const roomResult: RoomI = { - id, - roomId, - name, - rateLimit, - userMessageLimit, - membershipType - }; - // Add null check before accessing properties of room object - if (membershipType === 'BANDADA_GROUP') { - roomResult.bandadaAddress = bandadaAddress; - roomResult.bandadaGroupId = bandadaGroupId; - } - if (membershipType === 'IDENTITY_LIST') { - roomResult.identities = identities; - } - - res.status(200).json(roomResult); - } - }) - .catch((err) => console.error(err)); - } - }); - - /** This function gets the rooms that a user is a member of. - * @param {string} idc - The id of the identity to get rooms for. - * @returns {Array} - An array of room objects. - */ - app.get( - ['/rooms/:idc'], - limiter, - asyncHandler(async (req: Request, res: Response) => { - // const { proof } = req.body as { proof: SNARKProof }; - // console.log('PROOF', proof); - - const isValid = await verifyIdentityProof(req.body as idcProof); - console.log('VALID?', isValid); - if (isValid) { - try { - res.status(200).json(await findRoomsByIdentity(req.params.idc)); - } catch (error) { - console.error(error); - res.status(500).json({ error: 'Internal Server Error' }); - } - } - }) - ); - - /** - * This code is used to update the room identities of the rooms that the user is joining. - * The code updates the claim code and sets it to be claimed. - * It then updates the room identities of the user joining. - * Finally, it finds the rooms that have been updated and returns them. - * @param {string} code - The claim code to be updated - * @param {string} idc - The id of the identity to be added to the room - * @returns {Array} - An array of room objects - * @example { - * "code": "string", - * "idc": "string" - * } - */ - - - interface addRoomData { - roomName: string; - rateLimit: number; - userMessageLimit: number; - numClaimCodes?: number; - approxNumMockUsers?: number; - adminIdentities?: string[]; - roomType?: string; - bandadaAddress?: string; - bandadaAPIKey?: string; - bandadaGroupId?: string; - membershipType?: string; - roomId?: string; - admin?: boolean; - discordIds?: string[]; - } - - /* ~~~~ ADMIN ENDPOINTS ~~~~ */ - - /** createRoom is used to create a new room in the database - * @param {string} roomName - The name of the room - * @param {number} rateLimit - The rate limit of the room - * @param {number} userMessageLimit - The user message limit of the room - * @param {number} numClaimCodes - The number of claim codes to generate - * @param {number} approxNumMockUsers - The approximate number of mock users to generate - * @param {string[]} adminIdentities - The identities of the admins of the room - * @param {string} type - The type of room - * @param {string} bandadaAddress - The address of the Bandada group - * @param {string} bandadaGroupId - The id of the Bandada group - * @param {string} bandadaAPIKey - The API key of the Bandada group - * @param {string} membershipType - The type of membership - * @param {string} roomId - The id of the room - * @param {string[]} discordIds - The ids of the discord users to add to the room - * @returns {void} - * @example { - * "roomName": "string", - * "rateLimit": number, - * "userMessageLimit": number, - * "numClaimCodes": number, // optional - * "approxNumMockUsers": number, // optional - * "adminIdentities": string[], // optional - * "roomType": "string", // optional - * "bandadaAddress": "string", // optional - * "bandadaGroupId": "string", // optional - * "bandadaAPIKey": "string", // optional - * "membershipType": "string" // optional if not an IDENTITY_LIST - * "roomId": "string", // optional - * "discordIds": string[] // optional - * } - */ - app.post(['/room/add', '/api/room/add'], adminAuth, (req, res) => { - const roomMetadata = req.body as addRoomData; - const roomName = roomMetadata.roomName; - const rateLimit = roomMetadata.rateLimit; - const userMessageLimit = roomMetadata.userMessageLimit; - const numClaimCodes = roomMetadata.numClaimCodes ?? 0; - const adminIdentities = roomMetadata.adminIdentities; - 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; - const roomId = roomMetadata.roomId; - createRoom( - roomName, - rateLimit, - userMessageLimit, - numClaimCodes, - approxNumMockUsers, - type, - adminIdentities, - bandadaAddress, - bandadaGroupId, - bandadaAPIKey, - membershipType, - roomId - ) - .then((result) => { - const response = - result === null - ? { status: 400, message: 'Room already exists' } - : result - ? { - status: 200, - message: 'Room created successfully', - roomId: result.roomId, - claimCodes: result.claimCodes - } - : { status: 500, error: 'Internal Server Error' }; - - res.status(response.status).json(response); - }) - .catch((err) => { - console.error(err); - res.status(500).json({ error: String(err) }); - }); - }); - - /** - * This code is used to delete a room from the database. - * It takes in the roomId from the request body, and pass it to the removeRoom function. - * If removeRoom returns true, it means the room is deleted successfully, and the server returns a 200 status code. - * If removeRoom returns false, the server returns a 500 status code. - * If removeRoom throws an error, the server returns a 500 status code. - * @param {string} roomId - The id of the room to be deleted - * @returns {void} - * */ - - app.post( - ['/room/:roomId/delete', '/api/room/:roomId/delete'], - adminAuth, - (req: Request, res: Response) => { - const { roomId } = req.body as { roomId: string }; - removeRoom(roomId) - .then((result) => { - if (result) { - res.status(200).json({ message: 'Room deleted successfully' }); - } else { - res.status(500).json({ error: 'Internal Server Error' }); - } - }) - .catch((err) => { - console.error(err); - res.status(500).json({ error: String(err) }); - }); - } - ); - - /** - * This code deletes a message from a room - * It takes in the roomId and messageId from the request body, and pass it to the removeMessage function. - * If removeMessage returns true, it means the message is deleted successfully, and the server returns a 200 status code. - * If removeMessage returns false, the server returns a 500 status code. - * If removeMessage throws an error, the server returns a 500 status code. - * @param {string} roomId - The id of the room to be deleted - * @param {string} messageId - The id of the message to be deleted - * @returns {void} - * */ - - app.post( - ['/room/:roomId/message/delete', '/api/room/:roomId/message/delete'], - adminAuth, - (req, res) => { - const { roomId } = req.params; - const { messageId } = req.body as { messageId: string }; - - removeMessage(roomId, messageId) - .then((result) => { - if (result) { - res.status(200).json({ message: 'Message deleted successfully' }); - } else { - res.status(500).json({ error: 'Internal Server Error' }); - } - }) - .catch((err) => { - console.error(err); - res.status(500).json({ error: String(err) }); - }); - } - ); - - /** - * This code handles the get request to get a list of messages for a particular room. - * It uses the Prisma client to query the database and return the messages for a particular room. - * It also parses the proof from a string to a JSON object. - * @param {string} id - The id of the room to get messages for - * @returns {void} - */ - app.get('/api/room/: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 - } - }) - .then((messages) => { - messages.map((message: MessageI) => { - message.timeStamp = new Date(message.timeStamp as Date).getTime(); - message.proof = JSON.parse(message.proof as string) as RLNFullProof; - message.epoch = message.proof.epoch; - }); - pp('Express: fetching messages for room ' + id); - res.status(200).json(messages.reverse()); - }) - .catch((error: Error) => { - pp(error, 'error'); - res.status(500).send('Error fetching messages'); - }); - }); - + /** * Endpoint to add claim codes to all rooms or a subset of rooms * This code adds claim codes to the database. diff --git a/src/endpoints/rooms/rooms.ts b/src/endpoints/rooms/rooms.ts new file mode 100644 index 0000000..d33d63d --- /dev/null +++ b/src/endpoints/rooms/rooms.ts @@ -0,0 +1,324 @@ +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 { SNARKProof as idcProof } from 'idc-nullifier/dist/types/types'; +import { + findRoomById, + findRoomsByIdentity, + createRoom, + removeRoom, + removeMessage, +} from '../../data/db/'; +import { MessageI, RoomI } from 'discreetly-interfaces'; +import { RLNFullProof } from 'rlnjs'; +import basicAuth from 'express-basic-auth'; + +const router = express.Router(); +const prisma = new PrismaClient(); + +const adminPassword = process.env.ADMIN_PASSWORD ? process.env.ADMIN_PASSWORD : 'password'; + +const adminAuth = basicAuth({ + users: { + admin: adminPassword + } +}); + + // This code gets a room by its ID, and then checks if room is null. + // If room is null, it returns a 500 error. + // Otherwise, it returns a 200 status code and the room object. +router.get('/room/:id', limiter, (req, res) => { + if (!req.params.id) { + res.status(400).json({ error: 'Bad Request' }); + } else { + const requestRoomId = req.params.id ?? '0'; + pp(String('Express: fetching room info for ' + req.params.id)); + findRoomById(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); + } else { + const { + roomId, + name, + rateLimit, + userMessageLimit, + membershipType, + identities, + bandadaAddress, + bandadaGroupId + } = room || {}; + const id = String(roomId); + const roomResult: RoomI = { + id, + roomId, + name, + rateLimit, + userMessageLimit, + membershipType + }; + // Add null check before accessing properties of room object + if (membershipType === 'BANDADA_GROUP') { + roomResult.bandadaAddress = bandadaAddress; + roomResult.bandadaGroupId = bandadaGroupId; + } + if (membershipType === 'IDENTITY_LIST') { + roomResult.identities = identities; + } + + res.status(200).json(roomResult); + } + }) + .catch((err) => console.error(err)); + } +}); + +/** This function gets the rooms that a user is a member of. + * @param {string} idc - The id of the identity to get rooms for. + * @returns {Array} - An array of room objects. + */ +router.get( + '/room/:idc', + limiter, + asyncHandler(async (req: Request, res: Response) => { + // const { proof } = req.body as { proof: SNARKProof }; + // console.log('PROOF', proof); + + const isValid = await verifyIdentityProof(req.body as idcProof); + console.log('VALID?', isValid); + if (isValid) { + try { + res.status(200).json(await findRoomsByIdentity(req.params.idc)); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Internal Server Error' }); + } + } + }) +); + +/** + * This code is used to update the room identities of the rooms that the user is joining. + * The code updates the claim code and sets it to be claimed. + * It then updates the room identities of the user joining. + * Finally, it finds the rooms that have been updated and returns them. + * @param {string} code - The claim code to be updated + * @param {string} idc - The id of the identity to be added to the room + * @returns {Array} - An array of room objects + * @example { + * "code": "string", + * "idc": "string" + * } + */ + + +interface addRoomData { + roomName: string; + rateLimit: number; + userMessageLimit: number; + numClaimCodes?: number; + approxNumMockUsers?: number; + adminIdentities?: string[]; + roomType?: string; + bandadaAddress?: string; + bandadaAPIKey?: string; + bandadaGroupId?: string; + membershipType?: string; + roomId?: string; + admin?: boolean; + discordIds?: string[]; +} + +/* ~~~~ ADMIN ENDPOINTS ~~~~ */ + +/** createRoom is used to create a new room in the database + * @param {string} roomName - The name of the room + * @param {number} rateLimit - The rate limit of the room + * @param {number} userMessageLimit - The user message limit of the room + * @param {number} numClaimCodes - The number of claim codes to generate + * @param {number} approxNumMockUsers - The approximate number of mock users to generate + * @param {string[]} adminIdentities - The identities of the admins of the room + * @param {string} type - The type of room + * @param {string} bandadaAddress - The address of the Bandada group + * @param {string} bandadaGroupId - The id of the Bandada group + * @param {string} bandadaAPIKey - The API key of the Bandada group + * @param {string} membershipType - The type of membership + * @param {string} roomId - The id of the room + * @param {string[]} discordIds - The ids of the discord users to add to the room + * @returns {void} + * @example { + * "roomName": "string", + * "rateLimit": number, + * "userMessageLimit": number, + * "numClaimCodes": number, // optional + * "approxNumMockUsers": number, // optional + * "adminIdentities": string[], // optional + * "roomType": "string", // optional + * "bandadaAddress": "string", // optional + * "bandadaGroupId": "string", // optional + * "bandadaAPIKey": "string", // optional + * "membershipType": "string" // optional if not an IDENTITY_LIST + * "roomId": "string", // optional + * "discordIds": string[] // optional + * } + */ +router.post('/add', adminAuth, (req, res) => { + const roomMetadata = req.body as addRoomData; + const roomName = roomMetadata.roomName; + const rateLimit = roomMetadata.rateLimit; + const userMessageLimit = roomMetadata.userMessageLimit; + const numClaimCodes = roomMetadata.numClaimCodes ?? 0; + const adminIdentities = roomMetadata.adminIdentities; + 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; + const roomId = roomMetadata.roomId; + createRoom( + roomName, + rateLimit, + userMessageLimit, + numClaimCodes, + approxNumMockUsers, + type, + adminIdentities, + bandadaAddress, + bandadaGroupId, + bandadaAPIKey, + membershipType, + roomId + ) + .then((result) => { + const response = + result === null + ? { status: 400, message: 'Room already exists' } + : result + ? { + status: 200, + message: 'Room created successfully', + roomId: result.roomId, + claimCodes: result.claimCodes + } + : { status: 500, error: 'Internal Server Error' }; + + res.status(response.status).json(response); + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: String(err) }); + }); +}); + +/** + * This code is used to delete a room from the database. + * It takes in the roomId from the request body, and pass it to the removeRoom function. + * If removeRoom returns true, it means the room is deleted successfully, and the server returns a 200 status code. + * If removeRoom returns false, the server returns a 500 status code. + * If removeRoom throws an error, the server returns a 500 status code. + * @param {string} roomId - The id of the room to be deleted + * @returns {void} + * */ + +router.post( + '/:roomId/delete', + adminAuth, + (req: Request, res: Response) => { + const { roomId } = req.body as { roomId: string }; + removeRoom(roomId) + .then((result) => { + if (result) { + res.status(200).json({ message: 'Room deleted successfully' }); + } else { + res.status(500).json({ error: 'Internal Server Error' }); + } + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: String(err) }); + }); + } +); + +/** + * This code deletes a message from a room + * It takes in the roomId and messageId from the request body, and pass it to the removeMessage function. + * If removeMessage returns true, it means the message is deleted successfully, and the server returns a 200 status code. + * If removeMessage returns false, the server returns a 500 status code. + * If removeMessage throws an error, the server returns a 500 status code. + * @param {string} roomId - The id of the room to be deleted + * @param {string} messageId - The id of the message to be deleted + * @returns {void} + * */ + +router.post( + '/:roomId/message/delete', + adminAuth, + (req, res) => { + const { roomId } = req.params; + const { messageId } = req.body as { messageId: string }; + + removeMessage(roomId, messageId) + .then((result) => { + if (result) { + res.status(200).json({ message: 'Message deleted successfully' }); + } else { + res.status(500).json({ error: 'Internal Server Error' }); + } + }) + .catch((err) => { + console.error(err); + res.status(500).json({ error: String(err) }); + }); + } +); + +/** + * This code handles the get request to get a list of messages for a particular room. + * It uses the Prisma client to query the database and return the messages for a particular room. + * It also parses the proof from a string to a JSON object. + * @param {string} id - The id of the room to get messages for + * @returns {void} + */ +router.get('/room/: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 + } + }) + .then((messages) => { + messages.map((message: MessageI) => { + message.timeStamp = new Date(message.timeStamp as Date).getTime(); + message.proof = JSON.parse(message.proof as string) as RLNFullProof; + message.epoch = message.proof.epoch; + }); + pp('Express: fetching messages for room ' + id); + res.status(200).json(messages.reverse()); + }) + .catch((error: Error) => { + pp(error, 'error'); + res.status(500).send('Error fetching messages'); + }); +}); + +export default router; diff --git a/src/server.ts b/src/server.ts index 28e61cc..637bd45 100644 --- a/src/server.ts +++ b/src/server.ts @@ -35,7 +35,7 @@ const admin_password = process.env.PASSWORD : // eslint-disable-next-line @typescript-eslint/no-unsafe-call (generateRandomClaimCode(4) as string); -export const adminAuth = basicAuth({ +const adminAuth = basicAuth({ users: { admin: admin_password }