From 581a42f2885a53de275675667afacf7cf37f2320 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sat, 9 Jul 2022 13:43:18 -0700 Subject: [PATCH 1/6] Migrate stripeWebhook and createCheckoutSession to v2 (#636) --- functions/src/stripe.ts | 22 ++++++++++++---------- web/lib/firebase/api-call.ts | 1 + web/lib/service/stripe.ts | 5 ++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/functions/src/stripe.ts b/functions/src/stripe.ts index 67309aa8..450bbe35 100644 --- a/functions/src/stripe.ts +++ b/functions/src/stripe.ts @@ -1,4 +1,4 @@ -import * as functions from 'firebase-functions' +import { onRequest } from 'firebase-functions/v2/https' import * as admin from 'firebase-admin' import Stripe from 'stripe' @@ -42,9 +42,9 @@ const manticDollarStripePrice = isProd() 10000: 'price_1K8bEiGdoFKoCJW7Us4UkRHE', } -export const createCheckoutSession = functions - .runWith({ minInstances: 1, secrets: ['STRIPE_APIKEY'] }) - .https.onRequest(async (req, res) => { +export const createcheckoutsession = onRequest( + { minInstances: 1, secrets: ['STRIPE_APIKEY'] }, + async (req, res) => { const userId = req.query.userId?.toString() const manticDollarQuantity = req.query.manticDollarQuantity?.toString() @@ -86,14 +86,15 @@ export const createCheckoutSession = functions }) res.redirect(303, session.url || '') - }) + } +) -export const stripeWebhook = functions - .runWith({ +export const stripewebhook = onRequest( + { minInstances: 1, secrets: ['MAILGUN_KEY', 'STRIPE_APIKEY', 'STRIPE_WEBHOOKSECRET'], - }) - .https.onRequest(async (req, res) => { + }, + async (req, res) => { const stripe = initStripe() let event @@ -115,7 +116,8 @@ export const stripeWebhook = functions } res.status(200).send('success') - }) + } +) const issueMoneys = async (session: StripeSession) => { const { id: sessionId } = session diff --git a/web/lib/firebase/api-call.ts b/web/lib/firebase/api-call.ts index ef0cad1e..d65a44f3 100644 --- a/web/lib/firebase/api-call.ts +++ b/web/lib/firebase/api-call.ts @@ -53,6 +53,7 @@ export function getFunctionUrl(name: string) { export function createAnswer(params: any) { return call(getFunctionUrl('createanswer'), 'POST', params) } + export function changeUserInfo(params: any) { return call(getFunctionUrl('changeuserinfo'), 'POST', params) } diff --git a/web/lib/service/stripe.ts b/web/lib/service/stripe.ts index 395f7093..bedd68aa 100644 --- a/web/lib/service/stripe.ts +++ b/web/lib/service/stripe.ts @@ -1,12 +1,11 @@ -import { PROJECT_ID } from 'common/envs/constants' +import { getFunctionUrl } from 'web/lib/firebase/api-call' export const checkoutURL = ( userId: string, manticDollarQuantity: number, referer = '' ) => { - const endpoint = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/createCheckoutSession` - + const endpoint = getFunctionUrl('createcheckoutsession') return `${endpoint}?userId=${userId}&manticDollarQuantity=${manticDollarQuantity}&referer=${encodeURIComponent( referer )}` From 67a05c2f1be80e987c51f135861c9d2f24af4a14 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sat, 9 Jul 2022 13:54:15 -0700 Subject: [PATCH 2/6] Migrate transact function to v2 (#635) --- functions/src/index.ts | 2 +- functions/src/transact.ts | 46 ++++++++++++++--------------- web/components/tipper.tsx | 2 +- web/lib/firebase/api-call.ts | 4 +++ web/lib/firebase/fn-call.ts | 6 ---- web/pages/charity/[charitySlug].tsx | 2 +- 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 34fceaa7..35f29954 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,7 +3,6 @@ import * as admin from 'firebase-admin' admin.initializeApp() // v1 -export * from './transact' export * from './stripe' export * from './create-user' export * from './on-create-bet' @@ -28,6 +27,7 @@ export * from './on-create-txn' // v2 export * from './health' +export * from './transact' export * from './change-user-info' export * from './create-answer' export * from './place-bet' diff --git a/functions/src/transact.ts b/functions/src/transact.ts index cd091b83..113afc0b 100644 --- a/functions/src/transact.ts +++ b/functions/src/transact.ts @@ -1,40 +1,40 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' import { User } from '../../common/user' import { Txn } from '../../common/txn' import { removeUndefinedProps } from '../../common/util/object' +import { APIError, newEndpoint } from './api' export type TxnData = Omit -export const transact = functions - .runWith({ minInstances: 1 }) - .https.onCall(async (data: TxnData, context) => { - const userId = context?.auth?.uid - if (!userId) return { status: 'error', message: 'Not authorized' } +// TODO: We totally fail to validate most of the input to this function, +// so anyone can spam our database with malformed transactions. - const { amount, fromType, fromId } = data +export const transact = newEndpoint({}, async (req, auth) => { + const data = req.body + const { amount, fromType, fromId } = data - if (fromType !== 'USER') - return { - status: 'error', - message: "From type is only implemented for type 'user'.", - } + if (fromType !== 'USER') + throw new APIError(400, "From type is only implemented for type 'user'.") - if (fromId !== userId) - return { - status: 'error', - message: 'Must be authenticated with userId equal to specified fromId.', - } + if (fromId !== auth.uid) + throw new APIError( + 403, + 'Must be authenticated with userId equal to specified fromId.' + ) - if (isNaN(amount) || !isFinite(amount)) - return { status: 'error', message: 'Invalid amount' } + if (isNaN(amount) || !isFinite(amount)) + throw new APIError(400, 'Invalid amount') - // Run as transaction to prevent race conditions. - return await firestore.runTransaction(async (transaction) => { - await runTxn(transaction, data) - }) + // Run as transaction to prevent race conditions. + return await firestore.runTransaction(async (transaction) => { + const result = await runTxn(transaction, data) + if (result.status == 'error') { + throw new APIError(500, result.message ?? 'An unknown error occurred.') + } + return result }) +}) export async function runTxn( fbTransaction: admin.firestore.Transaction, diff --git a/web/components/tipper.tsx b/web/components/tipper.tsx index 6f7dfbcb..e4b6580f 100644 --- a/web/components/tipper.tsx +++ b/web/components/tipper.tsx @@ -11,7 +11,7 @@ import { debounce, sum } from 'lodash' import { useEffect, useRef, useState } from 'react' import { CommentTips } from 'web/hooks/use-tip-txns' import { useUser } from 'web/hooks/use-user' -import { transact } from 'web/lib/firebase/fn-call' +import { transact } from 'web/lib/firebase/api-call' import { track } from 'web/lib/service/analytics' import { Row } from './layout/row' import { Tooltip } from './tooltip' diff --git a/web/lib/firebase/api-call.ts b/web/lib/firebase/api-call.ts index d65a44f3..7882d9ba 100644 --- a/web/lib/firebase/api-call.ts +++ b/web/lib/firebase/api-call.ts @@ -54,6 +54,10 @@ export function createAnswer(params: any) { return call(getFunctionUrl('createanswer'), 'POST', params) } +export function transact(params: any) { + return call(getFunctionUrl('transact'), 'POST', params) +} + export function changeUserInfo(params: any) { return call(getFunctionUrl('changeuserinfo'), 'POST', params) } diff --git a/web/lib/firebase/fn-call.ts b/web/lib/firebase/fn-call.ts index 27a5e8f3..2f299aea 100644 --- a/web/lib/firebase/fn-call.ts +++ b/web/lib/firebase/fn-call.ts @@ -1,5 +1,4 @@ import { httpsCallable } from 'firebase/functions' -import { Txn } from 'common/txn' import { User } from 'common/user' import { randomString } from 'common/util/random' import './init' @@ -9,11 +8,6 @@ import { safeLocalStorage } from '../util/local' export const cloudFunction = (name: string) => httpsCallable(functions, name) -export const transact = cloudFunction< - Omit, - { status: 'error' | 'success'; message?: string; txn?: Txn } ->('transact') - export const createUser: () => Promise = () => { const local = safeLocalStorage() let deviceToken = local?.getItem('device-token') diff --git a/web/pages/charity/[charitySlug].tsx b/web/pages/charity/[charitySlug].tsx index 7c3ce51b..c3e0912a 100644 --- a/web/pages/charity/[charitySlug].tsx +++ b/web/pages/charity/[charitySlug].tsx @@ -10,7 +10,7 @@ import { Spacer } from 'web/components/layout/spacer' import { User } from 'common/user' import { useUser } from 'web/hooks/use-user' import { Linkify } from 'web/components/linkify' -import { transact } from 'web/lib/firebase/fn-call' +import { transact } from 'web/lib/firebase/api-call' import { charities, Charity } from 'common/charity' import { useRouter } from 'next/router' import Custom404 from '../404' From 43b1096313a41f6186ade3f5eb6bac9bc52a4504 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Sat, 9 Jul 2022 17:27:36 -0400 Subject: [PATCH 3/6] expand search bar when typing on mobile --- web/components/contract-search.tsx | 54 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 220a95ab..7c0460b4 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -22,6 +22,7 @@ import { Spacer } from './layout/spacer' import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants' import { trackCallback } from 'web/lib/service/analytics' import ContractSearchFirestore from 'web/pages/contract-search-firestore' +import { useWindowSize } from 'web/hooks/use-window-size' const searchClient = algoliasearch( 'GJQPAYENIF', @@ -104,6 +105,10 @@ export function ContractSearch(props: { const indexName = `${indexPrefix}contracts-${sort}` + const [isSearching, setIsSearching] = useState(false) + const { width } = useWindowSize() + const showOptions = !isSearching || (width ?? 0) >= 500 + if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) { return (