Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require project api key #2631

Merged
merged 5 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/functions_emulated-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ jobs:
npm run test:ci -- --forceExit --findRelatedTests ./test/cron/proposal.cron.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/db.roll.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/dbroll/nft.auction.roll.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/stake/delete.stake.reward.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/stake/stake.reward.cron.spec.ts
npm run test:ci -- --forceExit --findRelatedTests ./test/dbroll/set.project.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/stake/delete.stake.reward.spec.ts
" --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data
- name: Archive firestore data
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -298,6 +298,7 @@ jobs:
export GOOGLE_APPLICATION_CREDENTIALS="./test-service-account-key.json"
npm run milestone-sync &
firebase emulators:exec "
npm run test:ci -- --forceExit --findRelatedTests ./test/stake/stake.reward.cron.spec.ts &&
npm run test:ci -- --forceExit --findRelatedTests ./test/storage/resize.img.spec.ts
" --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data
- name: Archive firestore data
Expand Down
39 changes: 37 additions & 2 deletions .github/workflows/functions_online-emulated-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ jobs:
npm run test-online:ci -- --forceExit --findRelatedTests ./test/cron/proposal.cron.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/db.roll.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/dbroll/nft.auction.roll.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/dbroll/set.project.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/stake/delete.stake.reward.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/stake/stake.reward.cron.spec.ts &&
npm run test-online:ci -- --forceExit --findRelatedTests ./test/storage/resize.img.spec.ts
npm run test-online:ci -- --forceExit --findRelatedTests ./test/stake/stake.reward.cron.spec.ts
" --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data
- name: Archive firestore data
uses: actions/upload-artifact@v3
Expand All @@ -274,3 +274,38 @@ jobs:
name: firestore-data-test-online-chunk_5
path: ./packages/functions/firestore-data/
retention-days: 1
chunk_6:
needs: npm-install
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
- uses: actions/cache@v3
with:
path: |
node_modules
packages/functions/node_modules
packages/interfaces/node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('**/package.json') }}
- name: Init
run: |
npm run build:functions
npm install -g firebase-tools
- name: Test
working-directory: packages/functions
run: |
export GOOGLE_APPLICATION_CREDENTIALS="./test-service-account-key.json"
npm run milestone-sync &
firebase emulators:exec "
npm run test-online:ci -- --forceExit --findRelatedTests ./test/storage/resize.img.spec.ts
" --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data
- name: Archive firestore data
uses: actions/upload-artifact@v3
if: ${{ failure() }}
with:
name: firestore-data-test-online-chunk_6
path: ./packages/functions/firestore-data/
retention-days: 1
4 changes: 3 additions & 1 deletion packages/api/src/getMany.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const getManySchema = Joi.object({
startAfter: CommonJoi.uid(false),
});

