Skip to main content
Openinary authenticates two ways: Better Auth sessions for the web dashboard, and API keys for everything programmatic. Both live in one SQLite file. A key travels as a bearer token:
The auth layer is Better Auth 1.6 on a SQLite backend.

API keys

How you get the first one depends on the mode. In fullstack mode, visit /setup to create your admin account, then /api-keys in the dashboard to create the key. In API-only mode the server generates a key on first startup and prints it to the console. It is shown once and cannot be retrieved later, so copy it now:
After that: store keys in environment variables, never in source code. Create one key per service or environment. Set an expiration at creation time (expiresIn, in seconds). Rotate regularly, and disable unused keys promptly. Every route and its auth requirement is listed in the API Reference, along with the rate limiting defaults and options.

Database

All auth data sits in a single SQLite file at /app/data/auth.db, configurable via DB_PATH. On startup, scripts/secure-db.js sets its permissions to 600, owner read/write only. It runs in both Docker images, before the server starts.

Upload and delivery

Uploads are validated by content, not by extension or declared MIME type. The leading bytes must match the format the file claims to be, so a .jpg carrying an HTML document never reaches storage. Delivery then serves those bytes as they are, with X-Content-Type-Options: nosniff and a content type taken from the shared media table rather than sniffed from the body. That pairing is what closes the stored-XSS path on the public /t/* route, which returns uploaded bytes untouched. Both halves are load-bearing: strict at the door, no sniffing on the way out.

Docker security

The two images differ here, and the difference matters when you pick one. The full image bundles nginx and supervisord alongside the app and sets no USER, so every process in it runs as root. Application directories are owned by node, but that does not change which user the processes run under. If running non-root is a requirement for you, deploy the api image and put your own reverse proxy in front of it. Mount the /app/data volume with appropriate host permissions. Neither image needs a privileged container or extra capabilities.

Incident response

1

Disable the key

Go to /api-keys in the dashboard and disable or delete the key immediately.
2

Review logs

bash docker compose logs api | grep "api_key.success"
3

Issue a replacement

Create a new key and update all services that used the compromised one.
A healthy database returns ok. If it reports errors, restore from your most recent backup and restart.

Additional resources

Better Auth

Official Better Auth documentation.

API Key Plugin

Better Auth API Key plugin reference.