@h-ai/serv 0.1.0-alpha.46 → 0.1.0-alpha.47

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.
@@ -87,7 +87,7 @@ interface RefreshCookieConfig {
87
87
  * 与 Bearer accessToken。**不会**填充 `session`。
88
88
  * - `buildAuthContextFactory(verifyToken)`:**带认证的上下文工厂**。在 `parseRequestContext` 基础上
89
89
  * 异步调用 `verifyToken(accessToken)` 校验访问令牌,若成功则把会话写入 `context.session`。
90
- * 失败或 token 不存在均使 `session = undefined`,由下游 `requireAuth` 统一返回 401。
90
+ * 失败或 token 不存在均使 `session = undefined`,由下游 `.auth()` guard 统一返回 401。
91
91
  *
92
92
  * 应用层通常无需直接调用上述函数:`serv.createApp({ iam })` 内部自动选择最合适的工厂。
93
93
  * @module serv-context
@@ -156,7 +156,7 @@ declare function parseRequestContext(input: CreateServContextInput): ServContext
156
156
  * 1. 调用 `parseRequestContext` 提取元数据 + accessToken(仅来自 Authorization Bearer)
157
157
  * 2. 若 `accessToken` 存在 → 调用 `verifyToken(accessToken)`
158
158
  * 3. `verifyToken` 成功 → `session = result.data`;失败或 token 缺失 → `session = undefined`
159
- * 4. 下游 `requireAuth` / `requirePermission` / `requireRole` 据此判断 401/403
159
+ * 4. 下游 `.auth()` / `.permission()` / `.role()` guard 据此判断 401/403
160
160
  *
161
161
  * refresh token **不参与**这里的认证流程:它只在 access token 过期后,被 `/auth/refresh`
162
162
  * 从浏览器 httpOnly cookie 或受信原生 JSON body 中读出,用于换发新的 access token。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/serv",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.46",
4
+ "version": "0.1.0-alpha.47",
5
5
  "description": "Hai Framework service runtime powered by Hono and oRPC.",
6
6
  "license": "Apache-2.0",
7
7
  "exports": {
@@ -32,9 +32,9 @@
32
32
  "access": "public"
33
33
  },
34
34
  "peerDependencies": {
35
- "@h-ai/iam": "0.1.0-alpha.46",
36
- "@h-ai/storage": "0.1.0-alpha.46",
37
- "@h-ai/ai": "0.1.0-alpha.46"
35
+ "@h-ai/ai": "0.1.0-alpha.47",
36
+ "@h-ai/storage": "0.1.0-alpha.47",
37
+ "@h-ai/iam": "0.1.0-alpha.47"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@h-ai/ai": {
@@ -56,9 +56,9 @@
56
56
  "@scalar/api-reference": "^1.57.1",
57
57
  "hono": "^4.12.18",
58
58
  "zod": "^4.4.3",
59
- "@h-ai/api-contract": "0.1.0-alpha.46",
60
- "@h-ai/core": "0.1.0-alpha.46",
61
- "@h-ai/crypto": "0.1.0-alpha.46"
59
+ "@h-ai/api-contract": "0.1.0-alpha.47",
60
+ "@h-ai/crypto": "0.1.0-alpha.47",
61
+ "@h-ai/core": "0.1.0-alpha.47"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/node": "^25.9.1",
@@ -66,9 +66,9 @@
66
66
  "tsup": "^8.5.1",
67
67
  "typescript": "^6.0.3",
68
68
  "vitest": "^4.1.7",
69
- "@h-ai/iam": "0.1.0-alpha.46",
70
- "@h-ai/ai": "0.1.0-alpha.46",
71
- "@h-ai/storage": "0.1.0-alpha.46"
69
+ "@h-ai/ai": "0.1.0-alpha.47",
70
+ "@h-ai/iam": "0.1.0-alpha.47",
71
+ "@h-ai/storage": "0.1.0-alpha.47"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsup",
@@ -1,128 +0,0 @@
1
- import { core, err, HaiCommonError } from '@h-ai/core';
2
-
3
- // src/serv-i18n.ts
4
-
5
- // messages/en-US.json
6
- var en_US_default = {
7
- $schema: "https://inlang.com/schema/inlang-message-format",
8
- serv_validationFailed: "Validation failed",
9
- serv_validationRequired: "This field is required",
10
- serv_validationInvalid: "Invalid input format",
11
- serv_validationInvalidType: "Invalid field type",
12
- serv_validationStringMin: "Enter at least {min} characters",
13
- serv_validationStringMax: "Enter at most {max} characters",
14
- serv_validationNumberMin: "Value must be at least {min}",
15
- serv_validationNumberMax: "Value must be at most {max}",
16
- serv_validationArrayMin: "Select at least {min} item(s)",
17
- serv_validationArrayMax: "Select at most {max} item(s)",
18
- serv_validationTooSmall: "Value must be at least {min}",
19
- serv_validationTooBig: "Value must be at most {max}",
20
- serv_validationEmail: "Enter a valid email address",
21
- serv_validationUrl: "Enter a valid URL",
22
- serv_validationUuid: "Enter a valid UUID",
23
- serv_validationEnum: "Select a valid option",
24
- serv_errorUnauthorized: "Unauthorized",
25
- serv_errorForbidden: "Forbidden",
26
- serv_errorInternal: "Internal server error",
27
- serv_storageKeyRequired: "Storage key must not be empty",
28
- serv_storageKeyTooLong: "Storage key must not exceed {max} characters",
29
- serv_storageKeyIllegalChars: "Storage key contains illegal characters",
30
- serv_storageKeyDotSegments: "Storage key must not contain . or .. path segments",
31
- serv_iamCannotDeleteCurrentUser: "Cannot delete the current logged-in user",
32
- serv_transportInvalidPayload: "Invalid encrypted transport payload",
33
- serv_transportUnknownClient: "Unknown encryption client; key exchange required",
34
- serv_transportClientIdRequired: "Missing X-Client-Id header; key exchange required",
35
- serv_transportClientKeyNotFound: "Client not registered or session expired; complete key exchange again",
36
- serv_transportDecryptFailed: "Failed to decrypt request",
37
- serv_transportEncryptFailed: "Failed to encrypt response",
38
- serv_transportKeyExchangeFailed: "Key exchange failed"
39
- };
40
-
41
- // messages/zh-CN.json
42
- var zh_CN_default = {
43
- $schema: "https://inlang.com/schema/inlang-message-format",
44
- serv_validationFailed: "\u6570\u636E\u9A8C\u8BC1\u5931\u8D25",
45
- serv_validationRequired: "\u6B64\u5B57\u6BB5\u5FC5\u586B",
46
- serv_validationInvalid: "\u8F93\u5165\u683C\u5F0F\u65E0\u6548",
47
- serv_validationInvalidType: "\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E",
48
- serv_validationStringMin: "\u81F3\u5C11\u8F93\u5165 {min} \u4E2A\u5B57\u7B26",
49
- serv_validationStringMax: "\u6700\u591A\u8F93\u5165 {max} \u4E2A\u5B57\u7B26",
50
- serv_validationNumberMin: "\u6570\u503C\u4E0D\u80FD\u5C0F\u4E8E {min}",
51
- serv_validationNumberMax: "\u6570\u503C\u4E0D\u80FD\u5927\u4E8E {max}",
52
- serv_validationArrayMin: "\u81F3\u5C11\u9009\u62E9 {min} \u9879",
53
- serv_validationArrayMax: "\u6700\u591A\u9009\u62E9 {max} \u9879",
54
- serv_validationTooSmall: "\u4E0D\u80FD\u5C0F\u4E8E {min}",
55
- serv_validationTooBig: "\u4E0D\u80FD\u5927\u4E8E {max}",
56
- serv_validationEmail: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740",
57
- serv_validationUrl: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u7F51\u5740",
58
- serv_validationUuid: "\u8BF7\u8F93\u5165\u6709\u6548\u7684 UUID",
59
- serv_validationEnum: "\u8BF7\u9009\u62E9\u6709\u6548\u9009\u9879",
60
- serv_errorUnauthorized: "\u672A\u767B\u5F55\u6216\u767B\u5F55\u5DF2\u5931\u6548",
61
- serv_errorForbidden: "\u65E0\u6743\u6267\u884C\u8BE5\u64CD\u4F5C",
62
- serv_errorInternal: "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF",
63
- serv_storageKeyRequired: "\u5B58\u50A8 key \u4E0D\u80FD\u4E3A\u7A7A",
64
- serv_storageKeyTooLong: "\u5B58\u50A8 key \u4E0D\u80FD\u8D85\u8FC7 {max} \u4E2A\u5B57\u7B26",
65
- serv_storageKeyIllegalChars: "\u5B58\u50A8 key \u5305\u542B\u975E\u6CD5\u5B57\u7B26",
66
- serv_storageKeyDotSegments: "\u5B58\u50A8 key \u4E0D\u80FD\u5305\u542B . \u6216 .. \u8DEF\u5F84\u6BB5",
67
- serv_iamCannotDeleteCurrentUser: "\u4E0D\u80FD\u5220\u9664\u5F53\u524D\u767B\u5F55\u7528\u6237",
68
- serv_transportInvalidPayload: "\u4F20\u8F93\u5C42\u52A0\u5BC6\u8F7D\u8377\u683C\u5F0F\u65E0\u6548",
69
- serv_transportUnknownClient: "\u672A\u8BC6\u522B\u7684\u52A0\u5BC6\u5BA2\u6237\u7AEF\uFF0C\u8BF7\u5148\u5B8C\u6210\u5BC6\u94A5\u534F\u5546",
70
- serv_transportClientIdRequired: "\u7F3A\u5C11 X-Client-Id \u8BF7\u6C42\u5934\uFF0C\u8BF7\u5148\u5B8C\u6210\u5BC6\u94A5\u534F\u5546",
71
- serv_transportClientKeyNotFound: "\u5BA2\u6237\u7AEF\u672A\u6CE8\u518C\u6216\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u5B8C\u6210\u5BC6\u94A5\u534F\u5546",
72
- serv_transportDecryptFailed: "\u8BF7\u6C42\u89E3\u5BC6\u5931\u8D25",
73
- serv_transportEncryptFailed: "\u54CD\u5E94\u52A0\u5BC6\u5931\u8D25",
74
- serv_transportKeyExchangeFailed: "\u5BC6\u94A5\u534F\u5546\u5931\u8D25"
75
- };
76
-
77
- // src/serv-i18n.ts
78
- var SUPPORTED_SERV_LOCALES = ["zh-CN", "en-US"];
79
- var DEFAULT_SERV_LOCALE = "zh-CN";
80
- function normalizeServLocale(locale) {
81
- const candidate = locale?.split(",")[0]?.trim();
82
- if (!candidate)
83
- return DEFAULT_SERV_LOCALE;
84
- const exact = SUPPORTED_SERV_LOCALES.find((item) => item.toLowerCase() === candidate.toLowerCase());
85
- if (exact)
86
- return exact;
87
- const baseLanguage = candidate.split("-")[0]?.toLowerCase();
88
- if (baseLanguage === "en")
89
- return "en-US";
90
- if (baseLanguage === "zh")
91
- return "zh-CN";
92
- return DEFAULT_SERV_LOCALE;
93
- }
94
- var servM = core.i18n.createMessageGetter({ "zh-CN": zh_CN_default, "en-US": en_US_default });
95
- function buildHaiErrorBody(def, message) {
96
- return {
97
- success: false,
98
- error: {
99
- code: def.code,
100
- message,
101
- httpStatus: def.httpStatus,
102
- system: def.system,
103
- module: def.module
104
- }
105
- };
106
- }
107
- function mapHaiError(handler) {
108
- return async (options) => {
109
- try {
110
- return await handler(options);
111
- } catch (error) {
112
- return err(HaiCommonError.INTERNAL_ERROR, servM("serv_errorInternal", { locale: options.context.locale }), error);
113
- }
114
- };
115
- }
116
-
117
- // src/pipelines/serv-pipeline-require-auth.ts
118
- function requireAuth(handler) {
119
- return mapHaiError(async (options) => {
120
- if (!options.context.session)
121
- return err(HaiCommonError.UNAUTHORIZED, servM("serv_errorUnauthorized", { locale: options.context.locale }));
122
- return handler(options);
123
- });
124
- }
125
-
126
- export { buildHaiErrorBody, mapHaiError, normalizeServLocale, requireAuth, servM };
127
- //# sourceMappingURL=chunk-4QBF4PUG.js.map
128
- //# sourceMappingURL=chunk-4QBF4PUG.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../messages/en-US.json","../messages/zh-CN.json","../src/serv-i18n.ts","../src/pipelines/serv-pipeline-helper.ts","../src/pipelines/serv-pipeline-require-auth.ts"],"names":["err","HaiCommonError"],"mappings":"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","file":"chunk-4QBF4PUG.js","sourcesContent":["{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"serv_validationFailed\": \"Validation failed\",\n \"serv_validationRequired\": \"This field is required\",\n \"serv_validationInvalid\": \"Invalid input format\",\n \"serv_validationInvalidType\": \"Invalid field type\",\n \"serv_validationStringMin\": \"Enter at least {min} characters\",\n \"serv_validationStringMax\": \"Enter at most {max} characters\",\n \"serv_validationNumberMin\": \"Value must be at least {min}\",\n \"serv_validationNumberMax\": \"Value must be at most {max}\",\n \"serv_validationArrayMin\": \"Select at least {min} item(s)\",\n \"serv_validationArrayMax\": \"Select at most {max} item(s)\",\n \"serv_validationTooSmall\": \"Value must be at least {min}\",\n \"serv_validationTooBig\": \"Value must be at most {max}\",\n \"serv_validationEmail\": \"Enter a valid email address\",\n \"serv_validationUrl\": \"Enter a valid URL\",\n \"serv_validationUuid\": \"Enter a valid UUID\",\n \"serv_validationEnum\": \"Select a valid option\",\n \"serv_errorUnauthorized\": \"Unauthorized\",\n \"serv_errorForbidden\": \"Forbidden\",\n \"serv_errorInternal\": \"Internal server error\",\n \"serv_storageKeyRequired\": \"Storage key must not be empty\",\n \"serv_storageKeyTooLong\": \"Storage key must not exceed {max} characters\",\n \"serv_storageKeyIllegalChars\": \"Storage key contains illegal characters\",\n \"serv_storageKeyDotSegments\": \"Storage key must not contain . or .. path segments\",\n \"serv_iamCannotDeleteCurrentUser\": \"Cannot delete the current logged-in user\",\n \"serv_transportInvalidPayload\": \"Invalid encrypted transport payload\",\n \"serv_transportUnknownClient\": \"Unknown encryption client; key exchange required\",\n \"serv_transportClientIdRequired\": \"Missing X-Client-Id header; key exchange required\",\n \"serv_transportClientKeyNotFound\": \"Client not registered or session expired; complete key exchange again\",\n \"serv_transportDecryptFailed\": \"Failed to decrypt request\",\n \"serv_transportEncryptFailed\": \"Failed to encrypt response\",\n \"serv_transportKeyExchangeFailed\": \"Key exchange failed\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"serv_validationFailed\": \"数据验证失败\",\n \"serv_validationRequired\": \"此字段必填\",\n \"serv_validationInvalid\": \"输入格式无效\",\n \"serv_validationInvalidType\": \"字段类型不正确\",\n \"serv_validationStringMin\": \"至少输入 {min} 个字符\",\n \"serv_validationStringMax\": \"最多输入 {max} 个字符\",\n \"serv_validationNumberMin\": \"数值不能小于 {min}\",\n \"serv_validationNumberMax\": \"数值不能大于 {max}\",\n \"serv_validationArrayMin\": \"至少选择 {min} 项\",\n \"serv_validationArrayMax\": \"最多选择 {max} 项\",\n \"serv_validationTooSmall\": \"不能小于 {min}\",\n \"serv_validationTooBig\": \"不能大于 {max}\",\n \"serv_validationEmail\": \"请输入有效的邮箱地址\",\n \"serv_validationUrl\": \"请输入有效的网址\",\n \"serv_validationUuid\": \"请输入有效的 UUID\",\n \"serv_validationEnum\": \"请选择有效选项\",\n \"serv_errorUnauthorized\": \"未登录或登录已失效\",\n \"serv_errorForbidden\": \"无权执行该操作\",\n \"serv_errorInternal\": \"服务器内部错误\",\n \"serv_storageKeyRequired\": \"存储 key 不能为空\",\n \"serv_storageKeyTooLong\": \"存储 key 不能超过 {max} 个字符\",\n \"serv_storageKeyIllegalChars\": \"存储 key 包含非法字符\",\n \"serv_storageKeyDotSegments\": \"存储 key 不能包含 . 或 .. 路径段\",\n \"serv_iamCannotDeleteCurrentUser\": \"不能删除当前登录用户\",\n \"serv_transportInvalidPayload\": \"传输层加密载荷格式无效\",\n \"serv_transportUnknownClient\": \"未识别的加密客户端,请先完成密钥协商\",\n \"serv_transportClientIdRequired\": \"缺少 X-Client-Id 请求头,请先完成密钥协商\",\n \"serv_transportClientKeyNotFound\": \"客户端未注册或会话已过期,请重新完成密钥协商\",\n \"serv_transportDecryptFailed\": \"请求解密失败\",\n \"serv_transportEncryptFailed\": \"响应加密失败\",\n \"serv_transportKeyExchangeFailed\": \"密钥协商失败\"\n}\n","/**\n * @h-ai/serv — i18n 工具\n *\n * Serv 模块的国际化消息工具,从 @h-ai/core 调用 i18n API。\n * 与 kit-i18n 结构对齐,便于跨包统一维护。\n * @module serv-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\ntype ServMessageKey = keyof typeof messagesZhCN\n\nconst SUPPORTED_SERV_LOCALES = ['zh-CN', 'en-US'] as const\nconst DEFAULT_SERV_LOCALE = 'zh-CN'\n\n/**\n * 规范化 serv 请求 locale。\n *\n * 支持:\n * - 完整 locale:`zh-CN` / `en-US`\n * - 简写:`zh` / `en`\n * - `Accept-Language` 风格值:`en-US,en;q=0.9`\n *\n * 未识别时回退到默认 `zh-CN`。\n */\nexport function normalizeServLocale(locale: string | undefined): string {\n const candidate = locale?.split(',')[0]?.trim()\n if (!candidate)\n return DEFAULT_SERV_LOCALE\n\n const exact = SUPPORTED_SERV_LOCALES.find(item => item.toLowerCase() === candidate.toLowerCase())\n if (exact)\n return exact\n\n const baseLanguage = candidate.split('-')[0]?.toLowerCase()\n if (baseLanguage === 'en')\n return 'en-US'\n if (baseLanguage === 'zh')\n return 'zh-CN'\n\n return DEFAULT_SERV_LOCALE\n}\n\n/**\n * 获取 Serv 模块的 i18n 消息。\n *\n * 单次调用可通过 `options.locale` 指定该次调用使用的语言,\n * 用于在请求处理过程中根据 `Accept-Language` 头本地化错误消息,\n * 而不污染全局 locale(多并发请求安全)。\n *\n * @example\n * ```ts\n * // 使用全局 locale\n * servM('serv_validationRequired')\n *\n * // 使用请求自身的 locale\n * servM('serv_validationStringMin', { locale: 'en-US', params: { min: 3 } })\n * ```\n */\nexport const servM\n = core.i18n.createMessageGetter<ServMessageKey>({ 'zh-CN': messagesZhCN, 'en-US': messagesEnUS })\n\nexport type { ServMessageKey }\n","/**\n * @h-ai/serv — Pipeline 公共 helper\n *\n * 放置不属于 Hono middleware / oRPC wrapper 默认实现本身、\n * 但会被多个 pipeline 复用的公共工具。\n * @module pipelines/serv-pipeline-helper\n */\n\nimport type { ServProcedureHandler } from './serv-pipeline-types.js'\nimport { err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\n\n/** 构造 `HaiResult` 失败分支同构的 HTTP 错误定义。 */\nexport interface ServErrorBodyDef {\n readonly code: string | number\n readonly httpStatus: number\n readonly system: string\n readonly module: string\n}\n\n/**\n * 构造与 `HaiResult` 失败分支同构的 HTTP 响应体。\n *\n * - 字段顺序与 `haiResultSchema()` 输出一致,便于客户端统一解析。\n * - 仅暴露最小信息(code/message/httpStatus),避免泄漏内部细节。\n */\nexport function buildHaiErrorBody(def: ServErrorBodyDef, message: string) {\n return {\n success: false as const,\n error: {\n code: def.code,\n message,\n httpStatus: def.httpStatus,\n system: def.system,\n module: def.module,\n },\n }\n}\n\n/**\n * 捕获未处理异常并转换为 HaiResult。\n *\n * 虽然它不是 middleware / 鉴权 wrapper,但属于所有 procedure pipeline\n * 都可能复用的“公共包装能力”,因此与 `buildHaiErrorBody()` 一起放在 helper 中。\n *\n * @param handler - 被包装的 procedure handler\n * @returns 带异常保护的新 handler\n *\n * @example\n * ```ts\n * const safeHandler = serv.mapHaiError(myHandler)\n * ```\n */\nexport function mapHaiError<TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput> {\n return async (options) => {\n try {\n return await handler(options)\n }\n catch (error) {\n // 所有未捕获异常统一在这里收口,避免直接把异常冒泡到 HTTP 层。\n return err(HaiCommonError.INTERNAL_ERROR, servM('serv_errorInternal', { locale: options.context.locale }), error)\n }\n }\n}\n","/**\n * @h-ai/serv — requireAuth 默认实现\n * @module pipelines/serv-pipeline-require-auth\n */\n\nimport type { ServProcedureHandler } from './serv-pipeline-types.js'\nimport { err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { mapHaiError } from './serv-pipeline-helper.js'\n\n/**\n * 需要已验证会话的 procedure 包装器。\n *\n * `context.session` 由 `buildAuthContextFactory(verifyToken)`(或 `serv.createApp({ iam })` 自动启用)填充。\n * token 不存在或无效(verifyToken 失败)时 `session` 均为 `undefined`,统一返回 401 UNAUTHORIZED。\n * 内置调用 `mapHaiError`,无需外层再次封装。\n *\n * @param handler - 被包装的 procedure handler\n * @returns 带认证检查的新 handler\n *\n * @example\n * ```ts\n * const protectedHandler = serv.requireAuth(myHandler)\n * ```\n */\nexport function requireAuth<TInput, TOutput>(handler: ServProcedureHandler<TInput, TOutput>): ServProcedureHandler<TInput, TOutput> {\n return mapHaiError(async (options) => {\n // 关键点:只认 `context.session`,不认“请求头里似乎带了 access token”。\n // session 只能来自 createContext/verifyToken 的成功校验结果,避免把未验证 token 当成已登录。\n if (!options.context.session)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: options.context.locale }))\n return handler(options)\n })\n}\n"]}
@@ -1,16 +0,0 @@
1
- import { requireAuth, servM } from './chunk-4QBF4PUG.js';
2
- import { err, HaiCommonError } from '@h-ai/core';
3
-
4
- var WILDCARD_PERMISSION = "*";
5
- function requirePermission(permission, handler) {
6
- return requireAuth(async (options) => {
7
- const permissions = options.context.session?.permissions ?? [];
8
- if (!permissions.includes(permission) && !permissions.includes(WILDCARD_PERMISSION))
9
- return err(HaiCommonError.FORBIDDEN, servM("serv_errorForbidden", { locale: options.context.locale }));
10
- return handler(options);
11
- });
12
- }
13
-
14
- export { WILDCARD_PERMISSION, requirePermission };
15
- //# sourceMappingURL=chunk-YQV2OMQK.js.map
16
- //# sourceMappingURL=chunk-YQV2OMQK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/pipelines/serv-pipeline-require-permission.ts"],"names":[],"mappings":";;;AAcO,IAAM,mBAAA,GAAsB;AAiB5B,SAAS,iBAAA,CACd,YACA,OAAA,EACuC;AACvC,EAAA,OAAO,WAAA,CAAY,OAAO,OAAA,KAAY;AAEpC,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,OAAA,EAAS,eAAe,EAAC;AAC7D,IAAA,IAAI,CAAC,YAAY,QAAA,CAAS,UAAU,KAAK,CAAC,WAAA,CAAY,SAAS,mBAAmB,CAAA;AAChF,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,SAAA,EAAW,KAAA,CAAM,qBAAA,EAAuB,EAAE,MAAA,EAAQ,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACvG,IAAA,OAAO,QAAQ,OAAO,CAAA;AAAA,EACxB,CAAC,CAAA;AACH","file":"chunk-YQV2OMQK.js","sourcesContent":["/**\n * @h-ai/serv — requirePermission 默认实现\n * @module pipelines/serv-pipeline-require-permission\n */\n\nimport type { ServProcedureHandler } from './serv-pipeline-types.js'\nimport { err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { requireAuth } from './serv-pipeline-require-auth.js'\n\n/**\n * 通配符权限:拥有此权限的用户自动通过所有 {@link requirePermission} 检查。\n * 用于超级管理员场景。**禁止**普通用户被分配此权限。\n */\nexport const WILDCARD_PERMISSION = '*'\n\n/**\n * 需要指定权限的 procedure 包装器。\n *\n * 内置调用 `requireAuth`,在验证 Token 后进一步匹配 `session.permissions`。\n * 拥有 {@link WILDCARD_PERMISSION}(`'*'`)的用户自动通过所有权限检查。\n *\n * @param permission - 所需权限字符串(如 `'iam.users.read'`)\n * @param handler - 被包装的 procedure handler\n * @returns 带权限检查的新 handler\n *\n * @example\n * ```ts\n * const adminHandler = serv.requirePermission('iam.users.write', myHandler)\n * ```\n */\nexport function requirePermission<TInput, TOutput>(\n permission: string,\n handler: ServProcedureHandler<TInput, TOutput>,\n): ServProcedureHandler<TInput, TOutput> {\n return requireAuth(async (options) => {\n // 先认证,再鉴权;未登录请求在 requireAuth 中已经被挡住。\n const permissions = options.context.session?.permissions ?? []\n if (!permissions.includes(permission) && !permissions.includes(WILDCARD_PERMISSION))\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: options.context.locale }))\n return handler(options)\n })\n}\n"]}