-
Notifications
You must be signed in to change notification settings - Fork 0
dev15 bcrypt process
A writeup on setting up a login for QED on the public server. I’ve added the key to ubertool_keys folder on dropbox, and added it to cgi-qed config sheet on google drive. It should already be shared and viewable by the group.
-
First, a plaintext password was hashed and salted (mmm..) to prevent having any plaintext password files floating around the repo or server. A bit overkill for this, maybe, but could be useful in the future. In the python shell:
import bcrypt p = "some password".encode('utf-8') hash = bcrypt.hashpw(p, bcrypt.gensalt()) fileout = open("secret_key_login.txt", 'w') fileout.write(hash.decode()) fileout.close() -
The newly created secret_key_login.txt was then added to the qed repo, and added to the .gitignore file.
-
In order to ensure a login prior to viewing ALL pages within the QED site, a small middleware class was created at qed/login_middleware.py.
-
An addition to settings_docker.py was made to MIDDLEWARE_CLASSES, which adds the custom login code to django's middleware. This code runs between each request from front to backend.
MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', #rollbar 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', #rollbar 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'login_middleware.RequireLoginMiddleware', ) -
Django needs a couple settings in order to know it should authenticate a user, and where the login page is located. In the settings_docker.py file:
AUTH = False if os.environ.get('IS_PUBLIC') == "True": AUTH = True REQUIRE_LOGIN_PATH = '/login' SESSION_EXPIRE_AT_BROWSER_CLOSE = TrueNote: env vars in os.environ always strings..
-
The code in step 5 is using an env var, IS_PUBLIC, to determine if the server is public or not. An easy way for setting this env var is in temp_config/set_environment.py.
A conditional was added set_environment.py to check for DOCKER_HOSTNAME and whether it’s equal to the CGI server 3 hostname:
if self.docker_hostname == "ord-uber-vm003": _is_public = TrueThe env var was stored in os.environ like this (apparently environ can only store strings):
os.environ['IS_PUBLIC'] = str(_is_public) -
Next, the qed urls.py was modified to check the IS_PUBLIC env var that was set in set_environment.py:
if os.environ.get('IS_PUBLIC') == "True": urlpatterns = [ url(r'^$', include('splash_app.urls'),name='home'), url(r'^login/?$', login_middleware.login), url(r'^cts/', include('cts_app.urls')), url(r'^cyan/', include('cyan_app.urls')), ] else: urlpatterns = [ url(r'^', include('splash_app.urls')), url(r'^cts/', include('cts_app.urls')), url(r'^cyan/', include('cyan_app.urls')), url(r'^hms/', include('hms_app.urls')), url(r'^hwbi/', include('hwbi_app.urls')), url(r'^pisces/', include('pisces_app.urls')), url(r'^ubertool/', include('ubertool_app.urls')), ] -
That’s pretty much it for the setup. Now to get a little more into the login_middleware.py file. The bcrypt library is able to check a user-entered password against its encrypted version:
# check if password is correct: if not bcrypt.checkpw(password.encode('utf-8'), self.hashed_pass): # Password incorrect, go back to login page: return redirect('/login?next={}'.format(next_page))So bcrypt is only being used as a way to encrypt the password, and check it against the password a user enters. The plaintext password never shows up in the code or on the server, but only in our hearts and on our local machines in a secure place.
Anyway, the following code leverages django’s built-in authentication features and User class, which we can use for things like authentication, managing multiple users, logging out after a user is inactive for so long, etc. To do this a conditional that adds a user to the django default DB if they’re not already there. Then the final authentication returns the user object if it exist, and checks that it does indeed exist, and that they’re active.
# Add user to django db if not already there: if not User.objects.filter(username=username).exists(): _user = User.objects.create_user(username, 'to@do.com', password) _user.save() # save username and plain pass to django db user = authenticate(username=username, password=password) if user is not None: if user.is_active: # Redirect to a success page: django_login(request, user) return redirect(next_page) else: # Return a 'disabled account' error message return redirect('/login?next={}'.format(next_page)) else: # Return an 'invalid login' error message. return redirect('/login?next={}'.format(next_page)) -
To ensure the database is properly migrated when spinning this all up with Docker, the following lines were added to the docker_start.sh file:
django-admin.py migrate auth --noinput # used for login django-admin.py migrate sessions --noinput # used for loginNOTE: When changing passwords, the db.sqlite3 file had to be deleted in order to sync the updated encrypted password with django's reference of it. To automate this, docker_start.sh could try and remove the file before running the above migration statements, or a remigrate (perhaps django's makemigrations) function could do the trick.