Repository: matrix-org/synapse

Issue:
When using numeric only id, registration fails but after checking captcha, mail, ...
This error https://github.com/matrix-org/synapse/blob/b2b86990705de8a099093ec141ad83e09f182034/synapse/handlers/register.py#L177

You get this error very late in the register flow, a bit annoying to know it only after having completed all the register steps

Can it be checked like for 'User ID already taken.'?

![image](https://user-images.githubusercontent.com/9841565/82990505-9a72a100-9ffc-11ea-9160-16607ec218a1.png)




Discussion:
I'm wondering if this should be caught by the call to `/_matrix/client/r0/register/available`. My first thought was no because we want to check that the username is _available_, not _valid_. But then Synapse seems to ignore any username provided in a guest registration request, and determines the numeric username of the guest user itself, so it could be correct to interpret that as Synapse reserving numeric usernames and making those unavailable for registration.
Another solution would be to move the check on whether the username is numeric much sooner in the registration flow, e.g. as soon as we can see a username:

https://github.com/matrix-org/synapse/blob/9dc6f3075aea7c76c3d6a201f8a78ace76f99a3e/synapse/rest/client/v2_alpha/register.py#L441-L447
FTR RiotX does not use `/_matrix/client/r0/register/available`, so yes if the error comes from the registration flow, when we send the username, we (developers of RiotX) will be more than happy! And other clients will also get benefit from this fix
Personally I'd advocate that clients should always call `/available` before attempting registration, so that they don't have a hard dependency of one hs implem doing the right thing and they don't provide a poorer UX when using a hs implem that doesn't. Regardless of whether we do this in Synapse there's no guarantee that Dendrite, Conduit, Ruma or any other hs implem will do the same, or will even be able to.

For this reason I think we should do both, having `/available` do this for clients that support calling this endpoint, and move that check higher in the flow as a fallback if for some reason the client doesn't implement this call.

FTR it looks like simply moving that check to [`check_username`](https://github.com/matrix-org/synapse/blob/56db0b1365965c02ff539193e26c333b7f70d101/synapse/handlers/register.py#L79) would kill both birds with one stone.
Ok, I understand. But `/available` is not reliable, as per comment in [this issue](https://github.com/vector-im/riotX-android/issues/1410). Also when a user is creating an account, once he has sent their desired userId, which is available, `/available` still returns true for the same userId. So I think the userId should be reserved for the duration of the OAuth session, but this is another bug.

To my point of view, `/available` could be eventually use by the client app to propose alternatives when the desired userId is in use

And as per https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-register-available:

> Matrix clients may wish to use this API prior to attempting registration, however the clients must also be aware that using this API does not normally reserve the username. This can mean that the username becomes unavailable between checking its availability and attempting to register it.

Which confirm my point of view that it's better not to use this API during registration :)


Resolving patch:
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -128,6 +128,15 @@ def check_username(self, localpart, guest_access_token=None, assigned_user_id=No
                     errcode=Codes.FORBIDDEN,
                 )
 
+        if guest_access_token is None:
+            try:
+                int(localpart)
+                raise SynapseError(
+                    400, "Numeric user IDs are reserved for guest users."
+                )
+            except ValueError:
+                pass
+
     @defer.inlineCallbacks
     def register_user(
         self,
@@ -170,15 +179,6 @@ def register_user(
 
             was_guest = guest_access_token is not None
 
-            if not was_guest:
-                try:
-                    int(localpart)
-                    raise SynapseError(
-                        400, "Numeric user IDs are reserved for guest users."
-                    )
-                except ValueError:
-                    pass
-
             user = UserID(localpart, self.hs.hostname)
             user_id = user.to_string()
 


Test patch: