Compare commits

...

9 Commits

Author SHA1 Message Date
b5374abda9 fixup! Fix typo 2023-08-20 13:43:17 +02:00
7f7f40712d Fix typo 2023-08-20 12:03:07 +02:00
58ebbf601b Add template for unauthenticated response 2023-08-20 11:51:51 +02:00
d0cb54350f Add a comment and code style stuff 2023-08-20 11:37:17 +02:00
9a6e439cda Use session to store authentication info instead of cookie 2023-08-20 11:29:20 +02:00
Shirkanesi
a6ce11b10b Removed flask-login in favour of custom solution 2023-08-20 00:58:02 +02:00
Shirkanesi
79d59dc29c Add common names for venv to gitignore 2023-08-19 00:59:53 +02:00
Shirkanesi
79ed648c7b More work on auth 2023-08-19 00:59:37 +02:00
Shirkanesi
adea2b1545 Initial work on auth-tokens 2023-08-19 00:43:12 +02:00
7 changed files with 163 additions and 72 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
__pycache__ __pycache__
*.swp *.swp
jon/config.json jon/config.json
venv/
.venv/

View File

@ -1,9 +1,11 @@
import inspect import inspect
import json import json
import sys
from flask import Flask, render_template from flask import Flask, render_template
from . import ( from . import (
auth,
db, db,
entry, entry,
inventory, inventory,
@ -21,6 +23,12 @@ def create_app():
db.init_app(app) db.init_app(app)
# This function denies every request until `auth.ACCESS_TOKEN`
# is passed using `?token=` to authenticate the session.
@app.before_request
def before_req_fun():
return auth.before_request()
@app.context_processor @app.context_processor
def utility_processor(): def utility_processor():
return dict(inspect.getmembers(template_utils, inspect.isfunction)) return dict(inspect.getmembers(template_utils, inspect.isfunction))
@ -28,8 +36,12 @@ def create_app():
app.register_blueprint(location.bp) app.register_blueprint(location.bp)
app.register_blueprint(inventory.bp) app.register_blueprint(inventory.bp)
app.register_blueprint(entry.bp) app.register_blueprint(entry.bp)
app.register_blueprint(auth.bp)
@app.route("/") @app.route("/")
def index(): def index():
return render_template("index.html") return render_template("index.html")
print(f"Jon started. Token: {auth.ACCESS_TOKEN}", file=sys.stderr)
return app return app

41
jon/auth.py Normal file
View File

@ -0,0 +1,41 @@
import random
import string
from flask import Blueprint, request, redirect, render_template, session
bp = Blueprint("auth", __name__, url_prefix="/auth")
ACCESS_TOKEN = "".join(random.choice(string.ascii_lowercase) for i in range(64))
ALLOWED_PATHS = [
"/favicon.ico",
"/static/jon.css"
]
def before_request():
"""
If the correct token query parameter is passed along with any request,
we mark this session authenticated by setting `session["authenticated"]`.
Unless the session is authenticated, all requests result in a 403 FORBIDDEN.
"""
if "token" in request.args:
if request.args["token"] == ACCESS_TOKEN:
session["authenticated"] = ()
# Reload the page without query parameters
return redirect(request.path)
# Don't deny any paths in `ALLOWED_PATHS`
if request.path in ALLOWED_PATHS:
return
if not "authenticated" in session:
return render_template("auth/denied.html"), 403
@bp.get("/logout")
def logout():
session.pop("authenticated", None)
return redirect("/")

65
jon/static/jon.css Normal file
View File

@ -0,0 +1,65 @@
html {
font-family: Helvetica, sans-serif;
}
h1 {
margin: 0;
}
nav > ul {
padding-left: 0;
}
nav > ul > li {
display: inline-block;
list-style: none;
}
nav > ul > li + li:before {
content: ' · ';
}
.current-page > a {
position: relative;
}
.current-page > a:after {
content: '↓';
font-size: 0.8em;
box-sizing: border-box;
position: absolute;
display: block;
right: 50%;
top: -1em;
width: 1em;
text-align: center;
margin-right: -0.5em;
animation: wiggle 0.8s ease-in-out 0s infinite;
/* animation-direction: alternate; */
}
.--align-left {
text-align: left;
}
.--align-right {
text-align: right;
}
.--centered {
text-align: center;
}
@keyframes wiggle {
0%, 100% { margin-top: 0; }
50% { margin-top: -0.5em; }
/* 100% { transform: rotate(1turn); } */
}
table {
border-spacing: .5em 0;
}
th {
font-size: .8em;
}
@media print {
body {
font-size: 8px;
}
}
.form-input > label {
font-size: .8em;
}
.form-input > input:not([type=radio]),
.form-input > select {
display: block;
}

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jon · not authenticated</title>
<link rel="stylesheet" href="/static/jon.css">
</head>
<body>
<header>
<h1>jon</h1>
{% if config.DEBUG %}
<details>
<summary><code>config</code></summary>
<pre>{% for key, value in config.items() %}{{ key }} = {{ value }}
{% endfor %}</pre>
</details>
{% endif %}
</header>
<main>
<p>
Damit kein Schabernack getrieben wird müssen wir sicherstellen, dass du die Person bist die jon ausgeführt hat.
Gib unten das Token ein, welches jon beim Starten ausgegeben hat.
</p>
<form method="GET">
<div class="form-input">
<label for="token">Token</label>
<input type="password" name="token" placeholder="Token" id="token">
</div>
<button type="submit">Authentifizieren</button>
</form>
</main>
</body>
</html>

View File

@ -3,83 +3,17 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>jon</title> <title>jon</title>
<style> <link rel="stylesheet" href="/static/jon.css">
html {
font-family: Helvetica, sans-serif;
}
h1 {
margin: 0;
}
nav > ul {
padding-left: 0;
}
nav > ul > li {
display: inline-block;
list-style: none;
}
nav > ul > li + li:before {
content: ' · ';
}
.current-page > a {
position: relative;
}
.current-page > a:after {
content: '↓';
font-size: 0.8em;
box-sizing: border-box;
position: absolute;
display: block;
right: 50%;
top: -1em;
width: 1em;
text-align: center;
margin-right: -0.5em;
animation: wiggle 0.8s ease-in-out 0s infinite;
/* animation-direction: alternate; */
}
.--align-left {
text-align: left;
}
.--align-right {
text-align: right;
}
.--centered {
text-align: center;
}
@keyframes wiggle {
0%, 100% { margin-top: 0; }
50% { margin-top: -0.5em; }
/* 100% { transform: rotate(1turn); } */
}
table {
border-spacing: .5em 0;
}
th {
font-size: .8em;
}
@media print {
body {
font-size: 8px;
}
}
.form-input > label {
font-size: .8em;
}
.form-input > input:not([type=radio]),
.form-input > select {
display: block;
}
</style>
</head> </head>
<body> <body>
<header> <header>
<h1>jon</h1> <h1>jon</h1>
<nav> <nav>
<ul> <ul>
<li {{ "class=current-page" if request.path == "/" else "" }}><a href="/">Home</a></li> <li{{ " class=current-page" if request.path == "/" else "" }}><a href="/">Home</a></li>
<li {{ "class=current-page" if request.path.startswith("/inventory") else "" }}><a href="/inventory">Inventar</a></li> <li{{ " class=current-page" if request.path.startswith("/inventory") else "" }}><a href="/inventory">Inventar</a></li>
<li {{ "class=current-page" if request.path.startswith("/entry") else "" }}><a href="/entry">Eintragen</a></li> <li{{ " class=current-page" if request.path.startswith("/entry") else "" }}><a href="/entry">Eintragen</a></li>
<li {{ "class=current-page" if request.path.startswith("/location") else "" }}> <li{{ " class=current-page" if request.path.startswith("/location") else "" }}>
<a href="/location"> <a href="/location">
{% if "location" not in session %} {% if "location" not in session %}
Raum wählen Raum wählen
@ -88,6 +22,7 @@
{% endif %} {% endif %}
</a> </a>
</li> </li>
<li{{ " class=current-page" if request.path.startswith("/auth/logout") else "" }}><a href="/auth/logout">Logout</a></li>
</ul> </ul>
</nav> </nav>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<form method="POST" action="."> <form method="POST">
<select name="location_id"> <select name="location_id">
<option value="" {{ "selected" if "location" not in session else ""}}>-</option> <option value="" {{ "selected" if "location" not in session else ""}}>-</option>
{% for location in locations %} {% for location in locations %}