feat(users): support creating app users with a Firebase Auth account

New users can now be created with a password, which creates a real
Firebase Auth account (so they can log into the mobile app immediately)
alongside the Firestore profile document. UserCreate is now
UserProfile + password (request-only, never persisted or echoed back);
deleting a user also removes their Auth account.

- backend/users: split UserCreate into UserProfile (persisted shape)
  and UserCreate (adds password), wire firebase_auth create/delete
- CreateUserModal: new lightweight modal for creating a user from
  other flows (e.g. device onboarding) without leaving the page
- UserForm: adds the password field for new users; also fixes
  useToast() being used undestructured (toast.success(...) was being
  called on the hook's return value instead of its .toast method)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 18:31:37 +03:00
co-authored by Claude Sonnet 5
parent 6e0a291228
commit 0d79a9f72c
4 changed files with 147 additions and 10 deletions
+26 -3
View File
@@ -84,12 +84,28 @@ def get_user(user_doc_id: str) -> UserInDB:
def create_user(data: UserCreate) -> UserInDB:
"""Create a new user document in Firestore."""
"""Create a new user: a Firebase Auth account (so they can log into the app
immediately) plus the matching Firestore profile document."""
if not data.password or len(data.password) < 6:
raise ValidationError("Password must be at least 6 characters.")
if not data.email:
raise ValidationError("Email is required.")
db = get_db()
doc_data = data.model_dump()
doc_data = data.model_dump(exclude={"password"})
doc_data["friendsList"] = []
doc_data["friendsInvited"] = []
try:
firebase_user = firebase_auth.create_user(
email=data.email,
password=data.password,
display_name=data.display_name or None,
)
except firebase_auth.EmailAlreadyExistsError:
raise ValidationError(f"A user with email {data.email} already exists.")
doc_data["uid"] = firebase_user.uid
_, doc_ref = db.collection(COLLECTION).add(doc_data)
return UserInDB(id=doc_ref.id, **doc_data)
@@ -111,13 +127,20 @@ def update_user(user_doc_id: str, data: UserUpdate) -> UserInDB:
def delete_user(user_doc_id: str) -> None:
"""Delete a user document from Firestore."""
"""Delete a user document from Firestore, plus their Firebase Auth account if one exists."""
db = get_db()
doc_ref = db.collection(COLLECTION).document(user_doc_id)
doc = doc_ref.get()
if not doc.exists:
raise NotFoundError("User")
uid = doc.to_dict().get("uid", "")
if uid:
try:
firebase_auth.delete_user(uid)
except firebase_auth.UserNotFoundError:
pass
doc_ref.delete()