diff --git a/functions/package.json b/functions/package.json index 34115a61..7b5c30b0 100644 --- a/functions/package.json +++ b/functions/package.json @@ -21,7 +21,6 @@ "main": "lib/functions/src/index.js", "dependencies": { "@amplitude/node": "1.10.0", - "cors": "2.8.5", "fetch": "1.1.0", "firebase-admin": "10.0.0", "firebase-functions": "3.21.2", diff --git a/functions/src/api.ts b/functions/src/api.ts index 6da6dbd7..ff315562 100644 --- a/functions/src/api.ts +++ b/functions/src/api.ts @@ -1,9 +1,7 @@ import * as admin from 'firebase-admin' -import { Response } from 'express' import { logger } from 'firebase-functions/v2' -import { onRequest, Request } from 'firebase-functions/v2/https' - -import * as Cors from 'cors' +import { HttpsOptions, onRequest, Request } from 'firebase-functions/v2/https' +import { log } from './utils' import { z } from 'zod' import { PrivateUser } from '../../common/user' @@ -33,6 +31,12 @@ export class APIError { } } +const auth = admin.auth() +const firestore = admin.firestore() +const privateUsers = firestore.collection( + 'private-users' +) as admin.firestore.CollectionReference + export const parseCredentials = async (req: Request): Promise => { const authHeader = req.get('Authorization') if (!authHeader) { @@ -47,8 +51,7 @@ export const parseCredentials = async (req: Request): Promise => { switch (scheme) { case 'Bearer': try { - const jwt = await admin.auth().verifyIdToken(payload) - return { kind: 'jwt', data: jwt } + return { kind: 'jwt', data: await auth.verifyIdToken(payload) } } catch (err) { // This is somewhat suspicious, so get it into the firebase console logger.error('Error verifying Firebase JWT: ', err) @@ -62,8 +65,6 @@ export const parseCredentials = async (req: Request): Promise => { } export const lookupUser = async (creds: Credentials): Promise => { - const firestore = admin.firestore() - const privateUsers = firestore.collection('private-users') switch (creds.kind) { case 'jwt': { if (typeof creds.data.user_id !== 'string') { @@ -77,8 +78,7 @@ export const lookupUser = async (creds: Credentials): Promise => { if (privateUserQ.empty) { throw new APIError(403, `No private user exists with API key ${key}.`) } - const privateUserSnap = privateUserQ.docs[0] - const privateUser = privateUserSnap.data() as PrivateUser + const privateUser = privateUserQ.docs[0].data() return { uid: privateUser.id, creds: { privateUser, ...creds } } } default: @@ -86,21 +86,6 @@ export const lookupUser = async (creds: Credentials): Promise => { } } -export const applyCors = ( - req: Request, - res: Response, - params: Cors.CorsOptions -) => { - return new Promise((resolve, reject) => { - Cors(params)(req, res, (result) => { - if (result instanceof Error) { - return reject(result) - } - return resolve(result) - }) - }) -} - export const zTimestamp = () => { return z.preprocess((arg) => { return typeof arg == 'number' ? new Date(arg) : undefined @@ -122,18 +107,21 @@ export const validate = (schema: T, val: unknown) => { } } +const DEFAULT_OPTS: HttpsOptions = { + minInstances: 1, + cors: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST], +} + export const newEndpoint = (methods: [string], fn: Handler) => - onRequest({ minInstances: 1 }, async (req, res) => { + onRequest(DEFAULT_OPTS, async (req, res) => { + log('Request processing started.') try { - await applyCors(req, res, { - origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST], - methods: methods, - }) if (!methods.includes(req.method)) { const allowed = methods.join(', ') throw new APIError(405, `This endpoint supports only ${allowed}.`) } const authedUser = await lookupUser(await parseCredentials(req)) + log('User credentials processed.') res.status(200).json(await fn(req, authedUser)) } catch (e) { if (e instanceof APIError) { diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 2effc90c..1b5dd8bc 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -13,6 +13,7 @@ import { } from '../../common/new-bet' import { addObjects, removeUndefinedProps } from '../../common/util/object' import { redeemShares } from './redeem-shares' +import { log } from './utils' const bodySchema = z.object({ contractId: z.string(), @@ -33,9 +34,11 @@ const numericSchema = z.object({ }) export const placebet = newEndpoint(['POST'], async (req, auth) => { + log('Inside endpoint handler.') const { amount, contractId } = validate(bodySchema, req.body) const result = await firestore.runTransaction(async (trans) => { + log('Inside main transaction.') const contractDoc = firestore.doc(`contracts/${contractId}`) const userDoc = firestore.doc(`users/${auth.uid}`) const [contractSnap, userSnap] = await Promise.all([ @@ -44,6 +47,7 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => { ]) if (!contractSnap.exists) throw new APIError(400, 'Contract not found.') if (!userSnap.exists) throw new APIError(400, 'User not found.') + log('Loaded user and contract snapshots.') const contract = contractSnap.data() as Contract const user = userSnap.data() as User @@ -82,6 +86,7 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => { throw new APIError(500, 'Contract has invalid type/mechanism.') } })() + log('Calculated new bet information.') if ( mechanism == 'cpmm-1' && @@ -95,7 +100,9 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => { const newBalance = user.balance - amount - loanAmount const betDoc = contractDoc.collection('bets').doc() trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet }) + log('Created new bet document.') trans.update(userDoc, { balance: newBalance }) + log('Updated user balance.') trans.update( contractDoc, removeUndefinedProps({ @@ -108,11 +115,14 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => { volume: volume + amount, }) ) + log('Updated contract properties.') return { betId: betDoc.id } }) + log('Main transaction finished.') await redeemShares(auth.uid, contractId) + log('Share redemption transaction finished.') return result })