Files
flowers/README.md
T

143 lines
5.6 KiB
Markdown
Raw Normal View History

2026-07-18 19:36:23 +02:00
# Flowers
2025-03-25 20:32:26 +00:00
2026-07-18 20:27:19 +02:00
Flowers is a small self-hosted credential vault built with Flask and MariaDB/MySQL. This version rewrites the legacy server and browser UI while intentionally preserving its on-disk data contract.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## Existing database compatibility
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
No data migration is required. The rewritten application continues to use:
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
- the `Flowers` database;
- one database account and table named `<username>_` per vault;
- the existing `organization`, `myID`, `myName`, `mySecret`, `dateCreated`, and `deleted` columns;
- the historical password transformation `(password * 10)[:43] + "="` for both the database password and Fernet key;
- Fernet ciphertext in the three protected columns;
- append-only edits, where the newest timestamp is the visible version; and
- `deleted = 1` for deactivation.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
The old form-encoded `POST /app` endpoint is also retained. Its `list` and `flower` properties remain JSON strings inside the outer JSON response for existing client compatibility.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
The legacy key derivation is preserved only because changing it would make existing data unreadable. It is not a modern password KDF. A future KDF upgrade requires an explicit, tested data migration rather than an in-place code change.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## What changed
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
- All stored values are passed to MariaDB as query parameters.
- Usernames are validated before being used as table/account identifiers.
- Database and runtime settings can be supplied through environment variables.
- Browser forms have CSRF protection.
- Database credentials are stored in server memory behind an opaque session token, not in Flask's signed browser cookie.
- Browser sessions expire after ten minutes of inactivity.
- Failed-login state is JSON rather than unsafe pickle data.
- The browser interface is responsive and no longer depends on jQuery, Pure CSS, or remote assets.
- The Flask application factory and WSGI entry point both work.
- Compatibility tests cover legacy encryption, reads, writes, listing, and the old API format.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## Requirements
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
- Python 3.10 or newer
- MariaDB or MySQL on the configured host
- the packages in `requirements.txt`
2026-07-18 19:36:23 +02:00
```bash
python3 -m venv .venv
source .venv/bin/activate
2026-07-18 20:27:19 +02:00
pip install -r requirements.txt
2026-07-18 19:36:23 +02:00
```
2026-07-18 20:27:19 +02:00
## Configuration
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
Local secrets are loaded from the git-ignored `secrets.yaml` file:
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
```yaml
flask:
secret_key: "a-long-random-value"
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
database:
admin_password: "the-provisioning-account-password"
2026-07-18 19:36:23 +02:00
```
2026-07-18 20:27:19 +02:00
Use `secrets.example.yaml` as a template. `FLOWERS_SECRETS_FILE` can point to a different file. Secret environment variables take precedence over YAML, which is useful for containers and managed deployments.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
The other settings remain environment-based:
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
| Environment variable | Default |
| --- | --- |
| `FLOWERS_SECRETS_FILE` | `secrets.yaml` beside the application |
| `FLOWERS_SECRET_KEY` | overrides `flask.secret_key` from YAML |
| `FLOWERS_DB_HOST` | `localhost` |
| `FLOWERS_DB_PORT` | `3306` |
| `FLOWERS_DB_NAME` | `Flowers` |
| `FLOWERS_DB_ADMIN_USER` | `flower` |
| `FLOWERS_DB_ADMIN_PASSWORD` | overrides `database.admin_password` from YAML |
| `FLOWERS_URL_PREFIX` | empty |
| `FLOWERS_SESSION_MINUTES` | `10` |
| `FLOWERS_COOKIE_SECURE` | `0`; set to `1` behind HTTPS |
| `FLOWERS_ACCESS_FILE` | `/tmp/wsgi_flower_accessfile` |
| `FLOWERS_LOG_FILE` | `/tmp/wsgi_flowers.log` |
For example:
2026-07-18 19:36:23 +02:00
```bash
waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app
```
2026-07-18 20:27:19 +02:00
The provisioning account is needed only by the web-based `/create` flow. Existing vault reads and writes connect with their existing per-user database accounts.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## Database bootstrap for a new installation
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
An existing Flowers database should be left untouched. For a new installation, create the database and provisioning user, replacing the example password:
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
```sql
CREATE DATABASE Flowers;
CREATE USER 'flower'@'localhost' IDENTIFIED BY 'replace-this';
GRANT CREATE, SELECT, UPDATE, INSERT ON Flowers.*
TO 'flower'@'localhost' WITH GRANT OPTION;
GRANT CREATE USER, RELOAD ON *.* TO 'flower'@'localhost';
FLUSH PRIVILEGES;
```
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
Put the same value in `database.admin_password` in `secrets.yaml` before starting Flowers.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## Run
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
Development:
2026-07-18 19:36:23 +02:00
```bash
2026-07-18 20:27:19 +02:00
python flowers.py
2026-07-18 19:36:23 +02:00
```
2026-07-18 20:27:19 +02:00
Production application server:
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
```bash
waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app
2026-07-18 19:36:23 +02:00
```
2026-07-18 20:27:19 +02:00
Keep the service behind HTTPS. If it is mounted under `/flowers`, set `FLOWERS_URL_PREFIX=/flowers` before starting it.
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
## Tests
2026-07-18 19:36:23 +02:00
2026-07-18 20:27:19 +02:00
The tests do not need a live database; they use the actual legacy Fernet implementation with a MariaDB-compatible fake connection.
2026-07-18 19:36:23 +02:00
```bash
2026-07-18 20:27:19 +02:00
pip install -r requirements-dev.txt
python -m pytest
2026-07-18 19:36:23 +02:00
```
2026-07-18 20:27:19 +02:00
They can also run using only the standard library test runner:
2026-07-18 19:36:23 +02:00
```bash
2026-07-18 20:27:19 +02:00
python -m unittest discover -s tests -v
2026-07-18 19:36:23 +02:00
```
2025-03-25 20:32:26 +00:00
2026-07-18 20:27:19 +02:00
## Remaining security constraints
2026-07-18 20:27:19 +02:00
The rewrite removes the immediately exploitable SQL interpolation and browser-cookie credential storage, but Flowers remains a small personal vault with a legacy storage design:
2025-03-25 20:32:26 +00:00
2026-07-18 20:27:19 +02:00
- the historical password-derived Fernet key is intentionally still weak;
- the web process still has provisioning privileges if `/create` is enabled;
- the in-memory credential store is suitable for one application process, not a multi-process cluster; and
- rate-limit state is local to one host.
2025-03-25 20:32:26 +00:00
2026-07-18 20:27:19 +02:00
Use a strong unique password made only of Base64-compatible characters, bind Waitress to localhost, and expose it only through an authenticated HTTPS reverse proxy. Set stable provisioning and Flask secrets before deployment.
2026-07-18 20:27:19 +02:00
The old `Services.py`, `SecretServices.py`, and ad-hoc root-level scripts are retained as historical migration references. Do not run them against production data without reviewing them first.