Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/selfhost-auth-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@cloudhal/api": patch
"@cloudhal/cli": patch
---

**Security (self-hosting, #165).** In 0.6.3 a self-hosted `cloudhal studio` (the Docker image included) exposed to a network before its first sign-up could be claimed by whoever reached it first: the first account to sign up became the Owner, and nothing on the Node path asked for the setup token the Cloudflare deployment already requires. If you run 0.6.3 beyond `localhost`, upgrade and check that the Owner account is yours. In 0.6.4:

- **A setup token for the first account.** While nobody has an account, `cloudhal studio` requires the one-time token from `CLOUDHAL_SETUP_TOKEN` (32 characters or more, letters, digits and `- . _ ~` only, e.g. `openssl rand -hex 32`; a shorter or other value stops the start) or, when that is unset, one it generates at start and prints as a `…/login#setup=<token>` link to its output (read it from the container's logs: `docker compose logs cloudhal`). Sign-up without the token is refused; once the Owner exists the link is no longer printed. An operator's own token is never printed.
- **Sign-in from the address browsers actually use.** With `--host` beyond loopback and no `--base-url`, Better Auth refused every sign-up, sign-in and sign-out with `403 INVALID_ORIGIN` because only `http://localhost:<port>` was trusted. Without a public origin Studio now also trusts the origin each request was made to, as the API's own CSRF check does (on a loopback `--host` only when that origin is `localhost`, `127.0.0.1` or `[::1]`, so a DNS name rebound to the machine is not trusted); with `--base-url` set, that origin alone (plus `CLOUDHAL_TRUSTED_ORIGINS`) may sign in. `--base-url` is the origin browsers open Studio at: required behind a reverse proxy and recommended whenever `--host` is not loopback; a start without it beyond loopback prints a one-line warning.
- **`--no-auth` stays on this machine.** It is refused unless `--host` is loopback (`127.0.0.1`, the default, or `::1`) and `--behind-proxy` is off.
- **Rate limits count the real client.** The public token endpoints (invites, password resets) and Better Auth's own limits keyed on `X-Forwarded-For` / `CF-Connecting-IP` as the client sent them. `serveNode` now derives the client's address itself: the socket's, or with `trustProxy` the last `X-Forwarded-For` hop, which the proxy appended; the API (`ApiDeps.clientAddressHeader`) and Better Auth (`AuthOptions.clientAddressHeader`) read only that value on Node, while Workers keep `CF-Connecting-IP`. Sessions record that address too, and Better Auth's own limits (sign-in and sign-up: 3 per 10 s per address) now apply on Node as on Workers, stored in the project's SQLite; before they were on only under `NODE_ENV=production`.
- Behind a trusted proxy, `forwardedUrl` takes the last value of `X-Forwarded-Proto` / `X-Forwarded-Host`, the one the proxy added, not the first, which a client can send.
- `--base-url` is also the origin of invite and password-reset links (`ApiDeps.studioOrigin`).
- Studio's setup banner no longer names only `cloudhal deploy`: it speaks of the setup link printed when Cloudhal started or was deployed (other languages are AI drafts awaiting review).
- Studio's login page reads `#setup=<token>` without form decoding, so an operator's own token containing `+` (from `openssl rand -base64`, for a Cloudflare deployment too) is no longer turned into a space and refused.
- **`--behind-proxy` needs `--base-url`.** `cloudhal studio` refuses to start with `--behind-proxy` / `CLOUDHAL_BEHIND_PROXY=1` and no `--base-url` / `CLOUDHAL_BASE_URL`. Behind a proxy the public origin is known, and as Better Auth's base URL it makes session cookies `Secure` (`__Secure-` prefix) on https and is the one origin sign-in accepts. In 0.6.3 that combination could not sign in at all, so no working setup breaks.
- `--init-if-empty` stages the scaffold in `.cloudhal-init` inside the folder and moves it into place with `cloudhal.json` last, so a container stopped during its first start no longer crash-loops on "is not empty": the next start redoes the scaffold when the staged `cloudhal.json` is missing, cut off or names no site, and finishes it otherwise. A fresh ext4 volume whose only entry is `lost+found` counts as empty.
- A bare `--host` means `0.0.0.0`, every IPv4 address; pass `--host ::` for IPv6 as well. The help says so.

New in `@cloudhal/api`: `CLIENT_ADDRESS_HEADER`, `clientAddress` and `inboundRequest` in `@cloudhal/api/node`; `trustedOriginsWithOwn` (with `loopbackOnly`) and `AuthOptions.trustedOrigins` as a function in `@cloudhal/api/auth`; `createAuthFor` takes `setupToken`, `trustRequestOrigin` and `originCheck`. `studio()` returns `setup` (the link, while nobody has an account) and `warnings`.
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/ar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): ar core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 97f41c0c8d7f8084
// hash: de0b53138e89cd52
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "فشل تسجيل الدخول",
setupTitle: "إعداد هذا الـStudio",
setupDescription:
"فتحت رابط الإعداد من `cloudhal deploy`. الحساب الذي تنشئه الآن يصبح المالك؛ وبعد ذلك يُغلق التسجيل.",
"فتحت رابط الإعداد الذي ظهر عند تشغيل Cloudhal أو نشره. الحساب الذي تنشئه الآن يصبح المالك؛ وبعد ذلك يُغلق التسجيل.",
},
sites: {
title: "المواقع",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/de.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): de core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 8fbb985a874ba058
// hash: 844f14a5128a34f6
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -73,7 +73,7 @@ export const messages: Messages = {
failed: "Anmeldung fehlgeschlagen",
setupTitle: "Dieses Studio einrichten",
setupDescription:
"Du hast den Einrichtungslink aus `cloudhal deploy` geöffnet. Das Konto, das du jetzt erstellst, wird Inhaber; danach ist die Registrierung geschlossen.",
"Du hast den Einrichtungslink geöffnet, den Cloudhal beim Start oder beim Deployment ausgegeben hat. Das Konto, das du jetzt erstellst, wird Inhaber; danach ist die Registrierung geschlossen.",
},
sites: {
title: "Websites",
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const en = {
failed: "Sign-in failed",
setupTitle: "Set up this Studio",
setupDescription:
"You opened the setup link from `cloudhal deploy`. The account you create now becomes the owner; afterwards sign-up closes.",
"You opened the setup link printed when Cloudhal started or was deployed. The account you create now becomes the owner; afterwards sign-up closes.",
},
sites: {
title: "Sites",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/es.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): es core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 8b6afc6dc8bd43f6
// hash: 9ea0d8cd7f137822
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "No se pudo iniciar sesión",
setupTitle: "Configura este Studio",
setupDescription:
"Abriste el enlace de configuración de `cloudhal deploy`. La cuenta que crees ahora será la propietaria; después se cierra el registro.",
"Abriste el enlace de configuración que Cloudhal mostró al iniciarse o al desplegarse. La cuenta que crees ahora será la propietaria; después se cierra el registro.",
},
sites: {
title: "Sitios",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/fa.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): fa core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 7186633ebe813150
// hash: a4005ab45f17daa9
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "ورود ناموفق بود",
setupTitle: "راه‌اندازی این Studio",
setupDescription:
"شما پیوند راه‌اندازی را از `cloudhal deploy` باز کرده‌اید. حسابی که اکنون می‌سازید مالک می‌شود؛ پس از آن ثبت‌نام بسته می‌شود.",
"شما پیوند راه‌اندازی‌ای را باز کرده‌اید که Cloudhal هنگام اجرا یا استقرار نشان داد. حسابی که اکنون می‌سازید مالک می‌شود؛ پس از آن ثبت‌نام بسته می‌شود.",
},
sites: {
title: "سایت‌ها",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/fr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): fr core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: ebbb2c53717d8197
// hash: 82efd0e745377b9e
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Échec de la connexion",
setupTitle: "Configurer ce Studio",
setupDescription:
"Vous avez ouvert le lien de configuration fourni par `cloudhal deploy`. Le compte que vous créez maintenant en devient le propriétaire ; les inscriptions se ferment ensuite.",
"Vous avez ouvert le lien de configuration affiché par Cloudhal à son démarrage ou à son déploiement. Le compte que vous créez maintenant en devient le propriétaire ; les inscriptions se ferment ensuite.",
},
sites: {
title: "Sites",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/he.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): he core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: b7872f5218e325f5
// hash: fb9798d4e9369c1e
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "ההתחברות נכשלה",
setupTitle: "הגדרת ה-Studio הזה",
setupDescription:
"קישור ההגדרה נפתח מתוך `cloudhal deploy`. החשבון שנוצר עכשיו הופך לבעלים; לאחר מכן ההרשמה נסגרת.",
"קישור ההגדרה שהוצג כש־Cloudhal הופעל או נפרס נפתח. החשבון שנוצר עכשיו הופך לבעלים; לאחר מכן ההרשמה נסגרת.",
},
sites: {
title: "אתרים",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/hi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): hi core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 6e267f40b5a2ddb3
// hash: 882e56567d7611da
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "साइन इन विफल रहा",
setupTitle: "यह Studio सेट अप करें",
setupDescription:
"आपने `cloudhal deploy` से सेटअप लिंक खोला है। अभी जो खाता आप बनाएँगे वह स्वामी बनेगा; इसके बाद साइन-अप बंद हो जाता है।",
"आपने वह सेटअप लिंक खोला है जो Cloudhal के शुरू या डिप्लॉय होने पर दिखाया गया था। अभी जो खाता आप बनाएँगे वह स्वामी बनेगा; इसके बाद साइन-अप बंद हो जाता है।",
},
sites: {
title: "साइटें",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/id.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): id core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: b4856ded9f2c2666
// hash: fa6d4f782fb8d938
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Gagal masuk",
setupTitle: "Siapkan Studio ini",
setupDescription:
"Anda membuka tautan penyiapan dari `cloudhal deploy`. Akun yang Anda buat sekarang menjadi pemilik; setelah itu pendaftaran ditutup.",
"Anda membuka tautan penyiapan yang ditampilkan saat Cloudhal dijalankan atau di-deploy. Akun yang Anda buat sekarang menjadi pemilik; setelah itu pendaftaran ditutup.",
},
sites: {
title: "Situs",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/it.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): it core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 1ea0c4faf23f67cc
// hash: 2cfeac004b958452
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Accesso non riuscito",
setupTitle: "Configura questo Studio",
setupDescription:
"Hai aperto il link di configurazione di `cloudhal deploy`. L’account che crei ora diventa il proprietario; dopo, la registrazione si chiude.",
"Hai aperto il link di configurazione mostrato da Cloudhal all’avvio o al deploy. L’account che crei ora diventa il proprietario; dopo, la registrazione si chiude.",
},
sites: {
title: "Siti",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/ja.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): ja core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: b3f228407b0fa9ba
// hash: 45f31b8df1f976e3
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "サインインに失敗しました",
setupTitle: "このStudioをセットアップ",
setupDescription:
"`cloudhal deploy`のセットアップリンクを開きました。ここで作成するアカウントがオーナーになり、その後は新規登録が締め切られます。",
"Cloudhal の起動時またはデプロイ時に表示されたセットアップリンクを開きました。ここで作成するアカウントがオーナーになり、その後は新規登録が締め切られます。",
},
sites: {
title: "サイト",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/ko.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): ko core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: f8aaccaf189e196d
// hash: 165c309380bb3c4e
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "로그인하지 못했어요",
setupTitle: "이 Studio 설정",
setupDescription:
"`cloudhal deploy`의 설정 링크를 열었어요. 지금 만드는 계정이 소유자가 되며, 그 후에는 가입이 닫혀요.",
"Cloudhal을 시작하거나 배포할 때 표시된 설정 링크를 열었어요. 지금 만드는 계정이 소유자가 되며, 그 후에는 가입이 닫혀요.",
},
sites: {
title: "사이트",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/nl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): nl core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: cbc76b64b136c8e1
// hash: 90b625c9682724a2
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -73,7 +73,7 @@ export const messages: Messages = {
failed: "Inloggen mislukt",
setupTitle: "Deze Studio instellen",
setupDescription:
"Je hebt de installatielink van `cloudhal deploy` geopend. Het account dat je nu aanmaakt, wordt de eigenaar; daarna sluit de registratie.",
"Je hebt de installatielink geopend die Cloudhal bij het starten of deployen liet zien. Het account dat je nu aanmaakt, wordt de eigenaar; daarna sluit de registratie.",
},
sites: {
title: "Sites",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/pl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): pl core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: d6de4290043bec9f
// hash: 384d9242a8e6de69
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Logowanie nie powiodło się",
setupTitle: "Skonfiguruj to Studio",
setupDescription:
"Otwarto link konfiguracyjny z `cloudhal deploy`. Konto, które teraz utworzysz, stanie się właścicielem; potem rejestracja zostanie zamknięta.",
"Otwarto link konfiguracyjny wyświetlony przy uruchomieniu lub wdrożeniu Cloudhal. Konto, które teraz utworzysz, stanie się właścicielem; potem rejestracja zostanie zamknięta.",
},
sites: {
title: "Witryny",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/pt-BR.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): pt-BR core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: c7a43832fa22b9af
// hash: f5164a39ad56cc1e
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Falha ao entrar",
setupTitle: "Configurar este Studio",
setupDescription:
"Você abriu o link de configuração do `cloudhal deploy`. A conta que você criar agora se torna a proprietária; depois disso, o cadastro é fechado.",
"Você abriu o link de configuração exibido quando o Cloudhal foi iniciado ou implantado. A conta que você criar agora se torna a proprietária; depois disso, o cadastro é fechado.",
},
sites: {
title: "Sites",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/ru.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): ru core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 48287cae510527d5
// hash: 4172d91f9afefbde
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Не удалось войти",
setupTitle: "Настройка этого Studio",
setupDescription:
"Вы открыли ссылку настройки из `cloudhal deploy`. Учётная запись, которую вы сейчас создадите, станет владельцем; после этого регистрация закроется.",
"Вы открыли ссылку настройки, которую Cloudhal показал при запуске или развёртывании. Учётная запись, которую вы сейчас создадите, станет владельцем; после этого регистрация закроется.",
},
sites: {
title: "Сайты",
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/i18n/tr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): tr core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: a8aab0fe1a674b26
// hash: 5386070af29019ab
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -72,7 +72,7 @@ export const messages: Messages = {
failed: "Giriş başarısız",
setupTitle: "Bu Studio'yu kur",
setupDescription:
"`cloudhal deploy` çıktısındaki kurulum bağlantısıyla geldin. Şimdi oluşturacağın hesap sahip olur; ardından yeni kayıtlar kapanır.",
"Cloudhal başlarken ya da dağıtılırken yazdırılan kurulum bağlantısıyla geldin. Şimdi oluşturacağın hesap sahip olur; ardından yeni kayıtlar kapanır.",
},
sites: {
title: "Siteler",
Expand Down
Loading
Loading