export const getMany = async (url: string) => {
export const getMany = async (project: string, url: string) => {
const body = getQueryParams<GetManyRequest>(url, getManySchema);

const baseCollectionPath =
Expand All @@ -52,6 +52,8 @@ export const getMany = async (url: string) => {
.collection(baseCollectionPath as COL)
.limit(getQueryLimit(body.collection));

query = query.where(`projects.${project}`, '==', true);

if (body.fieldName && body.fieldValue != null) {
try {
const filters = getFilters(body.fieldName, body.fieldValue);
Expand Down
4 changes: 3 additions & 1 deletion packages/api/src/getManyAdvanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ const getManyAdvancedSchema = Joi.object({
startAfter: CommonJoi.uid(false),
});

export const getManyAdvanced = async (url: string) => {
export const getManyAdvanced = async (project: string, url: string) => {
const body = getQueryParams<GetManyAdvancedRequest>(url, getManyAdvancedSchema);

const { collection, subCollection, uid } = body;
let query = getBaseQuery(collection, uid, subCollection).limit(getQueryLimit(body.collection));

query = query.where(`projects.${project}`, '==', true);

const { filters, operators } = getFilters(body.fieldName, body.fieldValue, body.operator);
try {
for (const [key, values] of Object.entries(filters)) {
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/getUpdatedAfter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const getUpdatedAfterSchema = Joi.object({
startAfter: CommonJoi.uid(false),
});

export const getUpdatedAfter = async (url: string) => {
export const getUpdatedAfter = async (project: string, url: string) => {
const body = getQueryParams<GetUpdatedAfterRequest>(url, getUpdatedAfterSchema);

const isSubCollectionQuery = body.subCollection && body.uid;
Expand All @@ -38,6 +38,7 @@ export const getUpdatedAfter = async (url: string) => {
.collection(baseCollectionPath as COL)
.where('updatedOn', '>=', updatedAfter.toDate())
.orderBy('updatedOn')
.where(`projects.${project}`, '==', true)
.limit(getQueryLimit(body.collection));

if (body.collection === PublicCollections.NFT) {
Expand Down
37 changes: 29 additions & 8 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ApiRoutes } from '@build-5/interfaces';
import cors from 'cors';
import express from 'express';
import http from 'http';
import jwt from 'jsonwebtoken';
import { get } from 'lodash';
import { Observable, first } from 'rxjs';
import ws from 'ws';
import { getAddresses } from './getAddresses';
Expand All @@ -23,12 +26,12 @@ const app = express();

app.use(cors());

app.get('/*', (req, res) => onConnection(req.url, res));
app.get('/*', (req, res) => onConnection(req, res));

const wsServer = new ws.Server({ noServer: true });

wsServer.on('connection', async (socket, request) => {
onConnection(request.url || '', socket);
onConnection(request, socket);
});

const server = app.listen(port);
Expand All @@ -41,9 +44,13 @@ server.on('upgrade', (request, socket, head) => {
});
});

const onConnection = async (url: string, res: express.Response | ws.WebSocket) => {
const onConnection = async (
req: express.Request | http.IncomingMessage,
res: express.Response | ws.WebSocket,
) => {
try {
const observable = await getObservable(url);
const project = getProjectId(req);
const observable = await getObservable(project, req.url || '');
if (res instanceof ws.WebSocket) {
sendLiveUpdates(res, observable);
return;
Expand All @@ -61,19 +68,19 @@ const onConnection = async (url: string, res: express.Response | ws.WebSocket) =
}
};

const getObservable = (url: string): Promise<Observable<unknown>> => {
const getObservable = (project: string, url: string): Promise<Observable<unknown>> => {
const route = url.replace('/api', '').split('?')[0];
switch (route) {
case ApiRoutes.GET_BY_ID:
return getById(url);
case ApiRoutes.GET_MANY_BY_ID:
return getManyById(url);
case ApiRoutes.GET_MANY:
return getMany(url);
return getMany(project, url);
case ApiRoutes.GET_MANY_ADVANCED:
return getManyAdvanced(url);
return getManyAdvanced(project, url);
case ApiRoutes.GET_UPDATED_AFTER:
return getUpdatedAfter(url);
return getUpdatedAfter(project, url);
case ApiRoutes.GET_TOKEN_PRICE:
return getTokenPrice(url);
case ApiRoutes.GET_AVG_PRICE:
Expand All @@ -94,3 +101,17 @@ const getObservable = (url: string): Promise<Observable<unknown>> => {
throw { code: 400, message: 'Invalid route' };
}
};

const getProjectId = (req: express.Request | http.IncomingMessage) => {
try {
const jwtToken = req.headers.authorization?.split(' ')[1];
const payload = jwt.verify(jwtToken || '', 'asdas#@#@xdas31sad');
const project = get(payload, 'project', '');
if (!project) {
throw { code: 401, message: 'Unauthorized' };
}
return project;
} catch {
throw { code: 401, message: 'Unauthorized' };
}
};
19 changes: 9 additions & 10 deletions packages/functions/scripts/dbUpgrades/1.0.0/auction.roll.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FirebaseApp, Firestore, build5Db } from '@build-5/database';
import { FirebaseApp, Firestore } from '@build-5/database';
import {
Auction,
AuctionType,
Expand All @@ -12,7 +12,7 @@ import {
import { randomBytes } from 'crypto';
import dayjs from 'dayjs';
import { Wallet } from 'ethers';
import { get, head } from 'lodash';
import { get, head, last } from 'lodash';

export const nftAuctionRoll = async (app: FirebaseApp) => {
const db = new Firestore(app);
Expand All @@ -28,18 +28,19 @@ export const nftAuctionRoll = async (app: FirebaseApp) => {
.startAfter(lastDoc)
.limit(500)
.get<Nft>();
lastDocId = last(nfts)?.uid || '';

const promises = nfts.map((n) =>
build5Db().runTransaction(async (transaction) => {
const nftDocRef = build5Db().doc(`${COL.NFT}/${n.uid}`);
db.runTransaction(async (transaction) => {
const nftDocRef = db.doc(`${COL.NFT}/${n.uid}`);
const nft = <Nft>await transaction.get(nftDocRef);

if (nft.auction || !nft.auctionTo || dayjs(nft.auctionTo.toDate()).isBefore(dayjs())) {
return;
}

const auction = await getAuctionData(nft);
const auctionDocRef = build5Db().doc(`${COL.AUCTION}/${auction.uid}`);
const auction = await getAuctionData(db, nft);
const auctionDocRef = db.doc(`${COL.AUCTION}/${auction.uid}`);
transaction.create(auctionDocRef, auction);

transaction.update(nftDocRef, { auction: auction.uid });
Expand All @@ -50,7 +51,7 @@ export const nftAuctionRoll = async (app: FirebaseApp) => {
} while (lastDocId);
};

const getAuctionData = async (nft: Nft) => {
const getAuctionData = async (db: Firestore, nft: Nft) => {
const auction: Auction = {
uid: getRandomEthAddress(),
space: nft.space,
Expand All @@ -77,9 +78,7 @@ const getAuctionData = async (nft: Nft) => {
auction.auctionHighestBidder = nft.auctionHighestBidder;
auction.auctionHighestBid = nft.auctionHighestBid || 0;

const paymentDocRef = build5Db().doc(
`${COL.TRANSACTION}/${get(nft, 'auctionHighestTransaction', '')}`,
);
const paymentDocRef = db.doc(`${COL.TRANSACTION}/${get(nft, 'auctionHighestTransaction', '')}`);
const payment = <Transaction>await paymentDocRef.get();
auction.bids.push({
bidder: nft.auctionHighestBidder,
Expand Down
93 changes: 93 additions & 0 deletions packages/functions/scripts/dbUpgrades/1.0.0/set.project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { FirebaseApp } from '@build-5/database';
import { BaseRecord, COL, SOON_PROJECT_ID, SUB_COL } from '@build-5/interfaces';
import admin from 'firebase-admin';
import { last } from 'lodash';

const collections = [
COL.AWARD,
COL.COLLECTION,
COL.NFT,
COL.SPACE,
COL.PROPOSAL,
COL.TRANSACTION,
COL.BADGES,
COL.TOKEN,
COL.TOKEN_MARKET,
COL.TOKEN_PURCHASE,
COL.STAKE,
COL.STAKE_REWARD,
COL.NFT_STAKE,
COL.AIRDROP,
];

const subCollections = [
SUB_COL.OWNERS,
SUB_COL.PARTICIPANTS,
SUB_COL.MEMBERS,
SUB_COL.GUARDIANS,
SUB_COL.ADMINS,
SUB_COL.BLOCKED_MEMBERS,
SUB_COL.KNOCKING_MEMBERS,
SUB_COL.DISTRIBUTION,
SUB_COL.STATS,
SUB_COL.VOTES,
SUB_COL.RANKS,
];

type DocType = admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData> | undefined;
type QuerySnap = admin.firestore.QuerySnapshot<admin.firestore.DocumentData>;

const dataToSet = { project: SOON_PROJECT_ID, projects: { [SOON_PROJECT_ID]: true } };

export const setProjectRoll = async (app: FirebaseApp) => {
const adminApp = app.getInstance() as admin.app.App;
const db = adminApp.firestore();

for (const col of collections) {
let lastDoc: DocType = undefined;

do {
const batch = db.batch();
let query = db.collection(col).limit(500);
if (lastDoc) {
query = query.startAfter(lastDoc);
}
const snap: QuerySnap = await query.get();
lastDoc = last(snap.docs);

snap.docs.forEach((d) => {
const data = d.data() as BaseRecord;
if (!data.project) {
batch.update(d.ref, dataToSet);
}
});

await batch.commit();
} while (lastDoc);
}

for (const subCol of subCollections) {
let lastDoc: DocType = undefined;

do {
const batch = db.batch();
let query = db.collectionGroup(subCol).limit(500);
if (lastDoc) {
query = query.startAfter(lastDoc);
}
const snap: QuerySnap = await query.get();
lastDoc = last(snap.docs);

snap.docs.forEach((d) => {
const data = d.data() as BaseRecord;
if (!data.project) {
batch.update(d.ref, dataToSet);
}
});

await batch.commit();
} while (lastDoc);
}
};

export const roll = setProjectRoll;
2 changes: 1 addition & 1 deletion packages/functions/src/runtime/https/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ import { onRequest } from './https';
import { noAuth } from './middlewares';

exports[WEN_FUNC.createMember] = onRequest({
name: WEN_FUNC.updateMember,
name: WEN_FUNC.createMember,
schema: Joi.object({}),
middleware: noAuth,
handler: createMemberControl,
Expand Down
Loading