Bandada Integration (#35)

This commit is contained in:
AtHeartEngineer
2023-08-22 13:30:35 -04:00
committed by GitHub
8 changed files with 228 additions and 91 deletions

View File

@@ -65,4 +65,4 @@
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
}
}
}

View File

@@ -20,8 +20,9 @@ model Rooms {
membershipType String @default("IDENTITY_LIST")
identities String[] @default([])
contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS"
bandadaAddress String?
bandadaGroupId String?
bandadaAddress String? // BANDADA as "url:groupID"
bandadaGroupId String? // Bandada Group ID
bandadaAPIKey String? // Bandada API Key
epochs Epoch[]
messages Messages[]
claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id])

View File

@@ -1,9 +1,9 @@
import { createRoom } from '../src/data/db';
function main() {
createRoom('1 Second Room', 1000, 1, 10).catch((err) => console.warn(err));
createRoom('10 Second Room', 10000, 2, 10).catch((err) => console.warn(err));
createRoom('100 Second Room', 100000, 10, 10).catch((err) => console.warn(err));
async function main(){
await createRoom('1 Second Room', 1000, 1, 10, 20, 'PUBLIC');
await createRoom('10 Second Room', 10000, 2, 10, 20, 'PUBLIC');
await createRoom('100 Second Room', 100000, 10, 10, 20, 'PUBLIC');
}
main();
await main();

View File

@@ -37,6 +37,7 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro
}
// Check that the message hash is correct
if (msgHash !== proof.snarkProof.publicSignals.x) {
console.warn(
'Message hash incorrect:',

View File

@@ -93,28 +93,90 @@ export function updateClaimCode(code: string): Promise<RoomsFromClaimCode> {
});
}
export function updateRoomIdentities(idc: string, roomIds: string[]): Promise<any> {
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<void> {
const identityCommitment = sanitizeIDC(idc);
return prisma.rooms
.findMany({
where: { id: { in: roomIds } }
})
.then((rooms) => {
const roomsToUpdate = rooms
.filter((room) => !room.identities.includes(idc))
.map((room) => room.id);
if (roomsToUpdate) {
return prisma.rooms.updateMany({
where: { id: { in: roomsToUpdate } },
data: { identities: { push: idc } }
});
}
addIdentityToIdentityListRooms(rooms, identityCommitment);
addIdentityToBandadaRooms(rooms, identityCommitment);
})
.catch((err) => {
pp(err, 'error');
});
}
function addIdentityToIdentityListRooms(rooms, identityCommitment: string): unknown {
const identityListRooms = rooms
.filter(
(room) =>
room.membershipType === 'IDENTITY_LIST' && !room.identities.includes(identityCommitment)
)
.map((room) => room.id as string);
if (identityListRooms.length > 0) {
return prisma.rooms.updateMany({
where: { id: { in: identityListRooms } },
data: { identities: { push: identityCommitment } }
});
}
}
function addIdentityToBandadaRooms(rooms, identityCommitment: string): void {
const bandadaGroupRooms = rooms
.filter(
(room) =>
room.membershipType === 'BANDADA_GROUP' && !room.identities.includes(identityCommitment)
)
.map((room) => room as RoomI);
if (bandadaGroupRooms.length > 0) {
bandadaGroupRooms.forEach(async (room) => {
if (!room.bandadaAPIKey) {
console.error('API key is missing for room:', room);
return;
}
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': room.bandadaAPIKey
}
};
await prisma.rooms.updateMany({
where: { id: room.id },
data: { identities: { push: identityCommitment } }
});
const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${identityCommitment}`;
fetch(url, requestOptions)
.then((res) => {
if (res.status == 200) {
console.debug(`Successfully added user to Bandada group ${room.bandadaAddress}`);
}
})
.catch((err) => {
console.error(err);
});
});
}
}
export async function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
const rooms = await prisma.rooms.findMany({
where: { id: { in: roomIds } }
@@ -127,7 +189,8 @@ export async function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
});
}
export function createSystemMessages(message: string, roomId?: string): Promise<any> {
// TODO: Make interface for this return type; which is like a MessageI
export function createSystemMessages(message: string, roomId?: string): Promise<unknown> {
const query = roomId ? { where: { roomId } } : undefined;
return prisma.rooms
.findMany(query)
@@ -150,6 +213,7 @@ export function createSystemMessages(message: string, roomId?: string): Promise<
})
.catch((err) => {
console.error(err);
return Promise.reject(err);
});
}
@@ -179,28 +243,36 @@ export function removeIdentityFromRoom(idc: string, room: RoomI): Promise<void |
* @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 = 'PUBLIC'
type: string,
bandadaAddress?: string,
bandadaGroupId?: string,
bandadaAPIKey?: string,
membershipType?: string
): Promise<boolean> {
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
console.log(claimCodes);
const mockUsers: string[] = genMockUsers(approxNumMockUsers);
const roomData = {
where: {
roomId: genId(serverConfig.id as string, name).toString()
roomId: genId(serverConfig.id as bigint, roomName).toString()
},
update: {},
create: {
roomId: genId(serverConfig.id as string, name).toString(),
name: name,
roomId: genId(serverConfig.id as bigint, roomName).toString(),
name: roomName,
rateLimit: rateLimit,
userMessageLimit: userMessageLimit,
identities: mockUsers,
type,
bandadaAddress,
bandadaGroupId,
bandadaAPIKey,
membershipType,
claimCodes: {
create: claimCodes
}

View File

@@ -1,7 +1,7 @@
import type { Express, RequestHandler, Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
import { serverConfig } from "../config/serverConfig";
import { pp } from "../utils";
import type { Express, RequestHandler, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { serverConfig } from '../config/serverConfig';
import { pp } from '../utils';
import {
getRoomByID,
getRoomsByIdentity,
@@ -10,9 +10,9 @@ import {
updateRoomIdentities,
findUpdatedRooms,
createRoom,
createSystemMessages,
} from "../data/db";
import { RoomI } from "discreetly-interfaces";
createSystemMessages
} from '../data/db';
import { RoomI } from 'discreetly-interfaces';
const prisma = new PrismaClient();
@@ -29,41 +29,74 @@ function asyncHandler(fn: {
}
export function initEndpoints(app: Express, adminAuth: RequestHandler) {
app.get(["/", "/api"], (req, res) => {
pp("Express: fetching server info");
app.get(['/', '/api'], (req, res) => {
pp('Express: fetching server info');
res.status(200).json(serverConfig);
});
app.get(["/room/:id", "/api/room/:id"], (req, res) => {
app.get(['/room/:id', '/api/room/:id'], (req, res) => {
if (!req.params.id) {
res.status(400).json({ error: "Bad Request" });
res.status(400).json({ error: 'Bad Request' });
} else {
const requestRoomId = req.params.id ?? "0";
pp(String("Express: fetching room info for " + req.params.id));
const requestRoomId = req.params.id ?? '0';
pp(String('Express: fetching room info for ' + req.params.id));
getRoomByID(requestRoomId)
.then((room: RoomI) => {
if (!room) {
// This is set as a timeout to prevent someone from trying to brute force room ids
setTimeout(
() => res.status(500).json({ error: "Internal Server Error" }),
1000
);
setTimeout(() => res.status(500).json({ error: 'Internal Server Error' }), 1000);
} else {
const {
roomId,
name,
rateLimit,
userMessageLimit,
membershipType,
identities,
contractAddress,
bandadaAddress,
bandadaGroupId,
type
} = room || {};
const id = String(roomId);
const roomResult: RoomI = {
id,
roomId,
name,
rateLimit,
userMessageLimit,
membershipType
};
// Add null check before accessing properties of room object
const { roomId, name, rateLimit, userMessageLimit } = room || {};
res.status(200).json({ roomId, name, rateLimit, userMessageLimit });
if (membershipType === 'BANDADA_GROUP') {
roomResult.bandadaAddress = bandadaAddress;
roomResult.bandadaGroupId = bandadaGroupId;
}
if (membershipType === 'IDENTITY_LIST') {
roomResult.identities = identities;
}
if (type === 'CONTRACT') {
roomResult.contractAddress = contractAddress;
}
res.status(200).json(roomResult);
}
})
.catch((err) => console.error(err));
}
});
app.get(["/rooms/:idc", "/api/rooms/:idc"], async (req, res) => {
pp(
String("Express: fetching rooms by identityCommitment " + req.params.idc)
);
res.status(200).json(await getRoomsByIdentity(req.params.idc));
});
app.get(
['/rooms/:idc', '/api/rooms/:idc'],
asyncHandler(async (req: Request, res: Response) => {
try {
pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc));
res.status(200).json(await getRoomsByIdentity(req.params.idc));
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
})
);
interface JoinData {
code: string;
@@ -71,22 +104,20 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
}
app.post(
["/join", "/api/join"],
['/join', '/api/join'],
asyncHandler(async (req: Request, res: Response) => {
const parsedBody: JoinData = req.body as JoinData;
if (!parsedBody.code || !parsedBody.idc) {
res
.status(400)
.json({ message: "{code: string, idc: string} expected" });
res.status(400).json({ message: '{code: string, idc: string} expected' });
}
const { code, idc } = parsedBody;
console.log("Invite Code:", code);
console.log('Invite Code:', code);
// Check if claim code is valid and not used before
const codeStatus = await findClaimCode(code);
if (!codeStatus || codeStatus.claimed) {
res.status(400).json({ message: "Claim code already used" });
res.status(400).json({ message: 'Claim code already used' });
return;
}
@@ -102,8 +133,8 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
// Return the room ids of the updated rooms
res.status(200).json({
status: "valid",
roomIds: updatedRooms.map((room: RoomI) => room.roomId),
status: 'valid',
roomIds: updatedRooms.map((room: RoomI) => room.roomId)
});
})
);
@@ -115,34 +146,46 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
numClaimCodes?: number;
approxNumMockUsers?: number;
roomType?: string;
bandadaAddress?: string;
bandadaAPIKey?: string;
bandadaGroupId?: string;
membershipType?: string;
}
/* ~~~~ ADMIN ENDPOINTS ~~~~ */
app.post(["/room/add", "/api/room/add"], adminAuth, (req, res) => {
app.post(['/room/add', '/api/room/add'], adminAuth, (req, res) => {
console.log(req.body);
const roomMetadata = req.body as addRoomData;
console.log(roomMetadata);
const roomName = roomMetadata.roomName;
const rateLimit = roomMetadata.rateLimit;
const userMessageLimit = roomMetadata.userMessageLimit;
const numClaimCodes = roomMetadata.numClaimCodes || 0;
const numClaimCodes = roomMetadata.numClaimCodes ?? 0;
const approxNumMockUsers = roomMetadata.approxNumMockUsers;
const type = roomMetadata.roomType;
const type = roomMetadata.roomType as unknown as string;
const bandadaAddress = roomMetadata.bandadaAddress;
const bandadaGroupId = roomMetadata.bandadaGroupId;
const bandadaAPIKey = roomMetadata.bandadaAPIKey;
const membershipType = roomMetadata.membershipType;
createRoom(
roomName,
rateLimit,
userMessageLimit,
numClaimCodes,
approxNumMockUsers,
type
type,
bandadaAddress,
bandadaGroupId,
bandadaAPIKey,
membershipType
)
.then((result) => {
console.log(result);
if (result) {
// TODO should return roomID and claim codes if they are generated
res.status(200).json({ message: "Room created successfully" });
res.status(200).json({ message: 'Room created successfully' });
} else {
res.status(500).json({ error: "Internal Server Error" });
res.status(500).json({ error: 'Internal Server Error' });
}
})
.catch((err) => {
@@ -151,26 +194,26 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
});
});
app.get("/api/room/:id/messages", (req, res) => {
app.get('/api/room/:id/messages', (req, res) => {
const { id } = req.params;
prisma.messages
.findMany({
where: {
roomId: id,
},
roomId: id
}
})
.then((messages) => {
pp("Express: fetching messages for room " + id);
pp('Express: fetching messages for room ' + id);
res.status(200).json(messages);
})
.catch((error: Error) => {
pp(error, "error");
res.status(500).send("Error fetching messages");
pp(error, 'error');
res.status(500).send('Error fetching messages');
});
});
app.get(["/logclaimcodes", "/api/logclaimcodes"], adminAuth, (req, res) => {
pp("Express: fetching claim codes");
app.get(['/logclaimcodes', '/api/logclaimcodes'], adminAuth, (req, res) => {
pp('Express: fetching claim codes');
prisma.claimCodes
.findMany()
.then((claimCodes) => {
@@ -178,12 +221,12 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: "Internal Server Error" });
res.status(500).json({ error: 'Internal Server Error' });
});
});
app.get(["/rooms", "/api/rooms"], adminAuth, (req, res) => {
pp(String("Express: fetching all rooms"));
app.get(['/rooms', '/api/rooms'], adminAuth, (req, res) => {
pp(String('Express: fetching all rooms'));
prisma.rooms
.findMany()
.then((rooms) => {
@@ -191,20 +234,40 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: "Internal Server Error" });
res.status(500).json({ error: 'Internal Server Error' });
});
});
app.post("/admin/message", adminAuth, async (req, res) => {
const { message, roomId } = req.body;
pp(String("Express: sending system message: " + message));
try {
const successMessage = roomId ? "Message sent to room " + roomId : "Messages sent to all rooms";
await createSystemMessages(message, roomId);
res.status(200).json({ message: successMessage });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal Server Error" });
}
});
app.post(
'/admin/message',
adminAuth,
asyncHandler(async (req: Request, res: Response) => {
const { message } = req.body as { message: string };
pp(String('Express: sending system message: ' + message));
try {
await createSystemMessages(message);
res.status(200).json({ message: 'Messages sent to all rooms' });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal Server Error' });
}
})
);
app.post(
'/admin/message/:roomId',
adminAuth,
asyncHandler(async (req: Request, res: Response) => {
const { roomId } = req.params;
const { message } = req.body as { message: string };
pp(String('Express: sending system message: ' + message + ' to ' + roomId));
try {
await createSystemMessages(message, roomId);
res.status(200).json({ message: 'Message sent to room ' + roomId });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal Server Error' });
}
})
);
}

View File

@@ -20,7 +20,7 @@ export function genMockUsers(numMockUsers: number): string[] {
for (let i = 0; i < newNumMockUsers; i++) {
mockUsers.push(
genId(
serverConfig.id as string,
serverConfig.id as bigint,
// Generates a random string of length 10
Math.random()
.toString(36)

View File

@@ -30,8 +30,8 @@ const room = {
type: 'PUBLIC'
};
const roomByIdTest = genId(serverConfig.id as string, room.roomName).toString();
let testCode = '';
const roomByIdTest = genId(serverConfig.id as bigint, room.roomName).toString();
let testCode = "";
const testIdentity = randBigint();
const username = 'admin';
const password = process.env.PASSWORD;