@h-ai/kit 0.1.0-alpha9 → 1.0.0-beta.2

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.
Files changed (52) hide show
  1. package/README.en.md +430 -0
  2. package/README.ja.md +430 -0
  3. package/README.md +351 -40
  4. package/dist/adapter/index.d.ts +17 -2
  5. package/dist/adapter/index.js.map +1 -1
  6. package/dist/browser.d.ts +19 -8
  7. package/dist/browser.js +10 -7
  8. package/dist/browser.js.map +1 -1
  9. package/dist/chunk-3OHUSKHI.js +175 -0
  10. package/dist/chunk-3OHUSKHI.js.map +1 -0
  11. package/dist/chunk-5VWPH7DO.js +126 -0
  12. package/dist/chunk-5VWPH7DO.js.map +1 -0
  13. package/dist/chunk-7EH47MTW.js +141 -0
  14. package/dist/chunk-7EH47MTW.js.map +1 -0
  15. package/dist/chunk-JRQPMPQC.js +63 -0
  16. package/dist/chunk-JRQPMPQC.js.map +1 -0
  17. package/dist/chunk-USFJN7IO.js +187 -0
  18. package/dist/chunk-USFJN7IO.js.map +1 -0
  19. package/dist/chunk-YKJRQCWR.js +363 -0
  20. package/dist/chunk-YKJRQCWR.js.map +1 -0
  21. package/dist/client/index.d.ts +51 -2
  22. package/dist/client/index.js +21 -2
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/index.d.ts +303 -409
  25. package/dist/index.js +598 -310
  26. package/dist/index.js.map +1 -1
  27. package/dist/kit-a2a-types-4dapztu2.d.ts +136 -0
  28. package/dist/kit-client-auth-BnhrkzZF.d.ts +422 -0
  29. package/dist/kit-crypto-types-CXr888wf.d.ts +200 -0
  30. package/dist/kit-i18n-EBzZKYN-.d.ts +502 -0
  31. package/dist/kit-types-BkNdyKj8.d.ts +822 -0
  32. package/dist/modules/a2a/index.d.ts +15 -113
  33. package/dist/modules/a2a/index.js +2 -55
  34. package/dist/modules/a2a/index.js.map +1 -1
  35. package/dist/modules/crypto/index.d.ts +31 -79
  36. package/dist/modules/crypto/index.js +3 -3
  37. package/dist/modules/crypto/index.js.map +1 -1
  38. package/package.json +36 -22
  39. package/dist/chunk-475WECWK.js +0 -110
  40. package/dist/chunk-475WECWK.js.map +0 -1
  41. package/dist/chunk-6TMSXG7J.js +0 -169
  42. package/dist/chunk-6TMSXG7J.js.map +0 -1
  43. package/dist/chunk-AD6S473Y.js +0 -137
  44. package/dist/chunk-AD6S473Y.js.map +0 -1
  45. package/dist/chunk-FRYJMRTP.js +0 -71
  46. package/dist/chunk-FRYJMRTP.js.map +0 -1
  47. package/dist/chunk-PHS7ZD5L.js +0 -274
  48. package/dist/chunk-PHS7ZD5L.js.map +0 -1
  49. package/dist/index-BotXLTck.d.ts +0 -203
  50. package/dist/kit-crypto-types-C-gEvx04.d.ts +0 -253
  51. package/dist/kit-i18n-CgpNTh9G.d.ts +0 -206
  52. package/dist/kit-types-BHlDNNpd.d.ts +0 -618
package/dist/index.js CHANGED
@@ -1,10 +1,17 @@
1
- import { defineCrud } from './chunk-FRYJMRTP.js';
2
- export { defineCrud, resolveOptions, resolveText } from './chunk-FRYJMRTP.js';
3
- import { createHandleFetch, createTokenStore, clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, createKitClient, configureAuth, getAccessToken } from './chunk-PHS7ZD5L.js';
4
- import { resolveA2AConfig, handleA2ARequest } from './chunk-475WECWK.js';
5
- import { transportEncryptionMiddleware } from './chunk-AD6S473Y.js';
6
- import { kitM, setAllModulesLocale } from './chunk-6TMSXG7J.js';
7
- import { core } from '@h-ai/core';
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';
4
+ import { resolveA2AConfig, handleA2ARequest } from './chunk-7EH47MTW.js';
5
+ import { transportEncryptionMiddleware } from './chunk-3OHUSKHI.js';
6
+ import './chunk-JRQPMPQC.js';
7
+ import { kitM, setAllModulesLocale } from './chunk-USFJN7IO.js';
8
+ import { core, HaiCommonError, err } from '@h-ai/core';
9
+ import { audit } from '@h-ai/audit';
10
+ import { TRANSPORT_PROTOCOL } from '@h-ai/crypto';
11
+ import { createHash } from 'crypto';
12
+ import { isIP } from 'net';
13
+ import process2 from 'process';
14
+ import { IamConfirmPasswordResetInputSchema, IamRequestPasswordResetInputSchema, IamSendOtpInputSchema, IamRegisterInputSchema, IamOtpLoginInputSchema, IamLdapLoginInputSchema, IamLoginInputSchema, IamRenewPasswordInputSchema, IamReauthenticateInputSchema, IamCreateCaptchaInputSchema, IamLogoutInputSchema, FeedbackSubmitInputSchema, FeedbackListInputSchema, FeedbackIdInputSchema, FeedbackEvidenceQuerySchema, FeedbackAppendEvidenceInputSchema, FeedbackSetStatusInputSchema, FeedbackRetryInputSchema, FeedbackCompleteInputSchema, FeedbackExportInputSchema, FeedbackUploadInputSchema, FeedbackAttachmentQuerySchema, FeedbackExportQuerySchema } from '@h-ai/api-contract';
8
15
  import { z } from 'zod';
9
16
 
10
17
  // src/guards/kit-permission.ts
@@ -51,8 +58,6 @@ function requirePermission(session, permission) {
51
58
  throw denied;
52
59
  }
53
60
  }
