Skip to main content

Overview

Openinary uses Better Auth for authentication with SQLite as the database backend. The system supports both web-based login (email/password) and API key authentication for programmatic access.

Authentication Architecture

Better Auth Configuration

Shared authentication between API and Web:
  • Single SQLite database at /data/auth.db
  • Better Auth v1.1.9 with API Key plugin
  • Automatic database initialization on first startup
  • Session-based auth for web, API key for API requests
The authentication system uses a shared package that both the API and Web applications access, ensuring consistent authentication across all interfaces.

Auth Database Security

SQLite Configuration

Location: /data/auth.db (configurable via DB_PATH) File Permissions:
  • Automatically set to 600 (owner read/write only)
  • Enforced by scripts/secure-db.sh on startup
  • Validated by health check endpoint
Tables:
  • user - User accounts (passwords bcrypt-hashed)
  • session - Web sessions
  • account - Auth providers
  • verification - Email/phone verification
  • apiKey - API keys (hashed)

What’s Protected

Hashed/Encrypted:
  • User passwords (bcrypt)
  • API keys (hashed)
  • Session tokens (secure cookies)

API Key Management

Initial Setup

Mode: MODE=fullstack (default)
1

Create admin account

Visit /setup to create your first admin account.
2

Create API keys

Go to /api-keys to create API keys via the web UI.

Using API Keys

Authorization header:
curl -H "Authorization: Bearer sk_your_key_here" \
  http://localhost:3000/t/image.jpg
All protected endpoints:
  • GET /t/* - Image and video transformation
  • POST /upload/* - File upload
  • GET/POST /storage/* - Storage operations

API Key Endpoints

MethodEndpointDescription
POST/api-keys/createCreate new API key
GET/api-keys/listList user’s API keys
PATCH/api-keys/:keyIdUpdate API key
DELETE/api-keys/:keyIdDelete API key
Example - Create key:
curl -X POST http://localhost:3000/api-keys/create \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Key", "expiresIn": 31536000}'

Docker Security

Non-Root User

All containers run as user node:
  • Prevents privilege escalation
  • Limits damage if compromised
  • Automatic ownership: chown -R node:node /app

Volume Permissions

# docker-compose.yml
volumes:
  - ./data:/app/data          # Database
  - ./backups:/backup         # Backups

Startup Security

scripts/secure-db.sh runs automatically:
  • Creates /data directory
  • Sets database permissions to 600
  • Verifies security
  • Displays status

Security Best Practices

API Keys

Do:
  • Store in environment variables
  • Use different keys per service
  • Set appropriate expiration
  • Rotate regularly
  • Disable unused keys
Don’t:
  • Commit to version control
  • Share between environments
  • Use same key everywhere
  • Keep expired keys enabled

Passwords

Requirements:
  • Minimum 8 characters (12+ in production)
  • Mixed case, numbers, symbols

Rate Limiting

  • Built-in: 100 requests/minute per API key
  • Configurable in packages/shared/src/auth.ts

Incident Response

1

Disable immediately

Disable the key via web UI at /api-keys.
2

Review audit logs

docker logs openinary_api | grep "api_key.success"
3

Generate new key

Create a replacement API key with appropriate permissions.
4

Update applications

Update all applications using the compromised key.
1

Check integrity

docker exec openinary_api sqlite3 /app/data/auth.db "PRAGMA integrity_check;"
2

Restore from backup

docker exec openinary_api /app/scripts/restore-db.sh /backup/latest.db.gz
docker compose restart

Security checkup

CriterionStatus
AuthenticationBetter Auth with API keys
Password Hashingbcrypt automatic
API Key HashingHashed in database
File PermissionsEnforced 600
BackupsDaily automated
Audit LoggingStructured JSON
HTTPS CookiesProduction enforced
Secret ValidationStartup checks
Non-Root ContainersUser node
Health Monitoring/health/database

Additional Resources