A social media-type application that I'm developing allows the user to select a unique username upon signup, the only issue is that the app handles username novelty verification client side. That is to say that the app retrieves all usernames from the relevant collection, and checks whether or not the requested username already exists within that list.
Unfortunately, with the way my firebase security rules are set up (please see below), the user must first exist before this aforementioned 'username novelty verification' can take place.
This means that no matter if the username is unique or not, the user is given an account under their desired email address first. This results in accounts being setup that have failed this verification, and have a subsequently null username field.
I understand that handling username verification client-side is inefficient at best, and dirty at worst, so if you could suggest any 'Firebase-side' verification methods, perhaps using Firebase 'Functions', that would be awesome.
Thanks
Firebase security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userID} {
allow read, create: if request.auth.uid != null;
allow update, write, delete: if (
(
request.resource.data.email == resource.data.email &&
request.resource.data.id == resource.data.id &&
request.auth.uid == userID
) &&
(request.resource.data.userBio != resource.data.userBio ||
request.resource.data.username != resource.data.username ||
request.resource.data.location != resource.data.location ||
request.resource.data.favPlaces != resource.data.favPlaces ||
request.resource.data.following != resource.data.following ||
request.resource.data.likedTrips != resource.data.likedTrips)
);
}
...
Email signup routine:
Future signUpWithEmail({
@required String email,
@required String password,
@required String username,
@required String userBio,
@required List favPlaces,
@required BuildContext context,
}) async {
var authResult;
try {
authResult = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
sendUserEmailVerification(authResult.user);
} catch (e) {
print(e.message);
}
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');
final documents = await collectionReference.where("username").get();
List results = [];
for (int i = 0; i < documents.docs.length; i++) {
results.add(documents.docs[i]["username"]);
}
if (!results.contains(username)) {
if (favPlaces.toString() != "[]") {
try {
await _firestoreService.createUser(
TRUser(
id: authResult.user.uid,
email: email,
username: username,
userBio: userBio,
following: [],
favPlaces: favPlaces,
likedTrips: []
)
);
Navigator.push(context, CupertinoPageRoute(builder: (context) => WelcomePage(username:username)));
return authResult.user != null;
} catch (e) {
return e;
}
} else {
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text("please select at least 1 location"),
actions: <Widget>[
CupertinoDialogAction(
onPressed: () => Navigator.pop(context),
isDefaultAction: true,
child: Text('okay'),
)
],
)
);
}
} else {
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text("this username is already in use"),
actions: <Widget>[
CupertinoDialogAction(
onPressed: () => Navigator.pop(context),
isDefaultAction: true,
child: Text('okay'),
)
],
)
);
}
}