A web application server for serving an online KüA-Plan, written in Rust, using a Postgresql database and providing a server-side dynamic web user interface and a REST API.
Originally, the online-kueaplan was designed with a client-side frontend / Progressive Web Application to provide improved offline functionality. The server-side web application would only serve static files and provide the REST api for modifying entries in the database and synchronizing the frontend's state with the latest changes in the database. The client-side frontend would either be based on a modern JavaScript/TypeScript framework or a Rust web frontend framework (using WASM).
Thus, a comprehensive REST API has been defined and implemented. In addition, the Rust structure definitions corresponding to the JSON object schemas of the API specification have been declared in a separate Rust crate, to be reused in the client-side frontend code.
In the meantime, due to immediate need, a very simplistic online KüA-Plan application has been implemented and used at SommerAkademie 2024: The simple-kueaplan. It is implemented in Python, uses server-side template rendering with Jinja and has no database backend, but is fed from YAML files in a Git repository. This system was well received by the event's participants, but the YAML files were cumbersome to use for the orgas.
In order to get the database-backed Rust KüA-Plan application to a usable state as fast as possible, a server-side user interface has been added, using the fundamental UI design of the simple-kueaplan.
The client-side frontend may be added any time later. Regardless, the existing REST API can be used for programmatic interaction with the KüA-Plan application.
The main server-side web application is a self-contained Rust crate kueaplan_server, living in the server/ directory.
It uses the Diesel framework for database access, Actix Web as the web frontend framework and Askama for HTML template rendering.
The user interface is based on Bootstrap 5.3. For some pages of the user interface, client-side helper functions have been added, using vanilla JavaScript. Most notably, TomSelect is used for creating multi-select inputs.
The static frontend artifacts from the server/static/ are embedded into the compiled server executable, to form a self-contained executable.
Same holds for the database migration SQL files from server/migrations/.
The Askama template files in server/templates are converted to Rust code at compile time by Askama's derive macro.
The application's Rust code is split into the following major modules:
data_store: abstract datastore interface, Rust models of the database entities and Diesel-based Postgresql implementation of the interfaceweb.api: REST API endpointsweb.ui: manifold framework and helper code for the building the user interfaceweb.ui.endpoints: server-side web user interface endpointscli: command-line interface commands (other thanserve).
The formal OpenAPI specification of the REST API, including JSON schemas, can be found in etc/spec.
Rust representations of the JSON schemas, annotated with the appropriate Serde attributes for use with serde_json are implemented in the separate Rust crate kueaplan_api_types in api_types/.
This crate is used by the server application for serializing and deserializing data in the REST API endpoints.
The kueaplan_server requires access to a single Postgresql database, on which it has CONNECT, USAGE, SELECT, INSERT, UPDATE and DELETE privileges. Running the database schema migrations (see below) requires CREATE, TRIGGER, EXECUTE and REFERENCES privileges in addition. The database should be created with an appropriate unicode encoding and collation.
Database schema migrations (including initial creationg) are not automatically executed at application startup. This allows to use a separate database user/role with reduced privileges for operation. However, the application checks the migration state at startup and refuses to start with an outdated database schema.
Setup example on a hosted Postgresql server:
CREATE DATABASE "kueaplan"
WITH OWNER "kueaplan"
ENCODING 'UTF8'
LC_COLLATE = 'de_DE.UTF-8'
LC_CTYPE = 'de_DE.UTF-8';The kueaplan_server is configured through environment variables.
Alternatively, the environment variables can be defined in a .env file, localed in the server's working directory.
The following environment variables are available.
| envrionment variable | example | description |
|---|---|---|
| DATABASE_URL | postgresql://username:password@localhost/databasename | (mandatory!) |
| SECRET | (mandatory!) true-random secret string, only known to the sever, which is used for symmetric cryptography | |
| LISTEN_PORT | 9000 | (mandatory!) HTTP listening port |
| LISTEN_ADDRESS | ::1 | (mandatory!) HTTP listen address. Use :: for listening on all IPv4 and IPv6 interfaces. |
| ADMIN_NAME | Anton Administrator | (mandatory!) displayed name of the admin of this instance (for error messages, etc.) |
| ADMIN_EMAIL | mail@example.com | (mandatory!) displayed email address of the admin of this instance (for error messages, etc.) |
| API_CORS_ALLOW_ANY_ORIGIN | true | enable Cross-Origin Ressource Sharing for the REST API from any origin domain (value must be 'true', '1', 'yes' or 'on') |
To start the server, run
kueaplan_server serveThe server runs as a simple (foreground) command line application. There is no "daemon mode", etc. It can be stopped gracefully with a simple SIGTERM. Use your favorite service manager to run it as a daemon service (recommanded: systemd. See below).
The kuaeplan_server has built-in functionality for initializing and the database schema and updating it to the current version.
However, these database schema migrations are not automatically executed at application startup.
Instead, the application checks the migration state at startup and refuses to start with an outdated database schema.
To execute all pending database schema migrations, run
kueaplan_server migrate-databaseThis command requires the configuration environment variables to be provided as environment or .env file (see above).
In particular, it uses the DATABASE_URL to select the Postgresql database to be migrated.
Management of events is only possible via the command-line interface, not in the web UI or the REST API. This is also true for managing admin passphrases of an event, which are required to authenticated for changing an event's metadata and managing user/orga passphrases.
For this purpose, the kuealan_server has the following additional command-line commands:
| Command | Comment |
|---|---|
kueplan_server event list |
|
kueplan_server event create |
(interactive) |
kueplan_server event delete <EVENT_ID_OR_SLUG> |
(interactive) |
kueplan_server event import <PATH> |
expects JSON format as exported by event export below |
kueplan_server event export <EVENT_ID_OR_SLUG> <PATH> |
|
kueplan_server passphrase list <EVENT_ID_OR_SLUG> |
|
kueplan_server passphrase create <EVENT_ID_OR_SLUG> |
(interactive) |
kueplan_server passphrase delete <EVENT_ID_OR_SLUG> <PASSPHRASE_ID> |
(interactive) |
All of these commands requires the configuration environment variables to be provided as environment or .env file (see above).
In particular, they use the DATABASE_URL to select the Postgresql database to be migrated.
For production use, it is recommended to run the kueaplan_server as a systemd service.
The following service file may be a good starting point.
It assumes that the kueaplan_server executable is placed in /usr/local/bin and there is a specific user and group kueaplan for running the server.
In addition, there needs to be a configuration file, defining the environment variables at /etc/kueaplan/env.
(Make sure to chose appropriate access permissions on that file, since it contains the application SECRET and probably the database user password.)
[Unit]
Description=Online KüA-Plan web application server
After=network.target postgresql.service
Requires=network.target postgresql.service
[Service]
Type=simple
User=kueaplan
Group=kueaplan
ExecStart=/usr/local/bin/kueaplan_server serve
EnvironmentFile=/etc/kueaplan/env
Restart=always
[Install]
WantedBy=multi-user.targetThe kueaplan_server can be run behind a HTTP reverse proxy server, e.g. for virtual host discrimination and TLS termination.
For the apache web server, the following virtual host configuration can be used as a starting point:
<VirtualHost *:443>
ServerName kueaplan.de
ServerAdmin mail@example.com
ProxyRequests Off
ProxyPreserveHost on
AllowEncodedSlashes NoDecode
ProxyPass / http://localhost:9000/ nocanon
ProxyPassReverse / http://localhost:9000/
RequestHeader set X-Forwarded-Proto https
# TODO SSL setup
</VirtualHost>
The kueaplan_server logs authentication errors at warning level, including the client's IP address. (Thanks to the X-Forwarded-For header, this does even work behind the reverse proxy.) This allows us to mitigate brute-force attacks on the authentication mechanism (esp. automated testing of passphrases), using fail2ban.
The following fail2ban config files can be used as starting point, assuming the systemd service unit, as shown above:
/etc/fail2ban/filter.d/kueaplan.conf:
[Definition]
failregex = kueaplan_server::[^ ]+ HTTP \d+ (authentication failed|invalid session token). Client: <<HOST>>
ignoreregex =
[Init]
journalmatch = _SYSTEMD_UNIT=kueaplan.service
/etc/fail2ban/jail.d/kueaplan.conf:
[kueaplan]
enabled = true
backend = systemd
filter = kueaplan
port = 80,443
maxretry = 15
bantime = 3600
findtime = 3600
Usually the Same-Origin Policy (SOP), enforced by web browsers, restricts access to web resources at different domains than the current site's origin via JavaScript. This prevents external web-browser-based tools, served on other domains or as local files, from accessing the KüA-Plan's REST API.
And in the case of the REST API, the cross-origin request usually doesn't impose a security issue: The REST API does not session authentication via session cookies, auth header, IP address or other information implicitly provided by a user's browser/computer. Instead, the custom authentication header must be explicitly provided with every API request. So, a malicious website cannot access any more information from the API via JavaScript in an authenticated user's web browser than the attacker could access by directly connecting to the API server. Thus, we can safely allow CORS on the REST API from every origin site.
This reasoning is only valid for public instances of the kueaplan_server without additional access control. When the online KüA-Plan is only served in an internal network or VPN, or additional session-based authentication is implemented, these additional security measures could be circumvented via a cross-origin request by a malicious site. We leave it up to the site admin to determine whether this might be an issue.
Thus, CORS for the REST API is not enabled by default, but must be explicitly enabled by setting the API_CORS_ALLOW_ANY_ORIGIN environment variable to true (or 1 or yes or on).
This software can be used, modified and published under the terms of the GNU Affero General Public License 3.0.
SPDX-Identifier: AGPL-3.0-or-later
When contributing to this software or forking this software, make sure to adapt the NOTICE file and the about.html template accordingly.
You should keep intact the existing copyright notices.
Besides other tools, the OpenAPI Generator can be used to generate Code or documentation from the API specification files.
This can be used to check the specification's syntax and semantics.
For generating an API documentation in HTML format, the command in etc/spec/gendocs.sh can be used.
- Rust code must be properly formatted, using rustfmt's default settings.
Run
cargo fmtto fix formatting before committing! - All Python code must be properly formatted, using ruff's formatter, with the settings specified in ruff.toml.
Run
ruff formatin the repository root to ensure proper formatting of all Python files before committing! - All files should be free of trailing whitespace and end with a single line-feed.
- Rust code must pass linting with
clippywithout any errors or warnings. Runcargo clippy --all-targets --all-features -- --deny warningsto check for issues. - All Python code must pass ruff linting, according to the config in ruff.toml.
The system tests in
tests/must pass MyPy typechecking, according to the config in tests/mypy.ini. Run the following commands to check for issues:ruff check cd tests uv run mypy
The pre-commit framework link can be used to automatically check and fix the code formatting before committing. To use it:
- Install pre-commit (e.g. using
uv tool install pre-commit) - Run
pre-commit installin this repository to install the Git Hooks according to the.pre-commit-config.yaml.
Running the Rust unittests:
cargo testRunning the system tests, written in Python: See tests/README.md.
Currently, the Online-KüA-Plan project follows a "no-AI policy": We want to keep this repository free of content generated with Large Language Models (LLMs). This allows us to ensure that all source code (and auxiliary material such as documentation) of the project is original and appropriately attributed to the original authors, and to feel confident about the applicabilty of the license.
That means: Please don't contribute any material generated by Large Language Models, using chatbots, coding agents or similar tools. This includes, but is not limited to, source code, documentation and commit messages.
This policy may be subject to future changes.