@h-ai/kit 1.0.0-beta.13 → 1.0.0-beta.15

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.en.md CHANGED
@@ -233,7 +233,7 @@ Servers must explicitly inject the same managed `serverKeyRing` and a shared `ke
233
233
 
234
234
  Transport applies by default to same-origin `/api/*` endpoints, SvelteKit `__data.json` page data, and key exchange itself. Page documents and static assets remain plaintext and do not require `X-Client-Id`. `multipart/form-data` requests such as uploads are sent unchanged; explicitly allow their paths through server `excludePaths`.
235
235
 
236
- Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. Requests have a five-minute lifetime and at most 30 seconds of future clock skew. The shared registry atomically consumes (clientId, requestId), retaining it until at least issuedAt plus six minutes to cover node skew; storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
236
+ Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. By default, requests expire after five minutes and client timestamps may be up to 30 seconds ahead. Set `maxRequestAgeSeconds` (1–600) and `maxFutureSkewSeconds` (0–300) on the server to adjust these limits in whole seconds. The shared registry atomically consumes (clientId, requestId) and retains the nonce until issuedAt plus 11 minutes, covering the longest configurable window and server clock differences. Storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
237
237
 
238
238
  Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD carry it in X-Hai-Transport; bodyless responses such as 204/304/HEAD use the same header. Other requests/responses carry a JSON envelope and X-Encrypted: true. The client must match the original request context and HTTP status; missing markers, old formats and authentication failures reject. Serv/Kit hosts explicitly allow plaintext through `encryption: disabled` path rules or host `excludePaths`. Callers must select ordinary requests; API Client/Kit wrappers use their own `excludePaths`. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
239
239
 
@@ -241,7 +241,7 @@ Set `transport.maxPlaintextBytes` for both request and response JSON plaintext,
241
241
 
242
242
  Ordinary routes retain `transport.maxPlaintextBytes` (auto/omitted: 1 MiB). Declare Feedback report intake, evidence, attachments and ZIP download capacity separately using `routePlaintextBytes` and `feedback.budget.maxTransportBytes`; `*` matches one complete path segment and explicit host route values take precedence. Handshakes, adapter-node and proxies use the maximum route capacity. HTTP adapters admit at most 2 explicitly declared material requests above 1 MiB and a separate 16 ordinary business requests, releasing admission after the entire response or failure. Busy admission returns 503 / `hai:crypto:072`; business writes are not automatically replayed.
243
243
 
244
- serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
244
+ Serv/Kit return an encrypted HTTP 400 for an out-of-window request after validating request binding, signature and MAC. The client throws `TransportTimeError` (`hai:crypto:071`), displays the server’s UTC time at validation, and asks the user to enable automatic synchronization or set the device time manually before refreshing. `serverTime` is Unix milliseconds. Clients still show synchronization guidance when an older server omits it. `api-client` maps the error to `VALIDATION_FAILED` and preserves `cause.transportCode` and `cause.serverTime`. Standard authentication actions convert authenticated transport time errors to `HaiResult` with the server time and synchronization guidance for AuthPanel.
245
245
 
246
246
  #### Shared transport configuration with `_kit.yml`
247
247
 
package/README.ja.md CHANGED
@@ -239,7 +239,7 @@ export const { apiFetch } = kit.client.create()
239
239
 
240
240
  transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.json`、鍵交換 endpoint に適用します。ページ文書・静的ファイルは平文で `X-Client-Id` は不要です。アップロードなどの `multipart/form-data` はそのまま送信し、サーバーの `excludePaths` で明示的に許可してください。
241
241
 
242
- 認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。要求の有効期限は 5 分、未来方向の時計差は最大 30 秒です。共有 registry が (clientId, requestId) を原子的に消費し、ノード間の時計差を含め issuedAt + 6 分まで保持します。保存障害では拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
242
+ 認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。既定では要求は 5 分で期限切れになり、クライアント時刻はサーバーより最大 30 秒先まで許容されます。サーバー側の `maxRequestAgeSeconds`(1~600)と `maxFutureSkewSeconds`(0~300)で整数秒単位に調整できます。共有レジストリは (clientId, requestId) をアトミックに消費し、設定可能な最長の有効期間とノード間の時刻差をカバーするため、nonce を issuedAt の 11 分後まで保持します。ストレージ障害時は要求を拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
243
243
 
244
244
  空の POST も空平文の認証封装を送ります。GET/HEAD は X-Hai-Transport ヘッダー、204/304/HEAD など本文なし応答も同じヘッダーを使います。他の要求・応答は JSON 本文と X-Encrypted: true を使います。クライアントは元の要求コンテキストと HTTP status を照合し、マーカー欠落、旧形式、認証失敗では reject します。平文 endpoint は Serv/Kit の `encryption: disabled` ルールまたはホストの `excludePaths` で明示的に許可し、呼出側も通常の要求を選びます。API Client/Kit のラッパーは自身の `excludePaths` を使用します。CORS では X-Hai-Transport を許可・公開してください。鍵交換の公開鍵認証は TLS に依存し、IAM の業務認可は別に必要です。
245
245
 
@@ -247,7 +247,7 @@ transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.jso
247
247
 
248
248
  通常業務は `transport.maxPlaintextBytes`(auto/省略は 1 MiB)を維持します。Feedback の受理・証拠・添付・ZIP ダウンロードは `routePlaintextBytes` と `feedback.budget.maxTransportBytes` で別途宣言します。`*` は完全な1パス区間のみ一致し、ホストの明示値が優先です。ハンドシェイク、adapter-node、プロキシは経路の最大値を使用します。HTTP アダプターは個別宣言された 1 MiB 超の資料経路を最大2件、通常業務を別枠で16件まで許可し、応答完了・失敗時に解放します。混雑は503 / `hai:crypto:072` を返し、業務書込は自動再送しません。
249
249
 
250
- serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
250
+ Serv/Kit は要求の結合、署名、MAC を検証した後、時刻の許容範囲を超えた要求に暗号化した HTTP 400 を返します。クライアントは `TransportTimeError`(`hai:crypto:071`)をスローし、検証時のサーバー UTC 時刻を表示して、自動時刻同期を有効にするか手動で時刻を設定してから再読み込みするよう案内します。`serverTime` は Unix ミリ秒です。旧サーバーが値を返さない場合も時刻調整の案内を表示します。`api-client` は `VALIDATION_FAILED` に変換し、`cause.transportCode` と `cause.serverTime` を保持します。標準認証アクションは検証済みの通信時刻エラーを `HaiResult` に変換し、サーバー時刻と時刻調整の案内を AuthPanel に渡します。
251
251
 
252
252
  #### `_kit.yml` による共通 transport 設定
253
253
 
package/README.md CHANGED
@@ -239,7 +239,7 @@ export const { apiFetch } = kit.client.create()
239
239
 
240
240
  transport 默认作用于同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求(以及密钥协商端点本身);页面文档与静态资源仍保持明文,不要求携带 `X-Client-Id`。文件上传等 `multipart/form-data` 请求会保持原样发送,需在服务端 `excludePaths` 中显式放行对应上传路径。
241
241
 
242
- 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
242
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求默认有效期为 5 分钟,客户端时间最多可领先服务器 30 秒。服务端可用 `maxRequestAgeSeconds`(1~600)和 `maxFutureSkewSeconds`(0~300)调整,单位为整数秒。共享 registry 原子消费 (clientId, requestId),nonce 保留到 issuedAt + 11 分钟,覆盖最长可配窗口及节点时钟差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
243
243
 
244
244
  空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口由 Serv/Kit 路径规则 `encryption: disabled` 或宿主 `excludePaths` 显式允许;调用方同步选择普通请求,API Client/Kit 包装器使用自己的 `excludePaths`。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
245
245
 
@@ -247,7 +247,7 @@ transport 默认作用于同源 `/api/*` endpoint 与 SvelteKit `__data.json`
247
247
 
248
248
  普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明 `feedback.budget.maxTransportBytes`。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
249
249
 
250
- serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
250
+ Serv/Kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的请求返回加密的 HTTP 400 错误。客户端抛出 `TransportTimeError`(`hai:crypto:071`),显示校验时的服务器 UTC 时间,提示开启设备的自动校时或手工修改时间后刷新。`serverTime` 为 Unix 毫秒,旧服务端未提供该值时仍显示校时提示。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 和 `cause.serverTime` 保留诊断信息。标准认证动作将已认证的传输时间错误转为 `HaiResult`,保留校验时间和校时提示,AuthPanel 可直接展示。
251
251
 
252
252
  #### 使用 `_kit.yml` 统一 transport 配置
253
253
 
package/dist/browser.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-BnhrkzZF.js';
2
- export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig } from './kit-client-auth-BnhrkzZF.js';
3
- import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-EBzZKYN-.js';
4
- export { K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig } from './kit-i18n-EBzZKYN-.js';
5
- export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, h as HandleConfig, i as HookCryptoConfig, K as KitConfig, j as KitConfigInput, k as KitPublicConfig, l as KitServerConfig, m as KitServerConfigInput, n as KitTransportConfig, o as KitTransportConfigInput, M as Middleware, p as MiddlewareContext, R as RateLimitConfig, q as RouteGuard, S as SessionData, r as SessionLike, T as TransportEncryptionOptions } from './kit-types-BkNdyKj8.js';
1
+ import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-DeWmc-1u.js';
2
+ export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig } from './kit-client-auth-DeWmc-1u.js';
3
+ import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-CjwdGeKY.js';
4
+ export { K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig } from './kit-i18n-CjwdGeKY.js';
5
+ export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, h as HandleConfig, i as HookCryptoConfig, K as KitConfig, j as KitConfigInput, k as KitPublicConfig, l as KitServerConfig, m as KitServerConfigInput, n as KitTransportConfig, o as KitTransportConfigInput, M as Middleware, p as MiddlewareContext, R as RateLimitConfig, q as RouteGuard, S as SessionData, r as SessionLike, T as TransportEncryptionOptions } from './kit-types-CnfBC89D.js';
6
6
  import '@h-ai/api-contract';
7
7
  import '@h-ai/core';
8
8
  import '@h-ai/crypto';
package/dist/browser.js CHANGED
@@ -1,6 +1,6 @@
1
- import { defineCrud } from './chunk-5VWPH7DO.js';
2
- export { KitConfigSchema, resolveKitConfig, resolveKitTransportConfig } from './chunk-5VWPH7DO.js';
3
- import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-YKJRQCWR.js';
1
+ import { defineCrud } from './chunk-WCMEGLBL.js';
2
+ export { KitConfigSchema, resolveKitConfig, resolveKitTransportConfig } from './chunk-WCMEGLBL.js';
3
+ import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-5JW3VW4Q.js';
4
4
  import './chunk-JRQPMPQC.js';
5
5
  import { setAllModulesLocale } from './chunk-USFJN7IO.js';
6
6
 
@@ -1,8 +1,8 @@
1
1
  import { normalizeTransportBasePath, resolveTransportPath, DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, shouldUseTransportForUrl } from './chunk-JRQPMPQC.js';
2
2
  import { kitM } from './chunk-USFJN7IO.js';
3
- import { ok, core, err } from '@h-ai/core';
4
- import { IamSessionErrorCodeSchema, apiContract, IamReauthenticationProofSchema, IamPublicConfigSchema, IamCaptchaChallengeSchema } from '@h-ai/api-contract';
5
- import { TRANSPORT_PROTOCOL } from '@h-ai/crypto';
3
+ import { ok, err, core } from '@h-ai/core';
4
+ import { IamSessionErrorCodeSchema, IamReauthenticationProofSchema, IamPublicConfigSchema, IamCaptchaChallengeSchema, apiContract } from '@h-ai/api-contract';
5
+ import { TRANSPORT_PROTOCOL, TransportTimeError, HaiCryptoError } from '@h-ai/crypto';
6
6
  import { z } from 'zod';
7
7
 
8
8
  var DEFAULT_TOKEN_COOKIE_NAME = "hai_access_token";
@@ -307,9 +307,18 @@ function createKitClient(config = {}) {
307
307
  function createKitAuthActions(options = {}) {
308
308
  const apiFetch = options.apiFetch ?? createKitClient().apiFetch;
309
309
  const base = options.basePath ?? "/api/auth";
310
+ async function request(schema, ...args) {
311
+ try {
312
+ const response = await apiFetch(...args);
313
+ return apiContract.haiResultSchema(schema).parse(await response.json());
314
+ } catch (error) {
315
+ if (error instanceof TransportTimeError)
316
+ return err(HaiCryptoError.TRANSPORT_TIME_INVALID, error.message, { transportCode: error.code, serverTime: error.serverTime });
317
+ throw error;
318
+ }
319
+ }
310
320
  async function post(path, input, init = {}) {
311
- const response = await apiFetch(`${base}/${path}`, { ...init, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }, { recoverSession: false });
312
- const result = apiContract.haiResultSchema(z.unknown().optional()).parse(await response.json());
321
+ const result = await request(z.unknown().optional(), `${base}/${path}`, { ...init, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }, { recoverSession: false });
313
322
  return result.success ? ok(result.data) : result;
314
323
  }
315
324
  return {
@@ -335,9 +344,8 @@ function createKitAuthActions(options = {}) {
335
344
  const result = await post("captcha", input);
336
345
  return result.success ? ok(IamCaptchaChallengeSchema.parse(result.data)) : result;
337
346
  },
338
- async publicConfig() {
339
- const response = await apiFetch(`${base}/config`, void 0, { recoverSession: false });
340
- return apiContract.haiResultSchema(IamPublicConfigSchema.nullable()).parse(await response.json());
347
+ publicConfig() {
348
+ return request(IamPublicConfigSchema.nullable(), `${base}/config`, void 0, { recoverSession: false });
341
349
  },
342
350
  login(input) {
343
351
  if (input.method === "ldap")
@@ -351,13 +359,12 @@ function createKitAuthActions(options = {}) {
351
359
  requestPasswordReset: (identifier, options2) => post("password-reset/request", { identifier, ...options2 }),
352
360
  confirmPasswordReset: (token, newPassword) => post("password-reset/confirm", { token, newPassword }),
353
361
  renewPassword: (input) => post("password/renew", input),
354
- async reauthenticate(password, operation) {
355
- const response = await apiFetch(`${base}/reauthenticate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password, operation }) });
356
- return apiContract.haiResultSchema(IamReauthenticationProofSchema).parse(await response.json());
362
+ reauthenticate(password, operation) {
363
+ return request(IamReauthenticationProofSchema, `${base}/reauthenticate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password, operation }) });
357
364
  }
358
365
  };
359
366
  }
360
367
 
361
368
  export { clearBrowserToken, configureAuth, createKitAuthActions, createKitClient, getAccessToken, installBrowserTransport, installBrowserTransportFetch, login, loginWithApiKey, loginWithLdap, loginWithOtp, logout, registerAndLogin, setBrowserToken };
362
- //# sourceMappingURL=chunk-YKJRQCWR.js.map
363
- //# sourceMappingURL=chunk-YKJRQCWR.js.map
369
+ //# sourceMappingURL=chunk-5JW3VW4Q.js.map
370
+ //# sourceMappingURL=chunk-5JW3VW4Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/kit-auth.ts","../src/client/kit-client.ts","../src/client/kit-client-auth.ts"],"names":["err","ok","options","core"],"mappings":"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","file":"chunk-5JW3VW4Q.js","sourcesContent":["/**\n * @h-ai/kit — Bearer 认证工具\n *\n * 提供服务端认证 Cookie 管理(login / logout)与浏览器端 Token 存储工具。\n * @module kit-auth\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { ApiKeyCredentials, AuthRequestContext, AuthResult, LdapCredentials, OtpCredentials, PasswordCredentials, RegisterOptions } from '@h-ai/iam'\nimport type { AuthOperations, AuthOperationsProvider } from './kit-types.js'\nimport { err, ok } from '@h-ai/core'\nimport { kitM } from './kit-i18n.js'\n\n/** 默认 Token Cookie 名 */\nconst DEFAULT_TOKEN_COOKIE_NAME = 'hai_access_token'\n\n/**\n * 应用级认证配置(由 createHandle 一次性初始化,生命周期同进程)\n *\n * 此状态非请求级:仅在 createHandle() 时写入一次,后续所有请求只读访问。\n */\nconst authState = {\n cookieName: DEFAULT_TOKEN_COOKIE_NAME,\n operations: null as AuthOperationsProvider | null,\n}\n\n/**\n * 配置认证参数\n *\n * 通常由 `createHandle` 在初始化时自动调用,无需手动调用。\n * @param config - 启动阶段注入的 Cookie 名和认证操作提供器。\n * @param config.cookieName - 认证 Cookie 名。\n * @param config.operations - 已初始化服务的认证操作或惰性提供器。\n * @example 模块内部上下文\n * ```ts\n * function configure(operations: AuthOperationsProvider) {\n * configureAuth({ cookieName: 'hai_access_token', operations })\n * }\n * ```\n */\nexport function configureAuth(config: { cookieName?: string, operations?: AuthOperationsProvider }): void {\n if (config.cookieName)\n authState.cookieName = config.cookieName\n if (config.operations)\n authState.operations = config.operations\n}\n\n/**\n * 服务端认证只读 Cookie 依赖;由当前请求提供,不能跨请求缓存。\n * @example 模块内部上下文\n * ```ts\n * const cookies: CookieReader = { get: name => name === 'hai_access_token' ? 'example-only-token' : undefined }\n * const token = getAccessToken(new Request('https://app.example.com/api'), cookies)\n * void token\n * ```\n */\ninterface CookieReader {\n get: (name: string) => string | undefined\n}\n\n/**\n * 服务端认证写入/删除 Cookie 依赖;由当前请求提供并执行安全属性。\n * @example 模块内部上下文\n * ```ts\n * function signIn(cookies: CookieWriter, credentials: PasswordCredentials) {\n * return login(cookies, credentials) // 业务调用者检查 success 后消费身份。\n * }\n * ```\n */\ninterface CookieWriter {\n set: (name: string, value: string, options: {\n path: string\n httpOnly: boolean\n sameSite: 'lax' | 'strict' | 'none'\n secure: boolean\n maxAge?: number\n }) => void\n delete: (name: string, options: { path: string }) => void\n}\n\n/**\n * 浏览器 Token 存储器;使用页面内存或平台安全存储,禁止写入 Web Storage。\n * @example 模块内部上下文\n * ```ts\n * const store: BrowserTokenStore = getDefaultBrowserTokenStore()\n * store.clear() // 当前页面内存状态;不持有服务端会话真值。\n * ```\n */\nexport interface BrowserTokenStore {\n get: () => string | null\n set: (token: string) => void\n clear: () => void\n}\n\nfunction createMemoryTokenStore(): BrowserTokenStore {\n let token: string | null = null\n return {\n get: () => token,\n set: (value: string) => { token = value },\n clear: () => { token = null },\n }\n}\n\n// 默认浏览器 Token 只保存在当前页面内存中,避免 auth: true 隐式落到 localStorage。\nconst defaultBrowserTokenStore = createMemoryTokenStore()\n\n/**\n * 获取默认浏览器 Token 存储(页面内存,不持久化)。\n * @returns 当前页面的内存令牌存储器;不持有服务端共享状态。\n * @example 模块内部上下文\n * ```ts\n * const store = getDefaultBrowserTokenStore()\n * store.set('example-only-token')\n * store.clear()\n * ```\n */\nexport function getDefaultBrowserTokenStore(): BrowserTokenStore {\n return defaultBrowserTokenStore\n}\n\n// ─── 内部工具(仅 kit 包内使用) ───\n\n/**\n * 从请求 Authorization 头提取 Bearer Token。\n */\nfunction getBearerTokenFromRequest(request: Request): string | null {\n const authorization = request.headers.get('authorization')\n if (!authorization)\n return null\n\n const [scheme, token] = authorization.split(' ')\n if (!scheme || !token)\n return null\n\n if (scheme.toLowerCase() !== 'bearer')\n return null\n\n const trimmed = token.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\n/**\n * 从请求中统一提取 Access Token(Bearer 优先,其次配置的 Cookie 名)。\n * @param request - 当前 HTTP 请求,优先读取 Authorization Bearer。\n * @param cookies - 当前请求的 Cookie 读取器;未提供时仅检查请求头。\n * @returns 解析到的令牌;均无值时为 null。\n * @example 模块内部上下文\n * ```ts\n * const request = new Request('https://app.example.com/api', { headers: { Authorization: 'Bearer example-only-token' } })\n * const token = getAccessToken(request)\n * void token\n * ```\n */\nexport function getAccessToken(request: Request, cookies?: CookieReader): string | null {\n return getBearerTokenFromRequest(request) ?? cookies?.get(authState.cookieName) ?? null\n}\n\n// ─── 服务端认证 Cookie 管理 ───\n\n/**\n * 写入 Access Token Cookie(内部使用,由 login 自动调用)。\n */\nfunction setToken(cookies: CookieWriter, token: string, maxAge?: number): void {\n // 共享文件也供浏览器内存 Token 使用,不引入 Node 内建模块;未知宿主保持安全默认值。\n // eslint-disable-next-line node/prefer-global/process\n const secure = typeof process === 'undefined' || process.env.NODE_ENV === 'production' || process.env.HAI_CORE_ENV === 'production'\n cookies.set(authState.cookieName, token, {\n path: '/',\n httpOnly: true,\n sameSite: 'lax',\n // 测试开关仅放宽共享状态要求,不降低生产凭证 Cookie 的传输安全。\n secure,\n maxAge,\n })\n}\n\n/**\n * 清除 Access Token Cookie(内部使用,由 logout 自动调用)。\n */\nfunction clearToken(cookies: CookieWriter): void {\n cookies.delete(authState.cookieName, { path: '/' })\n}\n\n// ─── 服务端高级认证 API(通过 kit.auth 暴露) ───\n\n/**\n * 获取已注入的认证操作;未配置时返回 null。\n *\n * 公共 API 不应 throw,因此此处用 null 信号配合上层短路返回 HaiResult。\n */\nfunction getAuthOperations(): AuthOperations | null {\n if (!authState.operations)\n return null\n return typeof authState.operations === 'function'\n ? authState.operations()\n : authState.operations\n}\n\n/**\n * 构造 \"auth 未配置\" 的标准 HaiResult 错误。\n *\n * 用于公共认证 API 在 `authState.operations` 未注入时返回,\n * 避免向调用方抛出未捕获异常。\n */\nfunction authNotConfiguredError(): HaiResult<never> {\n return err({\n code: 'KIT_AUTH_NOT_CONFIGURED',\n message: kitM('kit_authNotConfigured'),\n })\n}\n\n/**\n * 执行已配置的认证登录操作,并在成功时写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param credentials - 认证凭证\n * @param run - 具体 IAM 认证操作\n */\nasync function runAuthLogin<TCredentials>(\n cookies: CookieWriter,\n credentials: TCredentials,\n run: (ops: AuthOperations, credentials: TCredentials) => Promise<HaiResult<AuthResult>>,\n): Promise<HaiResult<AuthResult>> {\n const ops = getAuthOperations()\n if (!ops)\n return authNotConfiguredError()\n\n const result = await run(ops, credentials)\n if (result.success) {\n setToken(cookies, result.data.tokens.accessToken, result.data.tokens.expiresIn)\n }\n return result\n}\n\n/**\n * 服务端登录(密码):内部调用 iam.auth.login,成功时自动写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param credentials - 密码凭证(identifier + password)\n * @param context - 宿主提供的可信登录元数据,不从凭据输入复制 IP。\n * @returns 认证结果(成功时 Cookie 已自动写入)\n * @example 模块内部上下文\n * ```ts\n * async function signIn(cookies: CookieWriter, credentials: PasswordCredentials) {\n * const result = await login(cookies, credentials)\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport async function login(\n cookies: CookieWriter,\n credentials: PasswordCredentials,\n context?: AuthRequestContext,\n): Promise<HaiResult<AuthResult>> {\n return runAuthLogin(cookies, credentials, (ops, input) => ops.login(input, context))\n}\n\n/**\n * 服务端登录(OTP 验证码):内部调用 iam.auth.loginWithOtp,成功时自动写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param credentials - OTP 凭证(identifier + code)\n * @param context - 宿主提供的可信登录元数据。\n * @returns 认证结果(成功时 Cookie 已自动写入)\n * @example 模块内部上下文\n * ```ts\n * async function signIn(cookies: CookieWriter, credentials: OtpCredentials) {\n * const result = await loginWithOtp(cookies, credentials)\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport async function loginWithOtp(\n cookies: CookieWriter,\n credentials: OtpCredentials,\n context?: AuthRequestContext,\n): Promise<HaiResult<AuthResult>> {\n return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithOtp(input, context))\n}\n\n/**\n * 服务端登录(LDAP):内部调用 iam.auth.loginWithLdap,成功时自动写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param credentials - LDAP 凭证(username + password)\n * @param context - 宿主提供的可信登录元数据。\n * @returns 认证结果(成功时 Cookie 已自动写入)\n * @example 模块内部上下文\n * ```ts\n * async function signIn(cookies: CookieWriter, credentials: LdapCredentials) {\n * const result = await loginWithLdap(cookies, credentials)\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport async function loginWithLdap(\n cookies: CookieWriter,\n credentials: LdapCredentials,\n context?: AuthRequestContext,\n): Promise<HaiResult<AuthResult>> {\n return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithLdap(input, context))\n}\n\n/**\n * 服务端登录(API Key):内部调用 iam.auth.loginWithApiKey,成功时自动写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param credentials - API Key 凭证(key)\n * @param context - 宿主提供的可信登录元数据。\n * @returns 认证结果(成功时 Cookie 已自动写入)\n * @example 模块内部上下文\n * ```ts\n * async function signIn(cookies: CookieWriter, credentials: ApiKeyCredentials) {\n * const result = await loginWithApiKey(cookies, credentials)\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport async function loginWithApiKey(\n cookies: CookieWriter,\n credentials: ApiKeyCredentials,\n context?: AuthRequestContext,\n): Promise<HaiResult<AuthResult>> {\n return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithApiKey(input, context))\n}\n\n/**\n * 服务端注册并登录:内部调用 iam.auth.registerAndLogin,成功时自动写入 Token Cookie。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param options - 注册选项(username、password、email 等)\n * @returns 认证结果(成功时 Cookie 已自动写入)\n * @example 模块内部上下文\n * ```ts\n * async function signIn(cookies: CookieWriter, credentials: RegisterOptions) {\n * const result = await registerAndLogin(cookies, credentials)\n * if (!result.success) return result.error.message\n * return result.data\n * }\n * ```\n */\nexport async function registerAndLogin(\n cookies: CookieWriter,\n options: RegisterOptions,\n context?: AuthRequestContext,\n): Promise<HaiResult<AuthResult>> {\n return runAuthLogin(cookies, options, (ops, input) => ops.registerAndLogin(input, context))\n}\n\n/**\n * 服务端登出:委托 iam.auth.logout 原子撤销凭据,并始终清除当前请求的 Token Cookie。\n * Cookie 清理只代表本地退出;服务端撤销失败仍返回失败,不能由调用端改成成功响应。\n *\n * @param cookies - SvelteKit cookies 对象\n * @param accessToken - 访问令牌(为 null/undefined 时仅清除 Cookie)\n * @returns 服务端撤销结果;无令牌时仅清理 Cookie 并成功,有令牌但未配置认证操作时失败。\n * @remarks 底层控制流异常仍以 Promise 拒绝传播;Cookie 清理不吞掉业务失败。\n * @example 模块内部上下文\n * ```ts\n * async function signOut(cookies: CookieWriter, accessToken: string) {\n * const result = await logout(cookies, accessToken)\n * if (!result.success) return result.error.code\n * return result.data\n * }\n * ```\n */\nexport async function logout(\n cookies: CookieWriter,\n accessToken?: string | null,\n): Promise<HaiResult<void>> {\n try {\n if (!accessToken)\n return ok(undefined)\n const ops = getAuthOperations()\n if (!ops)\n return authNotConfiguredError()\n return await ops.logout(accessToken)\n }\n finally {\n // 本地凭据无论是否撤销确认都要移除,但失败结果仍交给调用者明确提示。\n clearToken(cookies)\n }\n}\n\n// ─── 浏览器端 Token 存储(通过 kit.auth 暴露) ───\n\n/**\n * 写入默认浏览器端 Access Token(仅页面内存,不持久化到 localStorage)。\n * @param token - 当前页面内存中使用的 access token,不写入 Web Storage。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * kit.auth.setBrowserToken('example-only-token')\n * // 页面注销时同步清除浏览器内存令牌。\n * kit.auth.clearBrowserToken()\n * ```\n */\nexport function setBrowserToken(token: string): void {\n defaultBrowserTokenStore.set(token)\n}\n\n/**\n * 清除默认浏览器端 Access Token(仅页面内存)。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * kit.auth.clearBrowserToken()\n * ```\n */\nexport function clearBrowserToken(): void {\n defaultBrowserTokenStore.clear()\n}\n","/**\n * @h-ai/kit — 统一客户端\n *\n * 将 CSRF Token 附加与可选传输加密封装为统一 `apiFetch`。\n * 应用层只需调用一次 `createKitClient()`,后续 API 调用即可复用同一套安全能力。\n * @module kit-client\n */\n\nimport type { CryptoFunctions, TransportClient } from '@h-ai/crypto'\nimport type { BrowserTokenStore } from '../kit-auth.js'\nimport type { KitPublicConfig } from '../kit-types.js'\nimport { IamSessionErrorCodeSchema } from '@h-ai/api-contract'\nimport { core } from '@h-ai/core'\nimport { TRANSPORT_PROTOCOL } from '@h-ai/crypto'\nimport { getDefaultBrowserTokenStore } from '../kit-auth.js'\nimport { DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, normalizeTransportBasePath, resolveTransportPath, shouldUseTransportForUrl } from '../modules/crypto/kit-transport-paths.js'\n\nconst DEFAULT_KEY_EXCHANGE_URL = `/api${TRANSPORT_PROTOCOL.DEFAULT_KEY_EXCHANGE_PATH}`\n\n// ─── 类型 ───\n\n/**\n * 传输加密客户端配置\n * @example\n * ```ts\n * import type { ClientTransportConfig } from '@h-ai/kit'\n * import { crypto } from '@h-ai/crypto'\n *\n * function configAfterInit(): ClientTransportConfig { return { crypto, excludePaths: ['/api/health'] } }\n * ```\n */\nexport interface ClientTransportConfig {\n /** 宿主构建挂载目录,默认空字符串;SvelteKit 使用 $app/paths.base。 */\n basePath?: string\n /** 可选本地明文上限;缺省跟随服务端握手。 */\n maxPlaintextBytes?: number\n /** @h-ai/crypto 服务实例 */\n crypto: CryptoFunctions\n /** 密钥交换端点 URL(默认 `'/api/_hai/key-exchange'`) */\n keyExchangeUrl?: string\n /** 排除路径(命中后强制走明文),需与服务端 transport.excludePaths 保持一致 */\n excludePaths?: readonly string[]\n}\n\n/**\n * Kit 客户端配置\n * @example\n * ```ts\n * import type { KitClientConfig } from '@h-ai/kit'\n *\n * const value: KitClientConfig = { auth: true }\n * void value\n * ```\n */\nexport interface KitClientConfig {\n /** 标准鉴别明确失效时原地登录原用户;成功才对被拒绝的请求重试一次。 */\n recoverSession?: () => Promise<boolean>\n /** 会话终端分组,默认web;服务端按终端与全局配额共同限制。 */\n terminalType?: 'web' | 'app' | 'desktop' | 'api'\n /**\n * 传输加密配置。\n * - 不提供 / undefined:不启用传输加密,仅 CSRF\n * - 提供对象:启用传输加密\n */\n transport?: ClientTransportConfig\n /** CSRF Cookie 名称(默认 `'hai_csrf'`) */\n csrfCookieName?: string\n /** CSRF Header 名称(默认 `'X-CSRF-Token'`) */\n csrfHeaderName?: string\n /**\n * 认证配置:自动从浏览器存储读取 Access Token 并注入请求头。\n * - `true`:使用默认页面内存存储(由 `kit.auth.setBrowserToken()` 写入,不读写 localStorage)\n * - `BrowserTokenStore`:使用自定义存储\n * - 不提供 / `false`:不自动注入\n */\n auth?: boolean | BrowserTokenStore\n}\n\n/**\n * Kit 客户端实例\n * @example\n * ```ts\n * import type { KitClient } from '@h-ai/kit'\n *\n * async function read(client: KitClient) {\n * const response = await client.apiFetch('/api/health')\n * if (!response.ok) throw new Error('Request failed')\n * return response.json()\n * }\n * ```\n */\nexport interface KitClient {\n /** 统一 API fetch(写请求自动附加 CSRF,启用 transport 时自动加解密) */\n apiFetch: (input: RequestInfo | URL, init?: RequestInit, options?: { recoverSession?: boolean, reauthorize?: () => Promise<string | null | undefined> }) => Promise<Response>\n /** 传输加密是否就绪(未启用传输加密时始终为 true) */\n readonly ready: boolean\n /**\n * 手动触发密钥交换(transport 启用时)。\n *\n * 注意:本方法是 Client 形态的合规例外,密钥协商失败时 `throw`(与 `@h-ai/api-client` 一致),\n * 调用方需自行 `try/catch`,不返回 `HaiResult`。\n */\n init: () => Promise<void>\n /** 销毁密钥状态 */\n destroy: () => void\n}\n\n// ─── 工具函数 ───\n\n/**\n * 从 document.cookie 中读取指定名称的 Cookie 值\n *\n * @param name - Cookie 名称\n * @returns Cookie 值;不存在或运行在服务端时返回 undefined\n */\nfunction getCookie(name: string): string | undefined {\n if (typeof document === 'undefined')\n return undefined\n const match = document.cookie.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`))\n return match?.[1]\n}\n\n/**\n * 判断当前请求体是否可以作为文本载荷进行传输加密。\n *\n * 传输加密中间件会把解密后的明文重新注入 `Request`,当前仅能可靠保留\n * JSON 字符串请求体。`FormData` / `Blob` / `ArrayBuffer` 等二进制或浏览器\n * 自动生成边界的请求体必须保持原样发送,否则文件上传会被序列化成 `{}`。\n *\n * @param body fetch 请求体\n * @returns 是否支持客户端传输加密\n */\nfunction canEncryptRequestBody(body: BodyInit | null | undefined): boolean {\n return body === undefined || body === null || typeof body === 'string'\n}\n\n/**\n * 判断某个 `Request` 的有效方法、请求头与原始正文是否可走传输加密。\n *\n * `Request.body` 已转换为流,必须结合 Content-Type 识别可重注入的 JSON,\n * 避免把 multipart 等二进制正文当文本加密。纯 BodyInit 则仍按原始类型判定。\n */\nfunction canUseTransportForRequest(request: Pick<Request, 'method' | 'headers' | 'body'>): boolean {\n const method = request.method.toUpperCase()\n if (method === 'GET' || method === 'HEAD') {\n return true\n }\n\n const headers = new Headers(request.headers)\n const contentType = headers.get('Content-Type') ?? ''\n if (contentType.includes('application/json')) {\n return true\n }\n\n return request.body === null\n}\n\nfunction getCurrentOrigin(): string {\n if (typeof window !== 'undefined' && window.location?.origin) {\n return window.location.origin\n }\n\n return 'http://localhost'\n}\n\nfunction toRequest(input: RequestInfo | URL, init?: RequestInit): Request {\n if (input instanceof Request)\n return init ? new Request(input, init) : input\n\n return new Request(input, init)\n}\n\n/**\n * 浏览器全局 fetch 包装的幂等安装状态;属于当前浏览器页面。\n * @example 模块内部上下文\n * ```ts\n * const state: BrowserTransportFetchState = globalThis\n * const installed = state.__haiKitTransportFetchInstalled ?? false\n * void installed\n * ```\n */\ntype BrowserTransportFetchState = typeof globalThis & {\n __haiKitOriginalFetch?: typeof fetch\n __haiKitTransportFetchInstalled?: boolean\n}\n\n/**\n * 在浏览器启动时安装同源 transport fetch 包装。\n *\n * 该包装作用于同源 `/api/*` 与 SvelteKit `__data.json` 请求,页面文档、\n * 静态资源以及 multipart/form-data 等上传请求保持原样。应用只需安装一次,\n * 后续 `fetch` / SvelteKit 内部 data fetch / `kit.client.create()` 明文发送路径\n * 都会共用这层传输加密能力。\n * @param transportConfig - 已初始化 crypto、同源密钥交换路径及排除路径;省略时不安装包装。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * // 不传配置时不安装全局包装;启用时应传已初始化 crypto 与同源端点。\n * kit.client.installBrowserTransportFetch()\n * ```\n */\nexport function installBrowserTransportFetch(transportConfig?: ClientTransportConfig): void {\n if (typeof window === 'undefined' || !transportConfig)\n return\n\n const fetchState = globalThis as BrowserTransportFetchState\n if (fetchState.__haiKitTransportFetchInstalled)\n return\n\n const originalFetch = fetchState.__haiKitOriginalFetch ?? globalThis.fetch.bind(globalThis)\n fetchState.__haiKitOriginalFetch = originalFetch\n\n const origin = getCurrentOrigin()\n const basePath = normalizeTransportBasePath(transportConfig.basePath)\n const keyExchangeUrl = new URL(transportConfig.keyExchangeUrl ?? DEFAULT_KEY_EXCHANGE_URL, origin).toString()\n const keyExchangePath = resolveTransportPath(keyExchangeUrl, origin)\n const transportClient = transportConfig.crypto.transport.createClient({\n keyExchangeUrl,\n fetch: originalFetch,\n maxPlaintextBytes: transportConfig.maxPlaintextBytes,\n })\n\n const wrappedFetch: typeof fetch = async (input, init) => {\n const request = toRequest(input, init)\n const requestUrl = new URL(request.url, origin)\n const headers = new Headers(request.headers)\n\n if (!shouldUseTransportForUrl(requestUrl, {\n origin,\n keyExchangePath,\n excludePaths: transportConfig.excludePaths,\n basePath,\n })) {\n return originalFetch(input, init)\n }\n\n if (headers.has(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER))\n return originalFetch(input, init)\n\n if (!canUseTransportForRequest(request))\n return originalFetch(input, init)\n\n return transportClient.encryptedFetch(request)\n }\n\n globalThis.fetch = wrappedFetch\n window.fetch = wrappedFetch\n fetchState.__haiKitTransportFetchInstalled = true\n}\n\n/**\n * 用 `config/_kit.yml` 解析后的 {@link KitPublicConfig} 公共投影 一次性安装浏览器端传输加密。\n *\n * 行为:\n * - `config.transport === false` 时直接返回(不启用传输加密);\n * - 否则启动 {@link installBrowserTransportFetch},并预热 {@link CryptoFunctions} 模块。\n *\n * 应用层(SvelteKit `+layout.svelte` 或浏览器入口)只需调用此函数一次,\n * 之后宿主 basePath 下的同源 `/api/*` 与 `__data.json` 请求都会自动走传输加密。\n *\n * @param config 已解析的 kit 公共传输配置\n * @param deps 外部依赖\n * @param deps.crypto `@h-ai/crypto` 模块实例\n * @param deps.basePath 宿主构建挂载目录,默认空字符串;SvelteKit 使用 $app/paths.base。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * import { crypto } from '@h-ai/crypto'\n *\n * function installAfterCryptoInit() {\n * kit.client.installBrowserTransport({ transport: { keyExchangePath: '/api/_hai/key-exchange', excludePaths: ['/api/health'] } }, { crypto })\n * }\n * ```\n */\nexport function installBrowserTransport(\n config: KitPublicConfig,\n deps: { crypto: CryptoFunctions, basePath?: string },\n): void {\n if (typeof window === 'undefined' || config.transport === false)\n return\n\n // crypto.init() 当前为同步副作用(signature 是 async 仅为接口一致性),\n // 此处不 await,以便 `+layout.svelte` 顶层模块加载阶段就完成初始化。\n void deps.crypto.init()\n\n installBrowserTransportFetch({\n crypto: deps.crypto,\n basePath: deps.basePath,\n keyExchangeUrl: config.transport.keyExchangePath,\n excludePaths: [...config.transport.excludePaths],\n })\n}\n\n// ─── 主函数 ───\n\n/**\n * 创建 Kit 客户端\n *\n * 返回统一的 `apiFetch`,内部自动完成:\n * 1. CSRF Token 附加(写方法自动读取 Cookie + 设置 Header)\n * 2. 密钥交换(首次写请求时 lazy init,transport 启用时)\n * 3. 请求体加密(transport 启用时)\n * 4. 响应体解密(transport 启用时)\n *\n * @param config - 客户端配置\n * @returns KitClient 实例\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * const client = kit.client.create({ auth: true })\n * try {\n * await client.init() // Client API 使用 Promise 拒绝;不是 HaiResult。\n * const response = await client.apiFetch('/api/health')\n * if (!response.ok) throw new Error('Request failed')\n * } finally {\n * client.destroy()\n * }\n * ```\n */\nexport function createKitClient(config: KitClientConfig = {}): KitClient {\n const {\n transport: transportConfig,\n csrfCookieName = 'hai_csrf',\n csrfHeaderName = 'X-CSRF-Token',\n auth: authConfig,\n } = config\n\n // ── Auth token store ──\n // 安全默认:auth:true 只读取页面内存 token,避免隐式 localStorage 持久化敏感凭证。\n const authStore: BrowserTokenStore | null = authConfig\n ? (authConfig === true ? getDefaultBrowserTokenStore() : authConfig)\n : null\n\n // ── 传输加密状态 ──\n const transportOrigin = getCurrentOrigin()\n const transportBasePath = normalizeTransportBasePath(transportConfig?.basePath)\n const transportKeyExchangeUrl = transportConfig?.keyExchangeUrl ?? DEFAULT_KEY_EXCHANGE_URL\n const transportKeyExchangePath = transportConfig\n ? resolveTransportPath(transportKeyExchangeUrl, transportOrigin)\n : DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH\n const transportClient: TransportClient | null = transportConfig\n ? transportConfig.crypto.transport.createClient({\n keyExchangeUrl: transportKeyExchangeUrl,\n fetch,\n maxPlaintextBytes: transportConfig.maxPlaintextBytes,\n })\n : null\n\n /**\n * 统一 API fetch\n */\n async function performFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const request = input instanceof Request ? input : undefined\n const method = (init.method ?? request?.method ?? 'GET').toUpperCase()\n const isWriteMethod = !['GET', 'HEAD', 'OPTIONS'].includes(method)\n const headers = new Headers(init.headers ?? request?.headers)\n headers.set('x-hai-terminal', config.terminalType ?? 'web')\n const suppliedId = headers.get('x-request-id')\n const traceId = suppliedId && /^[\\w.:-]{1,128}$/.test(suppliedId) ? suppliedId : core.id.uuid()\n headers.set('x-request-id', traceId)\n // 仅检查元数据,不构建/读取 Request,确保透传上传的流与浏览器边界保持可用。\n const canUseTransport = request && init.body === undefined\n ? canUseTransportForRequest({ method, headers, body: request.body })\n : canEncryptRequestBody(init.body)\n\n // ── Auth ──\n if (authStore) {\n const token = authStore.get()\n if (token) {\n headers.set('Authorization', `Bearer ${token}`)\n }\n }\n\n // ── CSRF ──\n if (isWriteMethod) {\n const csrfToken = getCookie(csrfCookieName)\n if (csrfToken) {\n headers.set(csrfHeaderName, csrfToken)\n }\n }\n\n // ── 传输加密 ──\n if (\n transportClient\n && canUseTransport\n && shouldUseTransportForUrl(request?.url ?? (input instanceof URL ? input : String(input)), {\n origin: transportOrigin,\n keyExchangePath: transportKeyExchangePath,\n excludePaths: transportConfig?.excludePaths,\n basePath: transportBasePath,\n })\n ) {\n return send(() => transportClient.encryptedFetch(input, { ...init, headers }))\n }\n\n // ── 发送(FormData/Blob/Stream 等保持原样,不破坏浏览器边界) ──\n return send(() => fetch(input, { ...init, headers }))\n\n async function send(operation: () => Promise<Response>): Promise<Response> {\n try {\n const response = await operation()\n if (!response.ok)\n core.logger.error('HTTP request failed', { traceId: response.headers.get('x-trace-id') ?? response.headers.get('x-request-id') ?? traceId, method, status: response.status })\n return response\n }\n catch (error) {\n core.logger.error('HTTP transport failed', { traceId, method, error })\n throw error\n }\n }\n }\n\n return {\n async apiFetch(input, init = {}, options) {\n const enabled = config.recoverSession && options?.recoverSession !== false\n const original = enabled || options?.reauthorize ? new Request(input instanceof Request ? input : new URL(String(input), getCurrentOrigin()), init) : undefined\n const retry = original?.clone()\n const response = await performFetch(original ?? input, original ? {} : init)\n if (!retry || retry.signal.aborted || new URL(retry.url).origin !== getCurrentOrigin())\n return response\n if (response.status !== 401 && !options?.reauthorize)\n return response\n if (response.status !== 401 && !response.headers.get('content-type')?.includes('application/json'))\n return response\n const failure: unknown = await response.clone().json().catch(() => undefined)\n const code = failure && typeof failure === 'object' && 'success' in failure && failure.success === false && 'error' in failure && failure.error && typeof failure.error === 'object' && 'code' in failure.error ? failure.error.code : undefined\n const proofRejected = code === 'hai:iam:027' && options?.reauthorize\n if (!proofRejected && (!enabled || response.status !== 401 || (typeof code === 'string' && code.startsWith('hai:iam:') && !IamSessionErrorCodeSchema.safeParse(code).success)))\n return response\n if (!proofRejected && !await config.recoverSession!())\n return response\n // 新会话不能复用旧用途证明;只在明确拒绝且宿主重新确认后重试一次。\n if (retry.headers.has('x-hai-reauthentication') || options?.reauthorize) {\n if (!options?.reauthorize)\n return response\n const ticket = await options.reauthorize()\n if (ticket === null || retry.signal.aborted)\n return response\n retry.headers.delete('x-hai-reauthentication')\n if (ticket)\n retry.headers.set('x-hai-reauthentication', ticket)\n }\n // 认证动作本身必须传 recoverSession:false,避免错误密码再次等待自己。\n return performFetch(retry)\n },\n get ready() { return !transportClient || transportClient.ready() },\n async init() {\n if (!transportClient)\n return\n const result = await transportClient.init()\n if (!result.success)\n throw new Error(result.error.message)\n },\n destroy() {\n transportClient?.destroy()\n },\n }\n}\n","/** 与标准 Kit 认证端点配套的表单动作;复用已有 CSRF/transport fetch。 */\nimport type { IamCreateCaptchaInputSchema, IamLoginInput, IamPublicConfig, IamRegisterInput } from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { KitClient } from './kit-client.js'\nimport { apiContract, IamCaptchaChallengeSchema, IamPublicConfigSchema, IamReauthenticationProofSchema } from '@h-ai/api-contract'\nimport { core, err, ok } from '@h-ai/core'\nimport { HaiCryptoError, TransportTimeError } from '@h-ai/crypto'\nimport { z } from 'zod'\nimport { createKitClient } from './kit-client.js'\n\n/**\n * 创建标准 Cookie 认证表单动作;复用可选 apiFetch 以保留 CSRF 和传输配置。\n * @param options - 可复用的 apiFetch 和标准认证端点 basePath。\n * @param options.apiFetch - 已装配 CSRF/transport 的 fetch;省略时使用默认 Kit 客户端。\n * @param options.basePath - 同源认证端点前缀,默认 /api/auth。\n * @returns 标准表单动作;业务和已认证的时间错误返回 HaiResult,网络/格式错误会拒绝 Promise。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * const actions = kit.client.createAuthActions({ basePath: '/api/auth' })\n * void actions\n * ```\n */\nexport function createKitAuthActions(options: { apiFetch?: KitClient['apiFetch'], basePath?: string } = {}) {\n const apiFetch = options.apiFetch ?? createKitClient().apiFetch\n const base = options.basePath ?? '/api/auth'\n async function request<T>(schema: z.ZodType<T>, ...args: Parameters<KitClient['apiFetch']>): Promise<HaiResult<T>> {\n try {\n const response = await apiFetch(...args)\n return apiContract.haiResultSchema(schema).parse(await response.json())\n }\n catch (error) {\n if (error instanceof TransportTimeError)\n return err(HaiCryptoError.TRANSPORT_TIME_INVALID, error.message, { transportCode: error.code, serverTime: error.serverTime })\n throw error\n }\n }\n async function post(path: string, input: object, init: Pick<RequestInit, 'keepalive'> = {}): Promise<HaiResult<unknown>> {\n // void 成功响应在 JSON 中省略 data,统一还原为 HaiResult 的 data: undefined。\n const result = await request(z.unknown().optional(), `${base}/${path}`, { ...init, method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }, { recoverSession: false })\n return result.success ? ok(result.data) : result\n }\n return {\n bindPageExit(options: { enabled?: boolean } = {}) {\n if (!options.enabled || typeof window === 'undefined')\n return () => {}\n const target = window\n let sent = false\n const onPageHide = (event: PageTransitionEvent) => {\n if (event.persisted || sent)\n return\n sent = true\n void post('session/close', {}, { keepalive: true }).then((result) => {\n if (!result.success)\n core.logger.warn('Page exit session revocation failed', { errorCode: result.error.code })\n }).catch(() => core.logger.warn('Page exit session request did not complete'))\n }\n target.addEventListener('pagehide', onPageHide)\n return () => target.removeEventListener('pagehide', onPageHide)\n },\n async createCaptcha(input: z.input<typeof IamCreateCaptchaInputSchema>) {\n const result = await post('captcha', input)\n return result.success ? ok(IamCaptchaChallengeSchema.parse(result.data)) : result\n },\n publicConfig(): Promise<HaiResult<IamPublicConfig | null>> {\n return request(IamPublicConfigSchema.nullable(), `${base}/config`, undefined, { recoverSession: false })\n },\n login(input: IamLoginInput & { method?: 'password' | 'ldap' | 'email' | 'sms', code?: string }) {\n if (input.method === 'ldap')\n return post('login/ldap', { username: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId })\n if (input.method === 'email' || input.method === 'sms')\n return post('login/otp', { identifier: input.identifier, code: input.code, captcha: input.captcha, expectedUserId: input.expectedUserId })\n return post('login', { identifier: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId })\n },\n register: (input: IamRegisterInput) => post('register', { ...input, confirmPassword: input.password }),\n sendOtp: (identifier: string, options?: { captcha?: string }) => post('otp/send', { identifier, ...options }),\n requestPasswordReset: (identifier: string, options?: { captcha?: string }) => post('password-reset/request', { identifier, ...options }),\n confirmPasswordReset: (token: string, newPassword: string) => post('password-reset/confirm', { token, newPassword }),\n renewPassword: (input: { identifier: string, password: string, newPassword: string, captcha?: string }) => post('password/renew', input),\n reauthenticate(password: string, operation: string) {\n return request(IamReauthenticationProofSchema, `${base}/reauthenticate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password, operation }) })\n },\n }\n}\n"]}
@@ -15,7 +15,9 @@ function transportEncryptionMiddleware(config, encryption) {
15
15
  const result = config.crypto.transport.createServer({
16
16
  serverKeyRing: config.serverKeyRing,
17
17
  keyStore: config.keyStore,
18
- maxPlaintextBytes: policy.maximum.plaintextBytes
18
+ maxPlaintextBytes: policy.maximum.plaintextBytes,
19
+ maxFutureSkewSeconds: config.maxFutureSkewSeconds,
20
+ maxRequestAgeSeconds: config.maxRequestAgeSeconds
19
21
  });
20
22
  if (!result.success) {
21
23
  throw core.error.buildHaiErrorInst(HaiCommonError.INTERNAL_ERROR, kitM("kit_transportKeyGenerationFailed"), result.error);
@@ -171,5 +173,5 @@ function replaceRequestBody(event, body) {
171
173
  }
172
174
 
173
175
  export { transportEncryptionMiddleware };
174
- //# sourceMappingURL=chunk-3OHUSKHI.js.map
175
- //# sourceMappingURL=chunk-3OHUSKHI.js.map
176
+ //# sourceMappingURL=chunk-6KS67QUS.js.map
177
+ //# sourceMappingURL=chunk-6KS67QUS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/crypto/kit-transport-middleware.ts"],"names":[],"mappings":"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","file":"chunk-6KS67QUS.js","sourcesContent":["/**\n * @h-ai/kit — 传输加密中间件\n *\n * SvelteKit 中间件,自动对请求/响应进行传输加密解密。\n * @module kit-transport-middleware\n */\n\nimport type { RequestAccessCheck } from '@h-ai/core'\nimport type { TransportEncryptionManager, TransportMessageContext } from '@h-ai/crypto'\nimport type { Middleware } from '../../kit-types.js'\nimport type { TransportEncryptionConfig } from './kit-crypto-types.js'\nimport { core, err, HaiCommonError } from '@h-ai/core'\nimport { authenticateTransportRequest, createTransportBodyPolicy, createTransportErrorResponse, isValidSM2PublicKey, readTransportBody, TRANSPORT_BODY_LIMITS, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError } from '@h-ai/crypto'\nimport { kitM } from '../../kit-i18n.js'\nimport { DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, normalizeTransportBasePath, shouldExcludeTransportPath, shouldHandleTransportPath } from './kit-transport-paths.js'\n\nconst ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ['application/json', 'text/sveltekit-data']\n\n/**\n * 创建传输加密中间件\n *\n * @param config - 传输加密配置\n * @param encryption - 已编译的路径/方法选择器;省略时沿用宿主默认范围。\n * @returns SvelteKit 中间件\n * @throws 显式启用但管理器初始化失败时抛出配置错误,调用方须停止宿主装配。\n * @example 模块内部上下文\n * ```ts\n * function install(config: TransportEncryptionConfig) {\n * return transportEncryptionMiddleware(config) // 受管密钥环、共享 keyStore 与 crypto 生命周期由应用管理。\n * }\n * ```\n */\nexport function transportEncryptionMiddleware(config: TransportEncryptionConfig, encryption?: RequestAccessCheck['encryption']): Middleware {\n // 未启用时直接透传\n if (!config.enabled) {\n return async (_context, next) => next()\n }\n\n const keyExchangePath = config.keyExchangePath ?? DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH\n const basePath = normalizeTransportBasePath(config.basePath)\n const excludePaths = config.excludePaths ?? []\n\n const policy = createTransportBodyPolicy(config.maxPlaintextBytes, config.routePlaintextBytes)\n\n // 初始化传输加密管理器\n const result = config.crypto.transport.createServer({\n serverKeyRing: config.serverKeyRing,\n keyStore: config.keyStore,\n maxPlaintextBytes: policy.maximum.plaintextBytes,\n maxFutureSkewSeconds: config.maxFutureSkewSeconds,\n maxRequestAgeSeconds: config.maxRequestAgeSeconds,\n })\n if (!result.success) {\n // 与 Serv 一致:静态配置或必需依赖失败即拒绝装配,不能让健康页面掩盖不可用的业务加密。\n throw core.error.buildHaiErrorInst(HaiCommonError.INTERNAL_ERROR, kitM('kit_transportKeyGenerationFailed'), result.error)\n }\n const manager = result.data\n\n return async (context, next) => {\n const { event } = context\n const pathname = getRequestPathname(event.request)\n\n // CORS 预检没有业务密文,仍交由请求访问策略判断。\n if (event.request.method === 'OPTIONS')\n return next()\n\n // 密钥交换端点:委托给 keyExchangeHandler\n if (pathname === keyExchangePath && event.request.method === 'POST') {\n return handleKeyExchange(manager, event.request)\n }\n\n const mode = encryption?.(pathname, event.request.method)\n if (mode === 'disabled') {\n if (event.request.headers.has(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) || event.request.headers.has(TRANSPORT_PROTOCOL.ENVELOPE_HEADER))\n return Response.json(err({ ...HaiCommonError.VALIDATION_ERROR, message: kitM('kit_transportPlaintextRequired'), traceId: context.requestId }), { status: 400 })\n return next()\n }\n // 仅对同源 API endpoint 与 SvelteKit __data 请求启用 transport;\n // 显式 required 也可保护自定义 JSON 端点;不能拿它包裹 HTML 页面。\n if (mode !== 'required' && !shouldHandleTransportPath(pathname, keyExchangePath, basePath)) {\n return next()\n }\n\n // 排除路径不做加解密\n if (mode !== 'required' && shouldExcludeTransportPath(pathname, excludePaths, keyExchangePath)) {\n return next()\n }\n\n // 请求头中无 X-Client-Id 时的处理\n const clientId = event.request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)\n if (!clientId)\n return jsonError(kitM('kit_transportClientIdRequired'), 400)\n\n const lease = policy.acquire(pathname)\n if (!lease)\n return jsonError(kitM('kit_transportBusy'), 503)\n try {\n // ── 解密请求 ──\n // 不依赖可伪造/省略的 Content-Length/Type 判空,非 null 流必须进入实际内容检查。\n let requestContext: TransportMessageContext\n try {\n const verified = await authenticateTransportRequest(manager, event.request, lease.limits)\n if (!verified.success) {\n if (verified.error.cause instanceof TransportTimeError && verified.error.cause.context)\n return createTransportErrorResponse(verified.error.cause, { manager, context: verified.error.cause.context })\n return jsonError(kitM('kit_transportDecryptFailed'), 400)\n }\n requestContext = verified.data.context\n if (event.request.method !== 'GET' && event.request.method !== 'HEAD')\n replaceRequestBody(event, verified.data.plaintext)\n }\n catch (error) {\n if (error instanceof TransportBodyTooLargeError)\n return createTransportErrorResponse(error)\n return jsonError(kitM('kit_transportInvalidPayload'), error instanceof RangeError ? 413 : 400)\n }\n\n // ── 执行后续中间件/端点 ──\n const response = await next()\n\n // ── 加密响应 ──\n\n // 只允许可安全加密的响应体离开受保护路由;无法加密时 fail-closed,禁止明文泄露。\n const bodyless = event.request.method === 'HEAD' || isEmptyResponse(response)\n if (bodyless)\n void response.body?.cancel().catch(() => {})\n\n const contentType = response.headers.get('Content-Type') ?? ''\n if (!bodyless && !canEncryptResponseContentType(contentType)) {\n void response.body?.cancel().catch(() => {})\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n try {\n const responseBody = bodyless\n ? ''\n : await readTransportBody(response.body, {\n maxBytes: lease.limits.plaintextBytes,\n direction: 'response',\n signal: event.request.signal,\n })\n const headers = new Headers(response.headers)\n headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER)\n headers.delete('Content-Length')\n headers.delete('Content-Encoding')\n\n const encryptedPayload = await manager.encryptResponse(requestContext, responseBody, response.status)\n if (!encryptedPayload.success) {\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)\n if (bodyless)\n headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, JSON.stringify(encryptedPayload.data))\n return new Response(bodyless ? null : JSON.stringify(encryptedPayload.data), {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n catch (error) {\n if (error instanceof TransportBodyTooLargeError)\n return createTransportErrorResponse(error, { manager, context: requestContext })\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n }\n finally { lease.release() }\n }\n}\n\nfunction canEncryptResponseContentType(contentType: string): boolean {\n return ENCRYPTABLE_RESPONSE_CONTENT_TYPES.some(type => contentType.includes(type))\n}\n\nfunction isEmptyResponse(response: Response): boolean {\n if ([204, 304].includes(response.status))\n return true\n // Content-Length 不能证明响应流为空,避免明文伪装零长度后绕过加密。\n return response.body === null\n}\n\nfunction getRequestPathname(request: Request): string {\n return new URL(request.url).pathname\n}\n\nfunction jsonError(message: string, status: number): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n/**\n * 处理密钥交换 POST 请求\n *\n * 接收客户端公钥,注册并返回服务端公钥与 clientId。\n *\n * @param manager - 传输加密管理器\n * @param request - 原始 Request\n * @returns JSON Response\n */\nasync function handleKeyExchange(\n manager: TransportEncryptionManager,\n request: Request,\n): Promise<Response> {\n let body: unknown\n try {\n body = JSON.parse(await readTransportBody(request.body, {\n maxBytes: TRANSPORT_BODY_LIMITS.keyExchangeBytes,\n signal: request.signal,\n }))\n }\n catch (error) {\n return jsonError(kitM('kit_transportInvalidPayload'), error instanceof RangeError ? 413 : 400)\n }\n if (!body || typeof body !== 'object' || !('clientPublicKey' in body)\n || !isValidSM2PublicKey(body.clientPublicKey)\n || new TextEncoder().encode(body.clientPublicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes) {\n return jsonError(kitM('kit_transportInvalidPayload'), 400)\n }\n\n try {\n const serverPublicKey = manager.getServerPublicKey()\n if (new TextEncoder().encode(serverPublicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes)\n return jsonError(kitM('kit_transportKeyExchangeFailed'), 500)\n const clientId = await manager.registerClientKey(body.clientPublicKey)\n\n return new Response(\n JSON.stringify({ serverKeyId: manager.getServerKeyId(), serverPublicKey, clientId, maxPlaintextBytes: manager.bodyLimits.plaintextBytes }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n }\n catch {\n return jsonError(kitM('kit_transportKeyExchangeFailed'), 500)\n }\n}\n\n/**\n * 用于替换已验证请求正文的最小可写容器。\n * @example 模块内部上下文\n * ```ts\n * const holder: RequestHolder = { request: new Request('https://app.example.com/api/articles', { method: 'POST', body: '{}' }) }\n * replaceRequestBody(holder, JSON.stringify({ title: 'Example' }))\n * ```\n */\ninterface RequestHolder {\n request: Request\n}\n\n/**\n * 重建 SvelteKit 事件中的 Request。\n *\n * 有界认证会消费密文 body;只将成功认证的明文恢复给后续端点。\n *\n * @param event SvelteKit 请求事件\n * @param body 新请求体\n */\nfunction replaceRequestBody(event: RequestHolder, body: string): void {\n const headers = new Headers(event.request.headers)\n headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n headers.delete('Content-Length')\n headers.delete('Content-Encoding')\n headers.set('Content-Type', 'application/json; charset=utf-8')\n const newRequest = new Request(event.request.url, {\n method: event.request.method,\n headers,\n body,\n signal: event.request.signal,\n })\n Object.defineProperty(event, 'request', {\n value: newRequest,\n writable: true,\n configurable: true,\n })\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core';
2
- import { TRANSPORT_PLAINTEXT_RANGE } from '@h-ai/crypto';
2
+ import { TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_TIME_LIMITS, TRANSPORT_PROTOCOL } from '@h-ai/crypto';
3
3
  import { z } from 'zod';
4
4
 
5
5
  // src/crud/kit-crud.ts
@@ -77,6 +77,8 @@ var KitTransportConfigInputSchema = z.union([
77
77
  z.literal(false),
78
78
  z.object({
79
79
  maxPlaintextBytes: z.union([z.number().int().min(TRANSPORT_PLAINTEXT_RANGE.min).max(TRANSPORT_PLAINTEXT_RANGE.max), z.literal("auto")]).transform((value) => value === "auto" ? void 0 : value).optional().describe("\u666E\u901A\u4E1A\u52A1\u53CC\u5411\u660E\u6587\u4E0A\u9650\uFF1Bauto \u4E3A 1 MiB\uFF1B\u5927\u6750\u6599\u4F7F\u7528 routePlaintextBytes \u5355\u72EC\u58F0\u660E").meta({ default: "auto" }),
80
+ maxFutureSkewSeconds: z.number().int().min(0).max(TRANSPORT_TIME_LIMITS.maxFutureSkewSeconds).optional().describe("\u5141\u8BB8\u5BA2\u6237\u7AEF\u8BF7\u6C42\u65F6\u95F4\u9886\u5148\u670D\u52A1\u5668\u7684\u79D2\u6570").meta({ default: TRANSPORT_PROTOCOL.CLOCK_SKEW_MS / 1e3 }),
81
+ maxRequestAgeSeconds: z.number().int().min(TRANSPORT_TIME_LIMITS.minRequestAgeSeconds).max(TRANSPORT_TIME_LIMITS.maxRequestAgeSeconds).optional().describe("\u8BF7\u6C42\u4ECE\u7B7E\u53D1\u8D77\u7684\u6709\u6548\u79D2\u6570").meta({ default: TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS / 1e3 }),
80
82
  routePlaintextBytes: z.record(AbsolutePathSchema, z.number().int().min(TRANSPORT_PLAINTEXT_RANGE.min).max(TRANSPORT_PLAINTEXT_RANGE.max)).optional().describe("\u5B8C\u6574\u8DEF\u5F84\u6BB5\u5230\u53CC\u5411\u660E\u6587\u5B57\u8282\u4E0A\u9650\u7684\u6620\u5C04\uFF1B\u661F\u53F7\u5339\u914D\u5355\u6BB5\uFF0C\u4F9B\u9644\u4EF6\u548C\u5F52\u6863\u72EC\u7ACB\u5BB9\u91CF"),
81
83
  keyExchangePath: AbsolutePathSchema.default("/api/_hai/key-exchange"),
82
84
  excludePaths: z.array(AbsolutePathSchema).default([])
@@ -122,5 +124,5 @@ function resolveKitConfig(input = {}) {
122
124
  }
123
125
 
124
126
  export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText };
125
- //# sourceMappingURL=chunk-5VWPH7DO.js.map
126
- //# sourceMappingURL=chunk-5VWPH7DO.js.map
127
+ //# sourceMappingURL=chunk-WCMEGLBL.js.map
128
+ //# sourceMappingURL=chunk-WCMEGLBL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crud/kit-crud.ts","../src/kit-config.ts"],"names":[],"mappings":"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","file":"chunk-WCMEGLBL.js","sourcesContent":["/**\n * @h-ai/kit — CRUD 操作定义\n *\n * 声明式 CRUD 资源定义工厂函数。\n * @module kit-crud\n */\n\nimport type {\n CrudFieldDef,\n CrudFieldOptions,\n CrudOperations,\n CrudResourceDef,\n CrudTableColumn,\n CrudText,\n} from './kit-crud-types.js'\n\n/**\n * 解析 CrudText 为字符串\n */\nfunction resolveText(text: CrudText): string {\n return typeof text === 'function' ? text() : text\n}\n\n/**\n * 按 order 排序字段\n */\nfunction sortFields(fields: CrudFieldDef[]): CrudFieldDef[] {\n return [...fields].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n}\n\n/**\n * 解析选项列表\n */\nfunction resolveOptions(options: CrudFieldOptions | undefined): Array<{ label: string, value: string | number | boolean }> {\n if (!options)\n return []\n return typeof options === 'function' ? options() : options\n}\n\n/**\n * 定义 CRUD 资源\n *\n * 返回 CrudOperations 对象,包含字段过滤、列构建和 API 代理等能力。\n * 与 @h-ai/ui 的 CrudPage 组件配合使用。\n *\n * @param resource - 资源定义\n * @returns CRUD 操作对象\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * import type { CrudApiDef } from '@h-ai/kit'\n *\n * function articleResource(api: CrudApiDef<{ id: string, title: string }>) {\n * return kit.crud.define({ name: 'article', label: 'Articles', fields: [{ id: 'title', label: 'Title', type: 'string' }], api })\n * }\n * ```\n */\nexport function defineCrud<T = Record<string, unknown>>(\n resource: CrudResourceDef<T>,\n): CrudOperations<T> {\n const fields = resource.fields\n\n function getListFields(): CrudFieldDef[] {\n return sortFields(fields.filter(f => f.inList !== false))\n }\n\n function getFilterFields(): CrudFieldDef[] {\n return sortFields(fields.filter(f => f.filterable === true))\n }\n\n function getCreateFields(): CrudFieldDef[] {\n return sortFields(fields.filter(f => f.inCreate !== false))\n }\n\n function getEditFields(): CrudFieldDef[] {\n return sortFields(fields.filter(f => f.inEdit !== false))\n }\n\n function getDetailFields(): CrudFieldDef[] {\n return sortFields(fields.filter(f => f.inDetail !== false))\n }\n\n function toTableColumns(): CrudTableColumn[] {\n return getListFields().map(f => ({\n key: f.id,\n label: resolveText(f.label),\n width: f.width,\n align: f.align,\n render: f.render\n ? (item: Record<string, unknown>) => f.render!(item[f.id], item)\n : undefined,\n }))\n }\n\n function getDefaultValues(): Record<string, unknown> {\n const defaults: Record<string, unknown> = {}\n for (const f of fields) {\n if (f.defaultValue !== undefined) {\n defaults[f.id] = f.defaultValue\n }\n else if (f.type === 'boolean' || f.type === 'checkbox') {\n defaults[f.id] = false\n }\n else if (f.type === 'multi-select') {\n defaults[f.id] = []\n }\n else if (f.type === 'number') {\n defaults[f.id] = undefined\n }\n else {\n defaults[f.id] = ''\n }\n }\n return defaults\n }\n\n return {\n resource,\n getListFields,\n getFilterFields,\n getCreateFields,\n getEditFields,\n getDetailFields,\n toTableColumns,\n getDefaultValues,\n api: resource.api,\n }\n}\n\n// re-export 解析工具,供 UI 层使用\nexport { resolveOptions, resolveText }\n","/**\n * @h-ai/kit — 宿主配置\n *\n * 为 `config/_kit.yml` 提供监听、传输与请求策略的默认值和校验。\n * 服务端消费完整配置;浏览器仅接收显式投影的 transport 公共字段。\n * @module kit-config\n */\n\nimport type { KitConfig, KitConfigInput, KitServerConfig, KitServerConfigInput, KitTransportConfig, KitTransportConfigInput } from './kit-types.js'\nimport { RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core'\nimport { TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TRANSPORT_TIME_LIMITS } from '@h-ai/crypto'\nimport { z } from 'zod'\n\nconst AbsolutePathSchema = z.custom<`/${string}`>(\n value => typeof value === 'string' && value.startsWith('/'),\n 'path must start with /',\n)\n\nconst KitTransportConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n maxPlaintextBytes: z.union([z.number().int().min(TRANSPORT_PLAINTEXT_RANGE.min).max(TRANSPORT_PLAINTEXT_RANGE.max), z.literal('auto')]).transform(value => value === 'auto' ? undefined : value).optional().describe('普通业务双向明文上限;auto 为 1 MiB;大材料使用 routePlaintextBytes 单独声明').meta({ default: 'auto' }),\n maxFutureSkewSeconds: z.number().int().min(0).max(TRANSPORT_TIME_LIMITS.maxFutureSkewSeconds).optional().describe('允许客户端请求时间领先服务器的秒数').meta({ default: TRANSPORT_PROTOCOL.CLOCK_SKEW_MS / 1000 }),\n maxRequestAgeSeconds: z.number().int().min(TRANSPORT_TIME_LIMITS.minRequestAgeSeconds).max(TRANSPORT_TIME_LIMITS.maxRequestAgeSeconds).optional().describe('请求从签发起的有效秒数').meta({ default: TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS / 1000 }),\n routePlaintextBytes: z.record(AbsolutePathSchema, z.number().int().min(TRANSPORT_PLAINTEXT_RANGE.min).max(TRANSPORT_PLAINTEXT_RANGE.max)).optional().describe('完整路径段到双向明文字节上限的映射;星号匹配单段,供附件和归档独立容量'),\n keyExchangePath: AbsolutePathSchema.default('/api/_hai/key-exchange'),\n excludePaths: z.array(AbsolutePathSchema).default([]),\n }),\n])\n\nconst ServerConfigInputSchema = z.object({\n host: z.string().min(1).default('127.0.0.1'),\n port: z.number().int().min(1).max(65535).default(3000),\n})\n\n/**\n * `config/_kit.yml` 对应的配置 Schema。\n * @example\n * ```ts\n * import { KitConfigSchema } from '@h-ai/kit'\n *\n * const config = KitConfigSchema.parse({ server: { port: 3000 }, transport: false })\n * void config\n * ```\n */\nexport const KitConfigSchema = z.object({\n server: ServerConfigInputSchema.optional(),\n transport: KitTransportConfigInputSchema.optional(),\n trustedProxies: TrustedProxiesSchema.default('auto'),\n access: RequestAccessConfigSchema.default(false),\n}).transform(({ server, transport, trustedProxies, access }) => ({\n server: resolveKitServerConfig(server ?? {}),\n transport: resolveKitTransportConfig(transport ?? false),\n trustedProxies,\n access,\n}))\n\n/**\n * 读取 `HOST` / `PORT` 环境变量覆盖(环境变量高于配置文件)。\n *\n * 浏览器端 `process` 不存在时安全回退为空;`PORT` 仅采纳 1-65535 的整数。\n */\nfunction readServerEnvOverride(): KitServerConfigInput {\n // eslint-disable-next-line node/prefer-global/process\n const env = typeof process !== 'undefined' ? process.env : undefined\n const override: { host?: string, port?: number } = {}\n const host = env?.HOST\n if (host)\n override.host = host\n const rawPort = env?.PORT\n if (rawPort) {\n const port = Number(rawPort)\n if (Number.isInteger(port) && port >= 1 && port <= 65535)\n override.port = port\n }\n return override\n}\n\n/**\n * 解析服务器监听配置并补齐默认值。\n *\n * 优先级:`HOST` / `PORT` 环境变量 > `config/_kit.yml` 的 `server` > 默认 `127.0.0.1:3000`。\n *\n * @param input - `config/_kit.yml` 中的 server 配置\n * @returns 完整 server 配置\n * @example 模块内部上下文\n * ```ts\n * const config = resolveKitServerConfig({ port: 4000 })\n * // HOST / PORT 环境变量优先于输入。\n * void config.port\n * ```\n */\nexport function resolveKitServerConfig(input: KitServerConfigInput = {}): KitServerConfig {\n return { ...ServerConfigInputSchema.parse(input), ...readServerEnvOverride() }\n}\n\n/**\n * 解析 transport 配置并补齐默认值。\n *\n * @param input - `config/_kit.yml` 中的 transport 配置\n * @returns 完整 transport 配置;`false` 表示关闭\n * @example\n * ```ts\n * import { resolveKitTransportConfig } from '@h-ai/kit'\n *\n * const transport = resolveKitTransportConfig({ excludePaths: ['/api/health'] })\n * if (transport) void transport.keyExchangePath\n * ```\n */\nexport function resolveKitTransportConfig(\n input: false | KitTransportConfigInput = false,\n): false | KitTransportConfig {\n return KitTransportConfigInputSchema.parse(input)\n}\n\n/**\n * 解析 `config/_kit.yml` 顶层配置并补齐默认值。\n *\n * 适合同一份配置同时供 SvelteKit 服务端 hook 与浏览器端 client 读取。\n * @param input - Kit 监听和公开 transport 输入;省略字段按 Schema 补默认值。\n * @returns 已补齐默认值的配置;不合法输入抛出 ZodError。\n * @example\n * ```ts\n * import { resolveKitConfig } from '@h-ai/kit'\n *\n * const config = resolveKitConfig({ transport: false })\n * void config.server.port\n * ```\n */\nexport function resolveKitConfig(input: KitConfigInput = {}): KitConfig {\n return KitConfigSchema.parse(input)\n}\n"]}
@@ -1,9 +1,9 @@
1
- export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, a as createKitAuthActions, c as createKitClient, i as installBrowserTransport, b as installBrowserTransportFetch } from '../kit-client-auth-BnhrkzZF.js';
1
+ export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, a as createKitAuthActions, c as createKitClient, i as installBrowserTransport, b as installBrowserTransportFetch } from '../kit-client-auth-DeWmc-1u.js';
2
2
  import '@h-ai/api-contract';
3
3
  import '@h-ai/core';
4
4
  import '@h-ai/crypto';
5
5
  import '@h-ai/iam';
6
- import '../kit-types-BkNdyKj8.js';
6
+ import '../kit-types-CnfBC89D.js';
7
7
  import '@sveltejs/kit';
8
8
  import 'zod';
9
9
 
@@ -1,4 +1,4 @@
1
- export { createKitAuthActions, createKitClient, installBrowserTransport, installBrowserTransportFetch } from '../chunk-YKJRQCWR.js';
1
+ export { createKitAuthActions, createKitClient, installBrowserTransport, installBrowserTransportFetch } from '../chunk-5JW3VW4Q.js';
2
2
  import '../chunk-JRQPMPQC.js';
3
3
  import '../chunk-USFJN7IO.js';
4
4
  import { invalidateAll, goto } from '$app/navigation';
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, l as login, f as loginWithOtp, g as loginWithLdap, h as loginWithApiKey, r as registerAndLogin, j as logout, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-BnhrkzZF.js';
2
- export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig } from './kit-client-auth-BnhrkzZF.js';
3
- import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-EBzZKYN-.js';
4
- export { C as CrudApiDef, b as CrudFieldDef, c as CrudFieldOption, e as CrudFieldOptions, f as CrudFieldType, g as CrudFieldValidation, h as CrudFilterParams, i as CrudOperations, j as CrudPaginatedResult, k as CrudPaginationParams, l as CrudResourceDef, m as CrudTableColumn, n as CrudText, K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig, o as resolveOptions, p as resolveText } from './kit-i18n-EBzZKYN-.js';
5
- import { S as SessionData, r as SessionLike, h as HandleConfig, s as rateLimitMiddleware, t as createCacheRateLimitStore, u as createMemoryRateLimitStore } from './kit-types-BkNdyKj8.js';
6
- export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, i as HookCryptoConfig, K as KitConfig, j as KitConfigInput, k as KitPublicConfig, l as KitServerConfig, m as KitServerConfigInput, n as KitTransportConfig, o as KitTransportConfigInput, v as MemoryRateLimitStore, M as Middleware, p as MiddlewareContext, w as RateLimitCache, R as RateLimitConfig, x as RateLimitEntry, y as RateLimitStore, q as RouteGuard, T as TransportEncryptionOptions } from './kit-types-BkNdyKj8.js';
1
+ import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, l as login, f as loginWithOtp, g as loginWithLdap, h as loginWithApiKey, r as registerAndLogin, j as logout, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-DeWmc-1u.js';
2
+ export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig } from './kit-client-auth-DeWmc-1u.js';
3
+ import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-CjwdGeKY.js';
4
+ export { C as CrudApiDef, b as CrudFieldDef, c as CrudFieldOption, e as CrudFieldOptions, f as CrudFieldType, g as CrudFieldValidation, h as CrudFilterParams, i as CrudOperations, j as CrudPaginatedResult, k as CrudPaginationParams, l as CrudResourceDef, m as CrudTableColumn, n as CrudText, K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig, o as resolveOptions, p as resolveText } from './kit-i18n-CjwdGeKY.js';
5
+ import { S as SessionData, r as SessionLike, h as HandleConfig, s as rateLimitMiddleware, t as createCacheRateLimitStore, u as createMemoryRateLimitStore } from './kit-types-CnfBC89D.js';
6
+ export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, i as HookCryptoConfig, K as KitConfig, j as KitConfigInput, k as KitPublicConfig, l as KitServerConfig, m as KitServerConfigInput, n as KitTransportConfig, o as KitTransportConfigInput, v as MemoryRateLimitStore, M as Middleware, p as MiddlewareContext, w as RateLimitCache, R as RateLimitConfig, x as RateLimitEntry, y as RateLimitStore, q as RouteGuard, T as TransportEncryptionOptions } from './kit-types-CnfBC89D.js';
7
7
  import { CryptoFunctions } from '@h-ai/crypto';
