Skip to content

Commit

Permalink
add login page
Browse files Browse the repository at this point in the history
  • Loading branch information
klementng committed Jan 25, 2024
1 parent 5081614 commit 58ff5f9
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 55 deletions.
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"CONFIG_DIR": "tests/data/"
"CONFIG_DIR": "tests/data/",
"TEMPLATE_FOLDER": "../../templates/"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

CONFIG_DIR = os.getenv("CONFIG_DIR", "/config")
CONFIG_PATH = os.getenv("CONFIG_PATH", os.path.join(CONFIG_DIR, 'config.yml'))

TEMPLATE_FOLDER = os.getenv("TEMPLATE_FOLDER", "/app/templates")
#CACHE_TTL = os.getenv("CACHE_TTL", '60')
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")

Expand Down
52 changes: 31 additions & 21 deletions server/core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import cachetools.func
import waitress
import werkzeug.exceptions
from flask import Flask, Response, abort, request, session
from flask import Flask, Response, abort, request, session, render_template
from flask_wtf.csrf import CSRFProtect

import server.auth.modules
Expand All @@ -27,7 +27,7 @@
app_config, auth_modules = parse_config(config.CONFIG_PATH)

logger = logging.getLogger(__name__)
app = Flask(__name__)
app = Flask(__name__,template_folder=config.TEMPLATE_FOLDER)

for k in os.environ.keys():
if k.startswith("FLASK_"):
Expand Down Expand Up @@ -88,38 +88,48 @@ def main(path):
a_h = request.headers.get("Authorization")

if a_h == None or a_h == "Basic Og==":
return Response(f"Logout successful <br> <a href={redirect_url}> Home</a>", 401)
return Response(f"Logout successful <br> <a href={redirect_url}>Home</a>", 401)

else:
return abort(401)

elif 'login' in request.args and ('username' not in request.form or 'password' not in request.form):
return render_template("index.html")


# Login
try:
module = auth_modules[request.path]
except KeyError:
return abort(404)

res = process_auth_session(module, request, session)
if res != None:
if 'remember' in request.args:
if session.get('auth') != None:
res = process_auth_session(module, request, session)

if 'remember' in request.args and res.status_code == 200:
session.permanent = True
session.modified = True
return flask.redirect(redirect_url)

else:
res = process_auth_header(module, request, session)

if redirect_url != None:
res.set_data(
str(res.data, 'utf-8') +
f"""
<p>You will be redirected in 5 seconds to {redirect_url} </p>\
<script>
var timer = setTimeout(
function() {{window.location='{redirect_url}'}}, 5000);
</script>
"""
)


if 'username' in request.form and 'password' in request.form :
res = process_post(module, request, session)

else:
res = process_auth_header(module, request, session)

if redirect_url != None:
res.set_data(
str(res.data, 'utf-8') +
f"""
<p>You will be redirected in 1 seconds to <a href={redirect_url}> {redirect_url}</a> </p>\
<script>
var timer = setTimeout(
function() {{window.location='{redirect_url}'}}, 1000);
</script>
"""
)

return res


Expand Down
92 changes: 60 additions & 32 deletions server/core/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

logger = logging.getLogger(__name__)

def parse_config(path:str):

def parse_config(path: str):
try:
logger.debug("Parsing YAML config file")

Expand All @@ -24,22 +25,22 @@ def parse_config(path:str):
modules = config["modules"]

for key in modules.keys():
modules[key] = AuthenticationModule.from_dict(modules[key]) # type: ignore
modules[key] = AuthenticationModule.from_dict(modules[key]) # type: ignore

return settings, modules

except Exception as e:
logger.fatal(f"Aborting. Invalid Configuration: {e} ")
raise AuthenticationConfigError(e)


def update_login_session(username:str, request:flask.Request, session:flask.sessions.SessionMixin):

