[#8337] show more helpful messages when username is wrong format
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 1fde18c..00d6128 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -45,6 +45,7 @@
 from tg import tmpl_context as c, app_globals as g
 from webob import exc, Request
 from paste.deploy.converters import asbool, asint
+from formencode import validators as fev
 
 from ming.utils import LazyProperty
 from ming.orm import state
@@ -479,6 +480,17 @@
 
         return False
 
+    def username_validator(self, long_message=True):
+        validator = fev.Regex(h.re_project_name)
+        if long_message:
+            validator._messages['invalid'] = (
+                'Usernames must include only small letters, numbers, and dashes.'
+                ' They must also start with a letter and be at least 3 characters'
+                ' long.')
+        else:
+            validator._messages['invalid'] = 'Usernames only include small letters, numbers, and dashes'
+        return validator
+
 
 class LocalAuthenticationProvider(AuthenticationProvider):
 
diff --git a/Allura/allura/lib/widgets/auth_widgets.py b/Allura/allura/lib/widgets/auth_widgets.py
index 3233015..47c6764 100644
--- a/Allura/allura/lib/widgets/auth_widgets.py
+++ b/Allura/allura/lib/widgets/auth_widgets.py
@@ -21,6 +21,7 @@
 
 from tg import request, tmpl_context as c
 from formencode import Invalid
+from formencode import validators as fev
 from webob import exc
 
 from .forms import ForgeForm
@@ -69,8 +70,15 @@
 
     @validator
     def validate(self, value, state=None):
+        super(LoginForm, self).validate(value, state=state)
+        auth_provider = plugin.AuthenticationProvider.get(request)
+
+        # can't use a validator attr on the username TextField, since the antispam encoded name changes and doesn't
+        # match the name used in the form submission
+        auth_provider.username_validator(long_message=False).to_python(value['username'])
+
         try:
-            plugin.AuthenticationProvider.get(request).login()
+            auth_provider.login()
         except exc.HTTPUnauthorized:
             msg = 'Invalid login'
             raise Invalid(
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 68254d9..930b6bc 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -778,12 +778,8 @@
         username = ew.TextField(
             name='username',
             label='Desired Username',
-            validator=fev.Regex(
-                h.re_project_name))
-        username.validator._messages['invalid'] = (
-            'Usernames must include only small letters, numbers, and dashes.'
-            ' They must also start with a letter and be at least 3 characters'
-            ' long.')
+            validator=plugin.AuthenticationProvider.get(None).username_validator(),
+        )
         fields = [
             ew.TextField(
                 name='display_name',
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index b8066b6..a7c6893 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -100,6 +100,16 @@
             _session_id=self.app.cookies['_session_id']))
         assert 'Invalid login' in str(r), r.showbrowser()
 
+    def test_login_invalid_username(self):
+        extra = {'username': '*anonymous'}
+        r = self.app.get('/auth/', extra_environ=extra)
+        f = r.forms[0]
+        encoded = self.app.antispam_field_names(f)
+        f[encoded['username']] = 'test@user.com'
+        f[encoded['password']] = 'foo'
+        r = f.submit(extra_environ={'username': '*anonymous'})
+        r.mustcontain('Usernames only include small letters, ')
+
     def test_login_diff_ips_ok(self):
         # exercises AntiSpam.validate methods
         extra = {'username': '*anonymous', 'REMOTE_ADDR': '11.22.33.44'}