Merge branch 'main' into readme

This commit is contained in:
AtHeartEngineer
2023-08-22 14:15:38 -04:00
committed by GitHub
16 changed files with 612 additions and 240 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ dist/
*.log *.log
.env .env
thunder-tests/ thunder-tests/
coverage/

View File

@@ -25,6 +25,7 @@
- [Run the server](#run-the-server) - [Run the server](#run-the-server)
- [Running tests](#running-tests) - [Running tests](#running-tests)
- [🔩 Usage](#-usage) - [🔩 Usage](#-usage)
- [Style Guide](#style-guide)
<br> <br>
@@ -133,3 +134,8 @@ npm run test
</tr> </tr>
</table> </table>
#### Style Guide
* Single Quotes

89
package-lock.json generated
View File

@@ -13,11 +13,12 @@
"@prisma/client": "^5.0.0", "@prisma/client": "^5.0.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"discreetly-claimcodes": "^1.1.3", "discreetly-claimcodes": "^1.1.5",
"discreetly-interfaces": "^0.1.23", "discreetly-interfaces": "^0.1.34",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"ffjavascript": "^0.2.60",
"helmet": "^7.0.0", "helmet": "^7.0.0",
"mongodb": "^5.7.0", "mongodb": "^5.7.0",
"poseidon-lite": "^0.2.0", "poseidon-lite": "^0.2.0",
@@ -3130,11 +3131,6 @@
"web-worker": "^1.2.0" "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": { "node_modules/circom_runtime/node_modules/wasmcurves": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz",
@@ -3465,14 +3461,14 @@
} }
}, },
"node_modules/discreetly-claimcodes": { "node_modules/discreetly-claimcodes": {
"version": "1.1.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.3.tgz", "resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.5.tgz",
"integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg==" "integrity": "sha512-pQueoGtBJk/FrTfGzepjqYfTLaymS+4t11byI4OcfjWQOagRsD7dtavGcowTVQ7Ib/vjKna5T+71WcgWZaAWuA=="
}, },
"node_modules/discreetly-interfaces": { "node_modules/discreetly-interfaces": {
"version": "0.1.23", "version": "0.1.34",
"resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.23.tgz", "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.34.tgz",
"integrity": "sha512-C1mqzLZY52aW83XHUBr3lxe8F7HFagx4V+MzigxPf5GTjDGhelIbnmihhV3RUtWb1uddo1Gm1CImD+meygf1bg==", "integrity": "sha512-7purPOWOowVH44ebdweBdZ4z2RsBQy5/H7xi6PdsHkaw1xwg8u3Ev2US5EdavP1igZ+SzebJdK8jT0ZTjzX8Kg==",
"dependencies": { "dependencies": {
"poseidon-lite": "^0.2.0", "poseidon-lite": "^0.2.0",
"rlnjs": "^3.1.4" "rlnjs": "^3.1.4"
@@ -4164,13 +4160,12 @@
} }
}, },
"node_modules/ffjavascript": { "node_modules/ffjavascript": {
"version": "0.2.55", "version": "0.2.60",
"resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.55.tgz", "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz",
"integrity": "sha512-8X0FCIPOWiK6DTWh3pnE3O6D6nIQsirStAXpWMzRDnoDX7SEnDX4I28aVhwjL7L35XS1vy2AU7zc0UCGYxdLjw==", "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==",
"dependencies": { "dependencies": {
"big-integer": "^1.6.48", "wasmbuilder": "0.0.16",
"wasmbuilder": "^0.0.12", "wasmcurves": "0.2.2",
"wasmcurves": "0.1.0",
"web-worker": "^1.2.0" "web-worker": "^1.2.0"
} }
}, },
@@ -6578,11 +6573,6 @@
"web-worker": "^1.2.0" "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": { "node_modules/r1csfile/node_modules/wasmcurves": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz",
@@ -6762,11 +6752,39 @@
"snarkjs": "^0.7.0" "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": { "node_modules/rlnjs/node_modules/poseidon-lite": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/poseidon-lite/-/poseidon-lite-0.0.2.tgz", "resolved": "https://registry.npmjs.org/poseidon-lite/-/poseidon-lite-0.0.2.tgz",
"integrity": "sha512-bGdDPTOQkJbBjbtSEWc3gY+YhqlGTxGlZ8041F8TGGg5QyGGp1Cfs4b8AEnFFjHbkPg6WdWXUgEjU1GKOKWAPw==" "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": { "node_modules/rollup": {
"version": "3.26.2", "version": "3.26.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz",
@@ -7105,11 +7123,6 @@
"web-worker": "^1.2.0" "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": { "node_modules/snarkjs/node_modules/wasmcurves": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz",
@@ -7857,20 +7870,16 @@
} }
}, },
"node_modules/wasmbuilder": { "node_modules/wasmbuilder": {
"version": "0.0.12", "version": "0.0.16",
"resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.12.tgz", "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz",
"integrity": "sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w==", "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA=="
"dependencies": {
"big-integer": "^1.6.48"
}
}, },
"node_modules/wasmcurves": { "node_modules/wasmcurves": {
"version": "0.1.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.1.0.tgz", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz",
"integrity": "sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q==", "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==",
"dependencies": { "dependencies": {
"big-integer": "^1.6.42", "wasmbuilder": "0.0.16"
"blakejs": "^1.1.0"
} }
}, },
"node_modules/web-worker": { "node_modules/web-worker": {

View File

@@ -12,7 +12,7 @@
"watch": "rollup --config rollup.config.mjs --watch", "watch": "rollup --config rollup.config.mjs --watch",
"serve": "nodemon -q dist/server.jcs", "serve": "nodemon -q dist/server.jcs",
"dev": "concurrently \"npm run watch\" \"npm run serve\"", "dev": "concurrently \"npm run watch\" \"npm run serve\"",
"test": "jest --detectOpenHandles", "test": "jest --detectOpenHandles --coverage",
"lint": "eslint ." "lint": "eslint ."
}, },
"engines": { "engines": {
@@ -27,11 +27,12 @@
"@prisma/client": "^5.0.0", "@prisma/client": "^5.0.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"discreetly-claimcodes": "^1.1.3", "discreetly-claimcodes": "^1.1.5",
"discreetly-interfaces": "^0.1.23", "discreetly-interfaces": "^0.1.34",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"ffjavascript": "^0.2.60",
"helmet": "^7.0.0", "helmet": "^7.0.0",
"mongodb": "^5.7.0", "mongodb": "^5.7.0",
"poseidon-lite": "^0.2.0", "poseidon-lite": "^0.2.0",

View File

@@ -10,27 +10,24 @@ datasource db {
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
enum RoomMembershipType {
IDENTITY_LIST
RLN_CONTRACT
BANDADA
}
model Rooms { model Rooms {
id String @id @default(auto()) @map("_id") @db.ObjectId id String @id @default(auto()) @map("_id") @db.ObjectId
roomId String @unique roomId String @unique
name String name String
rateLimit Int @default(1000) // epoch length in ms rateLimit Int @default(1000) // epoch length in ms
banRateLimit Int @default(1000000) // starting number of epochs banned for banRateLimit Int @default(1000000) // starting number of epochs banned for
userMessageLimit Int @default(1) // per epoch userMessageLimit Int @default(1) // per epoch
membershipType RoomMembershipType @default(IDENTITY_LIST) membershipType String @default("IDENTITY_LIST")
identities String[] @default([]) identities String[] @default([])
contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS" contractAddress String? // RLN_CONTRACT as "chainID:0xADDRESS"
bandadaAddress String? // BANDADA as "url:groupID" bandadaAddress String? // BANDADA as "url:groupID"
bandadaGroupId String? // Bandada Group ID
bandadaAPIKey String? // Bandada API Key
epochs Epoch[] epochs Epoch[]
messages Messages[] messages Messages[]
claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id]) claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id])
claimCodeIds String[] @default([]) @db.ObjectId claimCodeIds String[] @default([]) @db.ObjectId
type String @default("PUBLIC")
} }
model ClaimCodes { model ClaimCodes {

View File

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

View File

@@ -0,0 +1,36 @@
/* 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';
import { poseidon1 } from 'poseidon-lite';
/*
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);
}
export function getIdentityCommitmentFromSecret(secret: bigint): bigint {
return poseidon1([secret]);
}

View File

@@ -1,6 +1,6 @@
import type { MessageI, RoomI } from 'discreetly-interfaces'; import type { MessageI, RoomI } from 'discreetly-interfaces';
import { str2BigInt } from 'discreetly-interfaces'; import { str2BigInt } from 'discreetly-interfaces';
import { RLNVerifier } from 'rlnjs'; import { RLNFullProof, RLNVerifier } from 'rlnjs';
import vkey from './verification_key'; import vkey from './verification_key';
import { Group } from '@semaphore-protocol/group'; 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 currentEpoch = Math.floor(timestamp / rateLimit);
const rlnIdentifier = BigInt(msg.roomId); const rlnIdentifier = BigInt(msg.roomId);
const msgHash = str2BigInt(msg.message); const msgHash = str2BigInt(msg.message);
let proof: RLNFullProof | undefined;
// Check that the epoch falls within the range for the room // Check that the epoch falls within the range for the room
const epoch = BigInt(msg.epoch); const epoch = BigInt(msg.epoch);
if (epoch < currentEpoch - epochErrorRange || epoch > currentEpoch + epochErrorRange) { if (epoch < currentEpoch - epochErrorRange || epoch > currentEpoch + epochErrorRange) {
@@ -24,29 +25,39 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro
console.warn('Epoch out of range:', epoch, 'currentEpoch:', currentEpoch); console.warn('Epoch out of range:', epoch, 'currentEpoch:', currentEpoch);
return false; return false;
} }
if (typeof msg.proof === 'string') {
// Check that the internal nullifier doesn't have collisions proof = JSON.parse(msg.proof) as RLNFullProof;
// TODO! INTERNAL NULLIFIER (RLNjs cache) } 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 // Check that the message hash is correct
if (msgHash !== msg.proof.snarkProof.publicSignals.x) {
if (msgHash !== proof.snarkProof.publicSignals.x) {
console.warn( console.warn(
'Message hash incorrect:', 'Message hash incorrect:',
msgHash, msgHash,
'Hash in proof:', 'Hash in proof:',
msg.proof.snarkProof.publicSignals.x proof.snarkProof.publicSignals.x
); );
return false; return false;
} }
// Check that the merkle root is correct // Check that the merkle root is correct
const group = new Group(room.id, 20, room.membership?.identityCommitments); if (room.identities && Array.isArray(room.identities)) {
if (group.root !== msg.proof.snarkProof.publicSignals.root) { const group = new Group(room.id, 20, room.identities as bigint[] | undefined);
return false; if (group.root !== proof.snarkProof.publicSignals.root) {
return false;
}
} }
// Check that the proof is correct // Check that the proof is correct
return v.verifyProof(rlnIdentifier, msg.proof); return v.verifyProof(rlnIdentifier, proof);
} }
export default verifyProof; export default verifyProof;

View File

@@ -2,7 +2,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { PrismaClient } from '@prisma/client'; 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 { serverConfig } from '../config/serverConfig';
import { genMockUsers, genClaimCodeArray, pp } from '../utils'; import { genMockUsers, genClaimCodeArray, pp } from '../utils';
@@ -17,8 +18,8 @@ interface RoomsFromClaimCode {
roomIds: string[]; roomIds: string[];
} }
export function getRoomByID(id: string): Promise<RoomI | null> { export async function getRoomByID(id: string): Promise<RoomI | null> {
return prisma.rooms const room = await prisma.rooms
.findUnique({ .findUnique({
where: { where: {
roomId: id roomId: id
@@ -29,7 +30,12 @@ export function getRoomByID(id: string): Promise<RoomI | null> {
name: true, name: true,
identities: true, identities: true,
rateLimit: true, rateLimit: true,
userMessageLimit: true userMessageLimit: true,
membershipType: true,
contractAddress: true,
bandadaAddress: true,
bandadaGroupId: true,
type: true
} }
}) })
.then((room) => { .then((room) => {
@@ -39,6 +45,12 @@ export function getRoomByID(id: string): Promise<RoomI | null> {
console.error(err); console.error(err);
throw err; // Add this line to throw the error 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<string[]> { export async function getRoomsByIdentity(identity: string): Promise<string[]> {
@@ -81,30 +93,145 @@ export function updateClaimCode(code: string): Promise<RoomsFromClaimCode> {
}); });
} }
export function updateRoomIdentities(idc: string, roomIds: string[]): Promise<any> { function sanitizeIDC(idc: string): string {
return prisma.rooms.findMany({ try {
where: { id: { in: roomIds } }, const tempBigInt = BigInt(idc);
}) const tempString = tempBigInt.toString();
.then((rooms) => { if (idc === tempString) {
const roomsToUpdate = rooms return idc;
.filter(room => !room.identities.includes(idc)) } else {
.map(room => room.id); throw new Error('Invalid IDC provided.');
if (roomsToUpdate) {
return prisma.rooms.updateMany({
where: { id: { in: roomsToUpdate } },
data: { identities: { push: idc } }
});
} }
}).catch(err => { } catch (error) {
pp(err, 'error') throw new Error('Invalid IDC provided.');
}) }
} }
export function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> { export async function updateRoomIdentities(idc: string, roomIds: string[]): Promise<void> {
return prisma.rooms.findMany({ const identityCommitment = sanitizeIDC(idc);
return prisma.rooms
.findMany({
where: { id: { in: roomIds } }
})
.then((rooms) => {
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 } } where: { id: { in: roomIds } }
}); });
return new Promise((resolve, reject) => {
if (rooms) {
resolve(rooms as RoomI[]);
}
reject('No rooms found');
});
}
// 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)
.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.reject(err);
});
}
export function removeIdentityFromRoom(idc: string, room: RoomI): Promise<void | RoomI> {
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);
});
} }
/** /**
@@ -116,26 +243,36 @@ export function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
* @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room. * @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room.
*/ */
export async function createRoom( export async function createRoom(
name: string, roomName: string,
rateLimit = 1000, rateLimit = 1000,
userMessageLimit = 1, userMessageLimit = 1,
numClaimCodes = 0, numClaimCodes = 0,
approxNumMockUsers = 20 approxNumMockUsers = 20,
type: string,
bandadaAddress?: string,
bandadaGroupId?: string,
bandadaAPIKey?: string,
membershipType?: string
): Promise<boolean> { ): Promise<boolean> {
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes); const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
console.log(claimCodes); console.log(claimCodes);
const mockUsers: string[] = genMockUsers(approxNumMockUsers); const mockUsers: string[] = genMockUsers(approxNumMockUsers);
const roomData = { const roomData = {
where: { where: {
roomId: genId(serverConfig.id, name).toString() roomId: genId(serverConfig.id as bigint, roomName).toString()
}, },
update: {}, update: {},
create: { create: {
roomId: genId(serverConfig.id, name).toString(), roomId: genId(serverConfig.id as bigint, roomName).toString(),
name: name, name: roomName,
rateLimit: rateLimit, rateLimit: rateLimit,
userMessageLimit: userMessageLimit, userMessageLimit: userMessageLimit,
identities: mockUsers, identities: mockUsers,
type,
bandadaAddress,
bandadaGroupId,
bandadaAPIKey,
membershipType,
claimCodes: { claimCodes: {
create: claimCodes create: claimCodes
} }

View File

@@ -1,9 +1,72 @@
import { getRoomByID } from './db'; import { getRoomByID, removeIdentityFromRoom } from './db';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { MessageI } from 'discreetly-interfaces'; import { MessageI } from 'discreetly-interfaces';
import { shamirRecovery, getIdentityCommitmentFromSecret } from '../crypto/shamirRecovery';
import { RLNFullProof } from 'rlnjs';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
interface CollisionCheckResult {
collision: boolean;
secret?: bigint;
oldMessage?: MessageI;
}
async function checkRLNCollision(roomId: string, message: MessageI): Promise<CollisionCheckResult> {
return new Promise((res) => {
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;
}
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 unknown as MessageI
} as CollisionCheckResult);
}
})
.catch((err) => console.error(err));
});
}
function addMessageToRoom(roomId: string, message: MessageI): Promise<unknown> { function addMessageToRoom(roomId: string, message: MessageI): Promise<unknown> {
if (!message.epoch) { if (!message.epoch) {
throw new Error('Epoch not provided'); throw new Error('Epoch not provided');
@@ -29,29 +92,52 @@ function addMessageToRoom(roomId: string, message: MessageI): Promise<unknown> {
} }
}); });
} }
export interface createMessageResult {
success: boolean;
message?: MessageI;
idc?: string | bigint;
}
export function createMessage(roomId: string, message: MessageI): boolean { export function createMessage(roomId: string, message: MessageI): createMessageResult {
getRoomByID(roomId) getRoomByID(roomId)
.then((room) => { .then((room) => {
if (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,
addMessageToRoom(roomId, message) // if there is, we need to return an error and
.then((roomToUpdate) => { // reconstruct the secret from both messages, and ban the user
console.log(roomToUpdate); checkRLNCollision(roomId, message)
return true; .then((collisionResult) => {
if (!collisionResult.collision) {
addMessageToRoom(roomId, message)
.then((roomToUpdate) => {
console.log(roomToUpdate);
return { success: true };
})
.catch((error) => {
console.error(`Couldn't add message room ${error}`);
return false;
});
} else {
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) => { .catch((error) => {
console.error(`Error updating room: ${error}`); console.error(`Error getting room: ${error}`);
return false; return { success: false };
}); });
} else {
console.log('Room not found');
return false;
} }
}) })
.catch((error) => { .catch((error) => {
console.error(`Error getting room: ${error}`); console.error(`Error getting room: ${error}`);
return false; return { success: false };
}); });
return false; return { success: false };
} }

View File

@@ -9,7 +9,8 @@ import {
updateClaimCode, updateClaimCode,
updateRoomIdentities, updateRoomIdentities,
findUpdatedRooms, findUpdatedRooms,
createRoom createRoom,
createSystemMessages
} from '../data/db'; } from '../data/db';
import { RoomI } from 'discreetly-interfaces'; import { RoomI } from 'discreetly-interfaces';
@@ -45,19 +46,57 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
// This is set as a timeout to prevent someone from trying to brute force room ids // 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 { } 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 // Add null check before accessing properties of room object
const { roomId, name, rateLimit, userMessageLimit } = room || {}; if (membershipType === 'BANDADA_GROUP') {
res.status(200).json({ roomId, name, rateLimit, userMessageLimit }); 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)); .catch((err) => console.error(err));
} }
}); });
app.get(['/rooms/:idc', '/api/rooms/:idc'], async (req, res) => { app.get(
pp(String('Express: fetching rooms by identityCommitment ' + req.params.idc)); ['/rooms/:idc', '/api/rooms/:idc'],
res.status(200).json(await getRoomsByIdentity(req.params.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 { interface JoinData {
code: string; code: string;
@@ -105,18 +144,43 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
rateLimit: number; rateLimit: number;
userMessageLimit: number; userMessageLimit: number;
numClaimCodes?: number; numClaimCodes?: number;
approxNumMockUsers?: number;
roomType?: string;
bandadaAddress?: string;
bandadaAPIKey?: string;
bandadaGroupId?: string;
membershipType?: string;
} }
/* ~~~~ ADMIN ENDPOINTS ~~~~ */ /* ~~~~ 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; const roomMetadata = req.body as addRoomData;
console.log(roomMetadata); console.log(roomMetadata);
const roomName = roomMetadata.roomName; const roomName = roomMetadata.roomName;
const rateLimit = roomMetadata.rateLimit; const rateLimit = roomMetadata.rateLimit;
const userMessageLimit = roomMetadata.userMessageLimit; const userMessageLimit = roomMetadata.userMessageLimit;
const numClaimCodes = roomMetadata.numClaimCodes || 0; const numClaimCodes = roomMetadata.numClaimCodes ?? 0;
createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes) 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(
roomName,
rateLimit,
userMessageLimit,
numClaimCodes,
approxNumMockUsers,
type,
bandadaAddress,
bandadaGroupId,
bandadaAPIKey,
membershipType
)
.then((result) => { .then((result) => {
console.log(result);
if (result) { if (result) {
// TODO should return roomID and claim codes if they are generated // 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' });
@@ -173,4 +237,37 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
res.status(500).json({ error: 'Internal Server Error' }); 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

@@ -70,7 +70,11 @@ if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
initEndpoints(app, adminAuth); initEndpoints(app, adminAuth);
_app = initAppListeners(PORT); _app = initAppListeners(PORT);
listEndpoints(app); listEndpoints(app);
io = new SocketIOServer(_app, {}); io = new SocketIOServer(_app, {
cors: {
origin: '*'
}
});
initWebsockets(io); initWebsockets(io);
mock(io); mock(io);
} else { } else {
@@ -78,7 +82,11 @@ if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
serverConfigStartup.port = PORT; serverConfigStartup.port = PORT;
initEndpoints(app, adminAuth); initEndpoints(app, adminAuth);
_app = initAppListeners(PORT); _app = initAppListeners(PORT);
io = new SocketIOServer(_app, {}); io = new SocketIOServer(_app, {
cors: {
origin: '*'
}
});
initWebsockets(io); initWebsockets(io);
} }

View File

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

View File

@@ -3,7 +3,7 @@ import { Socket, Server as SocketIOServer } from 'socket.io';
import verifyProof from '../crypto/verifier'; import verifyProof from '../crypto/verifier';
import { getRoomByID } from '../data/db'; import { getRoomByID } from '../data/db';
import { pp } from '../utils'; import { pp } from '../utils';
import { createMessage } from '../data/messages'; import { createMessage, createMessageResult } from '../data/messages';
const userCount: Record<string, number> = {}; const userCount: Record<string, number> = {};
@@ -23,8 +23,9 @@ export function websocketSetup(io: SocketIOServer) {
verifyProof(msg, room) verifyProof(msg, room)
.then((v) => { .then((v) => {
validProof = v; validProof = v;
const validMessage: boolean = createMessage(String(msg.roomId), msg); // TODO import createMessageResult, and broadcast the idc and message ID that were removed to those room users
if (!validProof || !validMessage) { const validMessage: createMessageResult = createMessage(String(msg.roomId), msg);
if (!validProof || !validMessage.success) {
pp('INVALID MESSAGE', 'warn'); pp('INVALID MESSAGE', 'warn');
return; return;
} }
@@ -44,11 +45,14 @@ export function websocketSetup(io: SocketIOServer) {
socket.on('joinRoom', (roomID: bigint) => { socket.on('joinRoom', (roomID: bigint) => {
const id = roomID.toString(); const id = roomID.toString();
userCount[id] = userCount[id] ? userCount[id] + 1 : 1; 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) => { socket.on('leaveRoom', (roomID: bigint) => {
const id = roomID.toString(); const id = roomID.toString();
userCount[id] = userCount[id] ? userCount[id] - 1 : 0; userCount[id] = userCount[id] ? userCount[id] - 1 : 0;
io.to(id).emit('Members', userCount[id] ? userCount[id] : 0);
}); });
}); });
} }

View File

@@ -1,76 +1,73 @@
const request = require("supertest"); const request = require('supertest');
import _app from "../src/server"; import _app from '../src/server';
import { genId } from "discreetly-interfaces"; import { genId } from 'discreetly-interfaces';
import { serverConfig } from "../src/config/serverConfig"; import { serverConfig } from '../src/config/serverConfig';
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from '@prisma/client';
import { beforeAll, beforeEach, describe, expect, test } from "@jest/globals"; import { beforeAll, beforeEach, describe, expect, test } from '@jest/globals';
import { pp } from "../src/utils"; import { pp } from '../src/utils';
import { randBigint, randomRoomName } from "./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"; process.env.PORT = '3001';
beforeAll(async () => { beforeAll(async () => {
const prismaTest = new PrismaClient(); const prismaTest = new PrismaClient();
await prismaTest.messages.deleteMany();
await prismaTest.rooms.deleteMany(); await prismaTest.rooms.deleteMany();
await prismaTest.claimCodes.deleteMany(); await prismaTest.claimCodes.deleteMany();
}); });
afterAll(async () => {
_app.close();
});
const room = { const room = {
roomName: randomRoomName(), roomName: randomRoomName(),
rateLimit: 1000, rateLimit: 1000,
userMessageLimit: 1, userMessageLimit: 1,
numClaimCodes: 5, numClaimCodes: 5,
approxNumMockUsers: 10,
type: 'PUBLIC'
}; };
const roomByIdTest = genId(serverConfig.id, room.roomName).toString(); const roomByIdTest = genId(serverConfig.id as bigint, room.roomName).toString();
let testCode = ""; let testCode = "";
const testIdentity = randBigint(); const testIdentity = randBigint();
console.log(testIdentity) const username = 'admin';
const password = process.env.PASSWORD;
describe('Endpoints should all work hopefully', () => {
describe("Endpoints should all work hopefully", () => { test('It should respond with server info', async () => {
test("It should respond with server info", async () => {
await request(_app) await request(_app)
.get("/") .get('/')
.then((res) => { .then((res) => {
expect(res.status).toBe(200); expect(res.status).toBe(200);
expect(res.header["content-type"]).toBe( expect(res.header['content-type']).toBe('application/json; charset=utf-8');
"application/json; charset=utf-8"
);
expect(res.body.id).toBe(serverConfig.id); 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 () => { test('It should add a new room to the database', async () => {
const username = "admin"; const username = 'admin';
const password = process.env.PASSWORD; const password = process.env.PASSWORD;
const base64Credentials = Buffer.from(`${username}:${password}`).toString( const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
"base64"
);
await request(_app) await request(_app)
.post("/room/add") .post('/room/add')
.set("Authorization", `Basic ${base64Credentials}`) .set('Authorization', `Basic ${base64Credentials}`)
.send(room) .send(room)
.then((res) => { .then((res) => {
try { try {
expect(res.body).toEqual({ message: "Room created successfully" }); expect(res.body).toEqual({ message: 'Room created successfully' });
} catch (error) { } 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) await request(_app)
.get(`/api/room/${roomByIdTest}`) .get(`/api/room/${roomByIdTest}`)
.then((res) => { .then((res) => {
@@ -78,92 +75,40 @@ describe("Endpoints should all work hopefully", () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.body.name).toEqual(room.roomName); expect(res.body.name).toEqual(room.roomName);
} catch (error) { } 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 password = process.env.PASSWORD;
const base64Credentials = Buffer.from(`${username}:${password}`).toString( const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
"base64"
);
await request(_app) await request(_app)
.get("/api/rooms") .get('/api/rooms')
.set("Authorization", `Basic ${base64Credentials}`) .set('Authorization', `Basic ${base64Credentials}`)
.then((res) => { .then((res) => {
try { try {
expect(res.status).toEqual(200); 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); expect(res.body[0].name).toEqual(room.roomName);
} catch (error) { } 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", 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 () => { 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 password = process.env.PASSWORD;
const base64Credentials = Buffer.from(`${username}:${password}`).toString("base64"); const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
await request(_app) await request(_app)
.get("/logclaimcodes") .get('/logclaimcodes')
.set("Authorization", `Basic ${base64Credentials}`) .set('Authorization', `Basic ${base64Credentials}`)
.then(async (res) => { .then(async (res) => {
try { try {
testCode = res.body[0].claimcode; testCode = res.body[0].claimcode;
@@ -171,18 +116,17 @@ describe("Endpoints should all work hopefully", () => {
expect(res.status).toEqual(401); expect(res.status).toEqual(401);
expect(res.body.length).toBeGreaterThan(0); expect(res.body.length).toBeGreaterThan(0);
const joinTest = { const joinTest = {
code: testCode, code: testCode,
idc: testIdentity idc: testIdentity
}; };
await request(_app) await request(_app)
.post("/join") .post('/join')
.send(joinTest) .send(joinTest)
.then((res) => { .then((res) => {
expect(res.statusCode).toEqual(200); expect(res.statusCode).toEqual(200);
expect(res.body.status).toEqual("valid"); expect(res.body.status).toEqual('valid');
}); });
} catch (error) { } catch (error) {
console.error('Error in test: ', error); console.error('Error in test: ', error);
@@ -194,17 +138,50 @@ describe("Endpoints should all work hopefully", () => {
}); });
console.log(testIdentity); 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) await request(_app)
.get(`/api/rooms/${testIdentity}`) .get(`/api/rooms/${testIdentity}`)
.then((res) => { .then((res) => {
try { try {
console.log(res.body); console.log(res.body);
expect(res.statusCode).toEqual(200); expect(res.statusCode).toEqual(200);
} catch (error) { } 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 () => {
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'));
}); });
}); });

View File

@@ -1,7 +1,9 @@
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
export function randBigint(): bigint { 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 { export function randomRoomName(min = 5, max = 20): string {