Compare commits
9 Commits
74618bdaf7
...
b5374abda9
Author | SHA1 | Date | |
---|---|---|---|
b5374abda9 | |||
7f7f40712d | |||
58ebbf601b | |||
d0cb54350f | |||
9a6e439cda | |||
![]() |
a6ce11b10b | ||
![]() |
79d59dc29c | ||
![]() |
79ed648c7b | ||
![]() |
adea2b1545 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.swp
|
*.swp
|
||||||
jon/config.json
|
jon/config.json
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
@ -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
41
jon/auth.py
Normal 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
65
jon/static/jon.css
Normal 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;
|
||||||
|
}
|
36
jon/templates/auth/denied.html
Normal file
36
jon/templates/auth/denied.html
Normal 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>
|
@ -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>
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user