Add authentication and hide private info
This commit is contained in:
parent
583881927b
commit
d02b6eedae
|
@ -36,6 +36,19 @@ version = "0.5.0"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
Flask = "*"
|
Flask = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Simple integration of Flask and WTForms."
|
||||||
|
name = "flask-wtf"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.14.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Flask = "*"
|
||||||
|
WTForms = "*"
|
||||||
|
itsdangerous = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Various helpers to pass data to untrusted environments and back."
|
description = "Various helpers to pass data to untrusted environments and back."
|
||||||
|
@ -118,8 +131,24 @@ version = "1.0.1"
|
||||||
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||||
watchdog = ["watchdog"]
|
watchdog = ["watchdog"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A flexible forms validation and rendering library for Python web development."
|
||||||
|
name = "wtforms"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2.3.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
email = ["email-validator"]
|
||||||
|
ipaddress = ["ipaddress"]
|
||||||
|
locale = ["Babel (>=1.3)"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "27f45d27293b2411af59f2d60572508a045af3d996d09cd45001f73388f721fd"
|
content-hash = "4ecee2e19e0576a331bb8c4e9f0de82f74a31d41c5daafed8dce5eb73b541281"
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
|
|
||||||
|
@ -136,6 +165,10 @@ flask-login = [
|
||||||
{file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"},
|
{file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"},
|
||||||
{file = "Flask_Login-0.5.0-py2.py3-none-any.whl", hash = "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"},
|
{file = "Flask_Login-0.5.0-py2.py3-none-any.whl", hash = "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"},
|
||||||
]
|
]
|
||||||
|
flask-wtf = [
|
||||||
|
{file = "Flask-WTF-0.14.3.tar.gz", hash = "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720"},
|
||||||
|
{file = "Flask_WTF-0.14.3-py2.py3-none-any.whl", hash = "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2"},
|
||||||
|
]
|
||||||
itsdangerous = [
|
itsdangerous = [
|
||||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||||
|
@ -248,3 +281,7 @@ werkzeug = [
|
||||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||||
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
|
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
|
||||||
]
|
]
|
||||||
|
wtforms = [
|
||||||
|
{file = "WTForms-2.3.3-py2.py3-none-any.whl", hash = "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c"},
|
||||||
|
{file = "WTForms-2.3.3.tar.gz", hash = "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c"},
|
||||||
|
]
|
||||||
|
|
|
@ -8,13 +8,15 @@ authors = ["Your Name <you@example.com>"]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
flask = "^1.1.2"
|
flask = "^1.1.2"
|
||||||
flask-login = "^0.5.0"
|
flask-login = "^0.5.0"
|
||||||
|
flask_wtf = "^0.14.3"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
mypy = "^0.800"
|
mypy = "^0.800"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
redstring-check = "redstring.parser:main"
|
redstring-check = "redstring.parser:main"
|
||||||
redstring-server = "redstring.server:main"
|
redstring-server = "redstring.server:cli"
|
||||||
|
redstring-backend = "redstring.server:wsgi"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["poetry>=0.12"]
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
Logic for serving a collection of documents through a web frontend.
|
Logic for serving a collection of documents through a web frontend.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
abort,
|
abort,
|
||||||
|
@ -14,6 +17,10 @@ from flask import (
|
||||||
safe_join,
|
safe_join,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
|
from flask_login import login_user, logout_user, login_required, LoginManager, UserMixin
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import PasswordField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
from redstring.library import generate_index_document, generate_default_document
|
from redstring.library import generate_index_document, generate_default_document
|
||||||
from redstring.parser import (
|
from redstring.parser import (
|
||||||
|
@ -28,9 +35,28 @@ from redstring.parser import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Admin(UserMixin):
|
||||||
|
def get_id(self):
|
||||||
|
return 'admin'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
password = PasswordField('Password', validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
CONFIG_ENVVAR = 'REDSTRING_CONFIG'
|
CONFIG_ENVVAR = 'REDSTRING_CONFIG'
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.secret_key = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.login_view = 'login'
|
||||||
|
login_manager.init_app(app)
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return Admin() if user_id == 'admin' else None
|
||||||
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
|
@ -54,7 +80,25 @@ def document(document_id):
|
||||||
return render_template('doc.jinja', document=doc, index=False)
|
return render_template('doc.jinja', document=doc, index=False)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/login/', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.password.data == app.config['login']:
|
||||||
|
login_user(Admin())
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
return render_template('login.jinja', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout/')
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/new/', methods=['GET'])
|
@app.route('/new/', methods=['GET'])
|
||||||
|
@login_required
|
||||||
def new():
|
def new():
|
||||||
document_id = 'new'
|
document_id = 'new'
|
||||||
new_doc = generate_default_document(document_id)
|
new_doc = generate_default_document(document_id)
|
||||||
|
@ -65,6 +109,7 @@ def new():
|
||||||
|
|
||||||
|
|
||||||
@app.route('/edit/<document_id>', methods=['GET', 'POST'])
|
@app.route('/edit/<document_id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
def edit(document_id):
|
def edit(document_id):
|
||||||
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
||||||
|
|
||||||
|
@ -144,15 +189,28 @@ def edit(document_id):
|
||||||
return render_template('edit.jinja', document=doc, index=False)
|
return render_template('edit.jinja', document=doc, index=False)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def read_config(path):
|
||||||
|
with open(path) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def cli():
|
||||||
parser = argparse.ArgumentParser(description="Run the redstring server.")
|
parser = argparse.ArgumentParser(description="Run the redstring server.")
|
||||||
parser.add_argument("--config", help="Config file path.")
|
parser.add_argument("--config", help="Config file path.")
|
||||||
|
parser.add_argument("--debug", action="store_true")
|
||||||
|
parser.add_argument("--port", type=int, default=5000)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config_path = args.config or os.environ.get(CONFIG_ENVVAR) or '/etc/redstring.conf'
|
config_path = args.config or os.environ.get(CONFIG_ENVVAR) or '/etc/redstring.conf'
|
||||||
# TODO
|
config = read_config(config_path)
|
||||||
document_folder = args.config # TODO
|
app.config['root'] = config['root']
|
||||||
|
app.config['login'] = config['login']
|
||||||
|
app.run(debug=args.debug, port=args.port)
|
||||||
|
|
||||||
app.config['root'] = document_folder
|
|
||||||
|
|
||||||
app.run(debug=True, port=5000)
|
def wsgi():
|
||||||
|
config_path = os.environ.get(CONFIG_ENVVAR) or '/etc/redstring.conf'
|
||||||
|
config = read_config(config_path)
|
||||||
|
app.config['root'] = config['root']
|
||||||
|
app.config['login'] = config['login']
|
||||||
|
return app
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'base.jinja' %}
|
{% extends 'base.jinja' %}
|
||||||
|
|
||||||
{% set page_title = document.get_tag_value('title', document.get_tag('id').value) -%}
|
{% set page_title = document.get_tag_value('title', document.get_tag_value('id', 'redstring')) -%}
|
||||||
{% set page_summary = document.get_tag_value('summary', '') %}
|
{% set page_summary = document.get_tag_value('summary', '') %}
|
||||||
|
|
||||||
{% block page_scripts %}
|
{% block page_scripts %}
|
||||||
|
@ -38,14 +38,14 @@ window.onload = function () {
|
||||||
<div id="{{ tab.name }}-page" class="tab-page{% if selected %} tab-page-selected{% endif %}">
|
<div id="{{ tab.name }}-page" class="tab-page{% if selected %} tab-page-selected{% endif %}">
|
||||||
<table id="{{ tab.name }}-page-table" class="page-table">
|
<table id="{{ tab.name }}-page-table" class="page-table">
|
||||||
{% for tag in tab.tags %}
|
{% for tag in tab.tags %}
|
||||||
{%- if not tag.options.private -%}
|
{%- if not tag.options.private or current_user.is_authenticated -%}
|
||||||
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
||||||
<td>{{ tag.name }}</td>
|
<td>{{ tag.name }}</td>
|
||||||
<td>{{ make_tag_value(tag) }}</td>
|
<td>{{ make_tag_value(tag) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% for subtag in tag.subtags %}
|
{% for subtag in tag.subtags %}
|
||||||
{%- if not tag.options.private and not subtag.options.private -%}
|
{%- if (not tag.options.private and not subtag.options.private) or current_user.is_authenticated -%}
|
||||||
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
||||||
<td>{% if loop.last %}└{% else %}├{% endif %} {{ subtag.name }}</td>
|
<td>{% if loop.last %}└{% else %}├{% endif %} {{ subtag.name }}</td>
|
||||||
<td>{{ make_tag_value(subtag) }}</td>
|
<td>{{ make_tag_value(subtag) }}</td>
|
||||||
|
@ -69,15 +69,23 @@ window.onload = function () {
|
||||||
{# TODO: tab.priority support #}
|
{# TODO: tab.priority support #}
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div id="tabs">
|
<div id="tabs">
|
||||||
|
{% if index and current_user.is_authenticated %}
|
||||||
|
<div id="logout" class="tab tab-right"><a href="/logout/">logout</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{%- for tab in document -%}
|
{%- for tab in document -%}
|
||||||
{%- if not tab.options.private -%}
|
{%- if not tab.options.private or current_user.is_authenticated-%}
|
||||||
{{ make_content_tab(tab, loop.first) }}
|
{{ make_content_tab(tab, loop.first) }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
||||||
{%- if not index -%}
|
{%- if not index -%}
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
<div id="edit" class="tab tab-right"><a href="/edit/{{ document.get_tag_value('id') }}">edit</a></div>
|
<div id="edit" class="tab tab-right"><a href="/edit/{{ document.get_tag_value('id') }}">edit</a></div>
|
||||||
|
{% endif %}
|
||||||
<div id="index" class="tab tab-right"><a href="/index/">index</a></div>
|
<div id="index" class="tab tab-right"><a href="/index/">index</a></div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% for tab in document -%}
|
{% for tab in document -%}
|
||||||
{{ make_tab_page(tab, loop.first) }}
|
{{ make_tab_page(tab, loop.first) }}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends 'base.jinja' %}
|
||||||
|
|
||||||
|
{% set page_title = 'Login' -%}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div id="tabs">
|
||||||
|
<div id="tab-login" class="tab tab-down">login</div>
|
||||||
|
<div id="index" class="tab tab-right"><a href="/index/">index</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-page tab-page-selected">
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>{{ form.password.label }}<br>{{ form.password(size=32) }}
|
||||||
|
{% for error in form.password.errors %}
|
||||||
|
<br><span style="color: #ff0000">{{ error }}</span>
|
||||||
|
{% endfor %}</p>
|
||||||
|
<p>{{ form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock page_content %}
|
Loading…
Reference in New Issue