def update_login_session(username: str, request: flask.Request, session: flask.sessions.SessionMixin):
ses = session.get('auth')
authorized_path = request.path

if ses != None and ses['username'] == username:
ses['authorized_path'].append(authorized_path)

session["auth"] = {
'username': username,
'authorized_path': ses['authorized_path']
Expand All @@ -50,35 +51,35 @@ def update_login_session(username:str, request:flask.Request, session:flask.sess
'username': username,
'authorized_path': [authorized_path]
}

session.modified = True


def process_auth_session(module:AuthenticationModule, request:flask.Request, session:flask.sessions.SessionMixin):
def process_auth_session(module: AuthenticationModule, request: flask.Request, session: flask.sessions.SessionMixin) -> flask.Response:

@cachetools.func.ttl_cache(ttl=3)
def _func(path, session_id):

ses = session.get('auth')
if ses != None:
if path in ses['authorized_path']:
return flask.Response(f"", 200)

else:
if module.local != None:
user=module.local.db.get_user(ses['username'])
if user != None and user.verify_role(module.local.allowed_roles):
update_login_session(ses['username'], request, session)
return flask.Response(f"", 200)

return None
return _func(request.path, session.sid) # type: ignore


def process_auth_header(module:AuthenticationModule, request:flask.Request, session:flask.sessions.SessionMixin):

if ses == None:
flask.abort(401)

if path in ses['authorized_path']:
return flask.Response(f"", 200)

else:
if module.local != None:
user = module.local.db.get_user(ses['username'])

if user != None and user.verify_role(module.local.allowed_roles):
update_login_session(ses['username'], request, session)
return flask.Response(f"", 200)

return _func(request.path, session.sid) # type: ignore


def process_auth_header(module: AuthenticationModule, request: flask.Request, session: flask.sessions.SessionMixin):
"""Processes incoming 'Authorization' header
Args:
Expand All @@ -91,13 +92,40 @@ def process_auth_header(module:AuthenticationModule, request:flask.Request, sess

if request.authorization == None:
return flask.abort(401)

if request.authorization.type != 'basic':
return flask.abort(401, f"authentication '{request.authorization.type}' is not supported")


username = request.authorization.parameters['username']
password = request.authorization.parameters['password']

username = request.authorization.parameters.get('username')
password = request.authorization.parameters.get('password')

return process_login(module, request, session, username, password)


def process_post(module: AuthenticationModule, request: flask.Request, session: flask.sessions.SessionMixin):
"""Processes incoming 'Authorization' header
Args:
modules: authentication module
Returns:
Response
"""

username = request.form.get('username')
password = request.form.get('password')
remember = request.form.get('remember')

res = process_login(module, request, session, username, password)

if res.status_code == 200 and remember != None:
session.permanent = True
session.modified = True

return res


def process_login(module: AuthenticationModule, request: flask.Request, session: flask.sessions.SessionMixin, username: str | None, password: str | None):

if username == None or password == None:
return flask.abort(401)
Expand Down
98 changes: 98 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.84.0">
<title>Log in</title>

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link href="signin.css" rel="stylesheet">

<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}

@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}

html,
body {
height: 100%;
}

body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}

.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}

.form-signin .checkbox {
font-weight: 400;
}

.form-signin .form-floating:focus-within {
z-index: 2;
}

.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}

.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

</style>
</head>

<body class="text-center">
<main class="form-signin">
<form method="post">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>

<div class="form-floating">
<input type="username" name='username' class="form-control" id="floatingInput" placeholder="username">
<label for="floatingInput">Username</label>
</div>
<div class="form-floating">
<input type="password" name='password' class="form-control" id="floatingPassword" placeholder="Password">
<label for="floatingPassword">Password</label>
</div>

<div class="checkbox mb-3">
<label>
<input name='remember' type="checkbox" value="true"> Remember me
</label>
</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
</form>
</main>
</body>
</html>

0 comments on commit 58ff5f9

Please sign in to comment.