refactor: auto-generate session key and store in database
All checks were successful
check / check (push) Successful in 57s

Remove SESSION_KEY env var requirement. On first startup, a
cryptographically secure 32-byte key is generated and stored in a new
settings table. Subsequent startups load the key from the database.

- Add Setting model (key-value table) for application config
- Add Database.GetOrCreateSessionKey() method
- Session manager initializes in OnStart after database is connected
- Remove DevSessionKey constant and SESSION_KEY env var handling
- Remove prod validation requiring SESSION_KEY
- Update README: config table, Docker instructions, security notes
- Update config.yaml.example
- Update all tests to remove SessionKey references

Addresses owner feedback on issue #15.
This commit is contained in:
2026-03-01 21:57:19 -08:00
parent 5e683af2a4
commit 9b9ee1718a
11 changed files with 131 additions and 218 deletions

View File

@@ -68,12 +68,15 @@ Configuration is resolved in this order (highest priority first):
| `PORT` | HTTP listen port | `8080` |
| `DBURL` | SQLite connection string (main app DB) | *(required)* |
| `DATA_DIR` | Directory for per-webhook event DBs | `./data` (dev) / `/data/events` (prod) |
| `SESSION_KEY` | Base64-encoded 32-byte session key | *(required in prod)* |
| `DEBUG` | Enable debug logging | `false` |
| `METRICS_USERNAME` | Basic auth username for `/metrics` | `""` |
| `METRICS_PASSWORD` | Basic auth password for `/metrics` | `""` |
| `SENTRY_DSN` | Sentry error reporting DSN | `""` |
On first startup, webhooker automatically generates a cryptographically
secure session encryption key and stores it in the database. This key
persists across restarts — no manual key management is needed.
On first startup in development mode, webhooker creates an `admin` user
with a randomly generated password and logs it to stdout. This password
is only displayed once.
@@ -86,7 +89,6 @@ docker run -d \
-v /path/to/data:/data \
-e DBURL="file:/data/webhooker.db?cache=shared&mode=rwc" \
-e DATA_DIR="/data/events" \
-e SESSION_KEY="<base64-encoded-32-byte-key>" \
-e WEBHOOKER_ENVIRONMENT=prod \
webhooker:latest
```
@@ -196,6 +198,10 @@ tier** (event ingestion, delivery, and logging).
│ │ │ └──────────┘ └──────────────┘ │
│ │ │──1:N──│ APIKey │ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ │
│ │ Setting │ (key-value application config) │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
@@ -208,6 +214,22 @@ tier** (event ingestion, delivery, and logging).
└─────────────────────────────────────────────────────────────┘
```
#### Setting
A key-value pair for application-level configuration that is
auto-managed rather than user-provided. Used to store the session
encryption key and any future auto-generated settings.
| Field | Type | Description |
| ------- | ------ | ----------- |
| `key` | string | Primary key (setting name) |
| `value` | text | Setting value |
Currently stored settings:
- **`session_key`** — Base64-encoded 32-byte session encryption key,
auto-generated on first startup.
#### User
A registered user of the webhooker service.
@@ -397,16 +419,19 @@ webhooker uses **separate SQLite database files**: a main application
database for configuration data and per-webhook databases for event
storage.
**Main Application Database** (`DBURL`) — stores configuration only:
**Main Application Database** (`DBURL`) — stores configuration and
application state:
- **Settings** — auto-managed key-value config (e.g. session encryption
key)
- **Users** — accounts and Argon2id password hashes
- **Webhooks** — webhook configurations
- **Entrypoints** — receiver URL definitions
- **Targets** — delivery destination configurations
- **APIKeys** — programmatic access credentials
On first startup the main database is auto-migrated and an `admin` user
is created.
On first startup the main database is auto-migrated, a session
encryption key is generated and stored, and an `admin` user is created.
**Per-Webhook Event Databases** (`DATA_DIR`) — each webhook gets its own
dedicated SQLite file named `events-{webhook_uuid}.db`, containing:
@@ -565,6 +590,7 @@ webhooker/
│ │ ├── base_model.go # BaseModel with UUID primary keys
│ │ ├── database.go # GORM connection, migrations, admin seed
│ │ ├── models.go # AutoMigrate for config-tier models
│ │ ├── model_setting.go # Setting entity (key-value app config)
│ │ ├── model_user.go # User entity
│ │ ├── model_webhook.go # Webhook entity
│ │ ├── model_entrypoint.go # Entrypoint entity
@@ -625,7 +651,7 @@ Components are wired via Uber fx in this order:
5. `database.NewWebhookDBManager` — Per-webhook event database
lifecycle manager
6. `healthcheck.New` — Health check service
7. `session.New` — Cookie-based session manager
7. `session.New` — Cookie-based session manager (key from database)
8. `handlers.New` — HTTP handlers
9. `middleware.New` — HTTP middleware
10. `delivery.New` — Event-driven delivery engine
@@ -665,7 +691,8 @@ Applied to all routes in this order:
- Passwords hashed with Argon2id (64 MB memory cost)
- Session cookies are HttpOnly, SameSite Lax, Secure (prod only)
- Session key must be a 32-byte base64-encoded value
- Session key is a 32-byte value auto-generated on first startup and
stored in the database
- Prometheus metrics behind basic auth
- Static assets embedded in binary (no filesystem access needed at
runtime)