lib/api/create/auth-user.ts
import to from 'await-to-js';
import { v4 as uuid } from 'uuid';
import { FirebaseError, UserRecord, auth } from 'lib/api/firebase';
import { APIError } from 'lib/model/error';
import { User } from 'lib/model/user';
import clone from 'lib/utils/clone';
/**
* Creates the Firebase Authentication account for the given user.
* @param user - The user to create the account for.
* @return Promise that resolves to the created user; throws an `APIError` if we
* were unable to create the Firebase Authentication account.
* @todo Perhaps remove the `validatePhone` method as it isn't used anywhere
* else besides here.
* @todo Handle common Firebase Authentication errors such as
* `auth/phone-number-already-exists` or `auth/email-already-exists`.
*/
export default async function createAuthUser(user: User): Promise<User> {
await user.validatePhone();
const [err, userRecord] = await to<UserRecord, FirebaseError>(
auth.createUser({
disabled: false,
email: user.email || undefined,
emailVerified: false,
displayName: user.name,
photoURL: user.photo || undefined,
phoneNumber: user.phone || undefined,
})
);
if (err) {
// TODO: Find a better way to setup my testing environment such that I don't
// have to add these error handling exceptions. Ideally, I should be able to
// manipulate the state of my authentication backend during tests.
if (
['development', 'test'].includes(process.env.APP_ENV as string) &&
[
'auth/email-already-exists',
'auth/phone-number-already-exists',
].includes(err.code)
)
return new User(clone({ ...user, id: user.id || uuid() }));
const msg = `${err.name} (${err.code}) creating auth account`;
throw new APIError(`${msg} for ${user.toString()}: ${err.message}`, 500);
}
const record = userRecord as UserRecord;
const createdUser = new User(
clone({
...user,
email: record.email,
phone: record.phoneNumber,
photo: record.photoURL,
name: record.displayName,
id: record.uid,
})
);
return createdUser;
}