@h-ai/kit 0.1.0-alpha.71 → 0.1.0-alpha.73
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 +13 -1
- package/README.ja.md +13 -1
- package/README.md +13 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +1 -1
- package/dist/{chunk-YX2IFLPE.js → chunk-3L5QSJ3E.js} +48 -21
- package/dist/chunk-3L5QSJ3E.js.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/index.d.ts +37 -6
- package/dist/index.js +175 -101
- package/dist/index.js.map +1 -1
- package/dist/{kit-client-auth-B5y1g5_A.d.ts → kit-client-auth-CSB3wDSI.d.ts} +62 -9
- package/package.json +10 -8
- package/dist/chunk-YX2IFLPE.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -294,7 +294,7 @@ Export standard handlers from kit.auth.createApi(iam), use kit.auth.publicPaths
|
|
|
294
294
|
### Error handling
|
|
295
295
|
|
|
296
296
|
- Wrap API endpoints with `kit.handler()`.
|
|
297
|
-
- `kit.validate.body` / `kit.guard.require` throw `Response`
|
|
297
|
+
- `kit.validate.body` / `kit.guard.require` throw `Response`, handled by `kit.handler` as API control flow. Page `load` uses `kit.guard.check` with SvelteKit `error`; parse query schemas with `safeParse` and raise native page errors instead of throwing API responses.
|
|
298
298
|
- `kit.handler()` converts non-control-flow exceptions into `500 INTERNAL_ERROR` responses.
|
|
299
299
|
|
|
300
300
|
### CRUD fields
|
|
@@ -346,6 +346,18 @@ HTTP errors expose localized safe messages followed by `(traceId: …)`, with th
|
|
|
346
346
|
|
|
347
347
|
Use `core.error.toPublic(error, traceId?)` at HTTP/UI boundaries and `core.error.formatMessage(error)` after translating messages; formatting is idempotent. Pass complete Error objects to logs instead of discarding their context. Database failures log driver codes, sanitized messages/stacks and table/column context; SQL text, bound values and connection credentials are excluded.
|
|
348
348
|
|
|
349
|
+
When audit is initialized, createHandle binds verified session identity/roles, the platform getClientAddress result and request ID for domain audit.log calls. Automatic request.execute events contain the route template, method, status and success/failure/denial, including guard redirects and SvelteKit HTTP control flow. Bodies and error details are not copied. capture.enabled controls automatic capture; capture.includeReads enables successful reads. Failed reads remain recorded by default. Applications still explicitly record domain completion/failure events; disabling automatic capture does not disable domain logs.
|
|
350
|
+
|
|
351
|
+
## Optional interactive challenges
|
|
352
|
+
|
|
353
|
+
Export kit.auth.createApi(iam).createCaptcha as POST /api/auth/captcha; kit.auth.publicPaths includes it. kit.client.createAuthActions preserves captcha and supplies createCaptcha for automatic AuthPanel solving. Export /api/auth/password/renew for initial/expired password changes. IAM enforces proofs server-side.
|
|
354
|
+
|
|
355
|
+
See policy, defaults and recovery boundaries in [IAM](../iam/README.en.md).
|
|
356
|
+
|
|
357
|
+
## Page exit session policy
|
|
358
|
+
|
|
359
|
+
`session.endOnPageHide` defaults to `false`. Hosts call `actions.bindPageExit({ enabled: config.session.endOnPageHide })` and invoke its returned cleanup on unmount; standard admin/H5/fullstack templates already bind it. A non-BFCache `pagehide` (including reload and full document navigation) makes a best effort POST to `/auth/session/close`, revoking the held access and related refresh through the existing CSRF/encrypted transport with `keepalive` and no automatic refresh. This response does not clear cookies/local credentials, avoiding a late old response overwriting a new login. Explicit logout still revokes and clears current credentials normally. Browsers may omit page exit requests; configure server `idleTimeout`/`absoluteTimeout` as appropriate. Immediate revocation on browser close is not guaranteed.
|
|
360
|
+
|
|
349
361
|
## Development and testing
|
|
350
362
|
|
|
351
363
|
```bash
|
package/README.ja.md
CHANGED
|
@@ -300,7 +300,7 @@ kit.auth.createApi(iam) の標準 handler と kit.auth.publicPaths を使用し
|
|
|
300
300
|
### エラー処理
|
|
301
301
|
|
|
302
302
|
- API endpoint は `kit.handler()` でラップすることを推奨。
|
|
303
|
-
- `kit.validate.body` / `kit.guard.require` は SvelteKit
|
|
303
|
+
- `kit.validate.body` / `kit.guard.require` の `Response` は `kit.handler` が API 制御フローとして処理します。ページ `load` では `kit.guard.check` と SvelteKit `error` を使い、検索条件は `safeParse` で検証してネイティブなページエラーに変換します。API Response をそのまま throw しません。
|
|
304
304
|
- 制御フロー以外の例外は `kit.handler()` が `500 INTERNAL_ERROR` 応答に変換。
|
|
305
305
|
|
|
306
306
|
### CRUD fields
|
|
@@ -344,6 +344,18 @@ HTTP エラーはローカライズ済みの安全な表示に `(traceId: …)`
|
|
|
344
344
|
|
|
345
345
|
HTTP/UI 境界には core.error.toPublic(error, traceId?)、再翻訳後の表示には core.error.formatMessage(error) を使い、ID を重複追加しません。ログには完全な Error を渡してください。DB ログにはドライバーコード、脱機密化したメッセージ・スタック、表・列を残し、SQL 本文、バインド値と接続認証情報を記録しません。
|
|
346
346
|
|
|
347
|
+
audit の初期化後、createHandle は検証済みセッションの身元/役割、プラットフォーム getClientAddress と要求 ID を業務 audit.log に関連付けます。request.execute はルート雛形・メソッド・状態・成功/失敗/拒否を記録し、守衛リダイレクトと SvelteKit HTTP 制御フローも含みます。要求本文・内部エラーは複製しません。capture.enabled で自動記録、capture.includeReads で成功読取を制御し、失敗した読取は既定で記録します。業務完了/失敗はアプリが明示的に記録し、自動記録を無効にしても業務 log は維持します。
|
|
348
|
+
|
|
349
|
+
## 任意の対話チャレンジ
|
|
350
|
+
|
|
351
|
+
kit.auth.createApi(iam).createCaptcha を POST /api/auth/captcha として公開します。kit.auth.publicPaths は対応済みです。kit.client.createAuthActions は captcha を保持し createCaptcha を提供し、AuthPanel が自動求解します。初回/期限切れ変更には /api/auth/password/renew も公開します。IAM がサーバーで証明を強制検証します。
|
|
352
|
+
|
|
353
|
+
方針・既定値・復旧範囲は次を参照: [IAM](../iam/README.ja.md)。
|
|
354
|
+
|
|
355
|
+
## ページ離脱時のセッション方針
|
|
356
|
+
|
|
357
|
+
`session.endOnPageHide` の既定値は `false` です。ホストは `actions.bindPageExit({ enabled: config.session.endOnPageHide })` を呼び、破棄時に戻り値の解除関数を実行します。標準 admin/H5/fullstack テンプレートは接続済みです。BFCache 以外の `pagehide`(再読み込み、文書全体の移動を含む)で POST `/auth/session/close` を最善努力で送信し、保持する access と関連 refresh を失効します。既存の CSRF/暗号化転送を使用し、`keepalive` を付け、自動更新しません。古い応答が新しいログインを上書きしないよう Cookie/ローカル資格情報を消去しません。明示的ログアウトは通常どおり失効と消去を行います。ブラウザーが離脱要求を送らない場合もあるため、業務に応じてサーバーの `idleTimeout`/`absoluteTimeout` を併用してください。閉じた瞬間の失効は保証できません。
|
|
358
|
+
|
|
347
359
|
## 開発とテスト
|
|
348
360
|
|
|
349
361
|
```bash
|
package/README.md
CHANGED
|
@@ -300,7 +300,7 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
300
300
|
### 错误处理
|
|
301
301
|
|
|
302
302
|
- 推荐使用 `kit.handler()` 包装 API 端点。
|
|
303
|
-
- `kit.validate.body` / `kit.guard.require`
|
|
303
|
+
- `kit.validate.body` / `kit.guard.require` 抛出 `Response`,由 `kit.handler` 处理 API 控制流。页面 `load` 使用 `kit.guard.check` 和 SvelteKit `error`;校验查询参数用 `schema.safeParse` 后返回原生页面错误,不能直接抛 API Response。
|
|
304
304
|
- 非控制流异常会由 `kit.handler()` 统一转换为 `500 INTERNAL_ERROR` 响应。
|
|
305
305
|
|
|
306
306
|
### CRUD fields
|
|
@@ -344,6 +344,18 @@ HTTP 错误只向用户返回本地化的安全提示,末尾显示 `(traceId:
|
|
|
344
344
|
|
|
345
345
|
`core.error.toPublic(error, traceId?)` 用于接口/UI 边界;`core.error.formatMessage(error)` 用于重新翻译后的提示,重复调用不会重复追加标识。日志传入完整 Error 对象,不能只记录通用标题或提前 String(error) 丢失上下文。数据库失败记录驱动错误码、消息、堆栈及表/列;SQL 原文、绑定值和连接凭据不进入日志。
|
|
346
346
|
|
|
347
|
+
audit 已初始化时,createHandle 为业务 audit.log 绑定已验证会话的身份/角色快照、平台 getClientAddress 地址和请求编号。自动入口事件 request.execute 包含路由模板、方法、状态和成功/失败/拒绝,包含守卫跳转及 SvelteKit HTTP 控制流;不复制请求体或错误详情。capture.enabled 可关闭自动采集,capture.includeReads 可开启成功读取;默认保留失败读取、跳过成功读取。业务含义仍在应用完成/失败边界显式记录,关闭自动采集不影响业务 log。
|
|
348
|
+
|
|
349
|
+
## 可选交互挑战
|
|
350
|
+
|
|
351
|
+
将 kit.auth.createApi(iam).createCaptcha 导出为 POST /api/auth/captcha;kit.auth.publicPaths 已包含它。kit.client.createAuthActions() 保留标准交互请求的 captcha 字段,并提供 createCaptcha;AuthPanel 提交时自动求解。匿名首次/过期改密仍须导出 /api/auth/password/renew。服务端 IAM 检查证明,开启策略不会只依赖界面。
|
|
352
|
+
|
|
353
|
+
策略、默认值和恢复边界见 [IAM](../iam/README.md)。
|
|
354
|
+
|
|
355
|
+
## 页面离开会话策略
|
|
356
|
+
|
|
357
|
+
`session.endOnPageHide` 默认 `false`。启用后宿主调用 `actions.bindPageExit({ enabled: config.session.endOnPageHide })` 并在卸载时调用返回的清理函数;标准 admin/H5/fullstack 模板已经接入。非 BFCache 的 `pagehide`(也包括刷新、整页导航)通过 POST `/auth/session/close` 尽力撤销当前 access 及关联 refresh,沿用 CSRF/传输加密,使用 `keepalive` 且不自动刷新会话。该响应不清 Cookie/本地凭据,避免旧响应覆盖下一次登录。显式退出仍使用正常 logout,可靠撤销并清理当前凭据。浏览器可能不发送页面离开请求,因此必须按业务配合服务端 `idleTimeout`/`absoluteTimeout`;不能据此保证关闭即撤销。
|
|
358
|
+
|
|
347
359
|
## 开发测试
|
|
348
360
|
|
|
349
361
|
```bash
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-
|
|
2
|
-
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-
|
|
1
|
+
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-CSB3wDSI.js';
|
|
2
|
+
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-CSB3wDSI.js';
|
|
3
3
|
import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-DEzmzE5T.js';
|
|
4
4
|
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, M as Middleware, j as MiddlewareContext, R as RateLimitConfig, k as RouteGuard, S as SessionData, l as SessionLike, T as TransportEncryptionOptions } from './kit-types-B6rjOQWx.js';
|
|
5
5
|
import '@h-ai/api-contract';
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCrud } from './chunk-Y2YUA3VR.js';
|
|
2
2
|
export { KitConfigSchema, resolveKitConfig, resolveKitTransportConfig } from './chunk-Y2YUA3VR.js';
|
|
3
|
-
import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-
|
|
3
|
+
import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-3L5QSJ3E.js';
|
|
4
4
|
import './chunk-XC7I2L3G.js';
|
|
5
5
|
import { setAllModulesLocale } from './chunk-OVRZIDCD.js';
|
|
6
6
|
|
|
@@ -3,7 +3,7 @@ import { kitM } from './chunk-OVRZIDCD.js';
|
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import { ok, core, err } from '@h-ai/core';
|
|
5
5
|
import { TRANSPORT_PROTOCOL } from '@h-ai/crypto';
|
|
6
|
-
import { apiContract, IamPublicConfigSchema } from '@h-ai/api-contract';
|
|
6
|
+
import { apiContract, IamReauthenticationProofSchema, IamPublicConfigSchema, IamCaptchaChallengeSchema } from '@h-ai/api-contract';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
|
|
9
9
|
var DEFAULT_TOKEN_COOKIE_NAME = "hai_access_token";
|
|
@@ -82,20 +82,20 @@ async function runAuthLogin(cookies, credentials, run) {
|
|
|
82
82
|
}
|
|
83
83
|
return result;
|
|
84
84
|
}
|
|
85
|
-
async function login(cookies, credentials) {
|
|
86
|
-
return runAuthLogin(cookies, credentials, (ops, input) => ops.login(input));
|
|
85
|
+
async function login(cookies, credentials, context) {
|
|
86
|
+
return runAuthLogin(cookies, credentials, (ops, input) => ops.login(input, context));
|
|
87
87
|
}
|
|
88
|
-
async function loginWithOtp(cookies, credentials) {
|
|
89
|
-
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithOtp(input));
|
|
88
|
+
async function loginWithOtp(cookies, credentials, context) {
|
|
89
|
+
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithOtp(input, context));
|
|
90
90
|
}
|
|
91
|
-
async function loginWithLdap(cookies, credentials) {
|
|
92
|
-
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithLdap(input));
|
|
91
|
+
async function loginWithLdap(cookies, credentials, context) {
|
|
92
|
+
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithLdap(input, context));
|
|
93
93
|
}
|
|
94
|
-
async function loginWithApiKey(cookies, credentials) {
|
|
95
|
-
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithApiKey(input));
|
|
94
|
+
async function loginWithApiKey(cookies, credentials, context) {
|
|
95
|
+
return runAuthLogin(cookies, credentials, (ops, input) => ops.loginWithApiKey(input, context));
|
|
96
96
|
}
|
|
97
|
-
async function registerAndLogin(cookies, options) {
|
|
98
|
-
return runAuthLogin(cookies, options, (ops, input) => ops.registerAndLogin(input));
|
|
97
|
+
async function registerAndLogin(cookies, options, context) {
|
|
98
|
+
return runAuthLogin(cookies, options, (ops, input) => ops.registerAndLogin(input, context));
|
|
99
99
|
}
|
|
100
100
|
async function logout(cookies, accessToken) {
|
|
101
101
|
try {
|
|
@@ -272,30 +272,57 @@ function createKitClient(config = {}) {
|
|
|
272
272
|
function createKitAuthActions(options = {}) {
|
|
273
273
|
const apiFetch = options.apiFetch ?? createKitClient().apiFetch;
|
|
274
274
|
const base = options.basePath ?? "/api/auth";
|
|
275
|
-
async function post(path, input) {
|
|
276
|
-
const response = await apiFetch(`${base}/${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) });
|
|
275
|
+
async function post(path, input, init = {}) {
|
|
276
|
+
const response = await apiFetch(`${base}/${path}`, { ...init, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) });
|
|
277
277
|
const result = apiContract.haiResultSchema(z.unknown().optional()).parse(await response.json());
|
|
278
278
|
return result.success ? ok(result.data) : result;
|
|
279
279
|
}
|
|
280
280
|
return {
|
|
281
|
+
bindPageExit(options2 = {}) {
|
|
282
|
+
if (!options2.enabled || typeof window === "undefined")
|
|
283
|
+
return () => {
|
|
284
|
+
};
|
|
285
|
+
const target = window;
|
|
286
|
+
let sent = false;
|
|
287
|
+
const onPageHide = (event) => {
|
|
288
|
+
if (event.persisted || sent)
|
|
289
|
+
return;
|
|
290
|
+
sent = true;
|
|
291
|
+
void post("session/close", {}, { keepalive: true }).then((result) => {
|
|
292
|
+
if (!result.success)
|
|
293
|
+
core.logger.warn("Page exit session revocation failed", { errorCode: result.error.code });
|
|
294
|
+
}).catch(() => core.logger.warn("Page exit session request did not complete"));
|
|
295
|
+
};
|
|
296
|
+
target.addEventListener("pagehide", onPageHide);
|
|
297
|
+
return () => target.removeEventListener("pagehide", onPageHide);
|
|
298
|
+
},
|
|
299
|
+
async createCaptcha(input) {
|
|
300
|
+
const result = await post("captcha", input);
|
|
301
|
+
return result.success ? ok(IamCaptchaChallengeSchema.parse(result.data)) : result;
|
|
302
|
+
},
|
|
281
303
|
async publicConfig() {
|
|
282
304
|
const response = await apiFetch(`${base}/config`);
|
|
283
305
|
return apiContract.haiResultSchema(IamPublicConfigSchema.nullable()).parse(await response.json());
|
|
284
306
|
},
|
|
285
307
|
login(input) {
|
|
286
308
|
if (input.method === "ldap")
|
|
287
|
-
return post("login/ldap", { username: input.identifier, password: input.password });
|
|
309
|
+
return post("login/ldap", { username: input.identifier, password: input.password, captcha: input.captcha });
|
|
288
310
|
if (input.method === "email" || input.method === "sms")
|
|
289
|
-
return post("login/otp", { identifier: input.identifier, code: input.code });
|
|
290
|
-
return post("login", { identifier: input.identifier, password: input.password });
|
|
311
|
+
return post("login/otp", { identifier: input.identifier, code: input.code, captcha: input.captcha });
|
|
312
|
+
return post("login", { identifier: input.identifier, password: input.password, captcha: input.captcha });
|
|
291
313
|
},
|
|
292
314
|
register: (input) => post("register", { ...input, confirmPassword: input.password }),
|
|
293
|
-
sendOtp: (identifier) => post("otp/send", { identifier }),
|
|
294
|
-
requestPasswordReset: (identifier) => post("password-reset/request", { identifier }),
|
|
295
|
-
confirmPasswordReset: (token, newPassword) => post("password-reset/confirm", { token, newPassword })
|
|
315
|
+
sendOtp: (identifier, options2) => post("otp/send", { identifier, ...options2 }),
|
|
316
|
+
requestPasswordReset: (identifier, options2) => post("password-reset/request", { identifier, ...options2 }),
|
|
317
|
+
confirmPasswordReset: (token, newPassword) => post("password-reset/confirm", { token, newPassword }),
|
|
318
|
+
renewPassword: (input) => post("password/renew", input),
|
|
319
|
+
async reauthenticate(password, operation) {
|
|
320
|
+
const response = await apiFetch(`${base}/reauthenticate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password, operation }) });
|
|
321
|
+
return apiContract.haiResultSchema(IamReauthenticationProofSchema).parse(await response.json());
|
|
322
|
+
}
|
|
296
323
|
};
|
|
297
324
|
}
|
|
298
325
|
|
|
299
326
|
export { clearBrowserToken, configureAuth, createKitAuthActions, createKitClient, getAccessToken, installBrowserTransport, installBrowserTransportFetch, login, loginWithApiKey, loginWithLdap, loginWithOtp, logout, registerAndLogin, setBrowserToken };
|
|
300
|
-
//# sourceMappingURL=chunk-
|
|
301
|
-
//# sourceMappingURL=chunk-
|
|
327
|
+
//# sourceMappingURL=chunk-3L5QSJ3E.js.map
|
|
328
|
+
//# sourceMappingURL=chunk-3L5QSJ3E.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":["ok","options","core"],"mappings":"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","file":"chunk-3L5QSJ3E.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 process from 'node:process'\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 cookies.set(authState.cookieName, token, {\n path: '/',\n httpOnly: true,\n sameSite: 'lax',\n // 测试开关仅放宽共享状态要求,不降低生产凭证 Cookie 的传输安全。\n secure: process.env.NODE_ENV === 'production' || process.env.HAI_CORE_ENV === 'production',\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 { KitConfig } from '../kit-config.js'\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, 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 /** 可选本地明文上限;缺省跟随服务端握手。 */\n maxPlaintextBytes?: number\n /** @h-ai/crypto 服务实例 */\n crypto: CryptoFunctions\n /** 密钥交换端点 URL(默认 `'/api/_hai/key-exchange'`) */\n keyExchangeUrl?: string\n /** 排除路径(命中后强制走明文),需与服务端 transport.excludePaths 保持一致 */\n excludePaths?: 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 * 传输加密配置。\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) => 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 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 })) {\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 KitConfig} 一次性安装浏览器端传输加密。\n *\n * 行为:\n * - `config.transport === false` 时直接返回(不启用传输加密);\n * - 否则启动 {@link installBrowserTransportFetch},并预热 {@link CryptoFunctions} 模块。\n *\n * 应用层(SvelteKit `+layout.svelte` 或浏览器入口)只需调用此函数一次,\n * 之后所有同源 `/api/*` 与 `__data.json` 请求都会自动走传输加密。\n *\n * @param config 已解析的 kit 顶层配置\n * @param deps 外部依赖\n * @param deps.crypto `@h-ai/crypto` 模块实例\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({ server: { host: '127.0.0.1', port: 3000 }, transport: { keyExchangePath: '/api/_hai/key-exchange', excludePaths: ['/api/health'] } }, { crypto })\n * }\n * ```\n */\nexport function installBrowserTransport(\n config: KitConfig,\n deps: { crypto: CryptoFunctions },\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 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 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 apiFetch(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 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 })\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 apiFetch,\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, ok } from '@h-ai/core'\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 post(path: string, input: object, init: Pick<RequestInit, 'keepalive'> = {}): Promise<HaiResult<unknown>> {\n const response = await apiFetch(`${base}/${path}`, { ...init, method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) })\n // void 成功响应在 JSON 中省略 data,统一还原为 HaiResult 的 data: undefined。\n const result = apiContract.haiResultSchema(z.unknown().optional()).parse(await response.json())\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 async publicConfig(): Promise<HaiResult<IamPublicConfig | null>> {\n const response = await apiFetch(`${base}/config`)\n return apiContract.haiResultSchema(IamPublicConfigSchema.nullable()).parse(await response.json())\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 })\n if (input.method === 'email' || input.method === 'sms')\n return post('login/otp', { identifier: input.identifier, code: input.code, captcha: input.captcha })\n return post('login', { identifier: input.identifier, password: input.password, captcha: input.captcha })\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 async reauthenticate(password: string, operation: string) {\n const response = await apiFetch(`${base}/reauthenticate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password, operation }) })\n return apiContract.haiResultSchema(IamReauthenticationProofSchema).parse(await response.json())\n },\n }\n}\n"]}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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-CSB3wDSI.js';
|
|
2
2
|
import '@h-ai/api-contract';
|
|
3
3
|
import '@h-ai/core';
|
|
4
4
|
import '@h-ai/crypto';
|
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createKitAuthActions, createKitClient, installBrowserTransport, installBrowserTransportFetch } from '../chunk-
|
|
1
|
+
export { createKitAuthActions, createKitClient, installBrowserTransport, installBrowserTransportFetch } from '../chunk-3L5QSJ3E.js';
|
|
2
2
|
import '../chunk-XC7I2L3G.js';
|
|
3
3
|
import '../chunk-OVRZIDCD.js';
|
|
4
4
|
import { invalidateAll, goto } from '$app/navigation';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, o as login, p as loginWithOtp, q as loginWithLdap, t as loginWithApiKey, u as registerAndLogin, v as logout, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-
|
|
2
|
-
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-
|
|
1
|
+
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, o as login, p as loginWithOtp, q as loginWithLdap, t as loginWithApiKey, u as registerAndLogin, v as logout, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-CSB3wDSI.js';
|
|
2
|
+
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-CSB3wDSI.js';
|
|
3
3
|
import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-DEzmzE5T.js';
|
|
4
4
|
export { C as CrudApiDef, a as CrudFieldDef, b as CrudFieldOption, c as CrudFieldOptions, e as CrudFieldType, f as CrudFieldValidation, g as CrudFilterParams, h as CrudOperations, i as CrudPaginatedResult, j as CrudPaginationParams, k as CrudResourceDef, l as CrudTableColumn, m as CrudText, r as resolveOptions, n as resolveText } from './kit-i18n-DEzmzE5T.js';
|
|
5
5
|
import { S as SessionData, l as SessionLike, h as HandleConfig, r as rateLimitMiddleware, m as createCacheRateLimitStore, n as createMemoryRateLimitStore } from './kit-types-B6rjOQWx.js';
|
|
@@ -8,7 +8,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';
|
|
10
10
|
import * as _sveltejs_kit from '@sveltejs/kit';
|
|
11
|
-
import {
|
|
11
|
+
import { RequestEvent, Handle, RequestHandler } from '@sveltejs/kit';
|
|
12
12
|
import * as _h_ai_core from '@h-ai/core';
|
|
13
13
|
import { HaiError } from '@h-ai/core';
|
|
14
14
|
import * as zod_v4_core from 'zod/v4/core';
|
|
@@ -127,7 +127,7 @@ declare function hasPermission(session: SessionLike | null | undefined, permissi
|
|
|
127
127
|
/**
|
|
128
128
|
* 要求会话具有指定权限,不满足时 throw Response
|
|
129
129
|
*
|
|
130
|
-
*
|
|
130
|
+
* throw 的 Response 对象由 kit.handler 捕获并直接作为 API 响应返回。
|
|
131
131
|
* 搭配 `kit.handler()` 使用时,无需手动检查返回值。
|
|
132
132
|
*
|
|
133
133
|
* @param session - 当前会话数据,null/undefined 视为未认证
|
|
@@ -137,7 +137,7 @@ declare function hasPermission(session: SessionLike | null | undefined, permissi
|
|
|
137
137
|
* ```ts
|
|
138
138
|
* import { kit } from '@h-ai/kit'
|
|
139
139
|
*
|
|
140
|
-
* kit.guard.require({ userId: 'user_1', roles: ['editor'], permissions: ['article:read'] }, 'article:read') // 拒绝时抛出 Response
|
|
140
|
+
* kit.guard.require({ userId: 'user_1', roles: ['editor'], permissions: ['article:read'] }, 'article:read') // 拒绝时抛出 Response,由 kit.handler 传递。
|
|
141
141
|
* ```
|
|
142
142
|
*/
|
|
143
143
|
declare function requirePermission(session: SessionLike | null | undefined, permission: string): void;
|
|
@@ -170,6 +170,31 @@ interface RoleGuardConfig {
|
|
|
170
170
|
apiMode?: boolean;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/** 可选敏感操作再鉴别;由操作入口在现有权限/资源检查之后调用。 */
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 要求选定操作携带短期单次再鉴别票据;失败抛出 Response,由 kit.handler 返回。
|
|
177
|
+
* 不替代已有认证、权限与资源范围守卫;操作 ID 必须由服务端指定。
|
|
178
|
+
* @param iam - 已初始化的 IAM 服务。
|
|
179
|
+
* @param event - 当前请求与 Cookie,不能跨请求保存。
|
|
180
|
+
* @param operation - 当前服务端操作 ID。
|
|
181
|
+
* @returns 策略关闭、未选操作或证明校验成功时无返回值。
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { iam } from '@h-ai/iam'
|
|
185
|
+
* import type { SessionLike } from '@h-ai/kit'
|
|
186
|
+
* import type { RequestEvent } from '@sveltejs/kit'
|
|
187
|
+
* import { kit } from '@h-ai/kit'
|
|
188
|
+
* // session 由应用在当前请求完成鉴别后传入,不在跨请求闭包中保存。
|
|
189
|
+
* async function remove(event: Pick<RequestEvent, 'request' | 'cookies'>, session: SessionLike) {
|
|
190
|
+
* kit.guard.require(session, 'user:delete')
|
|
191
|
+
* await kit.guard.requireSensitive(iam, event, 'iam.users.delete')
|
|
192
|
+
* return kit.response.ok({ allowed: true })
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
declare function requireSensitiveOperation(iam: IamFunctions, event: Pick<RequestEvent, 'request' | 'cookies'>, operation: string): Promise<void>;
|
|
197
|
+
|
|
173
198
|
/**
|
|
174
199
|
* @h-ai/kit — Cookie 加密代理
|
|
175
200
|
*
|
|
@@ -290,7 +315,11 @@ declare function createFeedbackHandler(options: FeedbackHandlerOptions): Request
|
|
|
290
315
|
* ```
|
|
291
316
|
*/
|
|
292
317
|
declare function createAuthApi(iam: IamFunctions): {
|
|
318
|
+
closeSession: _sveltejs_kit.RequestHandler;
|
|
319
|
+
createCaptcha: _sveltejs_kit.RequestHandler;
|
|
320
|
+
reauthenticate: _sveltejs_kit.RequestHandler;
|
|
293
321
|
publicConfig: _sveltejs_kit.RequestHandler;
|
|
322
|
+
renewPassword: _sveltejs_kit.RequestHandler;
|
|
294
323
|
login: _sveltejs_kit.RequestHandler;
|
|
295
324
|
loginWithLdap: _sveltejs_kit.RequestHandler;
|
|
296
325
|
loginWithOtp: _sveltejs_kit.RequestHandler;
|
|
@@ -578,7 +607,7 @@ declare function fromError(haiError: HaiError, requestId?: string): Response;
|
|
|
578
607
|
* `core.zodValidation.createPrefixedZodMessageGetter()` 处理。
|
|
579
608
|
* 每种数据源都提供两类 API:
|
|
580
609
|
* - 安全返回:`FormValidationResult`
|
|
581
|
-
* - 失败抛出:`OrFail`(抛出 `Response
|
|
610
|
+
* - 失败抛出:`OrFail`(抛出 `Response`,由 kit.handler 作为 API 控制流处理)
|
|
582
611
|
* @module kit-validation
|
|
583
612
|
*/
|
|
584
613
|
|
|
@@ -696,6 +725,8 @@ declare const kit: {
|
|
|
696
725
|
/** API Handler 包装器(自动错误边界) */
|
|
697
726
|
handler: typeof handler;
|
|
698
727
|
guard: {
|
|
728
|
+
/** 选定敏感操作要求单次再鉴别;先调用现有权限与资源守卫。 */
|
|
729
|
+
requireSensitive: typeof requireSensitiveOperation;
|
|
699
730
|
/** 要求权限,不满足时 throw Response(SvelteKit 控制流) */
|
|
700
731
|
require: typeof requirePermission;
|
|
701
732
|
/** 检查会话是否具有指定权限(布尔) */
|