reCAPTCHA Bot Protection
Protect Tymeslot booking forms with Google reCAPTCHA v3 — invisible bot detection with score-based thresholds and no CAPTCHA friction for legitimate users.
Integration credentials are encrypted at rest with AES-256-GCM. A dedicated DATA_ENCRYPTION_KEY decouples that protection from SECRET_KEY_BASE, so you can rotate your session secret without making every stored credential unreadable.
Software Engineer & AI Developer
Tymeslot encrypts sensitive integration credentials — CalDAV passwords, calendar
OAuth tokens, video API keys, Slack and Telegram tokens, webhook secrets — before
they are written to the database. By default the encryption key is derived from
SECRET_KEY_BASE.
That works, but it welds your data-at-rest protection to your cookie-signing secret:
rotating SECRET_KEY_BASE
would make every stored credential undecryptable
, forcing every user to reconnect every integration.
Setting a dedicated
DATA_ENCRYPTION_KEY
separates the two secrets. Your session secret becomes freely rotatable, and your
at-rest credentials keep their own independent key.
Every encrypted value is self-describing: it carries a one-byte version prefix identifying the key that produced it, so several keys can coexist and data can move from one key to another with no downtime.
Legacy values
Written before you set a dedicated key, encrypted under the SECRET_KEY_BASE-derived
key. They keep opening — the legacy key stays available for decryption.
Current values
Written once
DATA_ENCRYPTION_KEY
is configured, encrypted under the dedicated key. This is what all new writes use.
DATA_ENCRYPTION_KEY
is absent, Tymeslot keeps reading and writing the legacy format exactly as before —
so deploying this version with no configuration change is completely safe. Setting
the key flips new writes to the dedicated key; a one-time sweep then migrates your
existing values.
How you provide the key depends on how you deploy. Once set, it must stay stable forever — losing it makes stored credentials unrecoverable.
Nothing to do. On the first boot after upgrading, the container generates a key,
persists it to
/app/data/data_encryption_key
(included in Cloudron backups), and starts writing new credentials under it. Then
run the migration in step 2.
.env
Generate a key and add it to your environment file, then restart:
echo "DATA_ENCRYPTION_KEY=$(openssl rand -base64 48 | tr -d '\n')" >> .env
New values are written under the new key immediately. Existing values keep decrypting under the old key automatically — nothing breaks.
Provide
DATA_ENCRYPTION_KEY
as an environment variable through whatever mechanism your platform uses (Railway
variables, a systemd unit, a secrets manager). The value must be Base64 that decodes
to at least 32 bytes —
openssl rand -base64 48
is a good source.
After the dedicated key is configured, existing credentials are still stored under the old key. A one-time sweep rewrites every encrypted value with the new key, so the two secrets become fully independent. The sweep is idempotent and resumable — safe to run repeatedly and to re-run after an interruption.
cloudron exec --app tymeslot.yourdomain.com -- sh -c \
'export DATA_ENCRYPTION_KEY=$(cat /app/data/data_encryption_key); \
/app/bin/tymeslot eval "Ecto.Migrator.with_repo(Tymeslot.Repo, fn _ -> IO.inspect(Tymeslot.Security.CredentialReencryption.run(), label: :reencryption) end)"'
docker exec tymeslot /app/bin/tymeslot eval \\\n 'Ecto.Migrator.with_repo(Tymeslot.Repo, fn _ -> IO.inspect(Tymeslot.Security.CredentialReencryption.run(), label: :reencryption) end)'
mix tymeslot.reencrypt_credentials
Each run reports per-table counts.
Re-run until the reported
migrated
is 0
— that confirms every credential is on the dedicated key.
unrecoverable
and left as-is. Those integrations follow the normal reconnection flow — the user is
prompted to reconnect. The sweep never deletes or overwrites data it cannot read.
The key is now the single thing standing between an attacker and your stored credentials. Treat losing it as equivalent to losing every integration credential at once.
/app/data,
which holds both), then at-rest encryption protects you against a leaked database
— a stray SQL dump — but not against a compromised full backup. To defend
against backup compromise, inject the key at runtime from a secrets manager that is
not part of the database backup.
/app/data/data_encryption_key
file on Cloudron, or the env var elsewhere). Losing it makes stored credentials
permanently unrecoverable.
SECRET_KEY_BASE
until the migration in step 2 reports 0.
Until then, some credentials still depend on the SECRET_KEY_BASE-derived
key.
The versioned format is built for rotation, not just this one migration. A future key gets its own version id and coexists with the current one: new writes use the new key, old values keep opening under the previous key, and the same re-encryption sweep walks the data onto the new key. Only once the sweep reports 0 is the previous key retired. Nothing is ever wiped.
DATA_ENCRYPTION_KEY
to a brand-new value in place would strand every credential already written under the
old one — the old key would no longer be in the keyring to read them. True rotation
keeps the old key available alongside the new one until the sweep finishes. Multi-key
rotation is introduced through a release that registers the additional key; follow the
upgrade notes for that version rather than editing the value by hand.
No. It is optional and recommended. Without it, Tymeslot works exactly as it always
has — credentials are encrypted with a key derived from SECRET_KEY_BASE.
You only need it if you want to rotate
SECRET_KEY_BASE
without breaking stored credentials, or want an independent key for defence in depth.
Nothing is broken. It is an advisory reminder that your at-rest credentials are still
keyed off SECRET_KEY_BASE,
so rotating that secret would make them undecryptable. Set
DATA_ENCRYPTION_KEY
and run the migration to clear it.
Every credential written under it becomes permanently unreadable, and each affected integration will prompt the user to reconnect. There is no recovery — this is why an offline backup of the key, stored separately from your database backups, is essential.
Re-run the sweep until the reported
migrated
count is 0. A run that migrates nothing means every credential is already on the
dedicated key. Only then is it safe to rotate SECRET_KEY_BASE.
Yes. The sweep migrates each value in place using a compare-and-swap, so a credential that another process refreshes mid-sweep is never clobbered, and it is fully idempotent — re-running after any interruption simply continues where it left off.
SECRET_KEY_BASE:
DATA_ENCRYPTION_KEY
no longer appears in the logs.
Protect Tymeslot booking forms with Google reCAPTCHA v3 — invisible bot detection with score-based thresholds and no CAPTCHA friction for legitimate users.
Deploy Tymeslot with Docker Compose on any VPS or home server. Includes reverse proxy setup, file upload volumes, and WebSocket configuration for LiveView.
Install Tymeslot on Cloudron in minutes. PostgreSQL, SSL certificates, email relay, and automatic updates are all provisioned and managed for you.