import logging
import os
import phonenumbers
+import re
import sshpubkeys
import time
import tornado.httpclient
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
# 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)
<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 %}
# Single-Sign-On for Discourse
(r"/sso/discourse", people.SSODiscourse),
+
+ # API
+ (r"/api/check/uid", auth.APICheckUID),
] + authentication_handlers)
# wiki.ipfire.org
# 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" })
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