231 lines
10 KiB
Markdown
231 lines
10 KiB
Markdown
# Flowers
|
|
|
|
Flowers is a small, self-hosted credential vault built with Flask and MariaDB/MySQL. It stores a label, login name, login ID, and secret for each entry, and provides both a server-rendered browser interface and a form-encoded JSON endpoint.
|
|
|
|
> [!WARNING]
|
|
> This is legacy, personal-use software and is **not production-ready in its current form**. The repository contains hard-coded application/database secrets and committed data exports (`db.sql` and `pins.csv`). The application also builds SQL with string interpolation, has no CSRF protection, and relies on an unusual password-derived encryption scheme. Treat every committed dump as sensitive, rotate any real credentials it contains, and review the [security notes](#security-notes) before exposing the service to a network.
|
|
|
|
## What it does
|
|
|
|
- Creates a separate database user and table for each Flowers user.
|
|
- Encrypts the login name, login ID, and secret with Fernet before storing them.
|
|
- Lists the most recent active record for each organization.
|
|
- Supports searching, viewing, adding, and soft-deleting records.
|
|
- Keeps older records when a new version of an organization is added.
|
|
- Locks an IP address out after three failed logins within five minutes.
|
|
- Exposes the same core operations through `POST /app` for a companion client.
|
|
|
|
The included `client/Daisy.py` is only a Kivy “Hello world” scaffold; it is not a working Flowers client.
|
|
|
|
## How the application is organized
|
|
|
|
```text
|
|
flowers.py Flask application factory
|
|
flower_vase.py Browser routes and the form-based JSON endpoint
|
|
FlowerServices.py Fernet encryption and MariaDB/MySQL operations
|
|
AccessControl.py File-backed failed-login throttling
|
|
Config.py Paths, limits, logging, and URL prefix
|
|
templates/ Jinja browser interface
|
|
static/ CSS, JavaScript, fonts, and images
|
|
flowers.wsgi Legacy mod_wsgi entry point (currently stale)
|
|
apache.conf Legacy Apache/mod_wsgi example
|
|
db.sql, pins.csv Historical data exports; sensitive, not seed data
|
|
Services.py,
|
|
SecretServices.py Older AES-based implementations retained for migration
|
|
```
|
|
|
|
### Data model
|
|
|
|
Each application username maps to:
|
|
|
|
- a database account named `<username>_`;
|
|
- a table named `<username>_` in the `Flowers` database; and
|
|
- a database password/Fernet key derived by repeating the application password and truncating it to 43 characters, then appending `=`.
|
|
|
|
Each table contains:
|
|
|
|
| Column | Purpose |
|
|
| --- | --- |
|
|
| `organization` | Plain-text label used to group versions of an entry |
|
|
| `myID` | Fernet-encrypted login ID |
|
|
| `myName` | Fernet-encrypted display/login name |
|
|
| `mySecret` | Fernet-encrypted secret |
|
|
| `dateCreated` | Creation time and version identifier |
|
|
| `deleted` | Soft-delete flag |
|
|
|
|
The organization and timestamp are not encrypted. “Editing” an item inserts a new row, so the latest timestamp becomes the visible version. Deactivation marks a selected row as deleted.
|
|
|
|
## Requirements
|
|
|
|
- Python 3
|
|
- MariaDB or MySQL running on `localhost`
|
|
- Python packages: Flask, Waitress, `fernet`, PyMySQL, and pytz
|
|
- Apache with `mod_proxy` only if using the optional reverse-proxy setup
|
|
|
|
No dependency lock file is included, so the project does not currently define tested package or Python version ranges.
|
|
|
|
## Local setup
|
|
|
|
### 1. Create a virtual environment
|
|
|
|
```bash
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install flask waitress fernet pymysql pytz
|
|
```
|
|
|
|
The code imports `Fernet` from the third-party `fernet` package, not from `cryptography.fernet`.
|
|
|
|
### 2. Bootstrap MariaDB/MySQL
|
|
|
|
The application expects a database named `Flowers` and a provisioning account named `flower`. With the repository's current defaults, an administrator can create them with:
|
|
|
|
```sql
|
|
CREATE DATABASE Flowers;
|
|
CREATE USER 'flower'@'localhost'
|
|
IDENTIFIED BY '608f0b988db4a96066af7dd8870de96c';
|
|
GRANT CREATE, SELECT, UPDATE, INSERT ON Flowers.*
|
|
TO 'flower'@'localhost' WITH GRANT OPTION;
|
|
GRANT CREATE USER, RELOAD ON *.* TO 'flower'@'localhost';
|
|
FLUSH PRIVILEGES;
|
|
```
|
|
|
|
That password is hard-coded in `SuperFlower` in `FlowerServices.py`. Change it in both the database and the code before real use. Do **not** import `db.sql` as ordinary sample data: it is a historical dump containing user-specific encrypted records.
|
|
|
|
### 3. Select the URL prefix
|
|
|
|
Set `URLPREFIX` in `Config.py`:
|
|
|
|
```python
|
|
URLPREFIX = '' # serve at http://127.0.0.1:5012/
|
|
# URLPREFIX = '/flowers' # serve below /flowers
|
|
```
|
|
|
|
### 4. Start the server
|
|
|
|
For local development:
|
|
|
|
```bash
|
|
python flowers.py
|
|
```
|
|
|
|
For a non-debug application server:
|
|
|
|
```bash
|
|
waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app
|
|
```
|
|
|
|
Open the configured base URL in a browser. To create the first vault user, visit `/create` under that base URL—for example, `http://127.0.0.1:5012/create` when `URLPREFIX` is empty. After creation, return to the home page and log in.
|
|
|
|
The creation form limits passwords to 10 characters. Because the password is also transformed directly into a Fernet key, use only characters valid in URL-safe Base64 (`A-Z`, `a-z`, `0-9`, `-`, and `_`) with the current implementation.
|
|
|
|
## Browser routes
|
|
|
|
All routes are relative to `URLPREFIX`.
|
|
|
|
| Route | Methods | Purpose |
|
|
| --- | --- | --- |
|
|
| `/` | GET | Login page |
|
|
| `/browser` | GET, POST | Login and browser actions (`list`, `show`, `new`, `save`, `deactivate`, `logout`) |
|
|
| `/create` | GET, POST | Create a database user, table, and initial demo entry |
|
|
| `/update_pwd` | GET, POST | Re-encrypt all records and change the database password |
|
|
| `/app` | POST | Form-encoded programmatic interface returning JSON text |
|
|
|
|
Browser sessions expire after 10 minutes of inactivity. Failed logins are recorded in `ACCESSFILE`; three failures from one IP within five minutes cause temporary denial.
|
|
|
|
## Programmatic endpoint
|
|
|
|
`POST /app` accepts `application/x-www-form-urlencoded` fields. Every request includes:
|
|
|
|
- `action`: `login`, `list`, `one`, `save`, or `deactivate`;
|
|
- `name`: Flowers username; and
|
|
- `password`: Flowers password.
|
|
|
|
Additional fields depend on the action:
|
|
|
|
| Action | Additional fields |
|
|
| --- | --- |
|
|
| `list` | none |
|
|
| `one` | `organization`, `dateCreated` |
|
|
| `save` | `organization`, `myname`, `myid`, `secret` |
|
|
| `deactivate` | `organization`, `dateCreated` |
|
|
|
|
Example login request:
|
|
|
|
```bash
|
|
curl -X POST http://127.0.0.1:5012/app \
|
|
--data-urlencode 'action=login' \
|
|
--data-urlencode 'name=demo' \
|
|
--data-urlencode 'password=replace-me'
|
|
```
|
|
|
|
A typical response is:
|
|
|
|
```json
|
|
{"result": 1, "message": "Access granted"}
|
|
```
|
|
|
|
For historical compatibility, the `list` and `flower` properties are JSON-encoded strings inside the outer JSON response, so clients must decode those properties a second time. The endpoint catches all exceptions and may return only the generic `{"result": -1, "message": "Invalid entry"}` response when an internal error occurs.
|
|
|
|
## Apache reverse proxy
|
|
|
|
Waitress can remain bound to localhost while Apache publishes the `/flowers` path. Set `URLPREFIX = '/flowers'`, enable the proxy modules, and add the proxy rules to the relevant virtual host:
|
|
|
|
```bash
|
|
sudo a2enmod proxy proxy_http
|
|
```
|
|
|
|
```apache
|
|
ProxyPass "/flowers" "http://127.0.0.1:5012/flowers"
|
|
ProxyPassReverse "/flowers" "http://127.0.0.1:5012/flowers"
|
|
```
|
|
|
|
Then run Waitress with the command shown above and configure HTTPS on Apache. The checked-in `apache.conf` and `flowers.wsgi` describe an older mod_wsgi deployment; `flowers.wsgi` imports an `app` object that no longer exists, so use the application factory or update that file before relying on mod_wsgi.
|
|
|
|
## Configuration
|
|
|
|
`Config.py` contains the available settings:
|
|
|
|
| Setting | Default purpose |
|
|
| --- | --- |
|
|
| `LOGFILE` | Application log path (`/tmp/wsgi_flowers.log`) |
|
|
| `DEBUG` | Enables debug messages and debug logging when non-zero |
|
|
| `INFO` | Enables informational file logging when non-zero |
|
|
| `MAXTEXTLEN` | Intended maximum text length used by the UI/domain code |
|
|
| `MINFIELDLEN` | Minimum accepted length for organization, login ID, and secret |
|
|
| `ACCESSFILE` | Pickle file used for failed-login throttling |
|
|
| `DONOTSETFILTER` | Sentinel used by the browser search/filter form |
|
|
| `URLPREFIX` | Blueprint prefix, such as `''` or `'/flowers'` |
|
|
|
|
The Flask session signing key is currently hard-coded in `flowers.py`. Configuration is not loaded from environment variables.
|
|
|
|
## Tests and maintenance scripts
|
|
|
|
There is currently no automated test suite. A syntax-only check for the primary modules can be run without a database:
|
|
|
|
```bash
|
|
python -m py_compile \
|
|
flowers.py flower_vase.py FlowerServices.py \
|
|
AccessControl.py Config.py Log.py
|
|
```
|
|
|
|
The files `test.py`, `test_fernet.py`, `import.py`, and `dumpl.py` are ad-hoc scripts with embedded usernames/passwords or destructive/database-dependent behavior. Read and edit them before running them; they are not safe, isolated unit tests.
|
|
|
|
## Security notes
|
|
|
|
Before any serious deployment, at minimum:
|
|
|
|
2. Move the Flask secret key, provisioning database password, database name/host, and filesystem paths to environment-based configuration.
|
|
3. Replace string-formatted SQL with parameterized queries and validate table/account identifiers.
|
|
4. Replace the current password-to-Fernet-key construction with a password KDF such as Argon2id, scrypt, or PBKDF2 using a unique salt and appropriate work factor.
|
|
5. Add CSRF protection, secure cookie settings, explicit error handling, and tests for authentication and authorization.
|
|
6. Run only behind HTTPS and restrict access at the firewall or reverse proxy.
|
|
7. Rework user provisioning so the web process does not hold `CREATE USER`, `RELOAD`, and grant privileges during normal operation.
|
|
8. Replace the pickle-backed access-control file with a safe, concurrency-aware rate limiter.
|
|
|
|
Fernet protects stored field contents from casual database inspection, but it does not compensate for weak/reused passwords, exposed keys, SQL injection, a compromised application host, or unencrypted transport.
|
|
|
|
## License
|
|
|
|
No license file is currently included. Unless a license is added, normal copyright restrictions apply.
|