]> git.ipfire.org Git - ipfire.org.git/commitdiff
people: Check if UID is valid and available on registration
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 28 Oct 2019 18:11:35 +0000 (18:11 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 28 Oct 2019 18:15:32 +0000 (18:15 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/backend/accounts.py
src/templates/auth/register.html
src/web/__init__.py
src/web/auth.py
src/web/base.py

index c310cf5300f8de86b9525016c2fb3890cf87470f..bba6a4faf7a3d421bef533d14a0f5a6b96e4f10c 100644 (file)
@@ -10,6 +10,7 @@ import ldap.modlist
 import logging
 import os
 import phonenumbers
+import re
 import sshpubkeys
 import time
 import tornado.httpclient
@@ -125,6 +126,18 @@ class Accounts(Object):
                for result in results:
                        return result
 
+       def uid_is_valid(self, uid):
+               # UID must be at least four characters
+               if len(uid) <= 4:
+                       return False
+
+               # https://unix.stackexchange.com/questions/157426/what-is-the-regex-to-validate-linux-users
+               m = re.match(r"^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$", uid)
+               if m:
+                       return True
+
+               return False
+
        def uid_exists(self, uid):
                if self.get_by_uid(uid):
                        return True
@@ -190,6 +203,10 @@ class Accounts(Object):
                # Convert all uids to lowercase
                uid = uid.lower()
 
+               # Check if UID is valid
+               if not self.uid_is_valid(uid):
+                       raise ValueError("UID is invalid: %s" % uid)
+
                # Check if UID is unique
                if self.uid_exists(uid):
                        raise ValueError("UID exists: %s" % uid)
index bd040feec22a37d6bde021fd8ff07dded7f960d4..80966242b6854bf15df949c55f4a9c58db29dd1e 100644 (file)
                                <input type="hidden" name="next" value="{{ next or "" }}">
 
                                <div class="form-group">
-                                       <input type="text" class="form-control form-control-lg"
-                                               name="uid" placeholder="{{ _("Username") }}" required autofocus>
+                                       <div class="input-group">
+                                               <div class="input-group-prepend">
+                                                       <span class="input-group-text">@</span>
+                                               </div>
+                                               <input type="text" class="form-control form-control-lg"
+                                                       name="uid" placeholder="{{ _("Username") }}" required autofocus
+                                                       pattern="[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)">
+                                               <div id="uid-invalid" class="invalid-feedback">
+                                                       {{ _("This username is invalid. Please choose a user name in UNIX format starting with a letter, followed by ASCII characters and digits only.") }}
+                                               </div>
+                                               <div id="uid-taken" class="invalid-feedback">
+                                                       {{ _("This username is not available.") }}
+                                               </div>
+                                       </div>
                                </div>
 
                                <div class="form-row mb-3">
                </div>
        </div>
 {% end block %}
+
+{% block javascript %}
+       <script>
+               var check_uid;
+
+               $("input[name=uid]").on("keyup", function(e) {
+                       if (check_uid)
+                               clearTimeout(check_uid);
+
+                       var uid = $(this);
+
+                       check_uid = setTimeout(function() {
+                               $.get("/api/check/uid", { uid : uid.val() },
+                                       function(data) {
+                                               // Reset all classes
+                                               uid.removeClass("is-valid is-invalid");
+
+                                               // Hide all feedback
+                                               $(".invalid-feedback").hide();
+
+                                               switch (data.result) {
+                                                       case "ok":
+                                                               uid.addClass("is-valid");
+                                                               break;
+
+                                                       case "invalid":
+                                                               uid.addClass("is-invalid");
+                                                               $("#uid-invalid").show();
+                                                               break;
+
+                                                       case "taken":
+                                                               uid.addClass("is-invalid");
+                                                               $("#uid-taken").show();
+                                                               break;
+                                               }
+                                       }
+                               );
+                       }, 250);
+               });
+       </script>
+{% end block %}
index 7fff703646afedbb81bfdded5cc697f22a7de2ce..de8ff2b5db6b4782e13116f73d66fb190c94e7a1 100644 (file)
@@ -287,6 +287,9 @@ class Application(tornado.web.Application):
 
                        # Single-Sign-On for Discourse
                        (r"/sso/discourse", people.SSODiscourse),
+
+                       # API
+                       (r"/api/check/uid", auth.APICheckUID),
                ]  + authentication_handlers)
 
                # wiki.ipfire.org
index 256c4ee84c8eb435bea4034bb8ba366f4c190567..4a311393b1a2cec796241bd658752be9c0be0dbe 100644 (file)
@@ -137,3 +137,24 @@ class ActivateHandler(AuthenticationMixin, base.BaseHandler):
 
                # Redirect to success page
                self.render("auth/activated.html", account=account)
+
+
+class APICheckUID(base.APIHandler):
+       @base.ratelimit(minutes=10, requests=100)
+       def get(self):
+               uid = self.get_argument("uid")
+               result = None
+
+               if not uid:
+                       result = "empty"
+
+               # Check if the username is syntactically valid
+               elif not self.backend.accounts.uid_is_valid(uid):
+                       result = "invalid"
+
+               # Check if the username is already taken
+               elif self.backend.accounts.uid_exists(uid):
+                       result = "taken"
+
+               # Username seems to be okay
+               self.finish({ "result" : result or "ok" })
index 396799bbe98cefbcdf63402b3a4383d636fd6772..437050acb9126c70376cbb24b0fa7efb4c1c5395 100644 (file)
@@ -247,6 +247,14 @@ class BaseHandler(tornado.web.RequestHandler):
                return self.backend.talk
 
 
+class APIHandler(BaseHandler):
+       def check_xsrf_cookie(self):
+               """
+                       Do nothing here, because we cannot verify the XSRF token
+               """
+               pass
+
+
 class NotFoundHandler(BaseHandler):
        def prepare(self):
                # Raises 404 as soon as it is called