README.md

Coffee Money Pool Tool #

This is a tool to manage a coffee money pool (Kaffeekasse). It can either run locally or on a server.

Deployment #

A ready-to-use docker-compose.yml is provided. It builds the server image from this repository, exposes the server on port 8080, and persists the SQLite database and the session secret in a named coffee-data volume. Start it with:

docker compose up -d

Pass extra server options through the commented-out command: block in docker-compose.yml, for example:

    command:
      - --ldap-url=ldap://ldap.example.org
      - --ldap-search-base=ou=people,dc=example,dc=org
      - --ldap-bind-dn=cn=coffee,ou=services,dc=example,dc=org
      - --path-prefix=/coffee/

For more information checkout the HTML documentation which can be found after starting the server. With the --path-prefix example above it will be at http://localhost:8080/coffee/.

The first POST /tokens request against an empty database automatically creates a superuser using the supplied credentials. With LDAP configured, the password is verified through LDAP first and no local hash is stored; without LDAP (or when LDAP does not know the user), an Argon2 hash is stored locally. Subsequent superusers can only be granted via direct SQL (UPDATE accounts SET superuser = TRUE WHERE name = ?).

Authentication methods #

Each account's authentication methods are stored as explicit rows in the account_identities table (method is one of password, ldap, oidc):

  • password — the secret column holds an Argon2 hash, verified at POST /tokens.
  • ldap — credentials are bind-checked against the configured LDAP server.
  • oidc — OpenID Connect. Providers are registered at runtime with coffee auth oidc add <slug> <issuer-url> <client-id> (admin-only, server must run with TLS) and take effect without a restart. List with coffee auth oidc list, remove with coffee auth oidc remove <slug>. A browser uses the authorization-code flow at GET /api/auth/oidc/<slug>/login; the CLI uses the device authorization grant (set auth-method: oidc in the client config, or coffee config --auth-method oidc).

OIDC account resolution is subject-first: a returning user is matched by the stable (issuer, subject) pair recorded in account_identities, so an email change at the IdP does not lock them out. The email claim is only used on the first-ever login to find and link the local account — set accounts.email for each user that should be able to log in via OIDC (existing accounts have no email until it is backfilled). Authorization is unchanged: the superuser flag stays a local DB flag regardless of how the user authenticated.

The client secret of an OIDC provider is encrypted at rest with the server secret file. Rotating that file makes stored provider secrets undecryptable — the affected provider is logged and skipped at startup and must be re-added.

Development Testing #

Prerequisites #

Coffee uses SQLite for local storage and can operate in two modes:

  • Standalone mode: Direct SQLite database access
  • Client/Server mode: HTTP client communicating with a server

Configuration Files #

On macOS, configuration files are stored in:

  • Config: ~/Library/Application Support/dev.coffee.coffee/config.yml
  • State (JWT tokens): ~/Library/Caches/dev.coffee.coffee/state.yml
  • Data: ~/Library/Application Support/dev.coffee.coffee/coffee.sqlite

On Linux:

  • Config: ~/.config/coffee/config.yml
  • State: ~/.cache/coffee/state.yml
  • Data: ~/.local/share/coffee/coffee.sqlite

Testing Standalone Mode #

To test locally without interfering with production configuration, use the --db-path flag to specify a separate test database:

# Use a test database
cargo run -- --db-path /tmp/test-coffee.sqlite add alice bob
cargo run -- --db-path /tmp/test-coffee.sqlite balances
cargo run -- --db-path /tmp/test-coffee.sqlite cash -i 10 alice

Important: When --db-path is specified, the tool runs in standalone mode and ignores any server-url in your config file.

Testing Client/Server Mode #

The Justfile provides ready-made commands for local development. TLS certificates are generated automatically on first run (idempotent).

1. Start a local test server #

just run-server

The server will:

  • Generate dev TLS certificates in certs/ if not already present
  • Create a test database at /tmp/coffee-test-server/test.sqlite
  • Generate a secret key at /tmp/coffee-test-server/secret
  • Listen on https://127.0.0.1:18080 with mTLS
  • Provide a health check at https://127.0.0.1:18080/healthy

2. Create the first superuser #

On a fresh database the server bootstraps the first superuser automatically. Simply log in with the desired credentials:

just run --user admin sip admin
# The CLI will prompt for a password; that password is stored as the admin's Argon2 hash.

Or with curl directly:

curl -s -X POST https://127.0.0.1:18080/api/tokens \
  --cacert certs/ca_cert.pem \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"mypassword"}'
# Returns a JWT on success.

Manual fallback — if you need a deterministic hash or are recovering a database, you can insert the account directly via SQL. Generate a hash with the argon2 CLI tool:

HASH=$(echo -n "mypassword" | argon2 $(openssl rand -base64 16) -id -e)
sqlite3 /tmp/coffee-test-server/test.sqlite <<EOF
INSERT INTO accounts (name, superuser) VALUES ('admin', 1);
INSERT INTO account_identities (account_id, method, secret)
  VALUES ((SELECT id FROM accounts WHERE name = 'admin'), 'password', '$HASH');
INSERT INTO balances (account_id, site_id)
  VALUES ((SELECT id FROM accounts WHERE name = 'admin'),
          (SELECT id FROM sites WHERE name = '/'));
EOF

Note: Each account must have a balances row for each site it participates in. The login query checks for an active balance to confirm the account is configured.

3. Run CLI commands against the dev server #

just run run --user alice sip alice

The run recipe isolates config and token cache under /tmp/coffee-test-client/ and embeds the dev CA certificate so TLS is trusted automatically.

Environment Variables #

These environment variables are recognised by the CLI:

  • COFFEE_URL: Override server URL (highest priority, overrides config file)
  • COFFEE_CONFIG_DIR: Override config directory location (stores config.yml)
  • COFFEE_DATA_DIR: Override data directory location (stores coffee.sqlite, standalone mode only)
  • COFFEE_CACHE_DIR: Override cache directory location (stores state.yml with JWT tokens)
  • COFFEE_LOG: Log filter for the CLI (e.g. COFFEE_LOG=coffee=debug)

The server uses RUST_LOG for its log filter and additionally supports COFFEE_DATA_DIR and COFFEE_SECRET_PATH.

Running Tests #

# Run all tests
cargo test --all-features

# Run specific test
cargo test test_name --all-features -- --nocapture

# Check compilation after dependency updates
cargo check
cargo check --all-features

Building #

Build the server image with:

just docker-build

This runs docker buildx build --platform linux/amd64 -t coffee-server:latest --load .. Adjust the PLATFORM variable in the Justfile if you build for a different architecture.

Roadmap #

In order of importance.