Files
Compass/backend/api/src/create-user.ts
2025-08-27 21:30:05 +02:00

158 lines
4.7 KiB
TypeScript

import * as admin from 'firebase-admin'
import { PrivateUser } from 'common/user'
import { randomString } from 'common/util/random'
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
import { getIp, track } from 'shared/analytics'
import { APIError, APIHandler } from './helpers/endpoint'
import { getDefaultNotificationPreferences } from 'common/user-notification-preferences'
import { removeUndefinedProps } from 'common/util/object'
import { generateAvatarUrl } from 'shared/helpers/generate-and-update-avatar-urls'
import { getStorage } from 'firebase-admin/storage'
import { DEV_CONFIG } from 'common/envs/dev'
import { PROD_CONFIG } from 'common/envs/prod'
import { RESERVED_PATHS } from 'common/envs/constants'
import { log, isProd, getUser, getUserByUsername } from 'shared/utils'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { insert } from 'shared/supabase/utils'
import { convertPrivateUser, convertUser } from 'common/supabase/users'
export const createUser: APIHandler<'create-user'> = async (
props,
auth,
req
) => {
const { deviceToken: preDeviceToken, adminToken } = props
const firebaseUser = await admin.auth().getUser(auth.uid)
const testUserAKAEmailPasswordUser =
firebaseUser.providerData[0].providerId === 'password'
if (
testUserAKAEmailPasswordUser &&
adminToken !== process.env.TEST_CREATE_USER_KEY
) {
throw new APIError(
401,
'Must use correct TEST_CREATE_USER_KEY to create user with email/password'
)
}
const host = req.get('referer')
log(`Create user from: ${host}`)
const ip = getIp(req)
const deviceToken = testUserAKAEmailPasswordUser
? randomString() + randomString()
: preDeviceToken
const fbUser = await admin.auth().getUser(auth.uid)
const email = fbUser.email
const emailName = email?.replace(/@.*$/, '')
const rawName = fbUser.displayName || emailName || 'User' + randomString(4)
const name = cleanDisplayName(rawName)
const bucket = getStorage().bucket(getStorageBucketId())
const avatarUrl = fbUser.photoURL
? fbUser.photoURL
: await generateAvatarUrl(auth.uid, name, bucket)
const pg = createSupabaseDirectClient()
let username = cleanUsername(name)
// Check username case-insensitive
const dupes = await pg.one<number>(
`select count(*) from users where username ilike $1`,
[username],
(r) => r.count
)
const usernameExists = dupes > 0
const isReservedName = RESERVED_PATHS.includes(username)
if (usernameExists || isReservedName) username += randomString(4)
const { user, privateUser } = await pg.tx(async (tx) => {
const preexistingUser = await getUser(auth.uid, tx)
if (preexistingUser)
throw new APIError(403, 'User already exists', {
userId: auth.uid,
})
// Check exact username to avoid problems with duplicate requests
const sameNameUser = await getUserByUsername(username, tx)
if (sameNameUser)
throw new APIError(403, 'Username already taken', { username })
const user = removeUndefinedProps({
avatarUrl,
isBannedFromPosting: Boolean(
(deviceToken && bannedDeviceTokens.includes(deviceToken)) ||
(ip && bannedIpAddresses.includes(ip))
),
link: {},
})
const privateUser: PrivateUser = {
id: auth.uid,
email,
initialIpAddress: ip,
initialDeviceToken: deviceToken,
notificationPreferences: getDefaultNotificationPreferences(),
blockedUserIds: [],
blockedByUserIds: [],
}
const newUserRow = await insert(tx, 'users', {
id: auth.uid,
name,
username,
data: user,
})
const newPrivateUserRow = await insert(tx, 'private_users', {
id: privateUser.id,
data: privateUser,
})
return {
user: convertUser(newUserRow),
privateUser: convertPrivateUser(newPrivateUserRow),
}
})
log('created user ', { username: user.username, firebaseId: auth.uid })
const continuation = async () => {
await track(auth.uid, 'create lover', { username: user.username })
}
return {
result: {
user,
privateUser,
},
continue: continuation,
}
}
function getStorageBucketId() {
return isProd()
? PROD_CONFIG.firebaseConfig.storageBucket
: DEV_CONFIG.firebaseConfig.storageBucket
}
// Automatically ban users with these device tokens or ip addresses.
const bannedDeviceTokens = [
'fa807d664415',
'dcf208a11839',
'bbf18707c15d',
'4c2d15a6cc0c',
'0da6b4ea79d3',
]
const bannedIpAddresses: string[] = [
'24.176.214.250',
'2607:fb90:bd95:dbcd:ac39:6c97:4e35:3fed',
'2607:fb91:389:ddd0:ac39:8397:4e57:f060',
'2607:fb90:ed9a:4c8f:ac39:cf57:4edd:4027',
'2607:fb90:bd36:517a:ac39:6c91:812c:6328',
]