Repository: jupyterhub/nativeauthenticator

Issue:
Add option for Open SignUp
Make an option that all signups are already authorized by default

Discussion:

Patch:
diff --git a/docs/options.rst b/docs/options.rst
index 2a33fb7..bd4009e 100644
--- a/docs/options.rst
+++ b/docs/options.rst
@@ -35,4 +35,16 @@ You can also define the number of seconds a user must wait before trying again.
 
 .. code-block:: python
 
-    c.Authenticator.secs_before_next_try = 1200
+    c.Authenticator.seconds_before_next_try = 1200
+
+
+Open SignUp
+-----------
+
+By default all users that make sign up on Native Authenticator need an admin approval so 
+they can actually log in the system. You can change this behavior by adding an option of 
+open signup, where all users that do sign up can already log in the system. To do so, just add this line to the config file:
+
+.. code-block:: python
+
+    c.Authenticator.open_signup = True
diff --git a/nativeauthenticator/handlers.py b/nativeauthenticator/handlers.py
index eebdd83..c5ed471 100644
--- a/nativeauthenticator/handlers.py
+++ b/nativeauthenticator/handlers.py
@@ -31,22 +31,37 @@ class SignUpHandler(LocalBase):
         html = self.render_template('signup.html')
         self.finish(html)
 
+    def get_result_message(self, message_type):
+        messages = {
+            'normal': 'Your information have been sent to the admin',
+            'error': ('Something went wrong. Be sure your password has at '
+                      f'least {self.authenticator.minimum_password_length} '
+                      'characters and is not too common.'),
+            'success':  ('The signup was successful. You can now go to '
+                         'home page and log in the system'),
+        }
+        return messages[message_type]
+
     async def post(self):
         username = self.get_body_argument('username', strip=False)
         password = self.get_body_argument('password', strip=False)
         user = self.authenticator.get_or_create_user(username, password)
 
-        result_message = 'Your information have been sent to the admin'
+        alert = 'alert-info'
+        result_message = self.get_result_message('normal')
+
+        if self.authenticator.open_signup:
+            alert = 'alert-success'
+            result_message = self.get_result_message('success')
+
         if not user:
-            result_message = f"""Something went wrong. Be sure your password
-                                has at least
-                                {self.authenticator.minimum_password_length}
-                                characters and is not too common."""
+            alert = 'alert-danger'
+            result_message = self.get_result_message('error')
 
         html = self.render_template(
             'signup.html',
-            result=bool(user),
             result_message=result_message,
+            alert=alert
         )
         self.finish(html)
 
diff --git a/nativeauthenticator/nativeauthenticator.py b/nativeauthenticator/nativeauthenticator.py
index d454a33..9d6ce77 100644
--- a/nativeauthenticator/nativeauthenticator.py
+++ b/nativeauthenticator/nativeauthenticator.py
@@ -18,26 +18,32 @@ class NativeAuthenticator(Authenticator):
     check_common_password = Bool(
         config=True,
         default=False,
-        help="""Creates a verification of password strength
-        when a new user makes signup"""
+        help=("Creates a verification of password strength "
+              "when a new user makes signup")
     )
     minimum_password_length = Integer(
         config=True,
         default=1,
-        help="""Check if the length of the password is at least this size on
-        signup"""
+        help=("Check if the length of the password is at least this size on "
+              "signup")
     )
     allowed_failed_logins = Integer(
         config=True,
         default=0,
-        help="""Configures the number of failed attempts a user can have
-                before being blocked."""
+        help=("Configures the number of failed attempts a user can have "
+              "before being blocked.")
     )
     seconds_before_next_try = Integer(
         config=True,
         default=600,
-        help="""Configures the number of seconds a user has to wait
-                after being blocked. Default is 600."""
+        help=("Configures the number of seconds a user has to wait "
+              "after being blocked. Default is 600.")
+    )
+    open_signup = Bool(
+        config=True,
+        default=False,
+        help=("Allows every user that made sign up to automatically log in "
+              "the system without needing admin authorization")
     )
 
     def __init__(self, add_new_table=True, *args, **kwargs):
@@ -132,7 +138,7 @@ class NativeAuthenticator(Authenticator):
 
         encoded_pw = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
         infos = {'username': username, 'password': encoded_pw}
-        if username in self.admin_users:
+        if username in self.admin_users or self.open_signup:
             infos.update({'is_authorized': True})
 
         user_info = UserInfo(**infos)
diff --git a/nativeauthenticator/templates/signup.html b/nativeauthenticator/templates/signup.html
index c92f3cd..00f8d7e 100644
--- a/nativeauthenticator/templates/signup.html
+++ b/nativeauthenticator/templates/signup.html
@@ -59,9 +59,7 @@ document.addEventListener('DOMContentLoaded', function() {
         </div>
       </div>
     </form>
-    {% if result %}
-      <div class="alert alert-info" role="alert">{{result_message}}</div>
-    {% endif %}
+    <div class="alert {{alert}}" role="alert">{{result_message}}</div>
   </div>
 
 


Test patch:
diff --git a/nativeauthenticator/tests/test_authenticator.py b/nativeauthenticator/tests/test_authenticator.py
index f3b34e0..383427a 100644
--- a/nativeauthenticator/tests/test_authenticator.py
+++ b/nativeauthenticator/tests/test_authenticator.py
@@ -24,12 +24,25 @@ pytestmark = pytest.mark.asyncio
 pytestmark = pytestmark(pytest.mark.usefixtures("tmpcwd"))
 
 
-async def test_create_user(tmpcwd, app):
-    '''Test if method get_or_create_user creates a new user'''
+@pytest.mark.parametrize("is_admin,open_signup,expected_authorization", [
+    (False, False, False),
+    (True, False, True),
+    (False, True, True),
+])
+async def test_create_user(is_admin, open_signup, expected_authorization,
+                           tmpcwd, app):
+    '''Test method get_or_create_user for new user and authorization '''
     auth = NativeAuthenticator(db=app.db)
-    auth.get_or_create_user('John Snow', 'password')
-    user_info = UserInfo.find(app.db, 'John Snow')
-    assert user_info.username == 'John Snow'
+
+    if is_admin:
+        auth.admin_users = ({'johnsnow'})
+    if open_signup:
+        auth.open_signup = True
+
+    auth.get_or_create_user('johnsnow', 'password')
+    user = UserInfo.find(app.db, 'johnsnow')
+    assert user.username == 'johnsnow'
+    assert user.is_authorized == expected_authorization
 
 
 @pytest.mark.parametrize("password,min_len,expected", [