diff --git a/.env.example b/.env.example index 7a4e77f..cc0655a 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ # Global Settings NODE_ENV=development JWT_SECRET="your_common_jwt_secret_key" -DATABASE_URL="postgresql://app_runtime_login:password@host:port/database" +DATABASE_URL="postgresql://app_runtime:password@host:port/database" # Frontend Configuration (Next.js) FRONTEND_URL="http://localhost:3000" @@ -24,4 +24,4 @@ DISCORD_CLIENT_ID="your_discord_client_id" DISCORD_CLIENT_SECRET="your_discord_client_secret" # Wrangler Hyperdrive Local Connection Emulation String (Secure, ignored by git) -CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://app_runtime_login:your_password@your_postgres_host:5432/postgres" +CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://app_runtime:your_password@your_postgres_host:5432/postgres" diff --git a/README.md b/README.md index b0f4b24..d21df06 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ cp member/.dev.vars.example member/.dev.vars ローカル開発時(`wrangler dev`)は、Hyperdriveの接続先をWranglerプロセスの環境変数で上書きします。`.dev.vars` の通常のWorker bindingとは別なので、APIを起動する各シェルで設定してください。 ```bash -export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://app_runtime_login:password@host:5432/database" +export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://app_runtime:password@host:5432/database" ``` -`app_runtime_login` はDB運用runbookで作る非superuser・非`BYPASSRLS`の専用ロールです。アプリケーションから`postgres`/owner資格情報を使わないでください。 +`app_runtime` はDB運用runbookで作る非superuser・非`BYPASSRLS`の専用ロールです。アプリケーションから`postgres`/owner資格情報を使わないでください。 ### 3. 各プロジェクトのローカル起動 それぞれのディレクトリで開発サーバーを起動します。 diff --git a/community/.dev.vars.example b/community/.dev.vars.example index 6e444b8..4341cb5 100644 --- a/community/.dev.vars.example +++ b/community/.dev.vars.example @@ -1,5 +1,5 @@ # Database -DATABASE_URL=postgresql://app_runtime_login:password@host:port/dbname +DATABASE_URL=postgresql://app_runtime:password@host:port/dbname # Better Auth Configuration BETTER_AUTH_SECRET=your_secure_random_32_char_string diff --git a/community/.env.example b/community/.env.example index 37a188c..35655b4 100644 --- a/community/.env.example +++ b/community/.env.example @@ -1,5 +1,5 @@ # Database -DATABASE_URL=postgresql://app_runtime_login:password@host:port/dbname +DATABASE_URL=postgresql://app_runtime:password@host:port/dbname # Better Auth Configuration BETTER_AUTH_SECRET=your_secure_random_32_char_string diff --git a/docs/hyperdrive-security.md b/docs/hyperdrive-security.md index 53c04df..ea5500d 100644 --- a/docs/hyperdrive-security.md +++ b/docs/hyperdrive-security.md @@ -60,9 +60,40 @@ this sequence. Each result must show `caching.disabled: true`, and the origin user must be the dedicated login rather than `postgres`. -7. Deploy both Workers and exercise sign-in, Discord verification, application - submission, approval, and directory access. Restore writes only after these - checks pass. +7. Apply the schema split, `20260727000000_split_auth_schema.sql`. +8. Confirm every Worker has the secrets listed under "Required secrets" before + deploying. A missing one fails at runtime, not at deploy time. +9. Deploy all three Workers and exercise sign-in, Discord verification, + application submission, approval, and directory access. Restore writes only + after these checks pass. + +## Required secrets + +Check these with `npx wrangler secret list` in each directory. Deploying a +Worker whose secrets were only ever set through the dashboard can drop values +that are not also in `wrangler.jsonc`, so re-check after any deploy. + +| Secret | community | member | frontend | +| --- | --- | --- | --- | +| `JWT_SECRET` | signs the app token | verifies it | verifies it in middleware | +| `BETTER_AUTH_SECRET` | yes | — | — | +| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | yes | — | — | +| `DISCORD_TOKEN` / `DISCORD_GUILD_ID` | yes | yes | — | +| `COMMUNITY_URL` / `FRONTEND_URL` | yes | yes | — | +| `COOKIE_DOMAIN` | yes | — | — | + +`JWT_SECRET` must be identical in all three. The frontend needs it because +`middleware.ts` verifies the token to route admins; without it every request +throws, redirects to `/login`, and bounces back into a redirect loop. + +`member` needs `DISCORD_TOKEN` and `DISCORD_GUILD_ID` because it re-verifies +guild membership when an application is submitted and again when it is +approved. A deployment that predates that behaviour will not have them, and the +join endpoint answers 500 until they are set. + +`DATABASE_URL` is only a fallback for the Hyperdrive binding. Leave it unset in +production rather than holding credentials that would bypass the dedicated +runtime role if Hyperdrive were unavailable. ## Database verification @@ -104,7 +135,7 @@ connection override is a Wrangler **process environment variable**, not a normal Worker variable: ```bash -export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE='postgresql://app_runtime_login:password@localhost:5432/postgres' +export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE='postgresql://app_runtime:password@localhost:5432/postgres' ``` Do not copy the repository-wide `.env` into both Workers; that exposes secrets diff --git a/member/.dev.vars.example b/member/.dev.vars.example index 5b2ffd5..d2f4006 100644 --- a/member/.dev.vars.example +++ b/member/.dev.vars.example @@ -1,5 +1,5 @@ # Database -DATABASE_URL=postgresql://app_runtime_login:password@host:port/dbname +DATABASE_URL=postgresql://app_runtime:password@host:port/dbname # Application JWT verification (must match community JWT_SECRET) JWT_SECRET=your_application_jwt_secret diff --git a/member/.env.example b/member/.env.example index f2f79f9..307acb2 100644 --- a/member/.env.example +++ b/member/.env.example @@ -1,5 +1,5 @@ # Database -DATABASE_URL=postgresql://app_runtime_login:password@host:port/dbname +DATABASE_URL=postgresql://app_runtime:password@host:port/dbname # Application JWT verification (must match the community service JWT_SECRET; # it must be different from BETTER_AUTH_SECRET) diff --git a/supabase/runbooks/promote_initial_admin.sql b/supabase/runbooks/promote_initial_admin.sql index 8271f64..66fe4d6 100644 --- a/supabase/runbooks/promote_initial_admin.sql +++ b/supabase/runbooks/promote_initial_admin.sql @@ -22,7 +22,8 @@ begin from public.community_identities identity join public.community_memberships membership on membership.identity_id = identity.identity_id - join public.account auth_account + -- The authentication tables live in app_auth since the schema split. + join app_auth.account auth_account on auth_account.id = identity.auth_account_id where identity.provider = 'discord' and identity.provider_account_id = target_discord_account_id diff --git a/supabase/runbooks/provision_app_runtime_login.sql b/supabase/runbooks/provision_app_runtime_login.sql index 9da64da..28640fb 100644 --- a/supabase/runbooks/provision_app_runtime_login.sql +++ b/supabase/runbooks/provision_app_runtime_login.sql @@ -75,8 +75,12 @@ begin raise exception 'runtime role % owns a database or database objects and cannot be safely constrained', runtime_role; end if; + -- Only the password is set here. Restating the attributes would require + -- SUPERUSER, which the operator role is not on a managed platform such as + -- Supabase, and it is unnecessary: the create above sets them, and the branch + -- for an existing role has already verified them. execute format( - 'alter role %I login inherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls password %L', + 'alter role %I password %L', runtime_role, runtime_password );