54
-
55
- // src/kit-response.ts
56
61
  function ok(data, requestId) {
57
62
  const response = {
58
63
  success: true,
@@ -79,18 +84,21 @@ function noContent() {
79
84
  return new Response(null, { status: 204 });
80
85
  }
81
86
  function error(code, message, status = 400, requestId, details) {
87
+ const safe = core.error.toPublic({ code, message, httpStatus: status }, requestId);
88
+ core.logger.error("API request failed", { traceId: safe.traceId, error: { code, message, httpStatus: status, details } });
82
89
  const response = {
83
90
  success: false,
84
91
  error: {
85
92
  code,
86
- message,
87
- details
93
+ message: safe.message,
94
+ traceId: safe.traceId,
95
+ details: status < 500 ? core.sanitize.sanitizeSensitiveFields(details) : void 0
88
96
  },
89
- requestId
97
+ requestId: requestId ?? core.context.get().requestId ?? safe.traceId
90
98
  };
91
99
  return new Response(JSON.stringify(response), {
92
100
  status,
93
- headers: { "Content-Type": "application/json" }
101
+ headers: { "Content-Type": "application/json", "X-Trace-Id": safe.traceId }
94
102
  });
95
103
  }
96
104
  function badRequest(message, requestId, details) {
@@ -124,12 +132,23 @@ function fromResult(result, httpStatusMap, requestId) {
124
132
  if (result.success) {
125
133
  return ok(result.data, requestId);
126
134
  }
127
- const { code, message } = result.error;
128
- const status = httpStatusMap?.[code] ?? 400;
129
- return error(String(code), message, status, requestId);
135
+ const status = httpStatusMap?.[result.error.code] ?? (typeof result.error.httpStatus === "number" ? result.error.httpStatus : 400);
136
+ return fromError({ ...result.error, httpStatus: status }, requestId);
130
137
  }
131
138
  function fromError(haiError, requestId) {
132
- return error(String(haiError.code), haiError.message, haiError.httpStatus ?? 400, requestId);
139
+ const safe = core.error.toPublic(haiError, requestId);
140
+ core.logger.error("API operation failed", { traceId: safe.traceId, error: haiError });
141
+ return error(String(safe.code), safe.message, safe.httpStatus ?? 400, safe.traceId);
142
+ }
143
+
144
+ // src/guards/kit-sensitive.ts
145
+ async function requireSensitiveOperation(iam, event, operation) {
146
+ const policy = iam.publicConfig?.reauthentication;
147
+ if (!policy?.enabled)
148
+ return;
149
+ const checked = await iam.auth.checkSensitiveOperation(getAccessToken(event.request, event.cookies) ?? "", operation, event.request.headers.get("x-hai-reauthentication") ?? void 0);
150
+ if (!checked.success)
151
+ throw fromError(checked.error);
133
152
  }
134
153
 
135
154
  // src/kit-utils.ts
@@ -150,63 +169,6 @@ function isSvelteKitControlFlow(value) {
150
169
  }
151
170
  return false;
152
171
  }
153
-
154
- // src/kit-handler.ts
155
- function handler(fn) {
156
- return async (event) => {
157
- try {
158
- return await fn(event);
159
- } catch (error2) {
160
- if (isResponseLike(error2)) {
161
- return error2;
162
- }
163
- if (isSvelteKitControlFlow(error2)) {
164
- throw error2;
165
- }
166
- const normalizedError = error2 instanceof Error ? {
167
- name: error2.name,
168
- message: error2.message,
169
- stack: error2.stack,
170
- cause: error2.cause
171
- } : error2;
172
- core.logger.error("Request handler error", {
173
- error: normalizedError,
174
- path: event.url.pathname,
175
- method: event.request.method
176
- });
177
- return internalError();
178
- }
179
- };
180
- }
181
-
182
- // src/kit-contract.ts
183
- function defineEndpoint(def) {
184
- return def;
185
- }
186
- function fromContract(endpoint, fn) {
187
- return handler(async (event) => {
188
- let raw;
189
- if (endpoint.method === "GET") {
190
- raw = Object.fromEntries(event.url.searchParams);
191
- } else {
192
- try {
193
- raw = await event.request.json();
194
- } catch {
195
- raw = {};
196
- }
197
- }
198
- const parsed = endpoint.input.safeParse(raw);
199
- if (!parsed.success) {
200
- const errors = parsed.error.issues.map((issue) => ({
201
- field: issue.path.join(".") || "_",
202
- message: issue.message
203
- }));
204
- return validationError(errors);
205
- }
206
- const result = await fn(parsed.data, event);
207
- return ok(result);
208
- });
209
- }
210
172
  function loggingMiddleware(config = {}) {
211
173
  const { logBody = false, logResponse = false, redactFields = ["password", "token", "secret", "authorization", "apikey", "api_key", "creditcard", "credit_card"] } = config;
212
174
  return async (context, next) => {
@@ -221,7 +183,8 @@ function loggingMiddleware(config = {}) {
221
183
  requestId,
222
184
  method: event.request.method,
223
185
  path: event.url.pathname,
224
- query: Object.fromEntries(event.url.searchParams),
186
+ // SvelteKit 预渲染禁止读取追踪 URL 的查询;日志只读原请求,不建立页面路由依赖。
187
+ query: redactObject(Object.fromEntries(new URL(event.request.url).searchParams), redactFields),
225
188
  userAgent: event.request.headers.get("user-agent"),
226
189
  ip: clientIp
227
190
  };
@@ -249,76 +212,95 @@ function loggingMiddleware(config = {}) {
249
212
  };
250
213
  }
251
214
  function redactObject(obj, fields) {
252
- if (typeof obj !== "object" || obj === null) {
253
- return obj;
254
- }
255
- if (Array.isArray(obj)) {
256
- return obj.map((item) => redactObject(item, fields));
257
- }
258
- if (obj instanceof Date || obj instanceof RegExp) {
259
- return obj;
260
- }
261
- const result = {};
262
- for (const [key, value] of Object.entries(obj)) {
263
- if (fields.includes(key.toLowerCase())) {
264
- result[key] = "[REDACTED]";
265
- } else if (typeof value === "object" && value !== null) {
266
- result[key] = redactObject(value, fields);
267
- } else {
268
- result[key] = value;
215
+ return core.sanitize.sanitizeSensitiveFields(obj, { matcher: fields });
216
+ }
217
+ function permitsMemory() {
218
+ return !core.module.requiresSharedState(process2.env);
219
+ }
220
+ function createCacheRateLimitStore(backend, options) {
221
+ if (!options.namespace || options.namespace.length > 128)
222
+ throw new TypeError("A rate limit namespace of 1-128 characters is required");
223
+ return {
224
+ scope: "shared",
225
+ async increment(key, windowMs) {
226
+ if (!backend.config || backend.config.type !== "redis" && !(backend.config.type === "memory" && options.allowMemory && permitsMemory()))
227
+ throw new Error("Rate limiting requires a shared Redis cache");
228
+ const digest = createHash("sha256").update(JSON.stringify([options.namespace, windowMs, key])).digest("hex");
229
+ const cacheKey = `hai:kit:ratelimit:{${digest}}`;
230
+ for (let attempt = 0; attempt < 64; attempt++) {
231
+ const read = await backend.kv.get(cacheKey);
232
+ if (!read.success)
233
+ throw new Error("Rate limit cache read failed");
234
+ const old = read.data ?? void 0;
235
+ if (old && (!Number.isSafeInteger(old.count) || old.count <= 0 || old.count >= Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(old.resetAt)))
236
+ throw new Error("Invalid rate limit state");
237
+ const next = old ? { count: old.count + 1, resetAt: old.resetAt } : { count: 1, resetAt: Date.now() + windowMs };
238
+ const written = await backend.kv.compareAndSwap(cacheKey, old, next, old ? { keepTtl: true } : { px: windowMs });
239
+ if (!written.success)
240
+ throw new Error("Rate limit cache commit failed");
241
+ if (written.data)
242
+ return next;
243
+ }
244
+ throw new Error("Rate limit contention limit exceeded");
269
245
  }
270
- }
271
- return result;
246
+ };
247
+ }
248
+ function createMemoryRateLimitStore() {
249
+ if (!permitsMemory())
250
+ throw new Error("Memory rate limiting is restricted to development and tests");
251
+ return new MemoryRateLimitStore();
252
+ }
253
+ function clientKey(address) {
254
+ if (isIP(address) !== 6)
255
+ return address;
256
+ const normalized = new URL(`http://[${address}]`).hostname.slice(1, -1);
257
+ const mapped = /^::ffff:([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
258
+ if (!mapped)
259
+ return normalized;
260
+ const value = Number.parseInt(mapped[1], 16) * 65536 + Number.parseInt(mapped[2], 16);
261
+ return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
272
262
  }
273
263
  var MemoryRateLimitStore = class {
264
+ scope = "memory";
274
265
  store = /* @__PURE__ */ new Map();
275
- cleanupTimer = null;
266
+ cleanupCursor;
276
267
  /**
277
- * 启动定期清理过期条目
278
- *
279
- * @param intervalMs - 清理间隔(毫秒)
268
+ * 有界惰性回收;保留迭代位置,避免每次从头扫描使尾部过期项永久滞留。
269
+ * @param now - 当前请求时间,等于 resetAt 时窗口已经失效。
280
270
  */
281
- startCleanup(intervalMs) {
282
- if (this.cleanupTimer)
283
- return;
284
- this.cleanupTimer = setInterval(() => {
285
- const now = Date.now();
286
- for (const [key, entry] of this.store) {
287
- if (entry.resetAt < now) {
288
- this.store.delete(key);
289
- }
271
+ prune(now) {
272
+ this.cleanupCursor ??= this.store.entries();
273
+ for (let index = 0; index < 64; index++) {
274
+ const next = this.cleanupCursor.next();
275
+ if (next.done) {
276
+ this.cleanupCursor = void 0;
277
+ break;
290
278
  }
291
- }, intervalMs);
292
- if (this.cleanupTimer && typeof this.cleanupTimer === "object" && "unref" in this.cleanupTimer) {
293
- this.cleanupTimer.unref();
279
+ const [key, entry] = next.value;
280
+ if (entry.resetAt <= now)
281
+ this.store.delete(key);
294
282
  }
295
283
  }
296
- get(key) {
297
- return this.store.get(key);
298
- }
299
- set(key, entry) {
300
- this.store.set(key, entry);
301
- }
302
- delete(key) {
303
- this.store.delete(key);
304
- }
305
284
  /**
306
285
  * 原子自增(内存实现为同步操作,天然原子)
307
286
  *
308
287
  * @param key - 限流键
309
288
  * @param windowMs - 窗口时长
310
- * @returns 自增后的条目
289
+ * @returns 自增后的快照,修改返回对象不会改变已保存的计数。
290
+ * @throws 新 key 超出容量或期限非法;中间件将其转换为 503。
291
+ * @example store.increment('client', 60_000)
311
292
  */
312
293
  increment(key, windowMs) {
313
294
  const now = Date.now();
314
- let entry = this.store.get(key);
315
- if (!entry || entry.resetAt < now) {
316
- entry = { count: 1, resetAt: now + windowMs };
317
- } else {
318
- entry.count++;
319
- }
295
+ if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647)
296
+ throw new TypeError(kitM("kit_rateLimitWindowInvalid"));
297
+ this.prune(now);
298
+ const current = this.store.get(key);
299
+ if (!current && this.store.size >= 1e4)
300
+ throw new Error(kitM("kit_rateLimitMemoryCapacity"));
301
+ const entry = !current || current.resetAt <= now ? { count: 1, resetAt: now + windowMs } : { count: current.count + 1, resetAt: current.resetAt };
320
302
  this.store.set(key, entry);
321
- return entry;
303
+ return { ...entry };
322
304
  }
323
305
  };
324
306
  function rateLimitMiddleware(config) {
@@ -327,38 +309,36 @@ function rateLimitMiddleware(config) {
327
309
  maxRequests,
328
310
  keyGenerator = (event) => {
329
311
  try {
330
- return event.getClientAddress?.() ?? "unknown";
312
+ return clientKey(event.getClientAddress?.() ?? "unknown");
331
313
  } catch {
332
314
  return "unknown";
333
315
  }
334
316
  },
335
317
  onLimitReached
336
318
  } = config;
337
- const store = config.store ?? (() => {
338
- const memStore = new MemoryRateLimitStore();
339
- memStore.startCleanup(windowMs);
340
- return memStore;
341
- })();
319
+ if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647 || !Number.isSafeInteger(maxRequests) || maxRequests <= 0)
320
+ throw new TypeError("Rate limit window/count must be positive integers (window <= 2147483647ms)");
321
+ const store = config.store;
322
+ if (!store || typeof store.increment !== "function")
323
+ throw new TypeError("An atomic rate limit store is required");
324
+ if (store.scope === "memory" && !permitsMemory())
325
+ throw new Error("Memory rate limiting is restricted to development and tests");
342
326
  return async (context, next) => {
343
327
  const { event, requestId } = context;
344
- const key = keyGenerator(event);
345
328
  const now = Date.now();
346
329
  let entry;
347
- if (store.increment) {
348
- entry = await store.increment(key, windowMs);
349
- } else {
350
- const existing = await store.get(key);
351
- if (!existing || existing.resetAt < now) {
352
- entry = { count: 1, resetAt: now + windowMs };
353
- } else {
354
- entry = { ...existing, count: existing.count + 1 };
355
- }
356
- await store.set(key, entry);
330
+ try {
331
+ entry = await store.increment(keyGenerator(event), windowMs);
332
+ if (!Number.isSafeInteger(entry.count) || entry.count <= 0 || !Number.isSafeInteger(entry.resetAt))
333
+ throw new TypeError("Invalid atomic rate limit result");
334
+ } catch {
335
+ core.logger.error("Rate limit store unavailable", { requestId });
336
+ return new Response(JSON.stringify({ success: false, error: { code: "RATE_LIMIT_UNAVAILABLE", message: kitM("kit_internalError") }, requestId }), { status: 503, headers: { "Content-Type": "application/json" } });
357
337
  }
358
338
  const remaining = Math.max(0, maxRequests - entry.count);
359
339
  const resetTime = Math.ceil(entry.resetAt / 1e3);
360
340
  if (entry.count > maxRequests) {
361
- core.logger.warn("Rate limit exceeded", { key, requestId });
341
+ core.logger.warn("Rate limit exceeded", { requestId });
362
342
  if (onLimitReached) {
363
343
  return onLimitReached(event);
364
344
  }
@@ -378,7 +358,7 @@ function rateLimitMiddleware(config) {
378
358
  "X-RateLimit-Limit": String(maxRequests),
379
359
  "X-RateLimit-Remaining": "0",
380
360
  "X-RateLimit-Reset": String(resetTime),
381
- "Retry-After": String(Math.ceil((entry.resetAt - now) / 1e3))
361
+ "Retry-After": String(Math.max(1, Math.ceil((entry.resetAt - now) / 1e3)))
382
362
  }
383
363
  }
384
364
  );
@@ -401,40 +381,40 @@ function createEncryptedCookieProxy(cookies, config) {
401
381
  const raw = target.get(name, opts);
402
382
  if (!raw || !names.has(name))
403
383
  return raw;
404
- if (!raw.startsWith(ENCRYPTED_PREFIX))
405
- return raw;
384
+ if (!raw.startsWith(ENCRYPTED_PREFIX)) {
385
+ logger.warn("Encrypted cookie has invalid format", { name });
386
+ return void 0;
387
+ }
406
388
  try {
407
389
  const ciphertext = raw.slice(ENCRYPTED_PREFIX.length);
408
390
  const separatorIndex = ciphertext.indexOf(":");
409
- if (separatorIndex === -1)
410
- return raw;
391
+ if (separatorIndex === -1) {
392
+ logger.warn("Encrypted cookie has invalid format", { name });
393
+ return void 0;
394
+ }
411
395
  const iv = ciphertext.slice(0, separatorIndex);
412
396
  const encrypted = ciphertext.slice(separatorIndex + 1);
413
397
  const result = symmetric.decryptWithIV(encrypted, encryptionKey, iv);
414
398
  if (!result.success || typeof result.data !== "string") {
415
- logger.warn("Cookie decryption failed, returning raw value", { name });
416
- return raw;
399
+ logger.warn("Cookie decryption failed", { name });
400
+ return void 0;
417
401
  }
418
402
  return result.data;
419
403
  } catch {
420
- logger.warn("Cookie decryption error, returning raw value", { name });
421
- return raw;
404
+ logger.warn("Cookie decryption error", { name });
405
+ return void 0;
422
406
  }
423
407
  };
424
408
  }
425
409
  if (prop === "set") {
426
410
  return (name, value, opts) => {
427
411
  if (names.has(name)) {
428
- try {
429
- const result = symmetric.encryptWithIV(value, encryptionKey);
430
- if (result.success && result.data) {
431
- value = `${ENCRYPTED_PREFIX}${result.data.iv}:${result.data.ciphertext}`;
432
- } else {
433
- logger.warn("Cookie encryption failed, storing plaintext", { name });
434
- }
435
- } catch {
436
- logger.warn("Cookie encryption error, storing plaintext", { name });
412
+ const result = symmetric.encryptWithIV(value, encryptionKey);
413
+ if (!result.success || !result.data) {
414
+ logger.error("Cookie encryption failed", { name });
415
+ throw new Error("Cookie encryption failed");
437
416
  }
417
+ value = `${ENCRYPTED_PREFIX}${result.data.iv}:${result.data.ciphertext}`;
438
418
  }
439
419
  return target.set(name, value, opts);
440
420
  };
@@ -450,7 +430,14 @@ function generateId(prefix) {
450
430
  const random = crypto.randomUUID().replace(/-/g, "").substring(0, 8);
451
431
  return `${prefix}_${timestamp}${random}`;
452
432
  }
433
+ function applyDefaultSecurityHeaders(response) {
434
+ response.headers.set("X-Content-Type-Options", "nosniff");
435
+ response.headers.set("X-Frame-Options", "DENY");
436
+ response.headers.set("Referrer-Policy", "no-referrer");
437
+ }
453
438
  function createHandle(config = {}) {
439
+ const resolveIp = core.access.createClientIpResolver(config.trustedProxies ?? "auto");
440
+ const checkAccess = config.access && core.access.createRequestCheck(config.access);
454
441
  const {
455
442
  auth: authConfig,
456
443
  rateLimit: rateLimitConfig,
@@ -461,6 +448,8 @@ function createHandle(config = {}) {
461
448
  middleware: customMiddleware = [],
462
449
  a2a: a2aInput
463
450
  } = config;
451
+ if (checkAccess && checkAccess.requiresEncryption && !cryptoConfig?.transport)
452
+ throw new Error("Request access rules require transport encryption, but Kit transport is not configured");
464
453
  const a2aResolved = resolveA2AConfig(a2aInput);
465
454
  if (authConfig?.cookieName || authConfig?.operations) {
466
455
  configureAuth({ cookieName: authConfig.cookieName, operations: authConfig.operations });
@@ -481,102 +470,172 @@ function createHandle(config = {}) {
481
470
  }
482
471
  if (rateLimitConfig) {
483
472
  builtinMiddleware.push(rateLimitMiddleware({
473
+ ...rateLimitConfig,
484
474
  windowMs: rateLimitConfig.windowMs ?? 6e4,
485
475
  maxRequests: rateLimitConfig.maxRequests ?? 100
486
476
  }));
487
477
  }
488
478
  const allMiddleware = [...builtinMiddleware, ...customMiddleware];
489
- const finalMiddleware = buildTransportMiddleware(allMiddleware, cryptoConfig);
479
+ const transportMiddleware = buildTransportMiddleware(cryptoConfig, checkAccess ? checkAccess.encryption : void 0);
490
480
  const cookieProxyConfig = buildCookieProxyConfig(cryptoConfig);
491
481
  return async ({ event, resolve }) => {
492
- const requestId = generateId("req");
493
- const locals = event.locals;
494
- locals.requestId = requestId;
495
- if (cookieProxyConfig) {
496
- const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
497
- Object.defineProperty(event, "cookies", {
498
- value: proxiedCookies,
499
- writable: true,
500
- configurable: true
501
- });
502
- }
482
+ const suppliedRequestId = event.request.headers.get("x-request-id");
483
+ const requestId = suppliedRequestId && /^[\w.:-]{1,128}$/.test(suppliedRequestId) ? suppliedRequestId : generateId("req");
484
+ let clientAddress;
503
485
  try {
504
- if (a2aResolved) {
505
- const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
506
- if (a2aResponse)
507
- return a2aResponse;
486
+ clientAddress = event.getClientAddress();
487
+ } catch {
488
+ }
489
+ const resolvedAddress = resolveIp(event.request, clientAddress);
490
+ if (resolvedAddress !== clientAddress) {
491
+ event.getClientAddress = () => {
492
+ if (!resolvedAddress)
493
+ throw new Error("Trusted client address is unavailable");
494
+ return resolvedAddress;
495
+ };
496
+ }
497
+ clientAddress = resolvedAddress;
498
+ const accessDecision = checkAccess ? checkAccess(event.url.pathname, event.request.method, clientAddress) : void 0;
499
+ return core.context.run({ requestId, userId: void 0, scopeId: void 0, jobId: void 0 }, () => audit.withContext({
500
+ actor: null,
501
+ requestId,
502
+ traceId: requestId,
503
+ scopeId: void 0,
504
+ ipAddress: clientAddress,
505
+ userAgent: event.request.headers.get("user-agent")?.slice(0, 4096)
506
+ }, async () => {
507
+ let actor = null;
508
+ let guardDenied = false;
509
+ let requestFailed = false;
510
+ const captureRequest = async (status) => {
511
+ if (!audit.isInitialized)
512
+ return;
513
+ const resource = event.route.id ?? "unmatched";
514
+ const written = await audit.capture({
515
+ action: "request.execute",
516
+ resource,
517
+ userId: actor?.id ?? null,
518
+ actorSnapshot: actor,
519
+ category: /\/(?:auth|iam|audit)(?:\/|$)/.test(resource) ? "system" : "business",
520
+ outcome: guardDenied || [401, 403, 423, 429].includes(status) ? "denied" : requestFailed || status >= 400 ? "failure" : "success",
521
+ details: { method: event.request.method, status }
522
+ }, { read: ["GET", "HEAD", "OPTIONS"].includes(event.request.method) });
523
+ if (!written.success)
524
+ core.logger.error("Request audit failed", { requestId, resource, error: written.error });
525
+ };
526
+ const context = {
527
+ event,
528
+ requestId
529
+ };
530
+ const locals = event.locals;
531
+ locals.requestId = requestId;
532
+ if (cookieProxyConfig) {
533
+ const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
534
+ Object.defineProperty(event, "cookies", {
535
+ value: proxiedCookies,
536
+ writable: true,
537
+ configurable: true
538
+ });
508
539
  }
509
- let session;
510
- if (authConfig) {
511
- const token = getAccessToken(event.request, event.cookies);
512
- if (token) {
513
- session = await authConfig.verifyToken(token) ?? void 0;
514
- locals.session = session;
515
- if (session) {
516
- locals.accessToken = token;
540
+ const runRequest = async () => {
541
+ try {
542
+ if (accessDecision && !accessDecision.allowed) {
543
+ guardDenied = true;
544
+ core.logger.info("Request access policy denied", { ruleId: accessDecision.ruleId, reason: accessDecision.reason, method: event.request.method, ipAddress: clientAddress });
545
+ return Response.json({ success: false, error: { ...HaiCommonError.REQUEST_ACCESS_DENIED, message: core.i18n.coreM("core_requestAccessDenied"), traceId: requestId } }, { status: 403 });
517
546
  }
518
- }
519
- }
520
- for (const guardConfig of guards) {
521
- const guardResult = await executeGuard(guardConfig, event, session);
522
- if (!guardResult.allowed) {
523
- if (guardResult.redirect) {
524
- return new Response(null, {
525
- status: 302,
526
- headers: { Location: guardResult.redirect }
527
- });
547
+ if (!transportMiddleware && event.request.method !== "OPTIONS" && (event.request.headers.has(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) || event.request.headers.has(TRANSPORT_PROTOCOL.ENVELOPE_HEADER)) && checkAccess && checkAccess.encryption(event.url.pathname, event.request.method) === "disabled") {
548
+ return Response.json(err({ ...HaiCommonError.VALIDATION_ERROR, message: kitM("kit_transportPlaintextRequired"), traceId: requestId }), { status: 400 });
549
+ }
550
+ if (a2aResolved) {
551
+ const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
552
+ if (a2aResponse)
553
+ return a2aResponse;
554
+ }
555
+ let session;
556
+ if (authConfig) {
557
+ const token = getAccessToken(event.request, event.cookies);
558
+ if (token) {
559
+ session = await authConfig.verifyToken(token) ?? void 0;
560
+ locals.session = session;
561
+ if (session) {
562
+ locals.accessToken = token;
563
+ }
564
+ }
565
+ }
566
+ context.session = session;
567
+ actor = session ? { id: session.userId, username: session.username, roles: session.roles } : null;
568
+ return await audit.withContext({ actor }, () => core.context.run({ userId: session?.userId }, async () => {
569
+ for (const guardConfig of guards) {
570
+ const guardResult = await executeGuard(guardConfig, event, session);
571
+ if (!guardResult.allowed) {
572
+ guardDenied = true;
573
+ if (guardResult.redirect) {
574
+ return new Response(null, {
575
+ status: 302,
576
+ headers: { Location: guardResult.redirect }
577
+ });
578
+ }
579
+ return new Response(
580
+ JSON.stringify({
581
+ success: false,
582
+ error: {
583
+ code: "FORBIDDEN",
584
+ message: guardResult.message ?? kitM("kit_accessDenied")
585
+ },
586
+ requestId
587
+ }),
588
+ {
589
+ status: guardResult.status ?? 403,
590
+ headers: { "Content-Type": "application/json" }
591
+ }
592
+ );
593
+ }
594
+ }
595
+ const response2 = await executeMiddlewareChain(
596
+ allMiddleware,
597
+ context,
598
+ () => resolve(event)
599
+ );
600
+ return response2;
601
+ }));
602
+ } catch (error2) {
603
+ if (isSvelteKitControlFlow(error2)) {
604
+ if (typeof error2 === "object" && error2 !== null && "status" in error2 && typeof error2.status === "number")
605
+ await captureRequest(error2.status);
606
+ throw error2;
607
+ }
608
+ requestFailed = true;
609
+ core.logger.error("Request failed", { requestId, error: error2 });
610
+ if (onError) {
611
+ return onError(error2, event);
528
612
  }
529
613
  return new Response(
530
614
  JSON.stringify({
531
615
  success: false,
532
616
  error: {
533
- code: "FORBIDDEN",
534
- message: guardResult.message ?? kitM("kit_accessDenied")
617
+ code: "INTERNAL_ERROR",
618
+ message: core.error.formatMessage({ message: kitM("kit_internalError"), traceId: requestId }),
619
+ traceId: requestId
535
620
  },
536
621
  requestId
537
622
  }),
538
623
  {
539
- status: guardResult.status ?? 403,
624
+ status: 500,
540
625
  headers: { "Content-Type": "application/json" }
541
626
  }
542
627
  );
543
628
  }
544
- }
545
- const context = {
546
- event,
547
- session,
548
- requestId
549
629
  };
550
- const response = await executeMiddlewareChain(
551
- finalMiddleware,
552
- context,
553
- () => resolve(event)
554
- );
630
+ const keyExchange = cryptoConfig?.transport ? cryptoConfig.transport.keyExchangePath ?? "/api/_hai/key-exchange" : void 0;
631
+ const deniedHandshake = accessDecision && !accessDecision.allowed && event.request.method === "POST" && event.url.pathname === keyExchange;
632
+ const response = deniedHandshake ? await runRequest() : transportMiddleware ? await transportMiddleware(context, runRequest) : await runRequest();
633
+ await captureRequest(response.status);
634
+ applyDefaultSecurityHeaders(response);
555
635
  response.headers.set("X-Request-Id", requestId);
636
+ response.headers.set("X-Trace-Id", requestId);
556
637
  return response;
557
- } catch (error2) {
558
- if (isSvelteKitControlFlow(error2)) {
559
- throw error2;
560
- }
561
- core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
562
- if (onError) {
563
- return onError(error2, event);
564
- }
565
- return new Response(
566
- JSON.stringify({
567
- success: false,
568
- error: {
569
- code: "INTERNAL_ERROR",
570
- message: kitM("kit_internalError")
571
- },
572
- requestId
573
- }),
574
- {
575
- status: 500,
576
- headers: { "Content-Type": "application/json" }
577
- }
578
- );
579
- }
638
+ }));
580
639
  };
581
640
  }
582
641
  function buildAuthGuard(verifyToken, loginUrl) {
@@ -584,7 +643,8 @@ function buildAuthGuard(verifyToken, loginUrl) {
584
643
  if (session) {
585
644
  return { allowed: true };
586
645
  }
587
- const isApiRoute = event.url.pathname.startsWith("/api/");
646
+ const routePath = event.route.id?.replace(/\/\([^/]*\)/g, "");
647
+ const isApiRoute = event.url.pathname.startsWith("/api/") || routePath?.startsWith("/api/") === true;
588
648
  if (isApiRoute) {
589
649
  return {
590
650
  allowed: false,
@@ -635,27 +695,28 @@ function matchPath(pathname, pattern) {
635
695
  }
636
696
  return pathname === pattern;
637
697
  }
638
- function buildTransportMiddleware(userMiddleware, cryptoConfig) {
698
+ function buildTransportMiddleware(cryptoConfig, encryption) {
639
699
  if (!cryptoConfig?.transport)
640
- return userMiddleware;
641
- const transportOpts = typeof cryptoConfig.transport === "object" ? cryptoConfig.transport : {};
642
- const transportMw = transportEncryptionMiddleware({
700
+ return null;
701
+ const transportOpts = cryptoConfig.transport;
702
+ return transportEncryptionMiddleware({
643
703
  enabled: true,
644
704
  crypto: cryptoConfig.crypto,
705
+ serverKeyRing: transportOpts.serverKeyRing,
706
+ basePath: transportOpts.basePath,
645
707
  keyExchangePath: transportOpts.keyExchangePath,
646
708
  excludePaths: transportOpts.excludePaths,
647
- encryptResponse: transportOpts.encryptResponse,
648
- requireEncryption: transportOpts.requireEncryption
649
- });
650
- return [transportMw, ...userMiddleware];
709
+ keyStore: transportOpts.keyStore,
710
+ maxPlaintextBytes: transportOpts.maxPlaintextBytes,
711
+ routePlaintextBytes: transportOpts.routePlaintextBytes
712
+ }, encryption);
651
713
  }
652
714
  function buildCookieProxyConfig(cryptoConfig) {
653
715
  if (!cryptoConfig?.encryptedCookies?.length)
654
716
  return null;
655
717
  const key = cryptoConfig.cookieEncryptionKey ?? (typeof process !== "undefined" ? process.env?.HAI_KIT_COOKIE_KEY : void 0);
656
718
  if (!key) {
657
- core.logger.warn("Cookie encryption configured but no key provided (set crypto.cookieEncryptionKey or HAI_KIT_COOKIE_KEY env)");
658
- return null;
719
+ throw new Error("Cookie encryption requires crypto.cookieEncryptionKey or HAI_KIT_COOKIE_KEY");
659
720
  }
660
721
  return {
661
722
  names: new Set(cryptoConfig.encryptedCookies),
@@ -672,28 +733,82 @@ function sequence(...handles) {
672
733
  return filtered[0];
673
734
  }
674
735
  return async ({ event, resolve }) => {
675
- return filtered.reduceRight(
676
- (next, handle) => (event2) => handle({ event: event2, resolve: next }),
677
- resolve
678
- )(event);
736
+ function applyHandle(index, currentEvent, options) {
737
+ if (index === filtered.length)
738
+ return resolve(currentEvent, options);
739
+ return filtered[index]({
740
+ event: currentEvent,
741
+ resolve: async (nextEvent, nextOptions) => {
742
+ const merged = {
743
+ ...options,
744
+ ...nextOptions,
745
+ preload: options?.preload ?? nextOptions?.preload,
746
+ filterSerializedResponseHeaders: options?.filterSerializedResponseHeaders ?? nextOptions?.filterSerializedResponseHeaders
747
+ };
748
+ if (options?.transformPageChunk || nextOptions?.transformPageChunk) {
749
+ merged.transformPageChunk = async ({ html, done }) => {
750
+ if (nextOptions?.transformPageChunk)
751
+ html = await nextOptions.transformPageChunk({ html, done }) ?? "";
752
+ if (options?.transformPageChunk)
753
+ html = await options.transformPageChunk({ html, done }) ?? "";
754
+ return html;
755
+ };
756
+ }
757
+ return applyHandle(index + 1, nextEvent, merged);
758
+ }
759
+ });
760
+ }
761
+ return applyHandle(0, event);
679
762
  };
680
763
  }
681
- function extractZodIssues(error2) {
682
- const zodError = error2;
683
- return zodError.issues ?? zodError.errors ?? [];
684
- }
685
- function zodIssuesToFormErrors(issues) {
686
- return issues.map((issue) => ({
687
- field: issue.path.join("."),
688
- message: issue.message
689
- }));
764
+ function handler(fn) {
765
+ return async (event) => {
766
+ try {
767
+ return await fn(event);
768
+ } catch (error2) {
769
+ if (isResponseLike(error2)) {
770
+ return error2;
771
+ }
772
+ if (isSvelteKitControlFlow(error2)) {
773
+ throw error2;
774
+ }
775
+ const normalizedError = error2 instanceof Error ? {
776
+ name: error2.name,
777
+ message: error2.message,
778
+ stack: error2.stack,
779
+ cause: error2.cause
780
+ } : error2;
781
+ core.logger.error("Request handler error", {
782
+ error: normalizedError,
783
+ path: event.url.pathname,
784
+ method: event.request.method
785
+ });
786
+ return internalError();
787
+ }
788
+ };
690
789
  }
691
- function createValidationResult(error2) {
790
+ var getKitZodMessage = core.zodValidation.createPrefixedZodMessageGetter(
791
+ "kit",
792
+ (messageKey, params) => kitM(messageKey, { params })
793
+ );
794
+ function validateParsedData(data, schema) {
795
+ const result = schema.safeParse(data);
796
+ if (result.success)
797
+ return { valid: true, data: result.data, errors: [] };
692
798
  return {
693
799
  valid: false,
694
- errors: zodIssuesToFormErrors(extractZodIssues(error2))
800
+ errors: core.zodValidation.mapZodErrorToFormErrors(result.error, getKitZodMessage)
695
801
  };
696
802
  }
803
+ function unwrapValidationResultOrThrow(result) {
804
+ if (result.valid && result.data !== void 0)
805
+ return result.data;
806
+ throw badRequest(
807
+ result.errors[0]?.message ?? kitM("kit_validationFailed"),
808
+ void 0,
809
+ { errors: result.errors }
810
+ );
811
+ }
697
812
  async function validateForm(request, schema) {
698
813
  try {
699
814
  const contentType = request.headers.get("content-type") ?? "";
@@ -709,11 +824,7 @@ async function validateForm(request, schema) {
709
824
  errors: [{ field: "_", message: kitM("kit_unsupportedContentType") }]
710
825
  };
711
826
  }
712
- const result = schema.safeParse(data);
713
- if (result.success) {
714
- return { valid: true, data: result.data, errors: [] };
715
- }
716
- return createValidationResult(result.error);
827
+ return validateParsedData(data, schema);
717
828
  } catch {
718
829
  return {
719
830
  valid: false,
@@ -723,51 +834,19 @@ async function validateForm(request, schema) {
723
834
  }
724
835
  function validateQuery(url, schema) {
725
836
  const data = Object.fromEntries(url.searchParams);
726
- const result = schema.safeParse(data);
727
- if (result.success) {
728
- return { valid: true, data: result.data, errors: [] };
729
- }
730
- return createValidationResult(result.error);
837
+ return validateParsedData(data, schema);
731
838
  }
732
839
  function validateParams(params, schema) {
733
- const result = schema.safeParse(params);
734
- if (result.success) {
735
- return { valid: true, data: result.data, errors: [] };
736
- }
737
- return createValidationResult(result.error);
840
+ return validateParsedData(params, schema);
738
841
  }
739
842
  async function validateFormOrFail(request, schema) {
740
- const result = await validateForm(request, schema);
741
- if (!result.valid || !result.data) {
742
- throw badRequest(
743
- result.errors[0]?.message ?? kitM("kit_validationFailed"),
744
- void 0,
745
- { errors: result.errors }
746
- );
747
- }
748
- return result.data;
843
+ return unwrapValidationResultOrThrow(await validateForm(request, schema));
749
844
  }
750
845
  function validateQueryOrFail(url, schema) {
751
- const result = validateQuery(url, schema);
752
- if (!result.valid || !result.data) {
753
- throw badRequest(
754
- result.errors[0]?.message ?? kitM("kit_validationFailed"),
755
- void 0,
756
- { errors: result.errors }
757
- );
758
- }
759
- return result.data;
846
+ return unwrapValidationResultOrThrow(validateQuery(url, schema));
760
847
  }
761
848
  function validateParamsOrFail(params, schema) {
762
- const result = validateParams(params, schema);
763
- if (!result.valid || !result.data) {
764
- throw badRequest(
765
- result.errors[0]?.message ?? kitM("kit_validationFailed"),
766
- void 0,
767
- { errors: result.errors }
768
- );
769
- }
770
- return result.data;
849
+ return unwrapValidationResultOrThrow(validateParams(params, schema));
771
850
  }
772
851
  var IdParamSchema = z.object({
773
852
  id: z.string().min(1, kitM("kit_idRequired"))
@@ -779,8 +858,205 @@ var PaginationQuerySchema = z.object({
779
858
  search: z.string().optional()
780
859
  });
781
860
 
861
+ // src/kit-auth-api.ts
862
+ var authApiPublicPaths = ["/api/auth/session/close", "/api/auth/logout", "/api/auth/captcha", "/api/auth/config", "/api/auth/login", "/api/auth/login/ldap", "/api/auth/login/otp", "/api/auth/register", "/api/auth/otp/send", "/api/auth/password-reset/request", "/api/auth/password-reset/confirm", "/api/auth/password/renew"];
863
+ function requestContext(event) {
864
+ let ip;
865
+ try {
866
+ ip = event.getClientAddress();
867
+ } catch {
868
+ }
869
+ const terminal = event.request.headers.get("x-hai-terminal");
870
+ return { ip, userAgent: event.request.headers.get("user-agent") ?? void 0, terminalType: terminal === "app" || terminal === "desktop" || terminal === "api" ? terminal : "web" };
871
+ }
872
+ function createAuthApi(iam) {
873
+ function response(result) {
874
+ return result.success ? ok(result.data) : fromError(result.error);
875
+ }
876
+ return {
877
+ closeSession: handler(async ({ request, cookies }) => {
878
+ const input = await validateFormOrFail(request, IamLogoutInputSchema);
879
+ return response(await iam.auth.logout(input.accessToken ?? getAccessToken(request, cookies) ?? ""));
880
+ }),
881
+ createCaptcha: handler(async ({ request }) => {
882
+ const input = await validateFormOrFail(request, IamCreateCaptchaInputSchema);
883
+ return response(await iam.auth.createCaptcha(input));
884
+ }),
885
+ reauthenticate: handler(async ({ request, cookies }) => {
886
+ const input = await validateFormOrFail(request, IamReauthenticateInputSchema);
887
+ return response(await iam.auth.reauthenticate(getAccessToken(request, cookies) ?? "", input.password, input.operation));
888
+ }),
889
+ publicConfig: handler(async () => response(await iam.getPublicConfig())),
890
+ renewPassword: handler(async ({ request }) => {
891
+ const input = await validateFormOrFail(request, IamRenewPasswordInputSchema);
892
+ return response(await iam.user.renewPassword(input));
893
+ }),
894
+ login: handler(async (event) => {
895
+ const { request, cookies } = event;
896
+ const input = await validateFormOrFail(request, IamLoginInputSchema);
897
+ const result = await login(cookies, input, requestContext(event));
898
+ return result.success ? ok({ user: result.data.user }) : fromError(result.error);
899
+ }),
900
+ loginWithLdap: handler(async (event) => {
901
+ const { request, cookies } = event;
902
+ const input = await validateFormOrFail(request, IamLdapLoginInputSchema);
903
+ const result = await loginWithLdap(cookies, input, requestContext(event));
904
+ return result.success ? ok({ user: result.data.user }) : fromError(result.error);
905
+ }),
906
+ loginWithOtp: handler(async (event) => {
907
+ const { request, cookies } = event;
908
+ const input = await validateFormOrFail(request, IamOtpLoginInputSchema);
909
+ const result = await loginWithOtp(cookies, input, requestContext(event));
910
+ return result.success ? ok({ user: result.data.user }) : fromError(result.error);
911
+ }),
912
+ register: handler(async (event) => {
913
+ const { request, cookies } = event;
914
+ const input = await validateFormOrFail(request, IamRegisterInputSchema);
915
+ const result = await registerAndLogin(cookies, input, requestContext(event));
916
+ return result.success ? ok({ user: result.data.user }) : fromError(result.error);
917
+ }),
918
+ sendOtp: handler(async ({ request }) => {
919
+ const input = await validateFormOrFail(request, IamSendOtpInputSchema);
920
+ return response(await iam.auth.sendOtp(input.identifier, input));
921
+ }),
922
+ requestPasswordReset: handler(async ({ request }) => {
923
+ const input = await validateFormOrFail(request, IamRequestPasswordResetInputSchema);
924
+ return response(await iam.user.requestPasswordReset(input.identifier, input));
925
+ }),
926
+ confirmPasswordReset: handler(async ({ request }) => {
927
+ const input = await validateFormOrFail(request, IamConfirmPasswordResetInputSchema);
928
+ return response(await iam.user.confirmPasswordReset(input.token, input.newPassword));
929
+ })
930
+ };
931
+ }
932
+ function createFeedbackHandler(options) {
933
+ const basePath = (options.basePath ?? "/api/feedback").replace(/\/+$/, "");
934
+ return async (event) => {
935
+ let actor;
936
+ try {
937
+ actor = await options.resolveContext(event);
938
+ } catch {
939
+ return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
940
+ }
941
+ if (!actor?.scopeId || !actor.userId)
942
+ return fromError({ ...HaiCommonError.UNAUTHORIZED, message: kitM("kit_unauthorized") });
943
+ if (options.feedback.isInitialized === false)
944
+ return fromError({ ...HaiCommonError.SERVICE_UNAVAILABLE, message: kitM("kit_feedbackUnavailable") });
945
+ const suffix = event.url.pathname.startsWith(`${basePath}/`) ? event.url.pathname.slice(basePath.length + 1) : "";
946
+ let parts;
947
+ try {
948
+ parts = suffix.split("/").map(decodeURIComponent);
949
+ } catch {
950
+ return invalid(400);
951
+ }
952
+ if (parts.length === 1 && parts[0] === "policy" && event.request.method === "GET")
953
+ return ok(options.feedback.budget);
954
+ if (parts[0] !== "reports" || parts.length > 4)
955
+ return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
956
+ let body = {};
957
+ if (!["GET", "HEAD"].includes(event.request.method)) {
958
+ if (event.request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
959
+ return invalid(415);
960
+ try {
961
+ body = await readBoundedJson(event.request, options.feedback.budget.maxTransportBytes);
962
+ } catch (error2) {
963
+ return invalid(error2 instanceof RangeError ? 413 : 400);
964
+ }
965
+ }
966
+ if (!body || typeof body !== "object" || Array.isArray(body))
967
+ return invalid(400);
968
+ const bodyRecord = body;
969
+ if (parts[1] && Object.hasOwn(bodyRecord, "id") && bodyRecord.id !== parts[1])
970
+ return invalid(400);
971
+ const input = { ...Object.fromEntries(event.url.searchParams), ...bodyRecord, ...parts[1] ? { id: parts[1] } : {} };
972
+ const method = event.request.method;
973
+ const service = options.feedback;
974
+ async function execute(schema, operation) {
975
+ const parsed = schema.safeParse(input);
976
+ if (!parsed.success)
977
+ return invalid(400);
978
+ try {
979
+ const result = await core.context.run(actor, () => operation(parsed.data));
980
+ return result.success ? ok(result.data) : fromError(result.error);
981
+ } catch {
982
+ return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
983
+ }
984
+ }
985
+ if (parts.length === 1) {
986
+ if (method === "POST")
987
+ return execute(FeedbackSubmitInputSchema, (value) => service.submit(value, actor));
988
+ if (method === "GET")
989
+ return execute(FeedbackListInputSchema, (value) => service.list(value, actor));
990
+ }
991
+ if (parts.length === 2 && method === "GET")
992
+ return execute(FeedbackIdInputSchema, (value) => service.get(value.id, actor));
993
+ if (parts.length === 3) {
994
+ if (parts[2] === "evidence" && method === "GET")
995
+ return execute(FeedbackEvidenceQuerySchema, (value) => service.getEvidence(value, actor));
996
+ if (parts[2] === "evidence" && method === "POST")
997
+ return execute(FeedbackAppendEvidenceInputSchema, (value) => service.appendEvidence(value, actor));
998
+ if (parts[2] === "status" && method === "PATCH")
999
+ return execute(FeedbackSetStatusInputSchema, (value) => service.setStatus(value, actor));
1000
+ if (parts[2] === "retry" && method === "POST")
1001
+ return execute(FeedbackRetryInputSchema, (value) => service.retry(value, actor));
1002
+ if (parts[2] === "complete" && method === "POST")
1003
+ return execute(FeedbackCompleteInputSchema, (value) => service.completeCollection(value, actor));
1004
+ if (parts[2] === "exports" && method === "POST")
1005
+ return execute(FeedbackExportInputSchema, (value) => service.export(value, actor));
1006
+ }
1007
+ if (parts.length === 4 && parts[2] === "attachments") {
1008
+ Object.assign(input, { attachmentId: parts[3] });
1009
+ if (method === "POST")
1010
+ return execute(FeedbackUploadInputSchema, (value) => service.uploadAttachment(value, actor));
1011
+ if (method === "GET")
1012
+ return execute(FeedbackAttachmentQuerySchema, (value) => service.getAttachment(value, actor));
1013
+ }
1014
+ if (parts.length === 4 && parts[2] === "exports" && method === "GET") {
1015
+ Object.assign(input, { jobId: parts[3] });
1016
+ return execute(FeedbackExportQuerySchema, (value) => service.getExport(value, actor));
1017
+ }
1018
+ return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
1019
+ };
1020
+ }
1021
+ function invalid(status) {
1022
+ return fromError({ ...HaiCommonError.INVALID_REQUEST, httpStatus: status, message: kitM("kit_validationFailed") });
1023
+ }
1024
+ async function readBoundedJson(request, maximum) {
1025
+ const reader = request.body?.getReader();
1026
+ if (!reader)
1027
+ return {};
1028
+ const decoder = new TextDecoder();
1029
+ let length = 0;
1030
+ let text = "";
1031
+ try {
1032
+ while (true) {
1033
+ const chunk = await reader.read();
1034
+ if (chunk.done)
1035
+ break;
1036
+ length += chunk.value.byteLength;
1037
+ if (length > maximum) {
1038
+ await reader.cancel();
1039
+ throw new RangeError("Feedback request is too large");
1040
+ }
1041
+ text += decoder.decode(chunk.value, { stream: true });
1042
+ }
1043
+ text += decoder.decode();
1044
+ return text ? JSON.parse(text) : {};
1045
+ } finally {
1046
+ reader.releaseLock();
1047
+ }
1048
+ }
1049
+
782
1050
  // src/kit-main.ts
783
1051
  var kit = {
1052
+ /** 创建反馈的受控 catch-all HTTP handler,身份范围由宿主解析。 */
1053
+ createFeedbackHandler,
1054
+ /** 原子限流工厂;共享store生命周期由应用管理。 */
1055
+ middleware: {
1056
+ rateLimit: rateLimitMiddleware,
1057
+ createCacheRateLimitStore,
1058
+ createMemoryRateLimitStore
1059
+ },
784
1060
  // ─── Handle Hook ───
785
1061
  /** 创建 SvelteKit Handle Hook(含 auth / logging / rateLimit 内置配置) */
786
1062
  createHandle,
@@ -788,10 +1064,10 @@ var kit = {
788
1064
  sequence,
789
1065
  /** API Handler 包装器(自动错误边界) */
790
1066
  handler,
791
- /** 基于 API 契约创建类型安全的路由 handler */
792
- fromContract,
793
1067
  // ─── 路由守卫 ───
794
1068
  guard: {
1069
+ /** 选定敏感操作要求单次再鉴别;先调用现有权限与资源守卫。 */
1070
+ requireSensitive: requireSensitiveOperation,
795
1071
  /** 要求权限,不满足时 throw Response(SvelteKit 控制流) */
796
1072
  require: requirePermission,
797
1073
  /** 检查会话是否具有指定权限(布尔) */
@@ -844,10 +1120,23 @@ var kit = {
844
1120
  // ─── 客户端工具 ───
845
1121
  client: {
846
1122
  /** 创建统一客户端(CSRF + 传输加密透明合并) */
847
- create: createKitClient
1123
+ create: createKitClient,
1124
+ /** 标准认证表单动作(Cookie 模式)。 */
1125
+ createAuthActions: createKitAuthActions,
1126
+ /**
1127
+ * 用解析后的 {@link KitConfig} 一键安装浏览器端传输加密
1128
+ * (应用层推荐入口;内部预热 crypto 并调用 installBrowserTransportFetch)
1129
+ */
1130
+ installBrowserTransport,
1131
+ /** 底层入口:直接传 transport 配置安装浏览器全局 fetch 包装 */
1132
+ installBrowserTransportFetch
848
1133
  },
849
1134
  // ─── 认证工具 ───
850
1135
  auth: {
1136
+ /** 标准匿名认证端点白名单。 */
1137
+ publicPaths: authApiPublicPaths,
1138
+ /** 标准配置/登录/注册/验证码/重置 handler 集合。 */
1139
+ createApi: createAuthApi,
851
1140
  /** 服务端登录(密码):内部调用 iam.auth.login + 自动写入 Token Cookie */
852
1141
  login,
853
1142
  /** 服务端登录(OTP 验证码):内部调用 iam.auth.loginWithOtp + 自动写入 Token Cookie */
@@ -863,11 +1152,8 @@ var kit = {
863
1152
  /** 写入浏览器端 Access Token(客户端 login/register 用) */
864
1153
  setBrowserToken,
865
1154
  /** 清除浏览器端 Access Token(客户端 logout 用) */
866
- clearBrowserToken,
1155
+ clearBrowserToken
867
1156
  /** 创建浏览器端 Token 存储器(自定义 key 时使用) */
868
- createTokenStore,
869
- /** 创建浏览器端同源请求自动附加 Authorization 的 HandleFetch */
870
- createHandleFetch
871
1157
  },
872
1158
  // ─── CRUD ───
873
1159
  crud: {
@@ -877,10 +1163,12 @@ var kit = {
877
1163
  // ─── i18n ───
878
1164
  i18n: {
879
1165
  /** 统一设置所有 hai 模块的默认语言 */
880
- setLocale: setAllModulesLocale
1166
+ setLocale: setAllModulesLocale,
1167
+ /** 请求/SSR 语言隔离;异步处理使用同一请求上下文。 */
1168
+ runWithLocale: core.i18n.runWithLocale
881
1169
  }
882
1170
  };
883
1171
 
884
- export { IdParamSchema, PaginationQuerySchema, defineEndpoint, kit, matchPermission };
1172
+ export { IdParamSchema, PaginationQuerySchema, kit, matchPermission };
885
1173
  //# sourceMappingURL=index.js.map
886
1174
  //# sourceMappingURL=index.js.map