manifold/functions/src/create-user.ts
Austin Chen da4ce99755
Merge Manifold for Teams infra into main codebase (#61)
* Add dev target for TheoremOne

* Restrict signups to theoremone.co emails

* Add new indices

* Forbid reads from unauthenticated users

* Client-side render pages that need auth

These pages are now client-side rendered:
- /home
- /leaderboards
- /market/...
- /fold/...

* Hide 404 for private Manifolds

* Brand instance for TheoremOne

* Hide "Add Funds" and "Personalize your feed"

* "M$" =>  "T$"

* Hide Discord & About Page too

* Update placeholders for teams

* Update firestore.indexes.json

* Switch /analytics to propz

* Migrate per-env code into common/

* More migrations to PROJECT_ID

* Conditionally use SSG depending on public vs private instance

* Fix props to be empty object

* Move more logic into access

* Spin out config files for each environment

* Generify most of the customizable brand stuff

* Move IS_PRIVATE_MANIFOLD to access.ts

* Rename access.ts to envs/constants.ts

* Add "dev:dev" alias

* Rever firestore rules to existing settings

* Fixes according to James's review
2022-03-08 18:43:30 -08:00

111 lines
3.0 KiB
TypeScript

import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import {
PrivateUser,
STARTING_BALANCE,
SUS_STARTING_BALANCE,
User,
} from '../../common/user'
import { getUser, getUserByUsername } from './utils'
import { randomString } from '../../common/util/random'
import {
cleanDisplayName,
cleanUsername,
} from '../../common/util/clean-username'
import { sendWelcomeEmail } from './emails'
import { isWhitelisted } from '../../common/envs/constants'
export const createUser = functions
.runWith({ minInstances: 1 })
.https.onCall(async (data: { deviceToken?: string }, context) => {
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
const preexistingUser = await getUser(userId)
if (preexistingUser)
return {
status: 'error',
message: 'User already created',
user: preexistingUser,
}
const fbUser = await admin.auth().getUser(userId)
const email = fbUser.email
if (!isWhitelisted(email)) {
return { status: 'error', message: `${email} is not whitelisted` }
}
const emailName = email?.replace(/@.*$/, '')
const rawName = fbUser.displayName || emailName || 'User' + randomString(4)
const name = cleanDisplayName(rawName)
let username = cleanUsername(name)
const sameNameUser = await getUserByUsername(username)
if (sameNameUser) {
username += randomString(4)
}
const avatarUrl = fbUser.photoURL
const { deviceToken } = data
const deviceUsedBefore =
!deviceToken || (await isPrivateUserWithDeviceToken(deviceToken))
const ipAddress = context.rawRequest.ip
const ipCount = ipAddress ? await numberUsersWithIp(ipAddress) : 0
const balance =
deviceUsedBefore || ipCount > 2 ? SUS_STARTING_BALANCE : STARTING_BALANCE
const user: User = {
id: userId,
name,
username,
avatarUrl,
balance,
totalDeposits: balance,
createdTime: Date.now(),
totalPnLCached: 0,
creatorVolumeCached: 0,
}
await firestore.collection('users').doc(userId).create(user)
console.log('created user', username, 'firebase id:', userId)
const privateUser: PrivateUser = {
id: userId,
username,
email,
initialIpAddress: ipAddress,
initialDeviceToken: deviceToken,
}
await firestore.collection('private-users').doc(userId).create(privateUser)
await sendWelcomeEmail(user, privateUser)
return { status: 'success', user }
})
const firestore = admin.firestore()
const isPrivateUserWithDeviceToken = async (deviceToken: string) => {
const snap = await firestore
.collection('private-users')
.where('initialDeviceToken', '==', deviceToken)
.get()
return !snap.empty
}
const numberUsersWithIp = async (ipAddress: string) => {
const snap = await firestore
.collection('private-users')
.where('initialIpAddress', '==', ipAddress)
.get()
return snap.docs.length
}