Skip to content

Commit

Permalink
feat(script): user authentication
Browse files Browse the repository at this point in the history
Closes #2
  • Loading branch information
nritsche committed Jul 21, 2020
1 parent d8dc0b0 commit 5da4ff4
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 3 deletions.
65 changes: 65 additions & 0 deletions bondia/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from chimedb.core import connect as connect_chimedb
from chimedb.core.mediawiki import MediaWikiUser

import panel as pn
import tornado


def get_user(request_handler):
user = request_handler.get_secure_cookie("user")
if user is not None and isinstance(user, (str, bytes, bytearray)):
user = tornado.escape.json_decode(user)
return user


next_url = "/"

login_url = "/login"


class LoginHandler(tornado.web.RequestHandler):
def get(self):
errormessage = self.get_argument("error", "")
self.render("login.html", errormessage=errormessage)

def post(self):
username = self.get_argument("username", "")
password = self.get_argument("password", "")

if not username:
error_msg = "?error=" + tornado.escape.url_escape("Invalid username.")
self.redirect(login_url + error_msg)
return

connect_chimedb()
try:
MediaWikiUser.authenticate(username, password)
except UserWarning as err:
error_msg = "?error=" + tornado.escape.url_escape(str(err))
self.redirect(login_url + error_msg)
else:
# make the username accessible to the panel application
pn.state.cache["username"] = username
self.set_current_user(username)
self.redirect(next_url)

def set_current_user(self, user):
if user:
self.set_secure_cookie("user", tornado.escape.json_encode(user))
else:
self.clear_cookie("user")

def get_user(self):
user = self.get_secure_cookie("user")
if user is not None and isinstance(user, (str, bytes, bytearray)):
user = tornado.escape.json_decode(user)
return user


logout_url = "/logout"


class LogoutHandler(tornado.web.RequestHandler):
def get(self):
self.clear_cookie("user")
self.redirect(next_url)
2 changes: 2 additions & 0 deletions bondia/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def render(self):

template.add_variable("subtitle", "CHIME Daily Validation")
template.add_variable("app_title", "BON DIA")
template.add_variable("username", pn.state.cache.get("username", "-"))
template.add_variable("num_unvalidated", 19)

return self.populate_template(template)

Expand Down
93 changes: 93 additions & 0 deletions bondia/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!--This is taken from https://github.com/bokeh/bokeh/blob/branch-2.2/examples/howto/server_auth/login.html-->
<!DOCTYPE html>
<html>
<head>
<title>login - bondia | CHIME Daily Validation</title>
<style>
*{
margin:0;
padding: 0;
box-sizing: border-box;
}
html{
height: 100%;
}
img {
max-width: 100%;
max-height: 100%;
}
body{
font-family: 'Segoe UI', sans-serif;;
font-size: 1rem;
line-height: 1.6;
height: 100%;
}
.wrap {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #fafafa;
}
.login-form{
width: 350px;
margin: 0 auto;
border: 1px solid #ddd;
padding: 2rem;
background: #ffffff;
}
.form-input{
background: #fafafa;
border: 1px solid #eeeeee;
padding: 12px;
width: 100%;
}
.form-group{
margin-bottom: 1rem;
}
.form-button{
background: #69d2e7;
border: 1px solid #ddd;
color: #ffffff;
padding: 10px;
width: 100%;
text-transform: uppercase;
}
.form-button:hover{
background: #69c8e7;
}
.form-header{
text-align: center;
margin-bottom : 2rem;
}
.form-footer{
text-align: center;
}
</style>
</head>
<body>
<div class="wrap">
<form class="login-form" action="/login" method="post">
{% module xsrf_form_html() %}
<div class="form-header">
<h3><img alt="CHIME logo" src="https://bao.chimenet.ca/wiki/images/2/20/Logo_chime_20140529_4cyl.svg"></h3>
<p>Login to access CHIME daily validation</p>
</div>
<!--Email Input-->
<div class="form-group">
<input name="username" type="text" class="form-input" autocapitalize="off" autocorrect="off" placeholder="username">
</div>
<!--Password Input-->
<div class="form-group">
<input name="password" type="password" class="form-input" placeholder="password">
</div>
<!--Login Button-->
<div class="form-group">
<button class="form-button" type="submit">Login</button>
</div>
<span class="errormessage">{{errormessage}}</span>
</form>
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion bondia/templates/mwc.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
{{ app_title }}
</span>
<div class="appMenu">
<mwc-button disabled outlined="" label="Unvalidated Days (18)"></mwc-button>
<div align="center"><p>User: <i>{{ username }}</i></p></div>
<mwc-button disabled outlined="" label="Unvalidated Days ({{ num_unvalidated }})"></mwc-button>
<hr>
<p><h4>Select Data</h4></p>
{{ embed(roots.rev_selector) }}
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
bokeh==2.1.1
caput @ git+https://github.com/radiocosmology/caput.git
chimedb @ https://github.com/chime-experiment/chimedb.git
chimedb.dataflag @ https://github.com/chime-experiment/chimedb_dataflag.git
ch_util @ git+https://github.com/chime-experiment/ch_util.git
click
datashader
Expand Down
20 changes: 18 additions & 2 deletions scripts/bondia-server
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#!/usr/bin/env python3.8

from bokeh.server.auth_provider import AuthModule
import click
import logging
import panel
import pathlib
import secrets
import yaml

from bondia.server import BondiaServer
from bondia import __version__
from bondia import auth, __version__

# This script is both to quickly start a webserver for a single session (use --show) and as a
# target for deployment via `panel serve` (make sure the config is at `/etc/bondia/bondia.conf`.
Expand Down Expand Up @@ -36,7 +38,13 @@ logger = logging.getLogger("bondia-server")
@click.option(
"--websocket_origin", help="Public hostnames which may connect to the websocket."
)
def start(configfile, show, port, num_procs, websocket_origin):
@click.option(
"--login/--no-login",
help="Require user authentication.",
default=True,
show_default=True,
)
def start(configfile, show, port, num_procs, websocket_origin, login):

# TODO: use caput.config
logging.basicConfig(level=logging.DEBUG)
Expand All @@ -53,6 +61,13 @@ def start(configfile, show, port, num_procs, websocket_origin):
# Prepare as much as possible in centralized server object
server = BondiaServer.from_config(config)

# Enable authentication
kwargs = {}
if login:
kwargs["auth_provider"] = AuthModule(auth.__file__)
kwargs["xsrf_cookies"] = True
kwargs["cookie_secret"] = secrets.token_hex()

# Create an instance of the GUI for each user session.
def instance():
return server.gui_instance()
Expand All @@ -64,6 +79,7 @@ def start(configfile, show, port, num_procs, websocket_origin):
title="bondia | CHIME Daily Validation",
num_procs=num_procs,
websocket_origin=websocket_origin,
**kwargs,
)


Expand Down

0 comments on commit 5da4ff4

Please sign in to comment.