8
8
  export { EncryptedPayload, TransportEncryptionManager, TransportKeyPair } from '@h-ai/crypto';
9
9
  import { FeedbackFunctions, FeedbackContext } from '@h-ai/feedback';
@@ -15,8 +15,8 @@ import * as zod_v4_core from 'zod/v4/core';
15
15
  import * as zod from 'zod';
16
16
  import { z } from 'zod';
17
17
  import { IamFunctions } from '@h-ai/iam';
18
- export { A as A2AApiKeyAuthConfig, K as KitA2AHandlerConfig, R as ResolvedA2AConfig } from './kit-a2a-types-4dapztu2.js';
19
- export { C as CryptoCsrfConfig, a as CryptoServiceLike, E as EncryptedCookieConfig, T as TransportEncryptionConfig, W as WebhookVerifyConfig } from './kit-crypto-types-CXr888wf.js';
18
+ export { A as A2AApiKeyAuthConfig, K as KitA2AHandlerConfig, R as ResolvedA2AConfig } from './kit-a2a-types-ikgvWS3T.js';
19
+ export { C as CryptoCsrfConfig, a as CryptoServiceLike, E as EncryptedCookieConfig, T as TransportEncryptionConfig, W as WebhookVerifyConfig } from './kit-crypto-types-Brd0l8Hv.js';
20
20
  import '@h-ai/api-contract';
