diff --git a/pdm.lock b/pdm.lock index db2f26c0657aaa4fce64d9ec68bb2854b96a79c0..0446e970d31f12e4e6d66ad1e5565eaff4986f1d 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:63f5da6995402ed21545e227c2419cafb694852ed08e87053164e13708530363" +content_hash = "sha256:ffa1c8b51d11a5b46e0f1e2240677b7ca08edc5983c2b8780b0ba2c0341a1862" [[metadata.targets]] requires_python = ">=3.12" @@ -457,6 +457,21 @@ files = [ {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] +[[package]] +name = "flask-multipass" +version = "0.5.5" +requires_python = "~=3.8" +summary = "A pluggable solution for multi-backend authentication with Flask" +groups = ["default"] +dependencies = [ + "blinker", + "flask", +] +files = [ + {file = "flask_multipass-0.5.5-py3-none-any.whl", hash = "sha256:ee7cb3d3e2b92ca7864ac824ae6b3943091c58bb01678b55707dde069209f59c"}, + {file = "flask_multipass-0.5.5.tar.gz", hash = "sha256:2ea8a0a8b7171e40a5fce575e99ec796d6af84888970ab44ecb2ab06ef89d86c"}, +] + [[package]] name = "flask-shell-ipython" version = "0.5.3" diff --git a/pyproject.toml b/pyproject.toml index f593f05708023c52865baee11bf620e9b8e17d3c..95d9616954fc0eae38b989a80056e01e2db5f2d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "flask-weasyprint", "python-webpack-boilerplate", "pyipp @ git+https://github.com/ctalkington/python-ipp", + "Flask-Multipass>=0.5.5", ] [tool.pdm.dev-dependencies] diff --git a/schilder2000/__init__.py b/schilder2000/__init__.py index df234a283fdcb3e32a1292b07ad48fca12767d11..9460218b6c3ee9702184024492f41bb8426118ae 100644 --- a/schilder2000/__init__.py +++ b/schilder2000/__init__.py @@ -1,14 +1,16 @@ from pathlib import Path +from flask_multipass import Multipass from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect from webpack_boilerplate.config import setup_jinja2_ext -from .helpers import Flask +from .helpers import Flask, identity_handler, require_login db = SQLAlchemy() csrf = CSRFProtect() +multipass = Multipass() def create_app(): @@ -25,6 +27,12 @@ def create_app(): csrf.init_app(app) + app.config["MULTIPASS_LOGIN_FORM_TEMPLATE"] = "login.html.j2" + app.config["MULTIPASS_LOGIN_SELECTOR_TEMPLATE"] = "login_select.html.j2" + app.config["MULTIPASS_SUCCESS_ENDPOINT"] = "views.index" + multipass.identity_handler(identity_handler) + multipass.init_app(app) + app.config.update( { "WEBPACK_LOADER": { @@ -38,12 +46,14 @@ def create_app(): from . import views + views.bp.before_request(require_login) app.register_blueprint(views.bp) from . import instance instance.bp.static_folder = instance_path / "static" instance.bp.template_folder = instance_path / "templates" + instance.bp.before_request(require_login) app.register_blueprint(instance.bp) return app diff --git a/schilder2000/helpers.py b/schilder2000/helpers.py index 1a6a5752bd1ba66d9adb2708a4848347af227971..653df7f850d739f2a537a72a95b3607794393b07 100644 --- a/schilder2000/helpers.py +++ b/schilder2000/helpers.py @@ -4,11 +4,16 @@ from flask import ( Flask as _Flask, Blueprint as FlaskBlueprint, current_app, + redirect, render_template, + url_for, + session, ) from jinja2 import BaseLoader, ChoiceLoader, PrefixLoader, Template +from flask_multipass import IdentityInfo + class Blueprint(FlaskBlueprint): def real_template_name( @@ -86,3 +91,16 @@ def get_template_attribute( return getattr(mod, attribute) else: return getattr(mod, attribute, default) + + +def identity_handler(identity_info: IdentityInfo): + session["identity"] = dict( + identifier=identity_info.identifier, + provider=identity_info.provider.name, + secure_login=identity_info.secure_login, + data=identity_info.data, + ) + +def require_login(): + if "identity" not in session: + return redirect(url_for("login")) diff --git a/schilder2000/templates/login.html.j2 b/schilder2000/templates/login.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..452343c3589f52e256470125706d91e5738b9f60 --- /dev/null +++ b/schilder2000/templates/login.html.j2 @@ -0,0 +1,15 @@ +{% extends "_base.html.j2" %} + +{% block title -%} + Anmeldung +{%- endblock title %} + +{% block main -%} + <form method="post"> + {%- for field in form -%} + {{ render_field(field) }} + {%- endfor -%} + + <input type="submit" value="Anmelden" /> + </form> +{%- endblock main %} diff --git a/schilder2000/templates/login_select.html.j2 b/schilder2000/templates/login_select.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..a3cc5367fcf8b067f1c34844e110d34bc4aa21f4 --- /dev/null +++ b/schilder2000/templates/login_select.html.j2 @@ -0,0 +1,14 @@ +{% extends "_base.html.j2" %} + +{% block title -%} + Anmeldung +{%- endblock title %} + +{% block main -%} + Available login providers: + <ul> + {% for provider in providers|sort(attribute='title') %} + <li><a href="{{ url_for(login_endpoint, provider=provider.name, next=next) }}">{{ provider.title }}</a></li> + {% endfor %} + </ul> +{%- endblock %} diff --git a/schilder2000/views.py b/schilder2000/views.py index fa26e05842519cc40f3df22b13901b14ba46cf7f..dfa02925884af9ecdce957bdb9fc20d255505b93 100644 --- a/schilder2000/views.py +++ b/schilder2000/views.py @@ -1,4 +1,4 @@ -from . import db +from . import db, multipass from .instance import list_templates, list_images from .models import Schild, SchildForm, PrintForm @@ -92,3 +92,7 @@ def create(): "schild.html.j2", form=form, ) + +@bp.route("/logout") +def logout(): + return multipass.logout(url_for(".index"), clear_session=True)