# Turnstile を設定する

> Turnstile のウィジェットを作り、2 つのキーを設定して、問い合わせフォームを使えるようにします。

このページの手順で Turnstile のウィジェットを作り、サイトキーとシークレットキーを設定します。両方を設定すると、問い合わせフォームが送信を受け付けるようになります。

[Turnstile](https://goodnight-web.dev/ja/docs/glossary.md#turnstile) は、自動プログラムによるフォームの送信を防ぐための、Cloudflare の無料の機能です。goodnight-web の問い合わせフォームには Turnstile が必要です。

## ウィジェットを作る

1. Cloudflare のダッシュボードで **Turnstile** を開き、**Add widget** を選びます。
2. **Widget name:** 見分けやすい名前を付けます。たとえば会社名です。
3. **Hostnames:** サイトのドメインを入れます。たとえば `example.com` です。試している間に `workers.dev` のアドレスも使うなら、それも追加します。`workers.dev` のアドレスを無効にするときに外してください。
4. **Widget mode:** 迷ったら **Managed** にします。ほとんどの訪問者には短い確認が出るだけで、一部の人にはクリックを求めます。
5. **Create** を選びます。

Cloudflare が 2 つの値を表示します（[Cloudflare: ダッシュボードでウィジェットを作る](https://developers.cloudflare.com/turnstile/get-started/widget-management/dashboard/)）。

- **サイトキー（Site key）:** 公開してよい値です。問い合わせページがウィジェットを表示するのに使います。
- **シークレットキー（Secret key）:** 秘密の値です。Worker が Turnstile の検証 API（siteverify）で送信を確かめるのに使います。

## キーを設定する

| キー             | 置く場所                                 | 公開してよいか                                                  |
| ---------------- | ---------------------------------------- | --------------------------------------------------------------- |
| サイトキー       | Worker の変数 `TURNSTILE_SITE_KEY`       | はい。問い合わせページが `/api/contact/form` から読み取ります。 |
| シークレットキー | Worker のシークレット `TURNSTILE_SECRET` | いいえ。コミットしたり、ページに入れたりしないでください。      |

### サイトキー

starter では、サイトキーを `wrangler.jsonc` に書きます。変更をコミットすると、次のデプロイで反映されます。

```jsonc
"vars": {
  "TURNSTILE_SITE_KEY": "0x4AAAAAAA...",
},
```

設定するのはここだけです。問い合わせページにはキーを書かず、Worker から読み込みます。

ダッシュボードでは設定しないでください。ダッシュボードだけで設定した値は、次のデプロイで `wrangler.jsonc` の値に置き換わります。`wrangler deploy` がファイルの変数をアップロードするためです。

### シークレットキー

Cloudflare のダッシュボードで Worker を開き、変数とシークレットに `TURNSTILE_SECRET` を種類 **Secret** で追加します。プロジェクトのフォルダーでターミナルを使うなら、次のコマンドでも同じことができます。

```sh
npx wrangler secret put TURNSTILE_SECRET
```

## 設定する前の動き

goodnight-web は、必要な設定がそろうまで機能を止めます（フェイルクローズ）。Turnstile がないと、問い合わせ API はボットの送信も受け付けてしまいます。そのため、受け付けそのものを止めます。

| 場所                | 設定する前の動き                                                                                       |
| ------------------- | ------------------------------------------------------------------------------------------------------ |
| `POST /api/contact` | 503 を返します。                                                                                       |
| 問い合わせページ    | 「お問い合わせフォームは準備中です」と表示します。                                                     |
| 管理画面            | 「お問い合わせフォームは受け付けを停止しています」というお知らせと、このページへのリンクを表示します。 |

<AdminScreen
  name='turnstile-notice'
  alt='管理画面のお知らせ「お問い合わせフォームは受け付けを停止しています」と、Turnstile の設定方法へのリンク'
  caption='TURNSTILE_SITE_KEY と TURNSTILE_SECRET を設定するまで、管理画面にこのお知らせが出ます。'
/>

訪問者には、送れたように見えて届かないフォームではなく、準備中の表示が見えます。両方のキーを設定すれば、ほかに何も変えずにフォームが動き始めます。

## Worker が確かめること

Worker は送信のたびに、フォームから届いたトークンを siteverify に送ります。次をすべて満たすときだけ受け付けます。

- `success` が true である。
- `hostname` があなたのサイトのホスト名である。リクエストが来たホスト名か、`createGoodnightWorker()` の `contact.turnstileHostnames` オプションに並べたホスト名のどれかです。
- `action` が、問い合わせフォームで設定した action である。

トークンの有効期限は 300 秒で、検証できるのは 1 回だけです（[Cloudflare: サーバー側の検証](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)）。確認に失敗したら 400 を返し、何も保存しません。

ほかの確認は[問い合わせフォームの保護](https://goodnight-web.dev/ja/docs/how-it-works/contact-protection.md)で説明しています。

## ローカルで開発する

`wrangler dev` でフォームの表示を確かめるときは、本物のキーではなく Cloudflare のテスト用キーを使います（[Cloudflare: テスト](https://developers.cloudflare.com/turnstile/troubleshooting/testing/)）。この組み合わせでは、ウィジェットは必ず成功し、siteverify はどのトークンも受け付けます。ただし、送信は Worker の確認で拒否されます（次の節）。

```txt
site key:   1x00000000000000000000AA
secret key: 1x0000000000000000000000000000000AA
```

テスト用キーは、Git が無視する `.dev.vars`（starter の `.dev.vars.example` をコピーしたファイル）に書きます。`wrangler.jsonc` にはコミットしないでください。

テスト用キーは本番のサイトで使わないでください。すべてのリクエストを通してしまいます。

### テスト用キーでは送信が拒否される

テスト用のシークレットキーでは、siteverify は `hostname` を `example.com` として返し、`action` を返しません（2026-10-22 に確認）。

```json
{
  "success": true,
  "hostname": "example.com",
  "metadata": { "result_with_testing_key": true }
}
```

Worker は `hostname` と `action` も確かめるので、テスト用キーでの送信は拒否します。フォームには、確認に失敗したと表示します。

これは意図したものです。ローカルの開発のために、本番の確認を緩めることはしません。

### starter の E2E テスト

starter の E2E テスト（`npm run test:e2e`）は、次の順に確かめます。

1. テスト用キーでの送信が、上のとおり拒否されること。
2. テスト専用の Worker `e2e/worker.ts` を起動し、フォームの送信から D1 への保存までが通ること。この Worker は、siteverify の応答に `result_with_testing_key` が付いているときに限り、`hostname` と `action` を期待どおりの値にします。

`e2e/worker.ts` はデプロイしないでください。