21
21
 
22
22
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { defineCrud } from './chunk-5VWPH7DO.js';
2
- export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-5VWPH7DO.js';
3
- import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, getAccessToken, configureAuth } from './chunk-YKJRQCWR.js';
1
+ import { defineCrud } from './chunk-WCMEGLBL.js';
2
+ export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-WCMEGLBL.js';
3
+ import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, getAccessToken, configureAuth } from './chunk-5JW3VW4Q.js';
4
4
  import { resolveA2AConfig, handleA2ARequest } from './chunk-7EH47MTW.js';
5
- import { transportEncryptionMiddleware } from './chunk-3OHUSKHI.js';
5
+ import { transportEncryptionMiddleware } from './chunk-6KS67QUS.js';
6
6
  import './chunk-JRQPMPQC.js';
7
7
  import { kitM, setAllModulesLocale } from './chunk-USFJN7IO.js';
8
8
  import { core, HaiCommonError, err } from '@h-ai/core';
@@ -708,6 +708,8 @@ function buildTransportMiddleware(cryptoConfig, encryption) {
708
708
  excludePaths: transportOpts.excludePaths,
709
709
  keyStore: transportOpts.keyStore,
710
710
  maxPlaintextBytes: transportOpts.maxPlaintextBytes,
711
+ maxFutureSkewSeconds: transportOpts.maxFutureSkewSeconds,
712
+ maxRequestAgeSeconds: transportOpts.maxRequestAgeSeconds,
711
713
  routePlaintextBytes: transportOpts.routePlaintextBytes
712
714
  }, encryption);
713
715
  }