@o3co/auth-provider-foundation 0.17.0-rc.2 → 0.17.0-rc.4

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 CHANGED
@@ -184,7 +184,7 @@ const modules = [
184
184
 
185
185
  - モジュールは `mfaFactorStore` を、それを必要とするものがあってもなくても起動時に、先に読むセクションの四つの URL から作って提供する。
186
186
  - 必須とするスロットは無く、自分のセクション以外の設定は読まない。
187
- - `storeTransport` は必須である: Store の資格情報・期限・レスポンス上限はユーザーリポジトリのものなので、組み立て側がユーザーリポジトリの HTTP 設定(`repositories.user.http`)を渡す。その `bearerToken`・`timeout`・`maxResponseBytes` を `"http"` ビルダーと同じように(文字列は数値として)読むので、一つのトークンが Store のすべてのエンドポイントに送られる。`{}` は設定が無いことを示す: 資格情報は送らず、期限と上限は 5000 ms と 1 MiB。設定が渡されないとき、またはキーのセクションでないときは、資格情報を送らずに済ませるのではなく起動を拒否する。通信が拒否する設定 — [コンストラクタでの検証](#コンストラクタでの検証)の規則 — も、`HttpMfaFactorStore` を名指して起動を拒否する(`provides-factory-failed`)。
187
+ - `storeTransport` は必須である: Store の資格情報・期限・レスポンス上限はユーザーリポジトリのものなので、組み立て側がユーザーリポジトリの HTTP 設定(`repositories.user.http`)を渡す。その `bearerToken`・`timeout`・`maxResponseBytes` を `"http"` ビルダーと同じように(文字列は数値として)読むので、一つのトークンが Store のすべてのエンドポイントに送られる。`{}` は設定が無いことを示す: 資格情報は送らず、期限と上限は 5000 ms と 1 MiB。設定が渡されないとき、またはキーのセクションでないときは、資格情報を送らずに済ませるのではなく起動を拒否する。通信が拒否する設定 — [コンストラクタでの検証](#コンストラクタでの検証)の規則 — も、`HttpMfaFactorStore` を名指して起動を拒否する(`provides-factory-failed`)。ユーザーリポジトリだけなら受け付ける値でも、要素ストア自身の上限 85 500 000 ms を超える `timeout` は同じく起動を拒否する。
188
188
  - レプリカで分岐する状態は宣言しない: 要素は Store のものである。
189
189
 
190
190
  要素の集合の構成は `createIf`、`removeIf` とリセットで書く。ポートには[契約](#store-の-mfa-エンドポイント)が各応答に与える意味で答え、それ以外はすべて throw する:
@@ -255,6 +255,8 @@ URL が無ければ何も書かれず、プロバイダーは Store が返す `m
255
255
 
256
256
  **`timeout` は `2147483647` ミリ秒以下の正の整数でなければならない。** `0`・負数・`NaN` は `setTimeout` ではいずれも「即時発火」に丸められ — すべてのリクエストが中断される — 、Node のタイマー範囲を超える値は 1ms に丸められるので、「気長に待つ」つもりの設定が最もせっかちな設定になる。空の環境変数による上書きはデフォルトにはならず起動失敗になる。期限は **ボディの読み取りを含む** やり取り全体に掛かる: ボディの読み取りは abort signal に頼らず期限と競わせる。リクエストの中断は進行中の読み取りを確実には止めないからである。これは slow-loris の形 — ヘッダーはすぐ届き、ボディが少しずつ届くか止まる — で、競わせなければ永久にハングする。期限を超えたリクエストは、エンドポイントを示す `timed out after <n>ms` のエラーで reject される。そのエラーの名前はどのリクエストでも `TimeoutError` なので、名前で分類するレポーターはそれをタイムアウトと読む。
257
257
 
258
+ **`HttpMfaFactorStore` はさらに `timeout` を 85 500 000 ミリ秒(23.75 時間)以下に保つ。** 超えると、構築は `timeout` と上限を名指す `RangeError` を投げる。その書き込み寿命 W は期限に `DEFAULT_CLOCK_SKEW_MS` を足したものであり、要素の集合の書き手は読み取りから最大 `MFA_SUBJECT_LEASE_MAX_MS` 後に条件付き書き込みを出すので、期限にその二つを足した値が `BUNDLED_STORE_WRITE_LIFETIME_MS` 以内でなければならない(いずれもコアのエクスポート)。
259
+
258
260
  **`maxResponseBytes` は正の整数でなければならず**、デフォルトは `DEFAULT_MAX_RESPONSE_BYTES`(1 MiB)。上限は `Content-Length` に対しても、ストリーム読み取り中にも適用されるので、ヘッダーを省く — あるいは偽る — Store も、メモリを使い果たす前に打ち切られる。
259
261
 
260
262
  **`bearerToken` は、設定するなら 32 バイト以上の鍵素材を持つ素の RFC 6750 トークンでなければならない。** 未設定(キーが無い)なら `Authorization` ヘッダーは送らない。設定した場合は、文字列であること、空でないこと(空の環境変数による上書きは「トークン無し」ではなく起動失敗)、英字・数字・`-._~+/` と末尾の `=` パディングだけから成ること — 空白も改行も、アダプターが付ける `Bearer ` の接頭辞も含まない — 、そして core の共有シークレットの下限(`MIN_SECRET_ENTROPY_BYTES`。`SESSION_STORE_SECRET` と `KEY_STORE_LOCAL_SECRET` が満たすのと同じもの)を満たすことが求められ、どれかを欠けば拒否される。hex や base64 の値はデコード後の長さで測るので、`openssl rand -hex 16` は見た目の長さに関わらず 16 バイトである。このトークンを持つ者は auth.provider として Store と話せる。形をここで検査するのは、`fetch` が拒否するヘッダー値は、`fetch` が投げるエラーの中にそのまま引用されるからである。どの拒否も値を引用せず、リクエストのどのエラーも値を含まず — 通信の失敗は通信自身のエラーを付けずに投げられる(ワイヤ契約を参照) — 、値は ECMAScript の private フィールドに保持されるので、リポジトリの `inspect()` や `JSON.stringify` にも現れない。これらの検査はリポジトリが組み立てられるときに行われ、デプロイがそうするのは `adapters.userRepository = "http"`(standalone テンプレートのデフォルト)のときである。core の `reference.conf` のデフォルトである `yaml` では、`http` ブロック — とその中のトークン — はまったく読まれない。
@@ -288,9 +290,9 @@ URL が無ければ何も書かれず、プロバイダーは Store が返す `m
288
290
  | [`endpointUrl.test.mts`](src/__tests__/endpointUrl.test.mts) | https またはループバックの規則 |
289
291
  | [`storeFailure.test.mts`](src/mfa/__tests__/storeFailure.test.mts) | MFA エンドポイントの失敗が投げるもの: Store の書いたものがエラーのどの形にも届かないこと、ボディを読まずに解放すること、応答に使うステータスを持たないこと。飛んだバージョンのログ行が、どれほど長くても主体と要素 ID を無害化し上限をつけて名指すこと |
290
292
  | [`section.test.mts`](src/mfa/__tests__/section.test.mts) | `foundation-mfa-factor-store` セクション: スキーマ、読み取り、パッケージのモジュールで `createApp` を通した欠落・不正・未知のキーでの起動拒否(モジュールだけを入れた構成も) |
291
- | [`module.test.mts`](src/mfa/__tests__/module.test.mts) | `createApp` を通した `foundationMfaFactorStoreModule`: 他に何も入れずにセクションの URL の上に Store を使うストアを提供すること。Store の通信設定が必須であること、そこから読むユーザーリポジトリのベアラートークン・期限・上限(文字列は数値として読む)、設定が無いかキーのセクションでないとき、およびユーザーリポジトリも拒否する値のときの起動拒否 |
293
+ | [`module.test.mts`](src/mfa/__tests__/module.test.mts) | `createApp` を通した `foundationMfaFactorStoreModule`: 他に何も入れずにセクションの URL の上に Store を使うストアを提供すること。Store の通信設定が必須であること、そこから読むユーザーリポジトリのベアラートークン・期限・上限(文字列は数値として読む)、設定が無いかキーのセクションでないとき、ユーザーリポジトリも拒否する値のとき、および要素ストアの書き込み寿命を超える期限のときの起動拒否 |
292
294
  | [`HttpMfaFactorStore.contract.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.contract.test.mts) | テストキットの `MfaFactorStore` スイートと要素の集合の条件付き書き込みのスイートを、偽の Store の上の `HttpMfaFactorStore` に対して、同じ Store の上の二つ目のアダプター、Store が閉じたアダプター、書き込み寿命の上限を越えて進めた Store の墓標の時計とともに走らせる |
293
- | [`HttpMfaFactorStore.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.test.mts) | 送るもの — 設定どおりの各 URL、ベアラートークン、バイト単位でそのままの封印されたデータと、その封印元を何も送らないこと、変換が拒否するものを送らないこと。各操作の応答と、契約を破る Store: `404`、`5xx`、リダイレクト、壊れた応答、読めないレコード、別の主体、重複した ID、飛んだバージョン、変更を書かなかった応答。集合のメンバー: 送るもの、結果のボディ付きの各ステータス、ボディの無い `404` や `409` は壊れた応答、他のステータスは想定外、世代の無い一覧は壊れた応答、期限で諦める条件付き書き込み、送った時刻に期限を足した `deadlineMs`、`408` で拒否され適用されない遅れた書き込み、期限内に適用される書き込み。Store が送ったものが投げるものに何も現れないこと。拒否された資格情報(このストアを名指す)、ヘッドまたはボディでの期限切れ、上限、届かない Store。構築、無条件の `create` も `remove` も持たないこと、ストアを検査したときにトークンもエンドポイントも見えないこと |
295
+ | [`HttpMfaFactorStore.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.test.mts) | 送るもの — 設定どおりの各 URL、ベアラートークン、バイト単位でそのままの封印されたデータと、その封印元を何も送らないこと、変換が拒否するものを送らないこと。各操作の応答と、契約を破る Store: `404`、`5xx`、リダイレクト、壊れた応答、読めないレコード、別の主体、重複した ID、飛んだバージョン、変更を書かなかった応答。集合のメンバー: 送るもの、結果のボディ付きの各ステータス、ボディの無い `404` や `409` は壊れた応答、他のステータスは想定外、世代の無い一覧は壊れた応答、期限で諦める条件付き書き込み、送った時刻に期限を足した `deadlineMs`、`408` で拒否され適用されない遅れた書き込み、期限内に適用される書き込み。Store が送ったものが投げるものに何も現れないこと。拒否された資格情報(このストアを名指す)、ヘッドまたはボディでの期限切れ、上限、届かない Store。構築、書き込み寿命を超える期限はユーザーリポジトリが投げない `RangeError` になること、無条件の `create` も `remove` も持たないこと、ストアを検査したときにトークンもエンドポイントも見えないこと |
294
296
  | [`foundationMfaFactorStoreConfig.test.mts`](src/testing/__tests__/foundationMfaFactorStoreConfig.test.mts) | testing 入口のセクションのビルダー |
295
297
  | [`foundationUserRepositoryHttpConfig.test.mts`](src/testing/__tests__/foundationUserRepositoryHttpConfig.test.mts) | testing 入口のユーザーリポジトリの `http` ブロックのビルダーと、`"http"` ビルダーがそれを受け取ること |
296
298
  | [`storeRequestMessages.test.mts`](src/mfa/__tests__/storeRequestMessages.test.mts) | MFA エンドポイントでの通信の失敗が、どのクライアントから送っても同じ文言になること |
package/README.md CHANGED
@@ -527,7 +527,9 @@ const modules = [
527
527
  keys, refuse the boot rather than send no credential. A setting the
528
528
  transport refuses — the rules of
529
529
  [constructor validation](#constructor-validation) — refuses the boot too
530
- (`provides-factory-failed`), naming `HttpMfaFactorStore`.
530
+ (`provides-factory-failed`), naming `HttpMfaFactorStore`; so does a
531
+ `timeout` above the factor store's own bound, 85 500 000 ms, which the user
532
+ repository alone would take.
531
533
  - It declares no replica-unsafe state: the factors are the Store's.
532
534
 
533
535
  It writes the factor set's membership through `createIf`, `removeIf` and
@@ -759,6 +761,13 @@ the race it hangs forever. A request that outlives the deadline rejects with a
759
761
  `timed out after <n>ms` error naming the endpoint, named `TimeoutError` on
760
762
  every request, so a reporter that classifies by name reads it as a timeout.
761
763
 
764
+ **`HttpMfaFactorStore` holds `timeout` to 85 500 000 milliseconds** (23.75
765
+ hours) as well; above it, construction throws a `RangeError` naming `timeout`
766
+ and the bound. Its write lifetime W is the timeout plus `DEFAULT_CLOCK_SKEW_MS`,
767
+ and the factor-set writer issues a conditional write up to
768
+ `MFA_SUBJECT_LEASE_MAX_MS` after its read, so the timeout plus those two must
769
+ be within `BUNDLED_STORE_WRITE_LIFETIME_MS` (all core's exports).
770
+
762
771
  **`maxResponseBytes` must be a positive integer**, defaulting to
763
772
  `DEFAULT_MAX_RESPONSE_BYTES` (1 MiB). The cap is enforced against
764
773
  `Content-Length` *and* while streaming, so a Store that omits the header — or
@@ -837,9 +846,9 @@ its `storeTransport`.
837
846
  | [`endpointUrl.test.mts`](src/__tests__/endpointUrl.test.mts) | the https-or-loopback rule |
838
847
  | [`storeFailure.test.mts`](src/mfa/__tests__/storeFailure.test.mts) | what the MFA endpoints' failures throw: nothing the Store wrote in any form the error leaves in, the body released unread, no status to answer with; a skipped version's log line naming the subject and the factor id, each sanitised and bounded, however long they are |
839
848
  | [`section.test.mts`](src/mfa/__tests__/section.test.mts) | the `foundation-mfa-factor-store` section: its schema, its reader, and through `createApp` with the package's module the boot refused for a missing, malformed or unknown key, the module installed alone included |
840
- | [`module.test.mts`](src/mfa/__tests__/module.test.mts) | `foundationMfaFactorStoreModule` through `createApp`: the Store-backed store provided over the section's URLs with nothing else installed; the Store transport settings required, the user repository's bearer token, deadline and cap read from them (text read as numbers), the boot refused for settings absent or not a section of keys and for a value the user repository refuses too |
849
+ | [`module.test.mts`](src/mfa/__tests__/module.test.mts) | `foundationMfaFactorStoreModule` through `createApp`: the Store-backed store provided over the section's URLs with nothing else installed; the Store transport settings required, the user repository's bearer token, deadline and cap read from them (text read as numbers), the boot refused for settings absent or not a section of keys and for a value the user repository refuses too, or a deadline past the factor store's write lifetime |
841
850
  | [`HttpMfaFactorStore.contract.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.contract.test.mts) | the test kit's `MfaFactorStore` suite and the factor set's conditional-write binding against `HttpMfaFactorStore` over the fake Store, with a second adapter on the same Store, one whose Store has closed, and the Store's tombstone clock moved past the write-lifetime bound |
842
- | [`HttpMfaFactorStore.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.test.mts) | what it sends — each URL as configured, the bearer token, the sealed data byte for byte and nothing it was sealed from, nothing the codec refuses; each operation's answers, and a Store that breaks the contract: `404`, `5xx`, a redirect, a malformed answer, an unreadable record, a foreign subject, a repeated id, a skipped version, an answer that did not write the changes; the set's members: what they send, each status with its outcome body, a `404` or `409` without one malformed, any other status unexpected, a list without its generation malformed, a conditional write given up at its deadline, its `deadlineMs` the send time plus the timeout, a late one refused `408` and not applied, one within its deadline applied; nothing the Store sent in anything thrown; the credential refused (naming this store), a deadline over the head or the body, the cap, an unreachable Store; construction, no unconditional `create` or `remove`, and neither the token nor the endpoints shown when the store is inspected |
851
+ | [`HttpMfaFactorStore.test.mts`](src/mfa/__tests__/HttpMfaFactorStore.test.mts) | what it sends — each URL as configured, the bearer token, the sealed data byte for byte and nothing it was sealed from, nothing the codec refuses; each operation's answers, and a Store that breaks the contract: `404`, `5xx`, a redirect, a malformed answer, an unreadable record, a foreign subject, a repeated id, a skipped version, an answer that did not write the changes; the set's members: what they send, each status with its outcome body, a `404` or `409` without one malformed, any other status unexpected, a list without its generation malformed, a conditional write given up at its deadline, its `deadlineMs` the send time plus the timeout, a late one refused `408` and not applied, one within its deadline applied; nothing the Store sent in anything thrown; the credential refused (naming this store), a deadline over the head or the body, the cap, an unreachable Store; construction, a timeout past the write lifetime a `RangeError` the user repository does not throw, no unconditional `create` or `remove`, and neither the token nor the endpoints shown when the store is inspected |
843
852
  | [`foundationMfaFactorStoreConfig.test.mts`](src/testing/__tests__/foundationMfaFactorStoreConfig.test.mts) | the testing entry's section builder |
844
853
  | [`foundationUserRepositoryHttpConfig.test.mts`](src/testing/__tests__/foundationUserRepositoryHttpConfig.test.mts) | the testing entry's builder of the user repository's `http` block, which the `"http"` builder takes |
845
854
  | [`storeRequestMessages.test.mts`](src/mfa/__tests__/storeRequestMessages.test.mts) | one wording for a transport failure at the MFA endpoints, whichever client sends to them |
@@ -25,6 +25,11 @@
25
25
  * write past it is never applied after the adapter stopped waiting. A Store
26
26
  * that reads it as passed answers `408`, which is `unexpected_status`. A conditional write that is sent and then fails,
27
27
  * its deadline included, is unknown: it may have committed.
28
+ *
29
+ * Its write lifetime W is the timeout plus `DEFAULT_CLOCK_SKEW_MS`, and the
30
+ * factor-set writer issues a conditional write up to `MFA_SUBJECT_LEASE_MAX_MS`
31
+ * after its read; a `timeout` that would take the two past
32
+ * `BUNDLED_STORE_WRITE_LIFETIME_MS` is a `RangeError` at construction.
28
33
  */
29
34
  import { type ConditionalCreateAnswer, type ConditionalSetRemoveAnswer, type MfaFactorRecord, type MfaFactorRecordUpdate, type MfaFactorStore, type StoreGeneration, type VersionedSet } from "@o3co/auth-provider-core";
30
35
  export interface HttpMfaFactorStoreOptions {
@@ -35,7 +40,7 @@ export interface HttpMfaFactorStoreOptions {
35
40
  readonly deleteUrl: string;
36
41
  /** The user repository's credential: sent as `Authorization: Bearer <token>`. */
37
42
  readonly bearerToken?: string;
38
- /** The whole exchange's deadline, in milliseconds. */
43
+ /** The whole exchange's deadline, in milliseconds: at most 85 500 000 (see the file header). */
39
44
  readonly timeout: number;
40
45
  /** The most bytes of an answer read. Default `DEFAULT_MAX_RESPONSE_BYTES`. */
41
46
  readonly maxResponseBytes?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"HttpMfaFactorStore.d.mts","sourceRoot":"","sources":["../../src/mfa/HttpMfaFactorStore.mts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EACN,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAE/B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EAUnB,KAAK,eAAe,EAIpB,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AA6BlC,MAAM,WAAW,yBAAyB;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAqBD,qBAAa,kBAAmB,YAAW,cAAc;;IACxD,QAAQ,CAAC,IAAI,WAAW;gBAKZ,EACX,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,gBAA6C,GAC7C,EAAE,yBAAyB;IActB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAc1D,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAOtE,QAAQ,CACb,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IAU7B,QAAQ,CACb,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,eAAe,GACvB,OAAO,CAAC,0BAA0B,CAAC;IAUhC,MAAM,CACX,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,qBAAqB,GACzB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAyBlC,iEAAiE;IAC3D,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsDzD"}
1
+ {"version":3,"file":"HttpMfaFactorStore.d.mts","sourceRoot":"","sources":["../../src/mfa/HttpMfaFactorStore.mts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAI/B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EAUnB,KAAK,eAAe,EAIpB,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAmDlC,MAAM,WAAW,yBAAyB;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gGAAgG;IAChG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAqBD,qBAAa,kBAAmB,YAAW,cAAc;;IACxD,QAAQ,CAAC,IAAI,WAAW;gBAKZ,EACX,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,gBAA6C,GAC7C,EAAE,yBAAyB;IActB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAc1D,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAOtE,QAAQ,CACb,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IAU7B,QAAQ,CACb,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,eAAe,GACvB,OAAO,CAAC,0BAA0B,CAAC;IAUhC,MAAM,CACX,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,qBAAqB,GACzB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAyBlC,iEAAiE;IAC3D,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsDzD"}
@@ -40,13 +40,35 @@
40
40
  * write past it is never applied after the adapter stopped waiting. A Store
41
41
  * that reads it as passed answers `408`, which is `unexpected_status`. A conditional write that is sent and then fails,
42
42
  * its deadline included, is unknown: it may have committed.
43
+ *
44
+ * Its write lifetime W is the timeout plus `DEFAULT_CLOCK_SKEW_MS`, and the
45
+ * factor-set writer issues a conditional write up to `MFA_SUBJECT_LEASE_MAX_MS`
46
+ * after its read; a `timeout` that would take the two past
47
+ * `BUNDLED_STORE_WRITE_LIFETIME_MS` is a `RangeError` at construction.
43
48
  */
44
- import { fromMfaStoreFactor, readMfaStoreCreateIfAnswer, readMfaStoreFactor, readMfaStoreListAnswer, readMfaStoreRemoveIfAnswer, readMfaStoreVersionedListAnswer, toMfaStoreCreateIfRequest, toMfaStoreRemoveIfRequest, toMfaStoreUpdateRequest, } from "@o3co/auth-provider-core";
49
+ import { BUNDLED_STORE_WRITE_LIFETIME_MS, DEFAULT_CLOCK_SKEW_MS, fromMfaStoreFactor, MFA_SUBJECT_LEASE_MAX_MS, readMfaStoreCreateIfAnswer, readMfaStoreFactor, readMfaStoreListAnswer, readMfaStoreRemoveIfAnswer, readMfaStoreVersionedListAnswer, toMfaStoreCreateIfRequest, toMfaStoreRemoveIfRequest, toMfaStoreUpdateRequest, } from "@o3co/auth-provider-core";
45
50
  import { assertSecureEndpoint } from "../endpointUrl.mjs";
46
51
  import { bearerAuthorization, checkStoreResponseCap, checkStoreTimeout, DEFAULT_MAX_RESPONSE_BYTES, postToStore, } from "../storeTransport.mjs";
47
52
  import { mfaStoreMalformedAnswer, mfaStoreRequestMessages, mfaStoreStatusError, mfaStoreUnreadableRecord, mfaStoreVersionSkipped, } from "./storeFailure.mjs";
48
53
  /** What this adapter's messages lead with. */
49
54
  const OWNER = "HttpMfaFactorStore";
55
+ /**
56
+ * The largest `timeout`: a conditional write's lifetime (the timeout plus
57
+ * `DEFAULT_CLOCK_SKEW_MS`), issued up to `MFA_SUBJECT_LEASE_MAX_MS` after its
58
+ * read, ends within `BUNDLED_STORE_WRITE_LIFETIME_MS` of that read.
59
+ */
60
+ const MAX_TIMEOUT_MS = BUNDLED_STORE_WRITE_LIFETIME_MS - MFA_SUBJECT_LEASE_MAX_MS - DEFAULT_CLOCK_SKEW_MS;
61
+ /** `timeout` as the transport takes it, and no greater than {@link MAX_TIMEOUT_MS}. */
62
+ function checkTimeout(timeout) {
63
+ const checked = checkStoreTimeout(timeout, OWNER);
64
+ if (checked > MAX_TIMEOUT_MS) {
65
+ throw new RangeError(`${OWNER}: "timeout" must be no greater than ${MAX_TIMEOUT_MS} milliseconds: ` +
66
+ `the timeout plus MFA_SUBJECT_LEASE_MAX_MS (${MFA_SUBJECT_LEASE_MAX_MS}) and ` +
67
+ `DEFAULT_CLOCK_SKEW_MS (${DEFAULT_CLOCK_SKEW_MS}) must be within ` +
68
+ `BUNDLED_STORE_WRITE_LIFETIME_MS (${BUNDLED_STORE_WRITE_LIFETIME_MS}), the factor store's write lifetime`);
69
+ }
70
+ return checked;
71
+ }
50
72
  /** The statuses a conditional create is answered with, each with its outcome body. */
51
73
  const CREATE_IF_STATUSES = new Set([200, 409]);
52
74
  /** The statuses a conditional removal is answered with, each with its outcome body. */
@@ -80,7 +102,7 @@ export class HttpMfaFactorStore {
80
102
  });
81
103
  this.#settings = Object.freeze({
82
104
  authorization: bearerAuthorization(bearerToken, OWNER),
83
- timeout: checkStoreTimeout(timeout, OWNER),
105
+ timeout: checkTimeout(timeout),
84
106
  maxResponseBytes: checkStoreResponseCap(maxResponseBytes, OWNER),
85
107
  });
