Files
flowers/README.md
T

5.4 KiB

Flowers

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.

Existing database compatibility

No data migration is required. The rewritten application continues to use:

  • 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.

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.

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.

What changed

  • 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.

Requirements

  • Python 3.10 or newer
  • MariaDB or MySQL on the configured host
  • the packages in requirements.txt
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Configuration

Local secrets are loaded from the git-ignored secrets.yaml file:

flask:
  secret_key: "a-long-random-value"

database:
  admin_password: "the-provisioning-account-password"

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.

The other settings remain environment-based:

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:

waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app

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.

Database bootstrap for a new installation

An existing Flowers database should be left untouched. For a new installation, create the database and provisioning user, replacing the example password:

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;

Put the same value in database.admin_password in secrets.yaml before starting Flowers.

Run

Development:

python flowers.py

Production application server:

waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app

Keep the service behind HTTPS. If it is mounted under /flowers, set FLOWERS_URL_PREFIX=/flowers before starting it.

Tests

The tests do not need a live database; they use the actual legacy Fernet implementation with a MariaDB-compatible fake connection.

pip install -r requirements-dev.txt
python -m pytest

They can also run using only the standard library test runner:

python -m unittest discover -s tests -v

Remaining security constraints

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:

  • 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.

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.