@h-ai/serv 1.0.0-beta.15 → 1.0.0-beta.17
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 +4 -2
- package/README.ja.md +4 -2
- package/README.md +4 -2
- package/dist/features/ai.d.ts +2 -2
- package/dist/features/feedback.d.ts +2 -2
- package/dist/features/iam.d.ts +1 -1
- package/dist/features/storage.d.ts +2 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +76 -70
- package/dist/index.js.map +1 -1
- package/dist/{serv-context-DE1JH5bd.d.ts → serv-context-BsNvQ-bb.d.ts} +3 -4
- package/dist/{serv-router-BJk04-oS.d.ts → serv-router-BnkxPvS6.d.ts} +1 -1
- package/package.json +16 -16
package/README.en.md
CHANGED
|
@@ -485,7 +485,7 @@ await apiClient.init({
|
|
|
485
485
|
})
|
|
486
486
|
```
|
|
487
487
|
|
|
488
|
-
>
|
|
488
|
+
> Refresh cookies use `HttpOnly` and `SameSite=Strict` and are scoped to `{apiPrefix}/auth/refresh`. Production adds `Secure` by default.
|
|
489
489
|
|
|
490
490
|
### API overview
|
|
491
491
|
|
|
@@ -638,7 +638,9 @@ const app = serv.createApp({
|
|
|
638
638
|
})
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
|
|
641
|
+
Production requires an explicit `_serv.yml` `cors.origin` allowlist and disables OpenAPI and docs. Web and Capacitor/Tauri origins match by their exact normalized values.
|
|
642
|
+
|
|
643
|
+
For a production HTTP endpoint on a controlled private network or VPN, set `HAI_SERV_REFRESH_COOKIE_SECURE=false` in the backend process environment. After restarting the backend and signing in, login and refresh responses omit `Secure` from the cookie, and HTTP carries the refresh credential in plaintext. `true` forces `Secure`. An empty or unrecognized value uses the environment default. When calling `serv.createApp` directly, an explicit `refreshCookie.secure` takes precedence over this variable, followed by the production check on `NODE_ENV` or `HAI_CORE_ENV`.
|
|
642
644
|
|
|
643
645
|
### Configuration driven authentication
|
|
644
646
|
|
package/README.ja.md
CHANGED
|
@@ -485,7 +485,7 @@ await apiClient.init({
|
|
|
485
485
|
})
|
|
486
486
|
```
|
|
487
487
|
|
|
488
|
-
>
|
|
488
|
+
> リフレッシュ Cookie には `HttpOnly` と `SameSite=Strict` が付き、パスは `{apiPrefix}/auth/refresh` に限定されます。本番では既定で `Secure` も付きます。
|
|
489
489
|
|
|
490
490
|
### API 一覧
|
|
491
491
|
|
|
@@ -638,7 +638,9 @@ const app = serv.createApp({
|
|
|
638
638
|
})
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
本番では `_serv.yml` の `cors.origin`
|
|
641
|
+
本番では `_serv.yml` の `cors.origin` に許可するオリジンを明示し、OpenAPI と docs は無効になります。Web と Capacitor/Tauri のオリジンは、正規化した値全体で照合します。
|
|
642
|
+
|
|
643
|
+
管理されたプライベートネットワークや VPN の本番 HTTP 入口では、バックエンドのプロセス環境に `HAI_SERV_REFRESH_COOKIE_SECURE=false` を設定できます。再起動してログインし直すと、ログイン応答と更新応答の Cookie から `Secure` が外れ、HTTP でリフレッシュ認証情報が平文で送信されます。`true` は `Secure` を強制し、空欄や未対応の値では環境の既定値が使われます。`serv.createApp` を直接呼ぶ場合は、明示した `refreshCookie.secure`、この環境変数、`NODE_ENV` または `HAI_CORE_ENV` による本番判定の順に適用されます。
|
|
642
644
|
|
|
643
645
|
### 設定による認証画面
|
|
644
646
|
|
package/README.md
CHANGED
|
@@ -485,7 +485,7 @@ await apiClient.init({
|
|
|
485
485
|
})
|
|
486
486
|
```
|
|
487
487
|
|
|
488
|
-
>
|
|
488
|
+
> 刷新 Cookie 带有 `HttpOnly` 和 `SameSite=Strict`,路径限制为 `{apiPrefix}/auth/refresh`。生产默认还会设置 `Secure`。
|
|
489
489
|
|
|
490
490
|
### API 概览
|
|
491
491
|
|
|
@@ -638,7 +638,9 @@ const app = serv.createApp({
|
|
|
638
638
|
})
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
|
|
641
|
+
生产环境要求 `_serv.yml` 的 `cors.origin` 显式列出允许的来源,并关闭 OpenAPI 和 docs。Web 与 Capacitor/Tauri Origin 按规范化后的完整值精确匹配。
|
|
642
|
+
|
|
643
|
+
受控内网或 VPN 的生产 HTTP 入口可以在后端进程环境设置 `HAI_SERV_REFRESH_COOKIE_SECURE=false`。重新启动后端并登录后,登录和刷新响应会下发省略 `Secure` 的 Cookie,刷新凭据会经 HTTP 明文传输。设置 `true` 可强制启用 `Secure`,留空或其他值沿用环境默认值。直接调用 `serv.createApp` 时,显式 `refreshCookie.secure` 优先,其次是此变量,再按 `NODE_ENV` 或 `HAI_CORE_ENV` 判断是否为生产。
|
|
642
644
|
|
|
643
645
|
### 配置驱动认证页面
|
|
644
646
|
|
package/dist/features/ai.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import * as _orpc_server from '@orpc/server';
|
|
|
2
2
|
import * as zod_v4_core from 'zod/v4/core';
|
|
3
3
|
import * as zod from 'zod';
|
|
4
4
|
import { AIFunctions } from '@h-ai/ai';
|
|
5
|
-
import { S as ServContext } from '../serv-context-
|
|
6
|
-
import { A as AuthenticatedServContext } from '../serv-router-
|
|
5
|
+
import { S as ServContext } from '../serv-context-BsNvQ-bb.js';
|
|
6
|
+
import { A as AuthenticatedServContext } from '../serv-router-BnkxPvS6.js';
|
|
7
7
|
import '@h-ai/core';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -2,8 +2,8 @@ import * as _orpc_server from '@orpc/server';
|
|
|
2
2
|
import * as zod from 'zod';
|
|
3
3
|
import * as zod_v4_core from 'zod/v4/core';
|
|
4
4
|
import { FeedbackFunctions, FeedbackContext } from '@h-ai/feedback';
|
|
5
|
-
import { S as ServContext } from '../serv-context-
|
|
6
|
-
import { A as AuthenticatedServContext } from '../serv-router-
|
|
5
|
+
import { S as ServContext } from '../serv-context-BsNvQ-bb.js';
|
|
6
|
+
import { A as AuthenticatedServContext } from '../serv-router-BnkxPvS6.js';
|
|
7
7
|
import '@h-ai/core';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/features/iam.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as zod_v4_core from 'zod/v4/core';
|
|
|
4
4
|
import * as zod from 'zod';
|
|
5
5
|
import { IamFunctions } from '@h-ai/iam';
|
|
6
6
|
export { TokenPair } from '@h-ai/iam';
|
|
7
|
-
import { S as ServContext } from '../serv-context-
|
|
7
|
+
import { S as ServContext } from '../serv-context-BsNvQ-bb.js';
|
|
8
8
|
import '@h-ai/core';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -2,8 +2,8 @@ import * as _orpc_server from '@orpc/server';
|
|
|
2
2
|
import * as zod_v4_core from 'zod/v4/core';
|
|
3
3
|
import * as zod from 'zod';
|
|
4
4
|
import { StorageFunctions } from '@h-ai/storage';
|
|
5
|
-
import { S as ServContext } from '../serv-context-
|
|
6
|
-
import { A as AuthenticatedServContext } from '../serv-router-
|
|
5
|
+
import { S as ServContext } from '../serv-context-BsNvQ-bb.js';
|
|
6
|
+
import { A as AuthenticatedServContext } from '../serv-router-BnkxPvS6.js';
|
|
7
7
|
import '@h-ai/core';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -3,15 +3,15 @@ import { HaiResult, TrustedProxies, RequestAccessConfigInput, ValidationFormErro
|
|
|
3
3
|
import { AIFunctions, AudioWsStartMessage, AudioFormat } from '@h-ai/ai';
|
|
4
4
|
import { AnyContractRouter, OpenAPI } from '@orpc/contract';
|
|
5
5
|
import { ProcedureHandler, ErrorMap, Meta, ProcedureHandlerOptions, ORPCErrorConstructorMap, Router } from '@orpc/server';
|
|
6
|
-
import { a as ServSession, S as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, c as ServRequestMetadata, p as parseRequestContext, d as buildAuthContextFactory } from './serv-context-
|
|
7
|
-
export { e as CreateServContextInput, N as NativeRefreshTokenTransportConfig, f as RefreshTokenPair } from './serv-context-
|
|
6
|
+
import { a as ServSession, S as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, c as ServRequestMetadata, p as parseRequestContext, d as buildAuthContextFactory } from './serv-context-BsNvQ-bb.js';
|
|
7
|
+
export { e as CreateServContextInput, N as NativeRefreshTokenTransportConfig, f as RefreshTokenPair } from './serv-context-BsNvQ-bb.js';
|
|
8
8
|
import { MiddlewareHandler } from 'hono';
|
|
9
9
|
import { z, ZodType } from 'zod';
|
|
10
10
|
import { TransportServerKeyRing, CryptoFunctions, TransportKeyStore } from '@h-ai/crypto';
|
|
11
11
|
import { ServerType } from '@hono/node-server';
|
|
12
12
|
import { AddressInfo } from 'node:net';
|
|
13
|
-
import { i as implement } from './serv-router-
|
|
14
|
-
export { A as AuthenticatedServContext, S as ServImplementer, a as ServRouteHandler, b as ServRoutePath, c as ServRouterBuilder } from './serv-router-
|
|
13
|
+
import { i as implement } from './serv-router-BnkxPvS6.js';
|
|
14
|
+
export { A as AuthenticatedServContext, S as ServImplementer, a as ServRouteHandler, b as ServRoutePath, c as ServRouterBuilder } from './serv-router-BnkxPvS6.js';
|
|
15
15
|
import { StorageFunctions } from '@h-ai/storage';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -1120,11 +1120,13 @@ interface ServRuntimeSecurityPolicyInput {
|
|
|
1120
1120
|
interface ServRuntimeSecurityPolicy {
|
|
1121
1121
|
readonly allowOrigin: (origin: string) => boolean;
|
|
1122
1122
|
readonly isNativeOrigin: (origin: string) => boolean;
|
|
1123
|
+
/** 刷新 Cookie 是否启用 Secure,默认在生产开启。 */
|
|
1123
1124
|
readonly secureRefreshCookie: boolean;
|
|
1124
1125
|
readonly exposeApiDocs: boolean;
|
|
1125
1126
|
}
|
|
1126
1127
|
/**
|
|
1127
1128
|
* 校验部署 Origin 并生成 Cookie、文档与跨端 CORS 安全策略。
|
|
1129
|
+
* `HAI_SERV_REFRESH_COOKIE_SECURE` 可覆盖刷新 Cookie 的 Secure 默认值。
|
|
1128
1130
|
*
|
|
1129
1131
|
* @param input - 部署环境与显式允许的 Web/原生 Origin
|
|
1130
1132
|
* @returns 可用于 HTTP 装配的策略
|
package/dist/index.js
CHANGED
|
@@ -702,6 +702,81 @@ function buildAuthContextFactory(verifyToken) {
|
|
|
702
702
|
}
|
|
703
703
|
};
|
|
704
704
|
}
|
|
705
|
+
function createRuntimeSecurityPolicy(input) {
|
|
706
|
+
const production = input.environment?.trim().toLowerCase() === "production";
|
|
707
|
+
const configuredOrigins = parseOrigins(input.corsOrigin);
|
|
708
|
+
const configuredNativeOrigins = parseNativeOrigins(input.nativeOrigins);
|
|
709
|
+
const allowsEveryOrigin = configuredOrigins === "*";
|
|
710
|
+
if (production && (allowsEveryOrigin || configuredOrigins.length === 0))
|
|
711
|
+
throw new Error("serv.cors.origin must contain explicit HTTP(S) origins in production");
|
|
712
|
+
const allowedOrigins = allowsEveryOrigin ? void 0 : new Set(configuredOrigins);
|
|
713
|
+
const allowedNativeOrigins = new Set(configuredNativeOrigins);
|
|
714
|
+
return {
|
|
715
|
+
allowOrigin: (origin) => {
|
|
716
|
+
const normalizedNativeOrigin = tryNormalizeNativeOrigin(origin);
|
|
717
|
+
if (normalizedNativeOrigin !== void 0 && allowedNativeOrigins.has(normalizedNativeOrigin))
|
|
718
|
+
return true;
|
|
719
|
+
if (allowedOrigins === void 0)
|
|
720
|
+
return true;
|
|
721
|
+
const normalizedWebOrigin = tryNormalizeWebOrigin(origin);
|
|
722
|
+
return normalizedWebOrigin !== void 0 && allowedOrigins.has(normalizedWebOrigin);
|
|
723
|
+
},
|
|
724
|
+
isNativeOrigin: (origin) => allowedNativeOrigins.has(tryNormalizeNativeOrigin(origin) ?? ""),
|
|
725
|
+
secureRefreshCookie: resolveSecureRefreshCookie(production),
|
|
726
|
+
exposeApiDocs: !production
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function resolveSecureRefreshCookie(defaultSecure) {
|
|
730
|
+
const override = process.env.HAI_SERV_REFRESH_COOKIE_SECURE;
|
|
731
|
+
if (override === "true" || override === "false")
|
|
732
|
+
return override === "true";
|
|
733
|
+
return defaultSecure;
|
|
734
|
+
}
|
|
735
|
+
function parseOrigins(value) {
|
|
736
|
+
const entries = (value ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
737
|
+
if (entries.includes("*"))
|
|
738
|
+
return "*";
|
|
739
|
+
return entries.map(normalizeWebOrigin);
|
|
740
|
+
}
|
|
741
|
+
function parseNativeOrigins(value) {
|
|
742
|
+
return (value ?? "").split(",").map((origin) => origin.trim()).filter(Boolean).map(normalizeNativeOrigin);
|
|
743
|
+
}
|
|
744
|
+
function normalizeWebOrigin(value) {
|
|
745
|
+
const normalized = tryNormalizeWebOrigin(value);
|
|
746
|
+
if (normalized === void 0)
|
|
747
|
+
throw new Error(`serv.cors.origin contains an invalid origin: ${value}`);
|
|
748
|
+
return normalized;
|
|
749
|
+
}
|
|
750
|
+
function normalizeNativeOrigin(value) {
|
|
751
|
+
const normalized = tryNormalizeNativeOrigin(value);
|
|
752
|
+
if (normalized === void 0)
|
|
753
|
+
throw new Error(`serv.cors.nativeOrigins contains an invalid origin: ${value}`);
|
|
754
|
+
return normalized;
|
|
755
|
+
}
|
|
756
|
+
function tryNormalizeWebOrigin(value) {
|
|
757
|
+
let url;
|
|
758
|
+
try {
|
|
759
|
+
url = new URL(value);
|
|
760
|
+
} catch {
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
if (!["http:", "https:"].includes(url.protocol) || url.origin === "null" || url.pathname !== "/" || url.search || url.hash)
|
|
764
|
+
return void 0;
|
|
765
|
+
return url.origin;
|
|
766
|
+
}
|
|
767
|
+
function tryNormalizeNativeOrigin(value) {
|
|
768
|
+
let url;
|
|
769
|
+
try {
|
|
770
|
+
url = new URL(value);
|
|
771
|
+
} catch {
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
if (!["http:", "https:", "capacitor:", "tauri:"].includes(url.protocol) || !url.hostname || url.username || url.password || !["", "/"].includes(url.pathname) || url.search || url.hash)
|
|
775
|
+
return void 0;
|
|
776
|
+
return ["capacitor:", "tauri:"].includes(url.protocol) ? `${url.protocol}//${url.host}` : url.origin;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/serv-cookie-auth.ts
|
|
705
780
|
var IAM_AUTH_PATHS = {
|
|
706
781
|
login: apiContract.pathOf(apiContract.iam.auth.login),
|
|
707
782
|
loginWithOtp: apiContract.pathOf(apiContract.iam.auth.loginWithOtp),
|
|
@@ -721,7 +796,7 @@ function resolveRefreshCookieConfig(apiPrefix, config, iam) {
|
|
|
721
796
|
return {
|
|
722
797
|
cookieName: config.cookieName ?? "hai_refresh_token",
|
|
723
798
|
maxAge: config.maxAge ?? 30 * 24 * 3600,
|
|
724
|
-
isSecure: config.secure ?? (process.env.NODE_ENV === "production" || process.env.HAI_CORE_ENV === "production"),
|
|
799
|
+
isSecure: config.secure ?? resolveSecureRefreshCookie(process.env.NODE_ENV === "production" || process.env.HAI_CORE_ENV === "production"),
|
|
725
800
|
refreshCookiePath: `${apiPrefix}${IAM_AUTH_PATHS.refresh}`,
|
|
726
801
|
onRefresh,
|
|
727
802
|
nativeTokenTransport: config.nativeTokenTransport
|
|
@@ -1482,75 +1557,6 @@ function listen(app, options = {}) {
|
|
|
1482
1557
|
return handle;
|
|
1483
1558
|
}
|
|
1484
1559
|
|
|
1485
|
-
// src/serv-runtime-security.ts
|
|
1486
|
-
function createRuntimeSecurityPolicy(input) {
|
|
1487
|
-
const production = input.environment?.trim().toLowerCase() === "production";
|
|
1488
|
-
const configuredOrigins = parseOrigins(input.corsOrigin);
|
|
1489
|
-
const configuredNativeOrigins = parseNativeOrigins(input.nativeOrigins);
|
|
1490
|
-
const allowsEveryOrigin = configuredOrigins === "*";
|
|
1491
|
-
if (production && (allowsEveryOrigin || configuredOrigins.length === 0))
|
|
1492
|
-
throw new Error("serv.cors.origin must contain explicit HTTP(S) origins in production");
|
|
1493
|
-
const allowedOrigins = allowsEveryOrigin ? void 0 : new Set(configuredOrigins);
|
|
1494
|
-
const allowedNativeOrigins = new Set(configuredNativeOrigins);
|
|
1495
|
-
return {
|
|
1496
|
-
allowOrigin: (origin) => {
|
|
1497
|
-
const normalizedNativeOrigin = tryNormalizeNativeOrigin(origin);
|
|
1498
|
-
if (normalizedNativeOrigin !== void 0 && allowedNativeOrigins.has(normalizedNativeOrigin))
|
|
1499
|
-
return true;
|
|
1500
|
-
if (allowedOrigins === void 0)
|
|
1501
|
-
return true;
|
|
1502
|
-
const normalizedWebOrigin = tryNormalizeWebOrigin(origin);
|
|
1503
|
-
return normalizedWebOrigin !== void 0 && allowedOrigins.has(normalizedWebOrigin);
|
|
1504
|
-
},
|
|
1505
|
-
isNativeOrigin: (origin) => allowedNativeOrigins.has(tryNormalizeNativeOrigin(origin) ?? ""),
|
|
1506
|
-
secureRefreshCookie: production,
|
|
1507
|
-
exposeApiDocs: !production
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
function parseOrigins(value) {
|
|
1511
|
-
const entries = (value ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
1512
|
-
if (entries.includes("*"))
|
|
1513
|
-
return "*";
|
|
1514
|
-
return entries.map(normalizeWebOrigin);
|
|
1515
|
-
}
|
|
1516
|
-
function parseNativeOrigins(value) {
|
|
1517
|
-
return (value ?? "").split(",").map((origin) => origin.trim()).filter(Boolean).map(normalizeNativeOrigin);
|
|
1518
|
-
}
|
|
1519
|
-
function normalizeWebOrigin(value) {
|
|
1520
|
-
const normalized = tryNormalizeWebOrigin(value);
|
|
1521
|
-
if (normalized === void 0)
|
|
1522
|
-
throw new Error(`serv.cors.origin contains an invalid origin: ${value}`);
|
|
1523
|
-
return normalized;
|
|
1524
|
-
}
|
|
1525
|
-
function normalizeNativeOrigin(value) {
|
|
1526
|
-
const normalized = tryNormalizeNativeOrigin(value);
|
|
1527
|
-
if (normalized === void 0)
|
|
1528
|
-
throw new Error(`serv.cors.nativeOrigins contains an invalid origin: ${value}`);
|
|
1529
|
-
return normalized;
|
|
1530
|
-
}
|
|
1531
|
-
function tryNormalizeWebOrigin(value) {
|
|
1532
|
-
let url;
|
|
1533
|
-
try {
|
|
1534
|
-
url = new URL(value);
|
|
1535
|
-
} catch {
|
|
1536
|
-
return void 0;
|
|
1537
|
-
}
|
|
1538
|
-
if (!["http:", "https:"].includes(url.protocol) || url.origin === "null" || url.pathname !== "/" || url.search || url.hash)
|
|
1539
|
-
return void 0;
|
|
1540
|
-
return url.origin;
|
|
1541
|
-
}
|
|
1542
|
-
function tryNormalizeNativeOrigin(value) {
|
|
1543
|
-
let url;
|
|
1544
|
-
try {
|
|
1545
|
-
url = new URL(value);
|
|
1546
|
-
} catch {
|
|
1547
|
-
return void 0;
|
|
1548
|
-
}
|
|
1549
|
-
if (!["http:", "https:", "capacitor:", "tauri:"].includes(url.protocol) || !url.hostname || url.username || url.password || !["", "/"].includes(url.pathname) || url.search || url.hash)
|
|
1550
|
-
return void 0;
|
|
1551
|
-
return ["capacitor:", "tauri:"].includes(url.protocol) ? `${url.protocol}//${url.host}` : url.origin;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
1560
|
// src/serv-storage-assets.ts
|
|
1555
1561
|
function storageAssets(config) {
|
|
1556
1562
|
const pathPrefix = config.pathPrefix.endsWith("/") ? config.pathPrefix : `${config.pathPrefix}/`;
|