86
108
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@o3co/auth-provider-foundation",
3
3
  "description": "Production HTTP user-authentication adapter for auth.provider",
4
- "version": "0.17.0-rc.2",
4
+ "version": "0.17.0-rc.4",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
7
7
  "node": ">=22.0.0"
@@ -40,16 +40,16 @@
40
40
  "zod": "^4.6.5"
41
41
  },
42
42
  "peerDependencies": {
43
- "@o3co/auth-provider-core": "^0.17.0-rc.2"
43
+ "@o3co/auth-provider-core": "^0.17.0-rc.4"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "^26.6.3",
46
+ "@types/node": "^26.6.4",
47
47
  "msw": "^2.15.0",
48
- "vitest": "^5.0.2",
49
- "@vitest/coverage-v8": "^5.0.2",
48
+ "vitest": "^5.0.3",
49
+ "@vitest/coverage-v8": "^5.0.3",
50
50
  "@o3co/ts.hocon": "^1.13.0",
51
- "@o3co/auth-provider-test-kit": "0.17.0-rc.2",
52
- "@o3co/auth-provider-core": "0.17.0-rc.2"
51
+ "@o3co/auth-provider-core": "0.17.0-rc.4",
52
+ "@o3co/auth-provider-test-kit": "0.17.0-rc.4"
53
53
  },
54
54
  "scripts": {
55
55
  "prebuild": "rimraf dist",