@h-ai/kit 1.0.0-beta.2 → 1.0.0-beta.21
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 +190 -110
- package/README.ja.md +192 -112
- package/README.md +195 -114
- package/dist/adapter/index.d.ts +8 -7
- package/dist/adapter/index.js.map +1 -1
- package/dist/browser.d.ts +9 -26
- package/dist/browser.js +4 -4
- package/dist/browser.js.map +1 -1
- package/dist/{chunk-JRQPMPQC.js → chunk-BCEBXG4X.js} +2 -2
- package/dist/chunk-BCEBXG4X.js.map +1 -0
- package/dist/{chunk-7EH47MTW.js → chunk-BVGXHIXR.js} +30 -9
- package/dist/chunk-BVGXHIXR.js.map +1 -0
- package/dist/{chunk-5VWPH7DO.js → chunk-GQZCU6PC.js} +14 -6
- package/dist/chunk-GQZCU6PC.js.map +1 -0
- package/dist/{chunk-YKJRQCWR.js → chunk-JL2C5ZGQ.js} +54 -20
- package/dist/chunk-JL2C5ZGQ.js.map +1 -0
- package/dist/{chunk-3OHUSKHI.js → chunk-WYGIKXHE.js} +23 -8
- package/dist/chunk-WYGIKXHE.js.map +1 -0
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.js +5 -5
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +18 -711
- package/dist/index.js +266 -149
- package/dist/index.js.map +1 -1
- package/dist/{kit-a2a-types-4dapztu2.d.ts → kit-a2a-types-sw7sI-ZT.d.ts} +3 -2
- package/dist/kit-config-BQKQnu8Y.d.ts +182 -0
- package/dist/kit-crypto-types-CqFENBqm.d.ts +47 -0
- package/dist/kit-types-Cr1cnB3M.d.ts +2051 -0
- package/dist/modules/a2a/index.d.ts +9 -5
- package/dist/modules/a2a/index.js +1 -1
- package/dist/modules/crypto/index.d.ts +8 -105
- package/dist/modules/crypto/index.js +3 -149
- package/dist/modules/crypto/index.js.map +1 -1
- package/package.json +22 -22
- package/dist/chunk-3OHUSKHI.js.map +0 -1
- package/dist/chunk-5VWPH7DO.js.map +0 -1
- package/dist/chunk-7EH47MTW.js.map +0 -1
- package/dist/chunk-JRQPMPQC.js.map +0 -1
- package/dist/chunk-YKJRQCWR.js.map +0 -1
- package/dist/kit-client-auth-BnhrkzZF.d.ts +0 -422
- package/dist/kit-crypto-types-CXr888wf.d.ts +0 -200
- package/dist/kit-i18n-EBzZKYN-.d.ts +0 -502
- package/dist/kit-types-BkNdyKj8.d.ts +0 -822
package/dist/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { defineCrud } from './chunk-
|
|
2
|
-
export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-
|
|
3
|
-
import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, getAccessToken, configureAuth } from './chunk-
|
|
4
|
-
import { resolveA2AConfig, handleA2ARequest } from './chunk-
|
|
5
|
-
import { transportEncryptionMiddleware } from './chunk-
|
|
6
|
-
import './chunk-
|
|
1
|
+
import { defineCrud } from './chunk-GQZCU6PC.js';
|
|
2
|
+
export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-GQZCU6PC.js';
|
|
3
|
+
import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, getAccessToken, configureAuth } from './chunk-JL2C5ZGQ.js';
|
|
4
|
+
import { resolveA2AConfig, handleA2ARequest } from './chunk-BVGXHIXR.js';
|
|
5
|
+
import { transportEncryptionMiddleware } from './chunk-WYGIKXHE.js';
|
|
6
|
+
import './chunk-BCEBXG4X.js';
|
|
7
7
|
import { kitM, setAllModulesLocale } from './chunk-USFJN7IO.js';
|
|
8
8
|
import { core, HaiCommonError, err } from '@h-ai/core';
|
|
9
|
+
import process from 'process';
|
|
9
10
|
import { audit } from '@h-ai/audit';
|
|
10
11
|
import { TRANSPORT_PROTOCOL } from '@h-ai/crypto';
|
|
11
|
-
import {
|
|
12
|
+
import { isHttpError, isRedirect } from '@sveltejs/kit';
|
|
13
|
+
import { createHash, hkdfSync, timingSafeEqual, createHmac } from 'crypto';
|
|
12
14
|
import { isIP } from 'net';
|
|
13
|
-
import
|
|
15
|
+
import { Buffer } from 'buffer';
|
|
14
16
|
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';
|
|
15
17
|
import { z } from 'zod';
|
|
16
18
|
|
|
@@ -40,14 +42,28 @@ function hasPermission(session, permission) {
|
|
|
40
42
|
function assertPermission(session, permission) {
|
|
41
43
|
if (!session) {
|
|
42
44
|
return new Response(
|
|
43
|
-
JSON.stringify({
|
|
44
|
-
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
success: false,
|
|
47
|
+
error: kitM("kit_unauthorized")
|
|
48
|
+
}),
|
|
49
|
+
{
|
|
50
|
+
status: 401,
|
|
51
|
+
headers: { "Content-Type": "application/json" }
|
|
52
|
+
}
|
|
45
53
|
);
|
|
46
54
|
}
|
|
47
55
|
if (!matchPermission(permission, session.permissions ?? [])) {
|
|
48
56
|
return new Response(
|
|
49
|
-
JSON.stringify({
|
|
50
|
-
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
success: false,
|
|
59
|
+
error: kitM("kit_forbidden", {
|
|
60
|
+
params: { permission }
|
|
61
|
+
})
|
|
62
|
+
}),
|
|
63
|
+
{
|
|
64
|
+
status: 403,
|
|
65
|
+
headers: { "Content-Type": "application/json" }
|
|
66
|
+
}
|
|
51
67
|
);
|
|
52
68
|
}
|
|
53
69
|
return void 0;
|
|
@@ -85,42 +101,57 @@ function noContent() {
|
|
|
85
101
|
}
|
|
86
102
|
function error(code, message, status = 400, requestId, details) {
|
|
87
103
|
const safe = core.error.toPublic({ code, message, httpStatus: status }, requestId);
|
|
88
|
-
|
|
104
|
+
const safeStatus = safe.httpStatus ?? 500;
|
|
105
|
+
core.logger.error("API request failed", {
|
|
106
|
+
traceId: safe.traceId,
|
|
107
|
+
error: {
|
|
108
|
+
code,
|
|
109
|
+
message,
|
|
110
|
+
httpStatus: status,
|
|
111
|
+
details
|
|
112
|
+
}
|
|
113
|
+
});
|
|
89
114
|
const response = {
|
|
90
115
|
success: false,
|
|
91
116
|
error: {
|
|
92
|
-
code,
|
|
117
|
+
code: String(safe.code),
|
|
93
118
|
message: safe.message,
|
|
94
119
|
traceId: safe.traceId,
|
|
95
|
-
|
|
120
|
+
httpStatus: safeStatus,
|
|
121
|
+
details: safeStatus < 500 ? core.sanitize.sanitizeSensitiveFields(details) : void 0
|
|
96
122
|
},
|
|
97
|
-
requestId:
|
|
123
|
+
requestId: safe.traceId
|
|
98
124
|
};
|
|
99
125
|
return new Response(JSON.stringify(response), {
|
|
100
|
-
status,
|
|
126
|
+
status: safeStatus,
|
|
101
127
|
headers: { "Content-Type": "application/json", "X-Trace-Id": safe.traceId }
|
|
102
128
|
});
|
|
103
129
|
}
|
|
104
130
|
function badRequest(message, requestId, details) {
|
|
105
|
-
return error(
|
|
131
|
+
return error(HaiCommonError.INVALID_REQUEST.code, message, 400, requestId, details);
|
|
106
132
|
}
|
|
107
133
|
function unauthorized(message, requestId) {
|
|
108
|
-
return error(
|
|
134
|
+
return error(HaiCommonError.UNAUTHORIZED.code, message ?? kitM("kit_authRequired"), 401, requestId);
|
|
109
135
|
}
|
|
110
136
|
function forbidden(message, requestId) {
|
|
111
|
-
return error(
|
|
137
|
+
return error(HaiCommonError.FORBIDDEN.code, message ?? kitM("kit_accessDenied"), 403, requestId);
|
|
112
138
|
}
|
|
113
139
|
function notFound(message, requestId) {
|
|
114
|
-
return error(
|
|
140
|
+
return error(HaiCommonError.NOT_FOUND.code, message ?? kitM("kit_resourceNotFound"), 404, requestId);
|
|
115
141
|
}
|
|
116
142
|
function conflict(message, requestId) {
|
|
117
|
-
return error(
|
|
143
|
+
return error(HaiCommonError.CONFLICT.code, message, 409, requestId);
|
|
118
144
|
}
|
|
119
145
|
function validationError(errors, requestId) {
|
|
120
|
-
return
|
|
146
|
+
return fromError({
|
|
147
|
+
...HaiCommonError.VALIDATION_ERROR,
|
|
148
|
+
message: kitM("kit_validationFailed"),
|
|
149
|
+
httpStatus: 422,
|
|
150
|
+
cause: errors
|
|
151
|
+
}, requestId);
|
|
121
152
|
}
|
|
122
153
|
function internalError(message, requestId) {
|
|
123
|
-
return error(
|
|
154
|
+
return error(HaiCommonError.INTERNAL_ERROR.code, message ?? kitM("kit_internalError"), 500, requestId);
|
|
124
155
|
}
|
|
125
156
|
function redirect(url, status = 302) {
|
|
126
157
|
return new Response(null, {
|
|
@@ -132,13 +163,25 @@ function fromResult(result, httpStatusMap, requestId) {
|
|
|
132
163
|
if (result.success) {
|
|
133
164
|
return ok(result.data, requestId);
|
|
134
165
|
}
|
|
135
|
-
const status = httpStatusMap?.[result.error.code] ?? (typeof result.error.httpStatus === "number" ? result.error.httpStatus :
|
|
166
|
+
const status = httpStatusMap?.[result.error.code] ?? (typeof result.error.httpStatus === "number" ? result.error.httpStatus : 500);
|
|
136
167
|
return fromError({ ...result.error, httpStatus: status }, requestId);
|
|
137
168
|
}
|
|
138
169
|
function fromError(haiError, requestId) {
|
|
139
170
|
const safe = core.error.toPublic(haiError, requestId);
|
|
140
171
|
core.logger.error("API operation failed", { traceId: safe.traceId, error: haiError });
|
|
141
|
-
|
|
172
|
+
const response = {
|
|
173
|
+
success: false,
|
|
174
|
+
error: {
|
|
175
|
+
...safe,
|
|
176
|
+
code: String(safe.code),
|
|
177
|
+
httpStatus: safe.httpStatus ?? 500
|
|
178
|
+
},
|
|
179
|
+
requestId: safe.traceId
|
|
180
|
+
};
|
|
181
|
+
return new Response(JSON.stringify(response), {
|
|
182
|
+
status: safe.httpStatus ?? 500,
|
|
183
|
+
headers: { "Content-Type": "application/json", "X-Trace-Id": safe.traceId }
|
|
184
|
+
});
|
|
142
185
|
}
|
|
143
186
|
|
|
144
187
|
// src/guards/kit-sensitive.ts
|
|
@@ -150,8 +193,6 @@ async function requireSensitiveOperation(iam, event, operation) {
|
|
|
150
193
|
if (!checked.success)
|
|
151
194
|
throw fromError(checked.error);
|
|
152
195
|
}
|
|
153
|
-
|
|
154
|
-
// src/kit-utils.ts
|
|
155
196
|
function isResponseLike(value) {
|
|
156
197
|
if (value instanceof Response)
|
|
157
198
|
return true;
|
|
@@ -161,16 +202,19 @@ function isResponseLike(value) {
|
|
|
161
202
|
return false;
|
|
162
203
|
}
|
|
163
204
|
function isSvelteKitControlFlow(value) {
|
|
164
|
-
|
|
165
|
-
return false;
|
|
166
|
-
if (typeof value === "object" && value !== null && "status" in value) {
|
|
167
|
-
const status = value.status;
|
|
168
|
-
return typeof status === "number";
|
|
169
|
-
}
|
|
170
|
-
return false;
|
|
205
|
+
return isHttpError(value) || isRedirect(value);
|
|
171
206
|
}
|
|
172
207
|
function loggingMiddleware(config = {}) {
|
|
173
|
-
const { logBody = false, logResponse = false, redactFields = [
|
|
208
|
+
const { logBody = false, logResponse = false, redactFields = [
|
|
209
|
+
"password",
|
|
210
|
+
"token",
|
|
211
|
+
"secret",
|
|
212
|
+
"authorization",
|
|
213
|
+
"apikey",
|
|
214
|
+
"api_key",
|
|
215
|
+
"creditcard",
|
|
216
|
+
"credit_card"
|
|
217
|
+
] } = config;
|
|
174
218
|
return async (context, next) => {
|
|
175
219
|
const { event, requestId } = context;
|
|
176
220
|
const startTime = Date.now();
|
|
@@ -215,7 +259,7 @@ function redactObject(obj, fields) {
|
|
|
215
259
|
return core.sanitize.sanitizeSensitiveFields(obj, { matcher: fields });
|
|
216
260
|
}
|
|
217
261
|
function permitsMemory() {
|
|
218
|
-
return !core.module.requiresSharedState(
|
|
262
|
+
return !core.module.requiresSharedState(process.env);
|
|
219
263
|
}
|
|
220
264
|
function createCacheRateLimitStore(backend, options) {
|
|
221
265
|
if (!options.namespace || options.namespace.length > 128)
|
|
@@ -230,14 +274,14 @@ function createCacheRateLimitStore(backend, options) {
|
|
|
230
274
|
for (let attempt = 0; attempt < 64; attempt++) {
|
|
231
275
|
const read = await backend.kv.get(cacheKey);
|
|
232
276
|
if (!read.success)
|
|
233
|
-
throw new Error("Rate limit cache read failed");
|
|
277
|
+
throw new Error("Rate limit cache read failed", { cause: read.error });
|
|
234
278
|
const old = read.data ?? void 0;
|
|
235
279
|
if (old && (!Number.isSafeInteger(old.count) || old.count <= 0 || old.count >= Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(old.resetAt)))
|
|
236
280
|
throw new Error("Invalid rate limit state");
|
|
237
281
|
const next = old ? { count: old.count + 1, resetAt: old.resetAt } : { count: 1, resetAt: Date.now() + windowMs };
|
|
238
282
|
const written = await backend.kv.compareAndSwap(cacheKey, old, next, old ? { keepTtl: true } : { px: windowMs });
|
|
239
283
|
if (!written.success)
|
|
240
|
-
throw new Error("Rate limit cache commit failed");
|
|
284
|
+
throw new Error("Rate limit cache commit failed", { cause: written.error });
|
|
241
285
|
if (written.data)
|
|
242
286
|
return next;
|
|
243
287
|
}
|
|
@@ -258,7 +302,12 @@ function clientKey(address) {
|
|
|
258
302
|
if (!mapped)
|
|
259
303
|
return normalized;
|
|
260
304
|
const value = Number.parseInt(mapped[1], 16) * 65536 + Number.parseInt(mapped[2], 16);
|
|
261
|
-
return [
|
|
305
|
+
return [
|
|
306
|
+
value >>> 24,
|
|
307
|
+
value >>> 16 & 255,
|
|
308
|
+
value >>> 8 & 255,
|
|
309
|
+
value & 255
|
|
310
|
+
].join(".");
|
|
262
311
|
}
|
|
263
312
|
var MemoryRateLimitStore = class {
|
|
264
313
|
scope = "memory";
|
|
@@ -331,37 +380,29 @@ function rateLimitMiddleware(config) {
|
|
|
331
380
|
entry = await store.increment(keyGenerator(event), windowMs);
|
|
332
381
|
if (!Number.isSafeInteger(entry.count) || entry.count <= 0 || !Number.isSafeInteger(entry.resetAt))
|
|
333
382
|
throw new TypeError("Invalid atomic rate limit result");
|
|
334
|
-
} catch {
|
|
335
|
-
|
|
336
|
-
|
|
383
|
+
} catch (cause) {
|
|
384
|
+
return fromError({
|
|
385
|
+
...HaiCommonError.SERVICE_UNAVAILABLE,
|
|
386
|
+
message: kitM("kit_internalError"),
|
|
387
|
+
cause
|
|
388
|
+
}, requestId);
|
|
337
389
|
}
|
|
338
390
|
const remaining = Math.max(0, maxRequests - entry.count);
|
|
339
391
|
const resetTime = Math.ceil(entry.resetAt / 1e3);
|
|
340
392
|
if (entry.count > maxRequests) {
|
|
341
|
-
core.logger.warn("Rate limit exceeded", { requestId });
|
|
342
393
|
if (onLimitReached) {
|
|
394
|
+
core.logger.warn("Rate limit exceeded", { requestId });
|
|
343
395
|
return onLimitReached(event);
|
|
344
396
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
{
|
|
355
|
-
status: 429,
|
|
356
|
-
headers: {
|
|
357
|
-
"Content-Type": "application/json",
|
|
358
|
-
"X-RateLimit-Limit": String(maxRequests),
|
|
359
|
-
"X-RateLimit-Remaining": "0",
|
|
360
|
-
"X-RateLimit-Reset": String(resetTime),
|
|
361
|
-
"Retry-After": String(Math.max(1, Math.ceil((entry.resetAt - now) / 1e3)))
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
);
|
|
397
|
+
const response2 = fromError({
|
|
398
|
+
...HaiCommonError.RATE_LIMIT_EXCEEDED,
|
|
399
|
+
message: kitM("kit_rateLimitExceeded")
|
|
400
|
+
}, requestId);
|
|
401
|
+
response2.headers.set("X-RateLimit-Limit", String(maxRequests));
|
|
402
|
+
response2.headers.set("X-RateLimit-Remaining", "0");
|
|
403
|
+
response2.headers.set("X-RateLimit-Reset", String(resetTime));
|
|
404
|
+
response2.headers.set("Retry-After", String(Math.max(1, Math.ceil((entry.resetAt - now) / 1e3))));
|
|
405
|
+
return response2;
|
|
365
406
|
}
|
|
366
407
|
const response = await next();
|
|
367
408
|
response.headers.set("X-RateLimit-Limit", String(maxRequests));
|
|
@@ -370,10 +411,16 @@ function rateLimitMiddleware(config) {
|
|
|
370
411
|
return response;
|
|
371
412
|
};
|
|
372
413
|
}
|
|
373
|
-
var ENCRYPTED_PREFIX = "enc:";
|
|
414
|
+
var ENCRYPTED_PREFIX = "enc:v2:";
|
|
374
415
|
function createEncryptedCookieProxy(cookies, config) {
|
|
375
416
|
const { names, symmetric, encryptionKey } = config;
|
|
376
417
|
const logger = core.logger.child({ module: "kit", scope: "cookie-proxy" });
|
|
418
|
+
if (!/^[\da-f]{32}$/i.test(encryptionKey))
|
|
419
|
+
throw new Error("Cookie encryption requires a 32-character hexadecimal key");
|
|
420
|
+
const keys = Buffer.from(hkdfSync("sha256", Buffer.from(encryptionKey, "hex"), Buffer.alloc(0), "hai:kit:cookie:v2", 48));
|
|
421
|
+
const cipherKey = keys.subarray(0, 16).toString("hex");
|
|
422
|
+
const macKey = keys.subarray(16);
|
|
423
|
+
const authenticate = (name, iv, ciphertext) => createHmac("sha256", macKey).update(JSON.stringify(["v2", name, iv, ciphertext])).digest();
|
|
377
424
|
return new Proxy(cookies, {
|
|
378
425
|
get(target, prop, receiver) {
|
|
379
426
|
if (prop === "get") {
|
|
@@ -387,14 +434,17 @@ function createEncryptedCookieProxy(cookies, config) {
|
|
|
387
434
|
}
|
|
388
435
|
try {
|
|
389
436
|
const ciphertext = raw.slice(ENCRYPTED_PREFIX.length);
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
437
|
+
const parts = ciphertext.split(":");
|
|
438
|
+
if (parts.length !== 3 || !/^[\da-f]{64}$/i.test(parts[2])) {
|
|
392
439
|
logger.warn("Encrypted cookie has invalid format", { name });
|
|
393
440
|
return void 0;
|
|
394
441
|
}
|
|
395
|
-
const iv =
|
|
396
|
-
|
|
397
|
-
|
|
442
|
+
const [iv, encrypted, signature] = parts;
|
|
443
|
+
if (!timingSafeEqual(authenticate(name, iv, encrypted), Buffer.from(signature, "hex"))) {
|
|
444
|
+
logger.warn("Cookie authentication failed", { name });
|
|
445
|
+
return void 0;
|
|
446
|
+
}
|
|
447
|
+
const result = symmetric.decryptWithIV(encrypted, cipherKey, iv);
|
|
398
448
|
if (!result.success || typeof result.data !== "string") {
|
|
399
449
|
logger.warn("Cookie decryption failed", { name });
|
|
400
450
|
return void 0;
|
|
@@ -409,12 +459,13 @@ function createEncryptedCookieProxy(cookies, config) {
|
|
|
409
459
|
if (prop === "set") {
|
|
410
460
|
return (name, value, opts) => {
|
|
411
461
|
if (names.has(name)) {
|
|
412
|
-
const result = symmetric.encryptWithIV(value,
|
|
462
|
+
const result = symmetric.encryptWithIV(value, cipherKey);
|
|
413
463
|
if (!result.success || !result.data) {
|
|
414
464
|
logger.error("Cookie encryption failed", { name });
|
|
415
465
|
throw new Error("Cookie encryption failed");
|
|
416
466
|
}
|
|
417
|
-
|
|
467
|
+
const { iv, ciphertext } = result.data;
|
|
468
|
+
value = `${ENCRYPTED_PREFIX}${iv}:${ciphertext}:${authenticate(name, iv, ciphertext).toString("hex")}`;
|
|
418
469
|
}
|
|
419
470
|
return target.set(name, value, opts);
|
|
420
471
|
};
|
|
@@ -475,7 +526,10 @@ function createHandle(config = {}) {
|
|
|
475
526
|
maxRequests: rateLimitConfig.maxRequests ?? 100
|
|
476
527
|
}));
|
|
477
528
|
}
|
|
478
|
-
const allMiddleware = [
|
|
529
|
+
const allMiddleware = [
|
|
530
|
+
...builtinMiddleware,
|
|
531
|
+
...customMiddleware
|
|
532
|
+
];
|
|
479
533
|
const transportMiddleware = buildTransportMiddleware(cryptoConfig, checkAccess ? checkAccess.encryption : void 0);
|
|
480
534
|
const cookieProxyConfig = buildCookieProxyConfig(cryptoConfig);
|
|
481
535
|
return async ({ event, resolve }) => {
|
|
@@ -488,15 +542,23 @@ function createHandle(config = {}) {
|
|
|
488
542
|
}
|
|
489
543
|
const resolvedAddress = resolveIp(event.request, clientAddress);
|
|
490
544
|
if (resolvedAddress !== clientAddress) {
|
|
491
|
-
event
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
545
|
+
event = {
|
|
546
|
+
...event,
|
|
547
|
+
getClientAddress: () => {
|
|
548
|
+
if (!resolvedAddress)
|
|
549
|
+
throw new Error("Trusted client address is unavailable");
|
|
550
|
+
return resolvedAddress;
|
|
551
|
+
}
|
|
495
552
|
};
|
|
496
553
|
}
|
|
497
554
|
clientAddress = resolvedAddress;
|
|
498
555
|
const accessDecision = checkAccess ? checkAccess(event.url.pathname, event.request.method, clientAddress) : void 0;
|
|
499
|
-
return core.context.run({
|
|
556
|
+
return core.context.run({
|
|
557
|
+
requestId,
|
|
558
|
+
userId: void 0,
|
|
559
|
+
scopeId: void 0,
|
|
560
|
+
jobId: void 0
|
|
561
|
+
}, () => audit.withContext({
|
|
500
562
|
actor: null,
|
|
501
563
|
requestId,
|
|
502
564
|
traceId: requestId,
|
|
@@ -519,33 +581,49 @@ function createHandle(config = {}) {
|
|
|
519
581
|
category: /\/(?:auth|iam|audit)(?:\/|$)/.test(resource) ? "system" : "business",
|
|
520
582
|
outcome: guardDenied || [401, 403, 423, 429].includes(status) ? "denied" : requestFailed || status >= 400 ? "failure" : "success",
|
|
521
583
|
details: { method: event.request.method, status }
|
|
522
|
-
}, {
|
|
584
|
+
}, {
|
|
585
|
+
read: ["GET", "HEAD", "OPTIONS"].includes(event.request.method)
|
|
586
|
+
});
|
|
523
587
|
if (!written.success)
|
|
524
588
|
core.logger.error("Request audit failed", { requestId, resource, error: written.error });
|
|
525
589
|
};
|
|
590
|
+
if (cookieProxyConfig) {
|
|
591
|
+
event = {
|
|
592
|
+
...event,
|
|
593
|
+
cookies: createEncryptedCookieProxy(event.cookies, cookieProxyConfig)
|
|
594
|
+
};
|
|
595
|
+
}
|
|
526
596
|
const context = {
|
|
527
597
|
event,
|
|
528
598
|
requestId
|
|
529
599
|
};
|
|
530
600
|
const locals = event.locals;
|
|
531
601
|
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
|
-
});
|
|
539
|
-
}
|
|
540
602
|
const runRequest = async () => {
|
|
541
603
|
try {
|
|
542
604
|
if (accessDecision && !accessDecision.allowed) {
|
|
543
605
|
guardDenied = true;
|
|
544
|
-
core.logger.info("Request access policy denied", {
|
|
545
|
-
|
|
606
|
+
core.logger.info("Request access policy denied", {
|
|
607
|
+
ruleId: accessDecision.ruleId,
|
|
608
|
+
reason: accessDecision.reason,
|
|
609
|
+
method: event.request.method,
|
|
610
|
+
ipAddress: clientAddress
|
|
611
|
+
});
|
|
612
|
+
return Response.json({
|
|
613
|
+
success: false,
|
|
614
|
+
error: {
|
|
615
|
+
...HaiCommonError.REQUEST_ACCESS_DENIED,
|
|
616
|
+
message: core.i18n.coreM("core_requestAccessDenied"),
|
|
617
|
+
traceId: requestId
|
|
618
|
+
}
|
|
619
|
+
}, { status: 403 });
|
|
546
620
|
}
|
|
547
621
|
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({
|
|
622
|
+
return Response.json(err({
|
|
623
|
+
...HaiCommonError.VALIDATION_ERROR,
|
|
624
|
+
message: kitM("kit_transportPlaintextRequired"),
|
|
625
|
+
traceId: requestId
|
|
626
|
+
}), { status: 400 });
|
|
549
627
|
}
|
|
550
628
|
if (a2aResolved) {
|
|
551
629
|
const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
|
|
@@ -576,20 +654,11 @@ function createHandle(config = {}) {
|
|
|
576
654
|
headers: { Location: guardResult.redirect }
|
|
577
655
|
});
|
|
578
656
|
}
|
|
579
|
-
return
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
);
|
|
657
|
+
return fromError({
|
|
658
|
+
...guardResult.status === 401 ? HaiCommonError.UNAUTHORIZED : HaiCommonError.FORBIDDEN,
|
|
659
|
+
message: guardResult.message ?? kitM("kit_accessDenied"),
|
|
660
|
+
httpStatus: guardResult.status ?? 403
|
|
661
|
+
}, requestId);
|
|
593
662
|
}
|
|
594
663
|
}
|
|
595
664
|
const response2 = await executeMiddlewareChain(
|
|
@@ -606,25 +675,15 @@ function createHandle(config = {}) {
|
|
|
606
675
|
throw error2;
|
|
607
676
|
}
|
|
608
677
|
requestFailed = true;
|
|
609
|
-
core.logger.error("Request failed", { requestId, error: error2 });
|
|
610
678
|
if (onError) {
|
|
679
|
+
core.logger.error("Request failed", { requestId, error: error2 });
|
|
611
680
|
return onError(error2, event);
|
|
612
681
|
}
|
|
613
|
-
return
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
message: core.error.formatMessage({ message: kitM("kit_internalError"), traceId: requestId }),
|
|
619
|
-
traceId: requestId
|
|
620
|
-
},
|
|
621
|
-
requestId
|
|
622
|
-
}),
|
|
623
|
-
{
|
|
624
|
-
status: 500,
|
|
625
|
-
headers: { "Content-Type": "application/json" }
|
|
626
|
-
}
|
|
627
|
-
);
|
|
682
|
+
return fromError({
|
|
683
|
+
...HaiCommonError.INTERNAL_ERROR,
|
|
684
|
+
message: kitM("kit_internalError"),
|
|
685
|
+
cause: error2
|
|
686
|
+
}, requestId);
|
|
628
687
|
}
|
|
629
688
|
};
|
|
630
689
|
const keyExchange = cryptoConfig?.transport ? cryptoConfig.transport.keyExchangePath ?? "/api/_hai/key-exchange" : void 0;
|
|
@@ -708,16 +767,20 @@ function buildTransportMiddleware(cryptoConfig, encryption) {
|
|
|
708
767
|
excludePaths: transportOpts.excludePaths,
|
|
709
768
|
keyStore: transportOpts.keyStore,
|
|
710
769
|
maxPlaintextBytes: transportOpts.maxPlaintextBytes,
|
|
770
|
+
maxFutureSkewSeconds: transportOpts.maxFutureSkewSeconds,
|
|
771
|
+
maxRequestAgeSeconds: transportOpts.maxRequestAgeSeconds,
|
|
711
772
|
routePlaintextBytes: transportOpts.routePlaintextBytes
|
|
712
773
|
}, encryption);
|
|
713
774
|
}
|
|
714
775
|
function buildCookieProxyConfig(cryptoConfig) {
|
|
715
776
|
if (!cryptoConfig?.encryptedCookies?.length)
|
|
716
777
|
return null;
|
|
717
|
-
const key = cryptoConfig.cookieEncryptionKey ??
|
|
778
|
+
const key = cryptoConfig.cookieEncryptionKey ?? process.env.HAI_KIT_COOKIE_KEY;
|
|
718
779
|
if (!key) {
|
|
719
780
|
throw new Error("Cookie encryption requires crypto.cookieEncryptionKey or HAI_KIT_COOKIE_KEY");
|
|
720
781
|
}
|
|
782
|
+
if (!/^[\da-f]{32}$/i.test(key))
|
|
783
|
+
throw new Error("Cookie encryption requires a 32-character hexadecimal key");
|
|
721
784
|
return {
|
|
722
785
|
names: new Set(cryptoConfig.encryptedCookies),
|
|
723
786
|
symmetric: cryptoConfig.crypto.symmetric,
|
|
@@ -772,18 +835,13 @@ function handler(fn) {
|
|
|
772
835
|
if (isSvelteKitControlFlow(error2)) {
|
|
773
836
|
throw error2;
|
|
774
837
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
message:
|
|
778
|
-
|
|
779
|
-
cause: error2
|
|
780
|
-
|
|
781
|
-
core.logger.error("Request handler error", {
|
|
782
|
-
error: normalizedError,
|
|
783
|
-
path: event.url.pathname,
|
|
784
|
-
method: event.request.method
|
|
838
|
+
return fromError({
|
|
839
|
+
...HaiCommonError.INTERNAL_ERROR,
|
|
840
|
+
message: kitM("kit_internalError"),
|
|
841
|
+
// 由 Core 统一规范化原始异常,保留驱动码和 AggregateError 的子错误。
|
|
842
|
+
cause: error2,
|
|
843
|
+
ext: { path: event.url.pathname, method: event.request.method }
|
|
785
844
|
});
|
|
786
|
-
return internalError();
|
|
787
845
|
}
|
|
788
846
|
};
|
|
789
847
|
}
|
|
@@ -821,14 +879,24 @@ async function validateForm(request, schema) {
|
|
|
821
879
|
} else {
|
|
822
880
|
return {
|
|
823
881
|
valid: false,
|
|
824
|
-
errors: [
|
|
882
|
+
errors: [
|
|
883
|
+
{
|
|
884
|
+
field: "_",
|
|
885
|
+
message: kitM("kit_unsupportedContentType")
|
|
886
|
+
}
|
|
887
|
+
]
|
|
825
888
|
};
|
|
826
889
|
}
|
|
827
890
|
return validateParsedData(data, schema);
|
|
828
891
|
} catch {
|
|
829
892
|
return {
|
|
830
893
|
valid: false,
|
|
831
|
-
errors: [
|
|
894
|
+
errors: [
|
|
895
|
+
{
|
|
896
|
+
field: "_",
|
|
897
|
+
message: kitM("kit_parseBodyFailed")
|
|
898
|
+
}
|
|
899
|
+
]
|
|
832
900
|
};
|
|
833
901
|
}
|
|
834
902
|
}
|
|
@@ -859,7 +927,20 @@ var PaginationQuerySchema = z.object({
|
|
|
859
927
|
});
|
|
860
928
|
|
|
861
929
|
// src/kit-auth-api.ts
|
|
862
|
-
var authApiPublicPaths = [
|
|
930
|
+
var authApiPublicPaths = [
|
|
931
|
+
"/api/auth/session/close",
|
|
932
|
+
"/api/auth/logout",
|
|
933
|
+
"/api/auth/captcha",
|
|
934
|
+
"/api/auth/config",
|
|
935
|
+
"/api/auth/login",
|
|
936
|
+
"/api/auth/login/ldap",
|
|
937
|
+
"/api/auth/login/otp",
|
|
938
|
+
"/api/auth/register",
|
|
939
|
+
"/api/auth/otp/send",
|
|
940
|
+
"/api/auth/password-reset/request",
|
|
941
|
+
"/api/auth/password-reset/confirm",
|
|
942
|
+
"/api/auth/password/renew"
|
|
943
|
+
];
|
|
863
944
|
function requestContext(event) {
|
|
864
945
|
let ip;
|
|
865
946
|
try {
|
|
@@ -867,7 +948,11 @@ function requestContext(event) {
|
|
|
867
948
|
} catch {
|
|
868
949
|
}
|
|
869
950
|
const terminal = event.request.headers.get("x-hai-terminal");
|
|
870
|
-
return {
|
|
951
|
+
return {
|
|
952
|
+
ip,
|
|
953
|
+
userAgent: event.request.headers.get("user-agent") ?? void 0,
|
|
954
|
+
terminalType: terminal === "app" || terminal === "desktop" || terminal === "api" ? terminal : "web"
|
|
955
|
+
};
|
|
871
956
|
}
|
|
872
957
|
function createAuthApi(iam) {
|
|
873
958
|
function response(result) {
|
|
@@ -930,18 +1015,33 @@ function createAuthApi(iam) {
|
|
|
930
1015
|
};
|
|
931
1016
|
}
|
|
932
1017
|
function createFeedbackHandler(options) {
|
|
933
|
-
const
|
|
1018
|
+
const configuredPath = options.basePath ?? "/api/feedback";
|
|
1019
|
+
let pathEnd = configuredPath.length;
|
|
1020
|
+
while (pathEnd > 0 && configuredPath[pathEnd - 1] === "/")
|
|
1021
|
+
pathEnd--;
|
|
1022
|
+
const basePath = configuredPath.slice(0, pathEnd);
|
|
934
1023
|
return async (event) => {
|
|
935
1024
|
let actor;
|
|
936
1025
|
try {
|
|
937
1026
|
actor = await options.resolveContext(event);
|
|
938
1027
|
} catch {
|
|
939
|
-
return fromError({
|
|
1028
|
+
return fromError({
|
|
1029
|
+
...HaiCommonError.INTERNAL_ERROR,
|
|
1030
|
+
message: kitM("kit_internalError")
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
if (!actor?.scopeId || !actor.userId) {
|
|
1034
|
+
return fromError({
|
|
1035
|
+
...HaiCommonError.UNAUTHORIZED,
|
|
1036
|
+
message: kitM("kit_unauthorized")
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
if (options.feedback.isInitialized === false) {
|
|
1040
|
+
return fromError({
|
|
1041
|
+
...HaiCommonError.SERVICE_UNAVAILABLE,
|
|
1042
|
+
message: kitM("kit_feedbackUnavailable")
|
|
1043
|
+
});
|
|
940
1044
|
}
|
|
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
1045
|
const suffix = event.url.pathname.startsWith(`${basePath}/`) ? event.url.pathname.slice(basePath.length + 1) : "";
|
|
946
1046
|
let parts;
|
|
947
1047
|
try {
|
|
@@ -951,8 +1051,12 @@ function createFeedbackHandler(options) {
|
|
|
951
1051
|
}
|
|
952
1052
|
if (parts.length === 1 && parts[0] === "policy" && event.request.method === "GET")
|
|
953
1053
|
return ok(options.feedback.budget);
|
|
954
|
-
if (parts[0] !== "reports" || parts.length > 4)
|
|
955
|
-
return fromError({
|
|
1054
|
+
if (parts[0] !== "reports" || parts.length > 4) {
|
|
1055
|
+
return fromError({
|
|
1056
|
+
...HaiCommonError.NOT_FOUND,
|
|
1057
|
+
message: core.i18n.coreM("core_errorNotFound")
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
956
1060
|
let body = {};
|
|
957
1061
|
if (!["GET", "HEAD"].includes(event.request.method)) {
|
|
958
1062
|
if (event.request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
|
|
@@ -968,7 +1072,11 @@ function createFeedbackHandler(options) {
|
|
|
968
1072
|
const bodyRecord = body;
|
|
969
1073
|
if (parts[1] && Object.hasOwn(bodyRecord, "id") && bodyRecord.id !== parts[1])
|
|
970
1074
|
return invalid(400);
|
|
971
|
-
const input = {
|
|
1075
|
+
const input = {
|
|
1076
|
+
...Object.fromEntries(event.url.searchParams),
|
|
1077
|
+
...bodyRecord,
|
|
1078
|
+
...parts[1] ? { id: parts[1] } : {}
|
|
1079
|
+
};
|
|
972
1080
|
const method = event.request.method;
|
|
973
1081
|
const service = options.feedback;
|
|
974
1082
|
async function execute(schema, operation) {
|
|
@@ -979,7 +1087,10 @@ function createFeedbackHandler(options) {
|
|
|
979
1087
|
const result = await core.context.run(actor, () => operation(parsed.data));
|
|
980
1088
|
return result.success ? ok(result.data) : fromError(result.error);
|
|
981
1089
|
} catch {
|
|
982
|
-
return fromError({
|
|
1090
|
+
return fromError({
|
|
1091
|
+
...HaiCommonError.INTERNAL_ERROR,
|
|
1092
|
+
message: kitM("kit_internalError")
|
|
1093
|
+
});
|
|
983
1094
|
}
|
|
984
1095
|
}
|
|
985
1096
|
if (parts.length === 1) {
|
|
@@ -1015,11 +1126,18 @@ function createFeedbackHandler(options) {
|
|
|
1015
1126
|
Object.assign(input, { jobId: parts[3] });
|
|
1016
1127
|
return execute(FeedbackExportQuerySchema, (value) => service.getExport(value, actor));
|
|
1017
1128
|
}
|
|
1018
|
-
return fromError({
|
|
1129
|
+
return fromError({
|
|
1130
|
+
...HaiCommonError.NOT_FOUND,
|
|
1131
|
+
message: core.i18n.coreM("core_errorNotFound")
|
|
1132
|
+
});
|
|
1019
1133
|
};
|
|
1020
1134
|
}
|
|
1021
1135
|
function invalid(status) {
|
|
1022
|
-
return fromError({
|
|
1136
|
+
return fromError({
|
|
1137
|
+
...HaiCommonError.INVALID_REQUEST,
|
|
1138
|
+
httpStatus: status,
|
|
1139
|
+
message: kitM("kit_validationFailed")
|
|
1140
|
+
});
|
|
1023
1141
|
}
|
|
1024
1142
|
async function readBoundedJson(request, maximum) {
|
|
1025
1143
|
const reader = request.body?.getReader();
|
|
@@ -1153,7 +1271,6 @@ var kit = {
|
|
|
1153
1271
|
setBrowserToken,
|
|
1154
1272
|
/** 清除浏览器端 Access Token(客户端 logout 用) */
|
|
1155
1273
|
clearBrowserToken
|
|
1156
|
-
/** 创建浏览器端 Token 存储器(自定义 key 时使用) */
|
|
1157
1274
|
},
|
|
1158
1275
|
// ─── CRUD ───
|
|
1159
1276
|
crud: {
|