@o3co/create-auth-provider 0.17.0-rc.1 → 0.17.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +3 -1
- package/README.md +5 -2
- package/package.json +1 -1
- package/templates/standalone/.env.example +7 -5
- package/templates/standalone/README.ja.md +53 -25
- package/templates/standalone/README.md +140 -58
- package/templates/standalone/config/application.conf +41 -36
- package/templates/standalone/config/clients.yaml.example +8 -0
- package/templates/standalone/config/development.conf +2 -2
- package/templates/standalone/config/mailpit.conf +10 -5
- package/templates/standalone/config/reference.conf +30 -5
- package/templates/standalone/docker-compose.mailpit.yml +3 -0
- package/templates/standalone/docker-compose.production.yml +5 -2
- package/templates/standalone/package.json +1 -1
- package/templates/standalone/src/__tests__/adapters.test.mts +92 -25
- package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +87 -0
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +112 -98
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +40 -11
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +86 -51
- package/templates/standalone/src/__tests__/audit-sink.test.mts +10 -9
- package/templates/standalone/src/__tests__/config-notices.test.mts +157 -0
- package/templates/standalone/src/__tests__/configured-mfa-mode.fixture.mts +48 -0
- package/templates/standalone/src/__tests__/core-federations.test.mts +57 -31
- package/templates/standalone/src/__tests__/core-sections.test.mts +7 -5
- package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +1 -1
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +434 -136
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +19 -21
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +9 -15
- package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +10 -4
- package/templates/standalone/src/__tests__/federation-types.test.mts +239 -0
- package/templates/standalone/src/__tests__/google-session.fixture.mts +60 -67
- package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +1 -1
- package/templates/standalone/src/__tests__/library-references.fixture.mts +51 -46
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +216 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +81 -16
- package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +122 -0
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +8 -4
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +149 -46
- package/templates/standalone/src/__tests__/own-modules.test.mts +78 -23
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +3 -3
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +214 -0
- package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +8 -4
- package/templates/standalone/src/__tests__/replica-safety.test.mts +131 -19
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +25 -23
- package/templates/standalone/src/__tests__/sections.test.mts +24 -0
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +215 -0
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +3 -7
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +130 -0
- package/templates/standalone/src/__tests__/session-requirements.test.mts +114 -31
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +274 -0
- package/templates/standalone/src/__tests__/smoke.test.mts +19 -28
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +148 -0
- package/templates/standalone/src/__tests__/store-credential.test.mts +8 -13
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +58 -38
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +243 -76
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +23 -31
- package/templates/standalone/src/adapters.mts +31 -18
- package/templates/standalone/src/app.mts +6 -5
- package/templates/standalone/src/bootRefusal.mts +117 -0
- package/templates/standalone/src/buildModules.mts +46 -96
- package/templates/standalone/src/configPath.mts +180 -153
- package/templates/standalone/src/mfaSwitch.mts +49 -27
- package/templates/standalone/src/modules.mts +37 -219
- package/templates/standalone/src/rootRenames.mts +38 -22
- package/templates/standalone/src/sections.mts +13 -2
- package/templates/standalone/tests/index.test.js +4 -3
- package/templates/versions.json +18 -18
- package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +0 -134
- package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +0 -202
- package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +0 -144
package/README.ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @o3co/create-auth-provider
|
|
2
2
|
|
|
3
|
-
最終更新: 2026-10-
|
|
3
|
+
最終更新: 2026-10-06
|
|
4
4
|
|
|
5
5
|
auth.provider 用の CLI スキャフォルダーです。内包するテンプレートの 1 つから新しいサーバープロジェクトを生成します。
|
|
6
6
|
|
|
@@ -86,6 +86,8 @@ SMTP リレー)を設定するか、`MFA_MODE=off` で MFA を無効にする
|
|
|
86
86
|
`mfaMode = ${?MFA_MODE}` をこの順に追記するので、後のテンプレートがデフォルトを
|
|
87
87
|
変えてもプロジェクトは off のままで、`MFA_MODE` を設定すれば作り直さずに MFA を
|
|
88
88
|
有効にできます。MFA パッケージは依存に残り、ほかには何も削除・変更しません。
|
|
89
|
+
プロジェクトに含まれるテスト(`pnpm run test`)はスイッチをプロジェクトの設定から
|
|
90
|
+
読み、その値が意味する動作を確かめるので、`--no-mfa` の有無にかかわらず通ります。
|
|
89
91
|
|
|
90
92
|
生成されるプロジェクトは pnpm のプロジェクトです。`Dockerfile` は
|
|
91
93
|
`pnpm install --frozen-lockfile` でインストールし、ビルドの許可リストは
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @o3co/create-auth-provider
|
|
2
2
|
|
|
3
|
-
Last updated: 2026-10-
|
|
3
|
+
Last updated: 2026-10-06
|
|
4
4
|
|
|
5
5
|
CLI scaffolder for auth.provider. Generates a new server project from one of the built-in templates.
|
|
6
6
|
|
|
@@ -85,7 +85,10 @@ switch installs and needs is in the template's README.
|
|
|
85
85
|
it appends `mfaMode = "off"` and then `mfaMode = ${?MFA_MODE}` to
|
|
86
86
|
`config/application.conf`, so the project stays off whatever default a later
|
|
87
87
|
template ships, and `MFA_MODE` still turns MFA on without re-scaffolding. The
|
|
88
|
-
MFA package stays a dependency; nothing else is removed or changed.
|
|
88
|
+
MFA package stays a dependency; nothing else is removed or changed. The
|
|
89
|
+
project's own suite (`pnpm run test`) reads the switch from the project's
|
|
90
|
+
configuration and asserts what it implies, so it passes with or without
|
|
91
|
+
`--no-mfa`.
|
|
89
92
|
|
|
90
93
|
The generated project is a pnpm project: its `Dockerfile` installs with
|
|
91
94
|
`pnpm install --frozen-lockfile`, and its build allowlist lives in
|
package/package.json
CHANGED
|
@@ -78,6 +78,10 @@ SESSION_STORE_STORAGE_REDIS_URL=redis://redis:6379
|
|
|
78
78
|
# defaults to redis://localhost:6379, which inside the container is the
|
|
79
79
|
# container, so leaving it unset makes the compose boot dial nothing.
|
|
80
80
|
REDIS_CLIENTS_URL=redis://redis:6379
|
|
81
|
+
# The Redis stores refuse to boot unless the server reports maxmemory-policy
|
|
82
|
+
# noeviction (README: Multi-replica deployments). Only for a server that does
|
|
83
|
+
# run noeviction but will not report it (INFO and CONFIG refused to this user):
|
|
84
|
+
# REDIS_CLIENTS_ASSUME_NO_EVICTION=true
|
|
81
85
|
|
|
82
86
|
# --- Federation grants (#593, optional) --------------------------------------
|
|
83
87
|
# Offline delegation of an upstream IdP's tokens to a backend or an agent, on a
|
|
@@ -126,8 +130,9 @@ REDIS_CLIENTS_URL=redis://redis:6379
|
|
|
126
130
|
# Or run without MFA:
|
|
127
131
|
# MFA_MODE=off
|
|
128
132
|
# To read the mail in development, leave every STANDARD_SMTP_MAIL_SENDER_*
|
|
129
|
-
# unset (USER and PASSWORD included)
|
|
130
|
-
#
|
|
133
|
+
# unset (USER and PASSWORD included), set MFA_ENCRYPTION_KEY to a key of your
|
|
134
|
+
# own (the run does not use the development sample key), and run the Mailpit
|
|
135
|
+
# overlay, which sets the sender's variables (README: Mailpit for the SMTP sender):
|
|
131
136
|
# docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up --build
|
|
132
137
|
|
|
133
138
|
# Development (override for local HTTP).
|
|
@@ -142,9 +147,6 @@ SESSION_STORE_NAME=auth.sid
|
|
|
142
147
|
# OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN=3600
|
|
143
148
|
# The most a token-exchange `expires_in` can obtain; unset = the default.
|
|
144
149
|
# OAUTH_ACCESS_TOKEN_MAX_EXPIRES_IN=3600
|
|
145
|
-
# DEPRECATED alias of OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN, read only while
|
|
146
|
-
# that one is unset.
|
|
147
|
-
# OAUTH_ACCESS_TOKEN_EXPIRES_IN=3600
|
|
148
150
|
# OAUTH_REFRESH_TOKEN_EXPIRES_IN=86400
|
|
149
151
|
# SESSION_STORE_MAX_AGE=3600000
|
|
150
152
|
# SESSION_STORE_SAME_SITE=lax # `none` additionally requires SESSION_STORE_SECURE=true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @o3co/auth-provider-standalone
|
|
2
2
|
|
|
3
|
-
最終更新: 2026-10-
|
|
3
|
+
最終更新: 2026-10-06
|
|
4
4
|
|
|
5
5
|
auth.provider のデプロイ可能なサーバーテンプレート。これは composition root であり、設定を読み込み、モジュールをロードし、Express サーバーを起動する。`@o3co/create-auth-provider` で生成される。
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ auth.provider のデプロイ可能なサーバーテンプレート。これは
|
|
|
11
11
|
**所有する**のは、1 つのデプロイに固有の選択である:
|
|
12
12
|
|
|
13
13
|
- どのモジュールをどの順序で合成し、各ストアスロットをどのアダプターで埋めるか — [`src/buildModules.mts`](src/buildModules.mts)([モジュール合成順序](#モジュール合成順序) を参照)。その選択は自身のキー、`adapters` セクション([`src/adapters.mts`](src/adapters.mts))と MFA のスイッチ `mfaMode`([`src/mfaSwitch.mts`](src/mfaSwitch.mts))から読む
|
|
14
|
-
- この scaffold だけが持つモジュール — `logging` と `http`(ホストプロセス自身の設定とその CORS のリスト、および core の `httpSettings` スロット)、署名鍵ストア、クライアントリポジトリとユーザーリポジトリ、監査 sink、共有される唯一の Redis 接続、in-memory
|
|
14
|
+
- この scaffold だけが持つモジュール — `logging` と `http`(ホストプロセス自身の設定とその CORS のリスト、および core の `httpSettings` スロット)、署名鍵ストア、クライアントリポジトリとユーザーリポジトリ、監査 sink、共有される唯一の Redis 接続、in-memory のユーザーセッションストア・コードリポジトリ・フェデレーショントークンストア — [`src/modules.mts`](src/modules.mts)。それらが所有するセクションのデフォルトは [`config/reference.conf`](config/reference.conf) にある。これらがパッケージではなく scaffold 側にあるのは、どれもこのテンプレートの設定セクションから自分のコンポーネントを組み立てるためである。別のソースを使いたいデプロイは、同じ形のモジュールを自前で配線する
|
|
15
15
|
- 設定をどこから読み、そのレイヤーをどう重ねるか — [`src/configPath.mts`](src/configPath.mts) と [`config/`](config/)
|
|
16
16
|
- ホストプロセス: Express アプリ、そのセキュリティヘッダー、起動処理 — [`src/app.mts`](src/app.mts)。health / readiness / metrics の各ルート、合成されたルーター、その後ろの core の終端のエラーハンドラーを、マウントする順に — [`src/routes.mts`](src/routes.mts)。リスナーはソケットが bind されたときに `server_listening`(info、`port`)を 1 行ログに出し、bind できなければその bind のエラーで起動を失敗させ、その後のサーバーのエラーは `server_error`(error)としてログに出す — [`src/listen.mts`](src/listen.mts)。このコードがログに出すエラー — 処理されなかったリクエストのエラー、共有 Redis 接続の `error` イベント、失敗したシャットダウン — はすべて core の [`loggableError`](../../packages/core/README.ja.md#logger) による射影としてログに出し、エラーそのものは出さない。エラーは上流や Redis が言ったことを運びうるためである(サーバーが接続を拒否したとき、接続のエラーは `AUTH` のハンドシェイクをパスワードごと運ぶ)
|
|
17
17
|
- 具体的な logger と監査ストリーム(pino) — [`src/logger.mts`](src/logger.mts) — およびメトリクス([`src/metrics.mts`](src/metrics.mts))
|
|
@@ -50,7 +50,7 @@ my-app:
|
|
|
50
50
|
|
|
51
51
|
**これがしないこと。** ファーストパーティで*ある*クライアントについて、`/authorize` を強制遷移に対して安全にするわけではない — それはここで受け入れているモデルのままである。これが防ぐのは、確認なしのコード発行を任せるべきではなかったクライアントが、誤ってその立場に登録されることである。
|
|
52
52
|
|
|
53
|
-
**マークの無い登録。** このフィールドの無い登録はファーストパーティではない: デフォルトのままでは、マークするまで `/authorize` で拒否され、警告付きで受け入れる猶予期間も無い。自分で運用するクライアントは、`/authorize` に向ける前にすべてマークすること。`oauth.authorize.allowUnmarkedClients` は設定項目ではない: これを設定した config や環境(`OAUTH_AUTHORIZE_ALLOW_UNMARKED_CLIENTS
|
|
53
|
+
**マークの無い登録。** このフィールドの無い登録はファーストパーティではない: デフォルトのままでは、マークするまで `/authorize` で拒否され、警告付きで受け入れる猶予期間も無い。自分で運用するクライアントは、`/authorize` に向ける前にすべてマークすること。`oauth.authorize.allowUnmarkedClients` は設定項目ではない: これを設定した config や環境(`OAUTH_AUTHORIZE_ALLOW_UNMARKED_CLIENTS`、値は問わない)は、黙って無視されるのではなく、起動時に失敗する: oauth モジュールがキーを削除されたキーとして(`config-path-relocated`)、変数を削除されたキーに束縛されていた変数として(`environment-variable-renamed`)拒否する。クライアントをマークしてから、キーを消し、変数を unset すること。
|
|
54
54
|
|
|
55
55
|
## マルチレプリカ構成
|
|
56
56
|
|
|
@@ -58,6 +58,8 @@ my-app:
|
|
|
58
58
|
|
|
59
59
|
ロードバランサ配下で standalone サーバーを複数インスタンス動かすときは、`REDIS_CLIENTS_URL` を**共有**の Redis 7.2+ インスタンスに向けること。共有の Redis URL が無いと、各レプリカは refresh token family を自分の接続(と、ローカル専用の Redis)に保持するため、トークンを発行していないレプリカに届いた refresh リクエストは `invalid_grant` を返し、ラウンドロビンの LB では 1 回おきにそうなる。
|
|
60
60
|
|
|
61
|
+
**その Redis は `maxmemory-policy noeviction`(Redis のデフォルト)で動かすこと。** 期限まで残らなければならないキーを持つ Redis のストア — attempt counter、ユーザーセッションストアのライフサイクルストア、フェデレーショントークンストア、MFA のストア — は起動時にポリシーを読む(`INFO memory`、次に `CONFIG GET maxmemory-policy`)。ほかのポリシーでは起動を拒否し、サーバーがポリシーを答えないときも拒否する。接続のユーザーが `INFO` や `CONFIG` を実行できない(ACL で制限されたユーザー、それらを改名するマネージドサービス)が、サーバーが `noeviction` で動いていると分かっているなら、`REDIS_CLIENTS_ASSUME_NO_EVICTION=true`(`redis-clients.assumeNoEviction`、デフォルト `false`)を設定すること。これはポリシーを読めないときにだけ読まれる: サーバーが答えたポリシーが常に優先するので、`allkeys-*` や `volatile-*` のサーバーを通すことはない。
|
|
62
|
+
|
|
61
63
|
**ロードバランサ配下では `HTTP_TRUST_PROXY` をプロキシのアドレスに設定すること。** デフォルトは `false` で、この場合 `req.ip` はクライアントではなく*ロードバランサの*アドレスになる。すると IP をキーとするすべての rate limit — OAuth エンドポイントの limiter と `POST /session/login` の総当たり対策ガード — が**全ユーザーで 1 つのバケットを共有する**: 誰からであれ最初の 20 回のログイン試行でウィンドウを使い切り、以降のユーザーは全員 `429` を受け取る。この障害は設定ミスではなく攻撃のように見えるため、深夜 3 時の診断が高くつく。このサービスの前段で何かが TLS を終端したりプロキシしたりしているなら必ず設定し、`X-Forwarded-For` を設定しているのがそのホップであることを確認すること。`ip` のない監査イベントは、リクエストのアドレスがアドレスでなかった — `req.ip` がアドレス以外を入れた `X-Forwarded-For` から来た — ことを意味するので、`HTTP_TRUST_PROXY` がそのヘッダーを設定するホップだけを信頼しているか確認すること。
|
|
62
64
|
|
|
63
65
|
取りうる形は 4 つで、いずれも Express 自身のものである:
|
|
@@ -73,7 +75,11 @@ my-app:
|
|
|
73
75
|
|
|
74
76
|
allowlist はネットワーク上の制御であって、暗号学的な制御ではない。エッジは受信した `X-Forwarded-*` ヘッダーに追記するのではなく**除去**しなければならず、エッジとこのプロセスの間のホップは、送信元アドレスを偽装できる者から到達できてはならない。
|
|
75
77
|
|
|
76
|
-
**`adapters.rateLimiter` を Redis に向けること。** デフォルトは `"memory"` でプロセスごとである: N レプリカでは設定したすべての limit が実質 N 倍になり、デプロイのたびにリセットされる。memory アダプターは**バケット枯渇によって回避可能**でもある: バケット数の上限を 10,000 とし、上限に達すると、新しいキーを受け入れる際にリセットが最も近いバケットを追い出す — そのため多数の送信元 IP を提示できる攻撃者(`HTTP_TRUST_PROXY` が実際のホップより広ければ、`req.ip` はクライアントの影響を受ける)は、標的のカウンターが追い出されてやり直しになるまでテーブルをかき回せる。これは開発用の 1 プロセスなら許容できるが、本番の rate limit
|
|
78
|
+
**`adapters.rateLimiter` を Redis に向けること。** デフォルトは `"memory"` でプロセスごとである: N レプリカでは設定したすべての limit が実質 N 倍になり、デプロイのたびにリセットされる。memory アダプターは**バケット枯渇によって回避可能**でもある: バケット数の上限を 10,000 とし、上限に達すると、新しいキーを受け入れる際にリセットが最も近いバケットを追い出す — そのため多数の送信元 IP を提示できる攻撃者(`HTTP_TRUST_PROXY` が実際のホップより広ければ、`req.ip` はクライアントの影響を受ける)は、標的のカウンターが追い出されてやり直しになるまでテーブルをかき回せる。これは開発用の 1 プロセスなら許容できるが、本番の rate limit ではない。`/session/login` はこのコンポーネントの上では動かず、device grant を加えたときの `POST /oauth/device/verification` も同じである: それぞれ自身の試行上限(`session.rateLimit.login`、`device-grant.rateLimit`)は試行カウンターの上で数えられ、リミッターの設定はこれを変えない。
|
|
79
|
+
|
|
80
|
+
**MFA ルートの予算はリミッターのものである。** MFA パッケージは自身のルートの予算を持たない: `/session/mfa` への POST はすべて、配線されたリミッターだけによって、クライアントアドレスごとにプレフィックス `mfa` の下で制限される。`config/reference.conf` は両方のリミッターに `limits.mfa { limit = 60, windowSeconds = 300 }`(`core-rate-limiter-memory.limits.mfa`、`redis-rate-limiter.limits.mfa`)を与えるので、`adapters.rateLimiter` を切り替えてもこの予算は保たれる。そこでは既定値なので、構成が配線しないリミッターがこれを持っていても、boot はそのセクションの名前を出さない。変えるなら、配線したリミッターについて `config/application.conf` か自分のレイヤーで変えること。エントリがなければリミッターの `defaultLimit`(60 秒あたり 60)が適用される。MFA のロック — トランザクションごとの試行回数、バックオフ、週ごとの失敗回数 — は MFA パッケージ自身のもので、リミッターの設定はこれを変えない。
|
|
81
|
+
|
|
82
|
+
**`adapters.attemptCounter` を Redis に向けること。** デフォルトは `"memory"` でカウンターを配線しない: ログイン(device grant を加えたときはデバイス検証も)は試行をプロセスごとに数えるので、N レプリカでは `session.rateLimit.login`(と `device-grant.rateLimit`)が上限の N 倍を許し、デプロイのたびにリセットされる。`CORE_DEPLOYMENT_MODE=multi` は起動を拒否する。`"redis"`(`ADAPTERS_ATTEMPT_COUNTER=redis`)は共有の Redis ソケットの上に `redisAttemptCounterModule` を入れる。その Redis は `maxmemory-policy noeviction`(Redis のデフォルト)で動かすこと: 追い出された窓は数え直しになるからである。モジュールはほかのポリシーでは起動を拒否する。サーバーがポリシーを答えないときも、`REDIS_CLIENTS_ASSUME_NO_EVICTION=true`(上記)でない限り拒否する。どちらも閉じる側に倒れる: カウンターが答えられない間、`/session/login` と `POST /oauth/device/verification` は `redis-rate-limiter.failMode` が何であっても `503` を返す。
|
|
77
83
|
|
|
78
84
|
**BFF の背後では、必要になる前に `limits.token` を上げておくこと。** OAuth エンドポイントの rate limit は `req.ip` をキーにする(`packages/core/src/ratelimit/guard.mts` はバケットキーを `<endpoint>:ip:<req.ip>` として組み立てる)。クライアントがブラウザやネイティブアプリで、このプロバイダーと直接通信しているなら、これは正しい identity である。しかし backend-for-frontend 構成 — サーバー側アプリがセッションを保持し、ユーザーに代わってコード交換と refresh を行う構成 — では誤った identity になる: すべての `/oauth/token` と `/oauth/introspect` の呼び出しが BFF の単一アドレスから届き、デプロイ全体で 1 つのバケットを共有する。デフォルトの 60 秒あたり 60 リクエストでは、**全ユーザー合計で毎分およそ 60 回の session グラント交換**が上限になる — しかもそれは rate limit として表に出てこない。BFF は想定していない `429` を受け取り、それを自分の呼び出し元への `502` に変え、ユーザーが報告する症状は「サインインがときどき壊れる」になる。それは起き始めるトラフィック量に達した時点で現れ、それより前には現れない。
|
|
79
85
|
|
|
@@ -94,9 +100,9 @@ redis-rate-limiter {
|
|
|
94
100
|
|
|
95
101
|
デフォルトは意図的に変えていない: 送信元 IP ごとに 60 秒あたり 60 回というのは、クライアントが本当に別々の IP であるデプロイにとって妥当な総当たり対策の上限であり、一方の構成に合わせて全体的に引き上げれば、もう一方の構成での防御が弱まる。BFF *自身の*ユーザーを守るスロットリングは、ユーザーごとの identity がまだ存在する BFF の前段に置くこと。
|
|
96
102
|
|
|
97
|
-
**2 つ以上のレプリカを動かすようになったら `CORE_DEPLOYMENT_MODE=multi` を設定すること。** すると、共有が必要な in-memory ストアがまだ配線されていれば起動が*失敗*し、該当するものすべてと、それぞれの代償が名指しされる — ユーザーセッションの分岐(back-channel logout が 1 つのレプリカにしか届かず、ログアウトしたセッションが他のレプリカでは有効なまま)、rate limit カウンターの倍増、アクセストークン失効の未伝播、一度きりのクライアントアサーションや WebAuthn チャレンジがレプリカごとに 1 回ずつ再利用できてしまうこと。このチェックはライブラリのモジュール名のリストではなく、インストールされた各モジュールが自身の manifest に持つ宣言を読むため、このテンプレート独自の in-memory モジュール — ユーザーセッションストア(`ADAPTERS_USER_SESSION_STORES=memory`)、認可コードリポジトリ(`ADAPTERS_CODE_REPOSITORY=memory`)、フェデレーショントークンストア(`ADAPTERS_FEDERATION_TOKEN_STORE=memory`、デフォルト) — も名指しで拒否される。`SESSION_STORE_STORAGE_TYPE=memory` のときの express-session 自身のストア(#474)と、デフォルトの memory の rate limiter(`core-rate-limiter-memory`、`ADAPTERS_RATE_LIMITER=memory`)も同様である。モードが未設定なら何も拒否されない: これらはすべて、起動時の 1 件の `replica_unsafe_adapters` 警告に列挙される。(
|
|
103
|
+
**2 つ以上のレプリカを動かすようになったら `CORE_DEPLOYMENT_MODE=multi` を設定すること。** すると、共有が必要な in-memory ストアがまだ配線されていれば起動が*失敗*し、該当するものすべてと、それぞれの代償が名指しされる — ユーザーセッションの分岐(back-channel logout が 1 つのレプリカにしか届かず、ログアウトしたセッションが他のレプリカでは有効なまま)、rate limit カウンターの倍増、アクセストークン失効の未伝播、一度きりのクライアントアサーションや WebAuthn チャレンジがレプリカごとに 1 回ずつ再利用できてしまうこと。このチェックはライブラリのモジュール名のリストではなく、インストールされた各モジュールが自身の manifest に持つ宣言を読むため、このテンプレート独自の in-memory モジュール — ユーザーセッションストア(`ADAPTERS_USER_SESSION_STORES=memory`)、認可コードリポジトリ(`ADAPTERS_CODE_REPOSITORY=memory`)、フェデレーショントークンストア(`ADAPTERS_FEDERATION_TOKEN_STORE=memory`、デフォルト) — も名指しで拒否される。`SESSION_STORE_STORAGE_TYPE=memory` のときの express-session 自身のストア(#474)と、デフォルトの memory の rate limiter(`core-rate-limiter-memory`、`ADAPTERS_RATE_LIMITER=memory`)も同様である。モードが未設定なら何も拒否されない: これらはすべて、起動時の 1 件の `replica_unsafe_adapters` 警告に列挙される。(WebAuthn-options のルートは個別に警告するプロセス単位のフォールバック limiter を持つが、それが働くのは `rateLimiter` をまったく配線しない構成だけで、このテンプレートは常に配線する。ログインは `ADAPTERS_ATTEMPT_COUNTER=memory` のときプロセスごとに数える: `multi` ではログインを名指しして起動が拒否され、モード未設定なら `attempt_counter_not_shared` を警告する。オペレーター runbook を参照。)`CORE_DEPLOYMENT_MODE=single` ではチェックは何も言わない。レプリカは 1 つだと宣言したからである。このテンプレートは DPoP をインストールしない。DPoP を加えた構成では、受け入れた DPoP proof はすべて `private_key_jwt` と同じ replay seen-set(`ADAPTERS_REPLAY_SEEN_SET`)に記録されるため、同じ扱いを受ける — `memory` は `CORE_DEPLOYMENT_MODE=multi` のもとで拒否され、モード未設定なら警告に列挙される。同梱の `redis` なら DPoP の記録もレプリカ間で共有される。dpop パッケージの [operator requirements](../../packages/dpop/README.md#operator-requirements) を参照。
|
|
98
104
|
|
|
99
|
-
この変数は `core.deployment.mode` を設定する。旧名の `DEPLOYMENT_MODE`
|
|
105
|
+
この変数は `core.deployment.mode` を設定する。旧名の `DEPLOYMENT_MODE` は、設定されていれば、単独でも `CORE_DEPLOYMENT_MODE` と並べても、値が何であれブートを拒否する。外すこと。
|
|
100
106
|
|
|
101
107
|
このチェックに*できない*ことも把握しておくこと: `CORE_DEPLOYMENT_MODE` を一度も設定しないまま N レプリカにスケールしても、何も失敗しない。すべての状態を自分のメモリに持つプロセスには、他のピアに気づくための共有媒体が無い — その状態は、まさにそれが真であるときに内側からは検出できない。この変数はスケールの一環として設定し、何かが壊れてからにしないこと。
|
|
102
108
|
|
|
@@ -204,9 +210,9 @@ pnpm run start
|
|
|
204
210
|
|
|
205
211
|
1. **`config/{ENV}.conf`** — 現在の環境の overlay。`ENV = CONFIG_ENV || NODE_ENV || "development"` で決まる。
|
|
206
212
|
2. **`config/application.conf`** — このデプロイの設定。
|
|
207
|
-
3. **読み込むモジュールの各パッケージの `reference.conf`**、その次に **`@o3co/auth-provider-core` のもの** — デフォルト。インストール済みパッケージから解決される: 各モジュールが自分のパッケージのファイルを宣言し、core の `moduleReferences(modules)` がそれを core のものを最後にして列挙する。テンプレート自身のモジュールは `config/reference.conf` を宣言し、そこには自身が所有するセクション — `logging`、`http`(その CORS のリスト `http.cors` を含む)、`key-store`、共有 Redis 接続の `redis-clients`、`repositories`、in-process のコードリポジトリの `standalone-in-memory-code-repository`、`audit-sink` — と、composition root 自身の `adapters` と `mfaMode
|
|
213
|
+
3. **読み込むモジュールの各パッケージの `reference.conf`**、その次に **`@o3co/auth-provider-core` のもの** — デフォルト。インストール済みパッケージから解決される: 各モジュールが自分のパッケージのファイルを宣言し、core の `moduleReferences(modules)` がそれを core のものを最後にして列挙する。テンプレート自身のモジュールは `config/reference.conf` を宣言し、そこには自身が所有するセクション — `logging`、`http`(その CORS のリスト `http.cors` を含む)、`key-store`、共有 Redis 接続の `redis-clients`、`repositories`、in-process のコードリポジトリの `standalone-in-memory-code-repository`、`audit-sink` — と、composition root 自身の `adapters` と `mfaMode`(下記)のデフォルト、そして両方のレートリミッターのセクションにある MFA ルートの予算(`core-rate-limiter-memory.limits.mfa`、`redis-rate-limiter.limits.mfa`。[マルチレプリカ構成](#マルチレプリカ構成) を参照)がある。デプロイ固有の値はそこではなく上の 2 ファイルに書く。`config/reference.conf` はこれらのキーの変数をデフォルトの横で束縛するので、上の 2 ファイルのどちらかが設定する値は変数に勝つ。ただし `HTTP_PORT`、`HTTP_TRUST_PROXY`、`HTTP_CORS_ALLOWED_ORIGINS`、`REDIS_CLIENTS_URL` / `REDIS_CLIENTS_PASSWORD` は `application.conf` が末尾の行でもう一度束縛するので、それぞれ、そのファイルがそれより上で設定する値に勝つ。上の 2 ファイルのどちらも設定していないキーは、ここから値を得る。
|
|
208
214
|
|
|
209
|
-
上の 2 ファイルは、環境変数の一つのスナップショットのもとで一度だけ読み(`readOwnLayers`)、その一度の読み込みから二つの段階を組み立てる — 起動中にファイルが置き換えられても、変数が変わっても、boot がモジュールを選んだものと違うものをパースすることはない。読み込みは二段階で行う([#728](https://github.com/o3co/auth.provider/issues/728)、[`src/configPath.mts`](src/configPath.mts))。まず、モジュールを知る前に、`buildModules`
|
|
215
|
+
上の 2 ファイルは、環境変数の一つのスナップショットのもとで一度だけ読み(`readOwnLayers`)、その一度の読み込みから二つの段階を組み立てる — 起動中にファイルが置き換えられても、変数が変わっても、boot がモジュールを選んだものと違うものをパースすることはない。読み込みは二段階で行う([#728](https://github.com/o3co/auth.provider/issues/728)、[`src/configPath.mts`](src/configPath.mts))。まず、モジュールを知る前に、`buildModules` がモジュールを選ぶもの — フェデレーションのエントリは決して読まない — を、上の 2 ファイルをテンプレートの `config/reference.conf` だけの上に重ねて読む(`readSwitches`)。`adapters` — 各スロットをどのアダプターで埋めるか、composition root 自身のセクション — は、テンプレート自身のスキーマで読む(`readAdapters`): 移動する前のパスにまだ書かれた選択や、それとともに改名された変数は、どのモジュールを選ぶよりも前にここで拒否され、新しいパスと変数を名指しする。composition root 自身のキーである MFA のスイッチ `mfaMode`(`MFA_MODE`)も同じように、テンプレート自身のスキーマで読み(`readMfaSwitch`)、それと並んで、Store を使う MFA 要素ストアの元になるユーザーリポジトリの HTTP 設定をパースせずに読む。フェデレーショングラントを組み込むかどうかは `federation-grants.enabled`(`FEDERATION_GRANTS_ENABLED`。このために `application.conf` がバインドする)から、core の `coerceBooleanFromEnv` で読む: `false` と読めない値はフェデレーショングラントのモジュールを組み込むので、不正な値でも機能がオフと読まれることはなく、boot が `federation-grants.enabled` で拒否する。セクションの古いパス `federationGrants` に書かれた設定も同じく組み込み、boot が新しいパスを名指しして拒否する。boot には `adapters` も `mfaMode` も渡さない。第一段階はこれ以外のキーを読まず、パッケージの `reference.conf` だけが設定するものも見えない — まだどれも重ねていない。`buildModules` に加えたモジュールは、自分のセクションを boot 時に読む(`deps.section`)のであって、組み立て時には読まない。ログレベルも boot の前に読むが、それは `logging` モジュールのセクションとして、そのモジュールのスキーマで、テンプレートの `reference.conf` の上に読む(`readLogging`): テンプレートは設定を読みモジュールを選ぶ間もログを出すので、logger は boot の前に存在する。テンプレートが boot の前に行う設定の拒否 — 第一段階での `adapters`・`mfaMode`・`logging`、および下で boot に渡すものを組み立てるとき(`resolveForBoot`)— は、boot が同じ場合に使う reason(`config-path-relocated`、`environment-variable-renamed`、`config-validation-failed`、`module-section-path-invalid`)を持つ `BootError` として投げる([`src/bootRefusal.mts`](src/bootRefusal.mts))ので、boot エラーの reason に対するアラートはこれも捉える。起動の失敗がすべて reason を持つわけではない: `config/` の外のファイルを指す `CONFIG_ENV` は素の `Error`、存在しない `{ENV}.conf` や HOCON がパースできないファイルは HOCON ライブラリのエラー、boot の後の失敗(リスナーがバインドできないポートなど)はそれ自身のエラーである。次に、読み込むすべてのモジュールの `reference.conf` の上に解決した設定を、MFA のスイッチが期待するとおりの `core.sessionRequirements` — スイッチが MFA を組み込むときは設定のリストに `mfa` を加えたもの。名前のリストでない値は書かれたまま渡し、boot が拒否する — を書き込み、`mfa` セクションは MFA のスイッチが決めるとおりにして([多要素認証](#多要素認証) を参照)、パースせずに `createApp` に渡す(`resolveForBoot`): boot はそれを、読み込まれたすべてのモジュールのスキーマで一度だけパースする。boot の後にテンプレートが読むもの — 信頼するホップ、ポート、readiness の期限 — は、`http` モジュールから読む: core の `httpSettings` スロットと、テンプレートの `httpHostSettings` である。それと並んで、テンプレートは設定の既定値 — 同じ `reference.conf` 群を、上の 2 ファイルも環境変数もなしで解決したもの(`configDefaultsFor`)— を boot に渡す。読み込まれたどのモジュールも所有しないトップレベルのセクションは残され、boot はそれをこの既定値で区別して、一度だけ warn で名前を出す: 読み込まれたパッケージの `reference.conf` が、構成が読み込まないモジュールのために設定するセクション(たとえば Redis 上のストアがないときの `redis-clients`)は、そのファイルが設定するとおりのままなら名前が出ず、ファイルか変数が変えると `config_sections_not_loaded` — その設定はどこにも届かない — になる。読み込まれたどのパッケージも設定しないセクションは `config_sections_ignored` で、セクション名の綴り間違いはここに現れる。読み込まれたパッケージの `reference.conf` が捕捉する改名済みの変数のうち、読み込まれたどのモジュールも宣言しないものは `environment_variables_not_applied` として名前が出る。それぞれの行は [オペレーター runbook](../../docs/operator-runbook.md) にある。
|
|
210
216
|
|
|
211
217
|
overlay の値は `application.conf` より優先される。scaffold には `development.conf` と `production.conf`、そして `mailpit.conf` が同梱されている。`mailpit.conf` は、[Docker](#docker) の Mailpit オーバーレイが選ぶ名前のもとでの development の overlay である。別の環境(例: `staging`)を追加するときは `config/staging.conf` を作成し、`CONFIG_ENV=staging` を設定する。`{ENV}.conf` が存在しない場合は起動時エラーになる — タイポは黙ってデフォルトにフォールバックせず、fail-fast する。
|
|
212
218
|
|
|
@@ -217,6 +223,7 @@ overlay の値は `application.conf` より優先される。scaffold には `de
|
|
|
217
223
|
| 変数 | デフォルト | 説明 |
|
|
218
224
|
|---|---|---|
|
|
219
225
|
| `ADAPTERS_RATE_LIMITER` | `memory` | `adapters.rateLimiter`: `memory` または `redis`。[マルチレプリカ構成](#マルチレプリカ構成) を参照 |
|
|
226
|
+
| `ADAPTERS_ATTEMPT_COUNTER` | `memory` | `adapters.attemptCounter`: ログインの試行上限(device grant を加えたときはデバイス検証のものも)を数えるカウンター。`memory`(配線しない: プロセスごと)または `redis`。[マルチレプリカ構成](#マルチレプリカ構成) を参照 |
|
|
220
227
|
| `ADAPTERS_USER_SESSION_STORES` | `memory` | `adapters.userSessionStores`: ユーザーセッションストア、`memory` または `redis`。`SESSION_STORE_STORAGE_TYPE` と揃える([Docker](#docker) を参照) |
|
|
221
228
|
| `ADAPTERS_ACCESS_TOKEN_DENYLIST` | `redis` | `adapters.accessTokenDenylist`: `memory` または `redis` |
|
|
222
229
|
| `ADAPTERS_REPLAY_SEEN_SET` | `redis` | `adapters.replaySeenSet`: `private_key_jwt` の背後の replay seen-set、`memory` または `redis` |
|
|
@@ -231,7 +238,7 @@ overlay の値は `application.conf` より優先される。scaffold には `de
|
|
|
231
238
|
| `ADAPTERS_USER_REPOSITORY` | `http` | `adapters.userRepository`: `http`、`yaml` または `static`(core の `yaml` の別名)。[ユーザーリポジトリ](#ユーザーリポジトリ) を参照 |
|
|
232
239
|
| `ADAPTERS_AUDIT_SINK` | `logger` | `adapters.auditSink`: セキュリティイベントの送り先、`logger`(stdout への pino エンベロープ付き NDJSON)または `console`(イベントの JSON そのまま)。`none` は存在しない。[監査ログ](#監査ログ) を参照 |
|
|
233
240
|
|
|
234
|
-
移動する前の場所 — `rateLimiter.adapter`、`oauth.code.adapter`、`audit.sink.type`、`repositories.user.type` など — にまだ書かれた選択や、それとともに改名された変数(`RATE_LIMITER_ADAPTER`、`CLIENT_TYPE`
|
|
241
|
+
移動する前の場所 — `rateLimiter.adapter`、`oauth.code.adapter`、`audit.sink.type`、`repositories.user.type` など — にまだ書かれた選択や、それとともに改名された変数(`RATE_LIMITER_ADAPTER`、`CLIENT_TYPE` など)が設定されていれば、単独でも新しい変数と並べても、値が何であれ、新しいパスと変数を名指しして boot の前に失敗する。古い変数は外すこと。
|
|
235
242
|
|
|
236
243
|
### HTTP
|
|
237
244
|
|
|
@@ -290,13 +297,14 @@ openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
|
|
|
290
297
|
|---|---|---|
|
|
291
298
|
| `OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN` | `3600` | リクエストが有効期間を指定しないときに全グラントが発行するアクセストークンの有効期間(秒)。正の整数、上限は 1 年(`31536000`)。 |
|
|
292
299
|
| `OAUTH_ACCESS_TOKEN_MAX_EXPIRES_IN` | デフォルトと同じ | token exchange リクエストの `expires_in` パラメータで得られる上限。超えるリクエストはこの値に切り詰められる。未設定ならデフォルトと同じで、これを設定しない限りどのトークンも延長されない。デフォルトがこれを超えると、両キーを名指しして起動に失敗する。オフラインで検証するリソースサーバーに対し、交換で発行されたトークンが失効後も通用し得る期間の上限でもある。 |
|
|
293
|
-
| `OAUTH_ACCESS_TOKEN_EXPIRES_IN` | — | `OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN` の**非推奨(deprecated)**エイリアス(config キーでは `oauth.accessToken.expiresIn` が `oauth.accessToken.defaultExpiresIn` のエイリアス)。新しい変数が未設定の間だけ読まれる。これがまだデフォルトを決めている間は、起動時に `config_key_deprecated`(warn)がログに出る。値は新しい変数へ移すこと。 |
|
|
294
300
|
| `OAUTH_REFRESH_TOKEN_EXPIRES_IN` | `86400` | リフレッシュトークンの有効期間(秒)。正の整数、上限は 1 年(`31536000`)。 |
|
|
295
301
|
|
|
296
302
|
どれも 10 進数字だけを読む。空文字で export すると、フォールバックではなく起動失敗になる(HOCON は `FOO=` を `""` に解決する)。16 進(`0x10`)、指数(`1e3`)、符号(`+5`)、小数(`5.0`)も同じく起動失敗になる。
|
|
297
303
|
|
|
298
304
|
`expires_in` リクエストパラメータを読むのは token exchange(RFC 8693)だけで、他のグラントはそれを無視してデフォルトを発行する。
|
|
299
305
|
|
|
306
|
+
デフォルトの旧名 `OAUTH_ACCESS_TOKEN_EXPIRES_IN`(config キーでは `oauth.accessToken.expiresIn`)は、設定されていれば、単独でも `OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN` と並べても、値が何であれブートを拒否する。旧キーは値が何であれブートを拒否する。値は消さずに新しい名前へ移すこと: 消すとデフォルトは `3600` になる。
|
|
307
|
+
|
|
300
308
|
### グラントタイプ
|
|
301
309
|
|
|
302
310
|
| 変数 | デフォルト | 説明 |
|
|
@@ -307,7 +315,7 @@ openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
|
|
|
307
315
|
| `OAUTH_AUTHORIZATION_GRANTS_CLIENT_CREDENTIALS_ENABLED` | `false` | client credentials グラントタイプを有効化 |
|
|
308
316
|
| `OAUTH_AUTHORIZATION_GRANTS_JWT_BEARER_ENABLED` | `false` | jwt-bearer グラントタイプ(RFC 7523)を有効化 |
|
|
309
317
|
|
|
310
|
-
スイッチはそれぞれモジュールのキー — `oauth-session.enabled`、ほかは `oauth-authorization.grants.<grant>.enabled` —
|
|
318
|
+
スイッチはそれぞれモジュールのキー — `oauth-session.enabled`、ほかは `oauth-authorization.grants.<grant>.enabled` — である。どちらのモジュールも常に読み込まれ、自分のスイッチを boot がパースする設定から boot で読む。テンプレートはそのどれも boot の前には読まない。旧名の `OAUTH_GRANTS_<GRANT>_ENABLED` は、設定されていれば、単独でも新名と並べても、値が何であれ boot を拒否する。
|
|
311
319
|
|
|
312
320
|
### Session
|
|
313
321
|
|
|
@@ -324,7 +332,7 @@ openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
|
|
|
324
332
|
| `SESSION_STORE_STORAGE_REDIS_URL` | `redis://localhost:6379` | セッションストア用 Redis 接続 URL |
|
|
325
333
|
| `SESSION_STORE_STORAGE_REDIS_PASSWORD` | — | セッションストア用 Redis パスワード |
|
|
326
334
|
|
|
327
|
-
cookie とそのストアはセッションストアのセクション `session-store` のもので、CSRF トークンの寿命、ログインページ、ログインの予算は session モジュールのセクション `session` のもの。`SESSION_STORE_*` の旧名 `SESSION_<KEY>`
|
|
335
|
+
cookie とそのストアはセッションストアのセクション `session-store` のもので、CSRF トークンの寿命、ログインページ、ログインの予算は session モジュールのセクション `session` のもの。`SESSION_STORE_*` の旧名 `SESSION_<KEY>` は、設定されていれば、単独でも新名と並べても、値が何であれ boot を拒否する。
|
|
328
336
|
|
|
329
337
|
ローカルの HTTP 開発のために `SESSION_STORE_SECURE=false` を設定する場合や、ドメインを共有する Cookie のために `SESSION_STORE_DOMAIN` を設定する場合は、`SESSION_STORE_NAME` も `auth.sid` のような `__Host-` でない値にすること — `SESSION_STORE_SECURE=false` なら接頭辞の無い値にする: `__Secure-` の名前も `SESSION_STORE_SECURE=true` を要する。`__Host-` や `__Secure-`(大文字小文字は問わない)の Cookie 名が、ブラウザがその prefix に対して拒否する属性と組み合わされると、`SESSION_STORE_NAME` が Cookie の名前(RFC 6265 のトークン: 空白、`;` などの区切り文字を含まない)でないと、また `SESSION_STORE_DOMAIN` がホスト名でない(スキーム、ポート、パスを含む)と、サーバーは fail-fast する。
|
|
330
338
|
|
|
@@ -373,7 +381,25 @@ http.cors {
|
|
|
373
381
|
|
|
374
382
|
### Google フェデレーション
|
|
375
383
|
|
|
376
|
-
フェデレーションは core のものである: `core.federations`、各フェデレーションに到達する名前(`/session/oauth/federation/<name>`)をキーとする 1 つのマップ。各キーは、そのパスから名付けた変数 `CORE_FEDERATIONS_<NAME>_<KEY>` に束縛される。トップレベルに書いたマップ(`federations { ... }`)は、各キーの `core.federations` の下のパスを名指しして起動を拒否する。`FEDERATIONS_GOOGLE_*` や `FEDERATIONS_OIDC_*`
|
|
384
|
+
フェデレーションは core のものである: `core.federations`、各フェデレーションに到達する名前(`/session/oauth/federation/<name>`)をキーとする 1 つのマップ。各キーは、そのパスから名付けた変数 `CORE_FEDERATIONS_<NAME>_<KEY>` に束縛される。トップレベルに書いたマップ(`federations { ... }`)は、各キーの `core.federations` の下のパスを名指しして起動を拒否する。`FEDERATIONS_GOOGLE_*` や `FEDERATIONS_OIDC_*` の変数が設定されていれば、単独でも新しい名前と並べても、値が何であれ、どのモジュールを選ぶよりも前に拒否される。
|
|
385
|
+
|
|
386
|
+
すべてのエントリは自分の `type` を書き、boot は有効化された各エントリを、その type を扱うモジュールにエントリの名前で渡す。テンプレートは同梱する 2 つの type — `google`(`@o3co/auth-provider-federation-google`)と `oidc`(`@o3co/auth-provider-federation-oidc`)— を、マップの内容にかかわらず常に読み込み、エントリを自分では読まない: エントリが持つキーは core と type のスキーマが読み、不正なものはそのパス(`core.federations.<name>.<key>`)を名指しして拒否される。`type` の無いエントリは、有効かどうかにかかわらず `core.federations.<name>.type` で拒否される(`config-validation-failed`)。読み込んだどのモジュールも扱わない type の有効なエントリは、そのエントリを名指しして起動を拒否する(`federation-type-unhandled`)。GitHub と Apple は同梱しない: 使いたいデプロイは、そのパッケージとモジュールを `buildModules` に加える — GitHub は type モジュール `githubFederationTypeModule()` と `type = "github"` のエントリで、Apple はその [README](../../packages/federation-apple/README.md) に従う。
|
|
387
|
+
|
|
388
|
+
`core.federations.google` は `type = "google"` を書いて同梱されている。2 つ目の Google クライアントは、同じ type の 2 つ目のエントリを、独自の名前と独自のコールバックで書く:
|
|
389
|
+
|
|
390
|
+
```hocon
|
|
391
|
+
core.federations {
|
|
392
|
+
google-work {
|
|
393
|
+
enabled = true
|
|
394
|
+
type = "google"
|
|
395
|
+
clientId = ${GOOGLE_WORK_CLIENT_ID}
|
|
396
|
+
clientSecret = ${GOOGLE_WORK_CLIENT_SECRET}
|
|
397
|
+
callbackURL = "https://auth.example.com/session/oauth/federation/google-work/callback"
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
`google` のエントリが下の変数のほかに受け付けるキー — `redirectAllowlist`、`sessionDomain`、`authCallbackUrl`、`clientUrl`、`requireAuthorizationResponseIss`、`endSessionEndpoint` — の完全な一覧は [パッケージの README](../../packages/federation-google/README.md) にある。`config/application.conf` は `endSessionEndpoint` 以外のすべてを示す。type が挙げていないキーはエントリを拒否させる。
|
|
377
403
|
|
|
378
404
|
| 変数 | デフォルト | 説明 |
|
|
379
405
|
|---|---|---|
|
|
@@ -411,7 +437,7 @@ core.federations {
|
|
|
411
437
|
}
|
|
412
438
|
```
|
|
413
439
|
|
|
414
|
-
|
|
440
|
+
実装を指定するのはエントリの `type` であり、名前ではない: `type = "oidc"` を書いた `google` という名前のエントリは、`google` という名前の汎用 OIDC フェデレーションである。
|
|
415
441
|
|
|
416
442
|
パッケージが受け付けるすべてのフィールド — `scopes`、`discovery` / `endpoints`、`privateKey`、`userInfo`、`idTokenSignedResponseAlg`、`clockToleranceSeconds` — は [パッケージの README](../../packages/federation-oidc/README.md) に記載されている。
|
|
417
443
|
|
|
@@ -455,7 +481,7 @@ core.federations {
|
|
|
455
481
|
| `REDIS_CODE_REPOSITORY_KEY_PREFIX` | `oauth:code:` | `redis-code-repository.keyPrefix`: Redis のリポジトリのキー名前空間([Redis の名前空間](#redis-の名前空間) を参照) |
|
|
456
482
|
| `STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN` | `600` | `standalone-in-memory-code-repository.defaultExpiresIn`: in-process のリポジトリのもの。正の整数(秒) |
|
|
457
483
|
|
|
458
|
-
`CLIENT_CODE_DEFAULT_EXPIRES_IN` と `CLIENT_CODE_KEY_PREFIX` はこれらとともに改名され、`CLIENT_CODE_ENDPOINT_URI` と `CLIENT_CODE_PASSWORD`
|
|
484
|
+
`CLIENT_CODE_DEFAULT_EXPIRES_IN` と `CLIENT_CODE_KEY_PREFIX` はこれらとともに改名され、`CLIENT_CODE_ENDPOINT_URI` と `CLIENT_CODE_PASSWORD` は削除された。改名されたものが、単独でも新しい名前と並べても、値が何であれ設定されていると、そして削除されたものが設定されていると、代わりに設定すべきものを名指しして起動に失敗する。
|
|
459
485
|
|
|
460
486
|
### フェデレーショントークンストア
|
|
461
487
|
|
|
@@ -486,7 +512,7 @@ core.federations {
|
|
|
486
512
|
| 変数 | デフォルト | 説明 |
|
|
487
513
|
|---|---|---|
|
|
488
514
|
| `MFA_MODE` | `required` | `mfaMode`: `required`、`optional` または `off` |
|
|
489
|
-
| `MFA_ENCRYPTION_KEY` | — | MFA の鍵リングの最初の鍵で、すべての要素のデータを封じる: 32 バイトの canonical な base64(`openssl rand -base64 32`)。MFA が有効なら必須。`CONFIG_ENV=development` では `config/development.conf` が代わりに MFA パッケージの公開サンプル鍵を置く: 自分の鍵はそこに書く。そのリングの横でこの変数を設定すると boot
|
|
515
|
+
| `MFA_ENCRYPTION_KEY` | — | MFA の鍵リングの最初の鍵で、すべての要素のデータを封じる: 32 バイトの canonical な base64(`openssl rand -base64 32`)。MFA が有効なら必須。`CONFIG_ENV=development` では `config/development.conf` が代わりに MFA パッケージの公開サンプル鍵を置く: 自分の鍵はそこに書く。そのリングの横でこの変数を設定すると boot は拒否される。サンプル鍵が受け入れられるのは、`CONFIG_ENV` と `NODE_ENV` のうち設定されているものがすべて `development` か `test` を示すときだけで、`CORE_DEPLOYMENT_MODE=multi` のもとでは拒否される |
|
|
490
516
|
| `MFA_PAGE_URL` | `/mfa` | デプロイの MFA ページ。ログインの第二要素とステップアップはここから始まる。テンプレートはページを同梱しない: ページの契約は [MFA パッケージのもの](../../packages/mfa/README.md#the-routes) |
|
|
491
517
|
| `MFA_STORE_TIMEOUT_MS` | `5000` | `mfa.storeTimeoutMs`、Store の呼び出し 1 回の時間: 各ストアの呼び出しごとのタイムアウト以上でなければならない。Store を呼ぶ構成(`ADAPTERS_USER_REPOSITORY=http`、または要素を Store に置く)で `REPOSITORIES_USER_HTTP_TIMEOUT` を下回ると boot を拒否するので、二つは一緒に上げる。37500 ms を超えても拒否する |
|
|
492
518
|
| `STANDARD_SMTP_MAIL_SENDER_HOST` | — | development 以外での SMTP リレー。MFA はここにアカウントのメールの証明とメール要素のコードを送る。MFA が有効なら、boot にはこれと `STANDARD_SMTP_MAIL_SENDER_FROM` が要る |
|
|
@@ -585,7 +611,7 @@ worker:
|
|
|
585
611
|
|
|
586
612
|
| 変数 | デフォルト | 説明 |
|
|
587
613
|
|---|---|---|
|
|
588
|
-
| `REDIS_SESSION_STORES_KEY_PREFIX` | `ss:` | ユーザーセッション、
|
|
614
|
+
| `REDIS_SESSION_STORES_KEY_PREFIX` | `ss:` | ユーザーセッション、subject のセッションインデックス、subject の失効境界、セッションライフサイクルのレコードの外側の prefix。 |
|
|
589
615
|
| `REDIS_REFRESH_TOKEN_FAMILY_STORE_KEY_PREFIX` | `rtfam:` | refresh token family レコードの prefix。 |
|
|
590
616
|
| `REDIS_CODE_REPOSITORY_KEY_PREFIX` | `oauth:code:` | OAuth 認可コードの prefix。 |
|
|
591
617
|
| `REDIS_FEDERATION_TOKEN_STORE_KEY_PREFIX` | `ft:` | フェデレーショントークンのレコード、そのセッションごとのインデックス、およびそのロックキーの prefix。 |
|
|
@@ -606,15 +632,15 @@ worker:
|
|
|
606
632
|
|
|
607
633
|
どのモジュールをどの順序で合成するかについては、[`src/buildModules.mts`](src/buildModules.mts) が唯一の信頼できる情報源である — そのコピーではなく、それ自体を読むこと。boot planner は `requires` / `provides` をトポロジカルに解決するが、ルートとミドルウェアは、モジュールが `before` / `after` を宣言しない限りリストの順にマウントする — したがって、何かをマウントするモジュールでは位置が意味を持つ。リストを編集するときに守るべきルールは次のとおり:
|
|
608
634
|
|
|
609
|
-
1. **`
|
|
610
|
-
2. **`/oauth` の下では順序は関係しない。** `federationGrantsModules`(フェデレーショングラントが有効な間)、`
|
|
635
|
+
1. **`sessionStoreModule` は先頭のままにする。** これは `express-session` の middleware をマウントし、`before` / `after` を宣言しないため、`req.session` を読むすべてのルートより前に来るのは、リスト内の位置のおかげである。replica safety は自分のセクションから宣言するので、`session-store.storage.type = "memory"` は replica-unsafe となり、`core.deployment.mode = "multi"` で拒否される。
|
|
636
|
+
2. **`/oauth` の下では順序は関係しない。** `federationGrantsModules`(フェデレーショングラントが有効な間)、`oauthEndpointsModule`、独自のモジュールは、いずれも `/oauth` の下にルートをマウントしうる。`oauthEndpointsModule` のルーターが body をパースするのは自身のルートだけなので、各モジュールへのリクエストは、リストの順に関係なくそのモジュール自身の parser に届く — ただし、`/oauth` 配下のどのモジュールも自分の body を自分でパースし、その parser を自分のパスちょうどに限定している場合に限る(同梱のモジュールはそうしている)。限定はルートとして行う(`router.all(path, parser)` か、ルート自身のハンドラ列)。`router.use(path, parser)` は `path` の下のすべてのパスにもマッチする。フェデレーショングラントのブラウザ側の半分は、自身の `after` によってセッション middleware の後ろに自らを並べる。
|
|
611
637
|
3. **ストアスロット 1 つにつきモジュール 1 つ。** 各アダプタースイッチ — `adapters.federationTokenStore`、`adapters.userSessionStores`、`adapters.rateLimiter`、`adapters.codeRepository`、`adapters.accessTokenDenylist`、`adapters.replaySeenSet`、`adapters.consentStore`、フェデレーショングラントの 2 つのストアスイッチ、MFA の 2 つのストアスイッチ — は、memory / Redis の組(MFA の要素については Store も)から 1 つを選ぶ。両者は同じスロットを提供するため、両方を配線すると起動時のスロット衝突になる。`adapters.consentStore = "none"` はどちらも配線せず、フェデレーショングラントのストアは機能が有効な間だけ、MFA のストアは `MFA_MODE` が MFA を組み込む間だけ配線される。
|
|
612
638
|
4. **共有 Redis 接続は、最初の Redis バックエンドのモジュールとともに加わる。** `standaloneRedisClientsModule` は、ここにあるすべての Redis アダプターが使う 1 本の ioredis 接続を自身のセクション(`redis-clients`)から開き、合成されたモジュールがそれを必要とするときには必ず追加される。同梱の合成では refresh token family ストアが Redis 上にあるため、デプロイには常にこれがある。in-memory の family ストアはテスト用の override(`overrides.refreshTokenFamilyModules`)である。
|
|
613
639
|
5. **テンプレート自身の設定モジュールは常に合成される。** `loggingModule` と `httpModule` は `logging {}` と `http {}` を所有し、CORS のリストは `http` のキーの一つ(`http.cors.allowedOrigins`)である。`httpModule` は core の `httpSettings` を提供する。これは authoritative なので、モジュールが読み込まれている間は `overrideComponents` のエントリで置き換えられない。`httpModule` はさらに、`app.mts` が boot の後にポートと readiness の期限を読むテンプレートの `httpHostSettings` を提供する。logger はモジュールではなく、boot の前に `logging` セクションから作られ(`readLogging`)、`logger` コンポーネントとして boot に渡される。
|
|
614
|
-
6.
|
|
615
|
-
7. **メール送信者は環境に従う。** 設定が `development` として選ばれたところでは `@o3co/auth-provider-standard` の開発用送信者で、これは各コードをログに書く。そのモジュールは、その名前と、設定されていれば `CONFIG_ENV` と `NODE_ENV` のそれぞれが `development` か `test` であるところでだけ入り、それ以外のところ、または `core.deployment.mode` が `multi` のところでは起動を拒否する。それ以外の名前では SMTP 送信者のモジュールで、そのセクションは `standard-smtp-mail-sender`([パッケージの README](../../packages/standard/README.md))。その送信者は `mailSender` スロットを何かが読むところでだけ作られ、そこでは起動に `STANDARD_SMTP_MAIL_SENDER_HOST` と `STANDARD_SMTP_MAIL_SENDER_FROM` が要る。`MFA_MODE` が `off` の間はスロットを読むものが無いので、テンプレートはそれらが無くても起動する。MFA が有効なら、MFA モジュールとオペレーターによるリセットがそれを読む。独自の送信者はこの選択を置き換える: [`src/app.mts`](src/app.mts) が `buildModules` を呼ぶところで、そのモジュールを `buildModules(switches, { environment: env,
|
|
640
|
+
6. **フェデレーションの type は常に合成される。** `googleFederationTypeModule()` と `oidcFederationTypeModule()` は `core.federations` の内容にかかわらずリストに入る: それぞれが自分の type を提供し、boot はその type の有効なエントリごとに 1 つのフェデレーションを、エントリの名前で組み立てる。したがってフェデレーションの追加と削除は設定だけで済む。リストの中でエントリを読むものは無い。type で振り分けられるエントリと同じ名前のフェデレーションを直接提供するモジュールは、起動時の `duplicate-contribute` になる: 別の種類のフェデレーションは type モジュールとして加え、そのエントリにその type を書く。
|
|
641
|
+
7. **メール送信者は環境に従う。** 設定が `development` として選ばれたところでは `@o3co/auth-provider-standard` の開発用送信者で、これは各コードをログに書く。そのモジュールは、その名前と、設定されていれば `CONFIG_ENV` と `NODE_ENV` のそれぞれが `development` か `test` であるところでだけ入り、それ以外のところ、または `core.deployment.mode` が `multi` のところでは起動を拒否する。それ以外の名前では SMTP 送信者のモジュールで、そのセクションは `standard-smtp-mail-sender`([パッケージの README](../../packages/standard/README.md))。その送信者は `mailSender` スロットを何かが読むところでだけ作られ、そこでは起動に `STANDARD_SMTP_MAIL_SENDER_HOST` と `STANDARD_SMTP_MAIL_SENDER_FROM` が要る。`MFA_MODE` が `off` の間はスロットを読むものが無いので、テンプレートはそれらが無くても起動する。MFA が有効なら、MFA モジュールとオペレーターによるリセットがそれを読む。独自の送信者はこの選択を置き換える: [`src/app.mts`](src/app.mts) が `buildModules` を呼ぶところで、そのモジュールを `buildModules(switches, { environment: env, mailSenderModules: [mySenderModule] })` のように渡す。`mailSenderModules` を渡すと、どの環境名でもそれが入り、同梱の送信者は入らないので、両方がスロットを提供することは無い。空のリストを渡すと送信者は入らず、送信者を必要とするモジュール(MFA のメール要素、または `requireEmailProof = "always"` の MFA モジュール)は起動を拒否する。
|
|
616
642
|
|
|
617
|
-
`jwksModule`(core 由来)は常に合成される: トークンに署名するプロバイダーは、issuer が設定されているかどうかにかかわらず検証鍵を公開する。各ルートモジュールが何をマウントするかは、それぞれのパッケージの README
|
|
643
|
+
`jwksModule`(core 由来)は常に合成される: トークンに署名するプロバイダーは、issuer が設定されているかどうかにかかわらず検証鍵を公開する。各ルートモジュールが何をマウントするかは、それぞれのパッケージの README にある。テンプレートは core の `sessionLifecycleModule` を合成する: ログインはそのセッションのレコードを開き、コード交換がそれに参加し、`POST /session/logout` と `POST /oauth/logout` はどちらもライフサイクルを通してセッションを終了する。終了はセッションの refresh token family を失効させ、relying party に伝える。[どのログアウトエンドポイントが何を無効化するか](../../docs/operator-runbook.md#which-logout-endpoint-invalidates-what) を参照。
|
|
618
644
|
|
|
619
645
|
## 組み込みルート
|
|
620
646
|
|
|
@@ -694,11 +720,13 @@ docker run -e HTTP_PORT=8080 -p 8080:8080 my-auth-provider
|
|
|
694
720
|
docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up --build
|
|
695
721
|
```
|
|
696
722
|
|
|
723
|
+
最初に実行する前に、自分の鍵を `.env` に `MFA_ENCRYPTION_KEY` として書く(`openssl rand -base64 32`)。この実行は development のサンプル鍵を使わず、鍵が無ければ boot は拒否される。
|
|
724
|
+
|
|
697
725
|
メールは <http://localhost:8025> で読む。Mailpit はメールを保持し、先へは何も配送しない。素の `docker compose up`(`make dev`)は Mailpit を起動せず、本番用のファイルにも Mailpit は無い。開発専用である: デプロイでは `STANDARD_SMTP_MAIL_SENDER_*` の変数で実際のリレーを指定する([多要素認証](#多要素認証))。
|
|
698
726
|
|
|
699
727
|
オーバーレイが app サービスに設定するもの:
|
|
700
728
|
|
|
701
|
-
- `CONFIG_ENV=mailpit`。`development` ではテンプレートは各コードをログに出す送信者を入れる([モジュール合成順序](#モジュール合成順序) のルール 7)。[`config/mailpit.conf`](config/mailpit.conf) は `development.conf` を include
|
|
729
|
+
- `CONFIG_ENV=mailpit`。`development` ではテンプレートは各コードをログに出す送信者を入れる([モジュール合成順序](#モジュール合成順序) のルール 7)。[`config/mailpit.conf`](config/mailpit.conf) は `development.conf` を include しない。`development.conf` の唯一の設定は MFA のサンプル鍵で、MFA パッケージがそれを受け入れるのは、設定されている環境名がすべて `development` か `test` を示すときだけだからである。鍵リングの最初の鍵は、MFA パッケージの `reference.conf` が束ねるとおり `MFA_ENCRYPTION_KEY` である。
|
|
702
730
|
- `MFA_MODE` は設定しない: MFA はテンプレートの出荷どおり、または `.env` が設定するとおりに有効である。MFA が off ならメールを送るものが無い。
|
|
703
731
|
- `ADAPTERS_MFA_FACTOR_STORE=redis` と `ADAPTERS_MFA_TRANSACTION_STORE=redis`(compose の Redis 上、`docker-compose.yml` と同じ): `development` と `test` 以外の名前では、MFA のストアをメモリに置けない。
|
|
704
732
|
- `STANDARD_SMTP_MAIL_SENDER_HOST=localhost`、`STANDARD_SMTP_MAIL_SENDER_PORT=1025`、`STANDARD_SMTP_MAIL_SENDER_SECURE=none`、`STANDARD_SMTP_MAIL_SENDER_FROM=auth@example.com`。SMTP 送信者が平文で送るのはループバックのホストにだけなので、Mailpit は app コンテナのネットワーク名前空間で動き(`network_mode: service:app`)、その `localhost` で待ち受ける。そのため Mailpit の Web UI のポートは、Mailpit ではなく app サービスがループバックに公開する。
|
|
@@ -749,14 +777,14 @@ probe は接続を開いた builder が登録するため、リストはこの
|
|
|
749
777
|
|
|
750
778
|
## カスタムモジュールの追加
|
|
751
779
|
|
|
752
|
-
カスタムモジュールを追加するには、`src/buildModules.mts` でインポートし、`buildModules` が返す配列に追加する。boot planner は依存関係をモジュールが `require` / `provide` するものによって解決するが、ルートとミドルウェアは、`before` / `after` の指定が無い限りリストの順にマウントする。したがって、他のルートが依存する middleware では位置が意味を持つ — セッションストアモジュール、[モジュール合成順序](#モジュール合成順序) のルール 1 である。`/oauth` のように別のモジュールも使うプレフィックスの下のルートは、自分の body を自分でパースし、その parser を自分のパスちょうどに限定する限り、特定の位置を必要としない(ルール 2): `
|
|
780
|
+
カスタムモジュールを追加するには、`src/buildModules.mts` でインポートし、`buildModules` が返す配列に追加する。boot planner は依存関係をモジュールが `require` / `provide` するものによって解決するが、ルートとミドルウェアは、`before` / `after` の指定が無い限りリストの順にマウントする。したがって、他のルートが依存する middleware では位置が意味を持つ — セッションストアモジュール、[モジュール合成順序](#モジュール合成順序) のルール 1 である。`/oauth` のように別のモジュールも使うプレフィックスの下のルートは、自分の body を自分でパースし、その parser を自分のパスちょうどに限定する限り、特定の位置を必要としない(ルール 2): `oauthEndpointsModule` のルーターがパースするのは自身のルートの body だけで、プレフィックス配下のすべてのリクエストに走る parser は、他のモジュールの body を代わりに読んでしまう。前者のようなものをマウントしないモジュールは末尾に置けばよい:
|
|
753
781
|
|
|
754
782
|
```diff
|
|
755
783
|
// src/buildModules.mts
|
|
756
784
|
+import { myCustomModule } from "./my-custom-module.mjs";
|
|
757
785
|
…
|
|
758
786
|
return [
|
|
759
|
-
|
|
787
|
+
sessionStoreModule,
|
|
760
788
|
…
|
|
761
789
|
...(federationGrantsEnabled || mfaInstalled ? [subjectRevocationServiceModule] : []),
|
|
762
790
|
+ myCustomModule,
|