- Passwords hashed with bcrypt (cost factor 10)
- Stored in
users.passwordcolumn - Login:
POST /auth/loginreturns user object (no token — session tracked by user ID)
- Uses
google-auth-librarywith OpenID Connect - Scopes: openid, email, profile
- Auto-creates user on first login
- Frontend stores user object in
localStorage - All API requests include
X-User-Idheader - Auth guard middleware validates user exists in DB
- No server-side session tokens or JWTs
All chat messages (task, group, direct) encrypted at rest.
| Parameter | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key size | 32 bytes (from MSG_ENCRYPTION_KEY env, hex-encoded) |
| IV | 16 bytes random per message |
| Storage | body = hex ciphertext, iv = hex(IV) + ":" + hex(AuthTag) |
Legacy messages with iv = NULL returned as plaintext (backward compatible).
All uploaded files encrypted before storage in file_blobs table.
| Parameter | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key size | 32 bytes (from FILE_ENCRYPTION_KEY_BASE64 env, base64-encoded) |
| IV | 12 bytes random per file |
| Storage | encrypted_data (BLOB), iv (BLOB), auth_tag (BLOB) |
Files are decrypted on-the-fly when downloaded or viewed.
- SHA-256 hash computed and stored for every uploaded file (
files.sha256) - Used for deduplication detection and integrity verification
When messages are soft-deleted:
deleted_attimestamp setdeleted_byuser ID recordedchat_eventsentry created with:- SHA-256 hash of plaintext body
- Body length
- Actor information
- Original encrypted body preserved for admin audit access
- Helmet — Sets security headers (X-Content-Type-Options, X-Frame-Options, etc.)
- CSP disabled — Content Security Policy off (single-origin SPA)
- CORS — Open (all origins allowed)
- Trust proxy — Enabled for correct IP detection behind reverse proxy
- JSON limit — 1 MB request body limit
- File limits — 10 MB (legacy disk), 50 MB (encrypted DB)
- All request bodies validated with Zod schemas
- Type coercion and constraint checking before database operations
- SQL injection prevented by parameterized queries (better-sqlite3 prepared statements)
- Auth guard middleware on all API routes (except public endpoints)
- No role-based access control at API level — all authenticated users have equal access
- File "hide" allows per-user visibility control without deleting shared resources
Keys are read from environment variables at startup:
- Never logged or exposed in API responses
- Missing
FILE_ENCRYPTION_KEY_BASE64throws at runtime on file operations - Missing
MSG_ENCRYPTION_KEYdefaults to null key (zero-filled) — must be set in production
- Set strong, unique values for both encryption keys before deploying
- Use a reverse proxy (nginx) with HTTPS termination
- Restrict network access to the server port
- Back up
data/app.dbregularly (contains all data including encrypted blobs) - Rotate keys requires re-encryption of all stored data