@h-ai/serv 1.0.0-beta.2 → 1.0.0-beta.20
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 +172 -124
- package/README.ja.md +172 -124
- package/README.md +172 -124
- package/dist/{chunk-TWMOZPGU.js → chunk-CZ6VA2EN.js} +3 -3
- package/dist/{chunk-TWMOZPGU.js.map → chunk-CZ6VA2EN.js.map} +1 -1
- package/dist/{chunk-I3F7RN4I.js → chunk-PLGCHDZM.js} +30 -13
- package/dist/chunk-PLGCHDZM.js.map +1 -0
- package/dist/features/ai.d.ts +5 -4
- package/dist/features/ai.js +34 -13
- package/dist/features/ai.js.map +1 -1
- package/dist/features/feedback.d.ts +20 -19
- package/dist/features/feedback.js +1 -1
- package/dist/features/feedback.js.map +1 -1
- package/dist/features/iam.d.ts +4 -3
- package/dist/features/iam.js +4 -2
- package/dist/features/iam.js.map +1 -1
- package/dist/features/storage.d.ts +4 -3
- package/dist/features/storage.js +34 -9
- package/dist/features/storage.js.map +1 -1
- package/dist/index.d.ts +97 -68
- package/dist/index.js +338 -222
- package/dist/index.js.map +1 -1
- package/dist/{serv-context-DE1JH5bd.d.ts → serv-context-DSsS_ksL.d.ts} +4 -60
- package/dist/{serv-router-BJk04-oS.d.ts → serv-router-DWjYC0zD.d.ts} +2 -18
- package/package.json +29 -29
- package/dist/chunk-I3F7RN4I.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/features/serv-feature-feedback.ts"],"names":[],"mappings":"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","file":"feedback.js","sourcesContent":["/** @h-ai/serv — 按需挂载反馈领域,不接收客户端身份或对象存储路径。 */\nimport type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\nimport type { ServContext } from '../serv-context.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\n\n/**\n * 已认证请求到反馈范围的可信映射,返回 null 时拒绝访问。\n * @example\n * ```ts\n * import type { FeedbackProcedureDeps } from '@h-ai/serv/features/feedback'\n * import { feedback } from '@h-ai/feedback'\n * const deps: FeedbackProcedureDeps = { feedback, resolveContext: ({ session }) => ({ scopeId: 'tenant-from-server', userId: session.userId, permissions: session.permissions }) }\n * ```\n */\nexport interface FeedbackProcedureDeps {\n readonly feedback: FeedbackFunctions\n readonly resolveContext: (context: AuthenticatedServContext) => FeedbackContext | null | Promise<FeedbackContext | null>\n}\n\n/**\n * 创建反馈的默认 procedures。宿主解析租户与权限,领域模块再次检查资源范围。\n * @example\n * ```ts\n * import { feedback } from '@h-ai/feedback'\n * import { createFeedbackProcedures } from '@h-ai/serv/features/feedback'\n * const procedures = createFeedbackProcedures({ feedback, resolveContext: ({ session }) => ({ scopeId: 'trusted-tenant', userId: session.userId, permissions: session.permissions }) })\n * ```\n */\nexport function createFeedbackProcedures(deps: FeedbackProcedureDeps) {\n const contextFor = async (context: AuthenticatedServContext) => {\n try {\n const actor = await deps.resolveContext(context)\n if (!actor?.scopeId || actor.userId !== context.session.userId)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: context.locale }))\n return { success: true as const, data: actor }\n }\n catch {\n return err(HaiCommonError.INTERNAL_ERROR, servM('serv_errorInternal', { locale: context.locale }))\n }\n }\n const router = implement(apiContract.feedback).context<ServContext>()\n return router\n .route('reports.policy')\n .auth()\n .handle(async ({ context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return deps.feedback.isInitialized === false ? err(HaiCommonError.SERVICE_UNAVAILABLE, servM('serv_feedbackUnavailable')) : ok(deps.feedback.budget)\n })\n .route('reports.submit')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.submit(input, actor.data))\n })\n .route('reports.list')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.list(input, actor.data))\n })\n .route('reports.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.get(input.id, actor.data))\n })\n .route('reports.getEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getEvidence(input, actor.data))\n })\n .route('reports.setStatus')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.setStatus(input, actor.data))\n })\n .route('reports.retry')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.retry(input, actor.data))\n })\n .route('reports.export')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.export(input, actor.data))\n })\n .route('reports.getExport')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getExport(input, actor.data))\n })\n .route('reports.appendEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.appendEvidence(input, actor.data))\n })\n .route('reports.completeCollection')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.completeCollection(input, actor.data))\n })\n .route('attachments.upload')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.uploadAttachment(input, actor.data))\n })\n .route('attachments.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getAttachment(input, actor.data))\n })\n .build()\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/features/serv-feature-feedback.ts"],"names":[],"mappings":";;;;AAgCO,SAAS,yBAAyB,IAAA,EAA6B;AACpE,EAAA,MAAM,UAAA,GAAa,OAAO,OAAA,KAAsC;AAC9D,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,cAAA,CAAe,OAAO,CAAA;AAC/C,MAAA,IAAI,CAAC,KAAA,EAAO,OAAA,IAAW,KAAA,CAAM,MAAA,KAAW,QAAQ,OAAA,CAAQ,MAAA;AACtD,QAAA,OAAO,GAAA,CAAI,cAAA,CAAe,SAAA,EAAW,KAAA,CAAM,qBAAA,EAAuB,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAC/F,MAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAe,IAAA,EAAM,KAAA,EAAM;AAAA,IAC/C,CAAA,CAAA,MACM;AACJ,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,cAAA,EAAgB,KAAA,CAAM,oBAAA,EAAsB,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAAA,IACnG;AAAA,EACF,CAAA;AACA,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,WAAA,CAAY,QAAQ,EAAE,OAAA,EAAqB;AACpE,EAAA,OAAO,MAAA,CACJ,KAAA,CAAM,gBAAgB,CAAA,CACtB,IAAA,GACA,MAAA,CAAO,OAAO,EAAE,OAAA,EAAQ,KAAM;AAC7B,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,aAAA,KAAkB,KAAA,GACnC,IAAI,cAAA,CAAe,mBAAA,EAAqB,KAAA,CAAM,0BAA0B,CAAC,CAAA,GACzE,EAAA,CAAG,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,EAC7B,CAAC,CAAA,CACA,KAAA,CAAM,gBAAgB,CAAA,CACtB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACnF,CAAC,CAAA,CACA,KAAA,CAAM,cAAc,CAAA,CACpB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACjF,CAAC,CAAA,CACA,KAAA,CAAM,aAAa,CAAA,CACnB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,EAAA,EAAI,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACnF,CAAC,CAAA,CACA,KAAA,CAAM,qBAAqB,CAAA,CAC3B,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACxF,CAAC,CAAA,CACA,KAAA,CAAM,mBAAmB,CAAA,CACzB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,SAAA,CAAU,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACtF,CAAC,CAAA,CACA,KAAA,CAAM,eAAe,CAAA,CACrB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAClF,CAAC,CAAA,CACA,KAAA,CAAM,gBAAgB,CAAA,CACtB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACnF,CAAC,CAAA,CACA,KAAA,CAAM,mBAAmB,CAAA,CACzB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,SAAA,CAAU,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EACtF,CAAC,CAAA,CACA,KAAA,CAAM,wBAAwB,CAAA,CAC9B,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,cAAA,CAAe,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAC3F,CAAC,CAAA,CACA,KAAA,CAAM,4BAA4B,CAAA,CAClC,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,kBAAA,CAAmB,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAC/F,CAAC,CAAA,CACA,KAAA,CAAM,oBAAoB,CAAA,CAC1B,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,gBAAA,CAAiB,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAC7F,CAAC,CAAA,CACA,KAAA,CAAM,iBAAiB,CAAA,CACvB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AACpC,IAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,OAAO,CAAA;AACtC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA;AACT,MAAA,OAAO,KAAA;AACT,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,IAAA,CAAK,QAAA,CAAS,aAAA,CAAc,KAAA,EAAO,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAC1F,CAAC,EACA,KAAA,EAAM;AACX","file":"feedback.js","sourcesContent":["/** @h-ai/serv — 按需挂载反馈领域,不接收客户端身份或对象存储路径。 */\nimport type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\nimport type { ServContext } from '../serv-context.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\n\n/**\n * 已认证请求到反馈范围的可信映射,返回 null 时拒绝访问。\n * @example\n * ```ts\n * import type { FeedbackProcedureDeps } from '@h-ai/serv/features/feedback'\n * import { feedback } from '@h-ai/feedback'\n * const deps: FeedbackProcedureDeps = { feedback, resolveContext: ({ session }) => ({ scopeId: 'tenant-from-server', userId: session.userId, permissions: session.permissions }) }\n * ```\n */\nexport interface FeedbackProcedureDeps {\n readonly feedback: FeedbackFunctions\n readonly resolveContext: (context: AuthenticatedServContext) => FeedbackContext | null | Promise<FeedbackContext | null>\n}\n\n/**\n * 创建反馈的默认 procedures。宿主解析租户与权限,领域模块再次检查资源范围。\n * @example\n * ```ts\n * import { feedback } from '@h-ai/feedback'\n * import { createFeedbackProcedures } from '@h-ai/serv/features/feedback'\n * const procedures = createFeedbackProcedures({ feedback, resolveContext: ({ session }) => ({ scopeId: 'trusted-tenant', userId: session.userId, permissions: session.permissions }) })\n * ```\n */\nexport function createFeedbackProcedures(deps: FeedbackProcedureDeps) {\n const contextFor = async (context: AuthenticatedServContext) => {\n try {\n const actor = await deps.resolveContext(context)\n if (!actor?.scopeId || actor.userId !== context.session.userId)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: context.locale }))\n return { success: true as const, data: actor }\n }\n catch {\n return err(HaiCommonError.INTERNAL_ERROR, servM('serv_errorInternal', { locale: context.locale }))\n }\n }\n const router = implement(apiContract.feedback).context<ServContext>()\n return router\n .route('reports.policy')\n .auth()\n .handle(async ({ context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return deps.feedback.isInitialized === false\n ? err(HaiCommonError.SERVICE_UNAVAILABLE, servM('serv_feedbackUnavailable'))\n : ok(deps.feedback.budget)\n })\n .route('reports.submit')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.submit(input, actor.data))\n })\n .route('reports.list')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.list(input, actor.data))\n })\n .route('reports.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.get(input.id, actor.data))\n })\n .route('reports.getEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getEvidence(input, actor.data))\n })\n .route('reports.setStatus')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.setStatus(input, actor.data))\n })\n .route('reports.retry')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.retry(input, actor.data))\n })\n .route('reports.export')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.export(input, actor.data))\n })\n .route('reports.getExport')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getExport(input, actor.data))\n })\n .route('reports.appendEvidence')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.appendEvidence(input, actor.data))\n })\n .route('reports.completeCollection')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.completeCollection(input, actor.data))\n })\n .route('attachments.upload')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.uploadAttachment(input, actor.data))\n })\n .route('attachments.get')\n .auth()\n .handle(async ({ input, context }) => {\n const actor = await contextFor(context)\n if (!actor.success)\n return actor\n return core.context.run(actor.data, () => deps.feedback.getAttachment(input, actor.data))\n })\n .build()\n}\n"]}
|
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 {
|
|
7
|
+
import { ServContext } from '../index.js';
|
|
8
8
|
import '@h-ai/core';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -711,8 +711,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
|
711
711
|
invite: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
|
|
712
712
|
metadata: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
713
713
|
email: zod.ZodOptional<zod.ZodString>;
|
|
714
|
-
username: zod.ZodString;
|
|
715
714
|
phone: zod.ZodOptional<zod.ZodString>;
|
|
715
|
+
username: zod.ZodString;
|
|
716
716
|
displayName: zod.ZodOptional<zod.ZodString>;
|
|
717
717
|
accountType: zod.ZodOptional<zod.ZodEnum<{
|
|
718
718
|
permanent: "permanent";
|
|
@@ -1335,4 +1335,5 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
|
1335
1335
|
};
|
|
1336
1336
|
};
|
|
1337
1337
|
|
|
1338
|
-
export {
|
|
1338
|
+
export { createIamProcedures };
|
|
1339
|
+
export type { IamProcedureDeps };
|
package/dist/features/iam.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mapHaiResult } from '../chunk-XKCLD42I.js';
|
|
2
|
-
import { implement, servM } from '../chunk-
|
|
2
|
+
import { implement, servM } from '../chunk-PLGCHDZM.js';
|
|
3
3
|
import { apiContract } from '@h-ai/api-contract';
|
|
4
4
|
import { core, err, HaiCommonError, ok } from '@h-ai/core';
|
|
5
5
|
|
|
@@ -58,7 +58,9 @@ function createIamProcedures(deps) {
|
|
|
58
58
|
];
|
|
59
59
|
const operation = operations.find((value) => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? "iam.profile.update";
|
|
60
60
|
return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input));
|
|
61
|
-
}).route("users.list").permission("iam.users.read").handle(({ input }) => iam.user.listUsers(input)).route("users.get").permission("iam.users.read").handle(({ input }) => iam.user.getUser(input.id, {
|
|
61
|
+
}).route("users.list").permission("iam.users.read").handle(({ input }) => iam.user.listUsers(input)).route("users.get").permission("iam.users.read").handle(({ input }) => iam.user.getUser(input.id, {
|
|
62
|
+
include: ["roles"]
|
|
63
|
+
})).route("users.create").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.create", () => createUser(iam, input))).route("users.invite").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.invite", () => createUser(iam, input))).route("users.update").permission("iam.users.write").handle(({ input, context }) => sensitive(context, "iam.users.update", () => updateUser(iam, input))).route("users.delete").permission("iam.users.write").handle(({ input, context }) => {
|
|
62
64
|
if (context.session.userId === input.id)
|
|
63
65
|
return err(HaiCommonError.FORBIDDEN, servM("serv_iamCannotDeleteCurrentUser", { locale: context.locale }));
|
|
64
66
|
return sensitive(context, "iam.users.delete", () => iam.user.deleteUser(input.id));
|
package/dist/features/iam.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n IamInviteUserInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/**\n * IAM 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/**\n * 创建默认 IAM procedures;按操作声明认证/权限。logout 只撤销请求持有的完整 opaque token,不要求该 access 尚有效,也不能按 userId 撤销。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n // 权限守卫先执行;额外鉴别只绑定已授权操作,不提升原有权限。\n async function sensitive<T>(context: ServContext, operation: string, run: () => Promise<HaiResult<T>>): Promise<HaiResult<T>> {\n const policy = iam.publicConfig?.reauthentication\n if (policy?.enabled) {\n const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', operation, context.request.headers.get('x-hai-reauthentication') ?? undefined)\n if (!checked.success)\n return checked\n }\n return run()\n }\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.reauthenticate')\n .auth()\n .handle(({ input, context }) => iam.auth.reauthenticate(context.accessToken ?? '', input.password, input.operation))\n\n .route('auth.login', ({ input, context }) => iam.auth.login(input, context))\n .route('auth.createCaptcha', ({ input }) => iam.auth.createCaptcha(input))\n .route('auth.loginWithOtp', ({ input, context }) => iam.auth.loginWithOtp(input, context))\n .route('auth.loginWithLdap', ({ input, context }) => iam.auth.loginWithLdap(input, context))\n .route('auth.publicConfig', () => iam.getPublicConfig())\n .route('auth.renewPassword', ({ input }) => iam.user.renewPassword(input))\n .route('auth.requestPasswordReset', ({ input }) => iam.user.requestPasswordReset(input.identifier, input))\n .route('auth.confirmPasswordReset', ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword))\n\n .route('auth.logout', ({ input, context }) => {\n // 仅撤销调用方持有的完整随机令牌;不要求 access 仍有效,也不接受 userId 撤销。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.closeSession', ({ input, context }) => {\n // 页面离开只撤销持有的会话,不清 Cookie,避免迟到响应覆盖下一文档的新登录。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier, input))\n .route('auth.register', ({ input, context }) => iam.auth.registerAndLogin(input, context))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(async ({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n const policy = await iam.getPublicConfig()\n if (!policy.success)\n return policy\n // 标准资料入口与独立资料/头像入口共用策略;混合更新优先使用资料用途。\n const operations = [\n ...(Object.keys(input).some(key => key !== 'avatarUrl') ? ['iam.profile.update'] : []),\n ...(input.avatarUrl !== undefined ? ['iam.profile.avatar'] : []),\n ]\n const operation = operations.find(value => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? 'iam.profile.update'\n return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input))\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, { include: ['roles'] }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.create', () => createUser(iam, input)))\n\n .route('users.invite')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.invite', () => createUser(iam, input)))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.update', () => updateUser(iam, input)))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return sensitive(context, 'iam.users.delete', () => iam.user.deleteUser(input.id))\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.resetPassword', () => iam.user.adminResetPassword(input.id, input.newPassword)))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.create', () => iam.authz.createRole(input)))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input, context }) => {\n const { id, ...data } = input\n return sensitive(context, 'iam.roles.update', () => iam.authz.updateRole(id, data))\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.delete', () => iam.authz.deleteRole(input.id)))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.create', () => iam.authz.createPermission(input)))\n\n .route('permissions.setSensitiveOperations')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.sensitiveOperations', () => iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.delete', () => iam.authz.deletePermission(input.id)))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput | IamInviteUserInput): Promise<HaiResult<User>> {\n const invited = !('password' in input)\n const registerResult = 'password' in input ? await iam.user.createUser(input) : await iam.user.inviteUser(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n if (invited) {\n // 邀请可能已经送达,不能回滚外部已发布凭据;保留账号供管理员补齐配置。\n iamFeatureLogger.error('Invited account requires configuration repair', { userId, reason })\n return\n }\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds !== undefined) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n IamInviteUserInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/**\n * IAM 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/**\n * 创建默认 IAM procedures;按操作声明认证/权限。logout 只撤销请求持有的完整 opaque token,不要求该 access 尚有效,也不能按 userId 撤销。\n *\n * @example\n * ```ts\n * import { iam } from '@h-ai/iam'\n * import { createIamProcedures } from '@h-ai/serv/features/iam'\n *\n * // 宿主已初始化 IAM 及共享依赖。\n * const procedures = createIamProcedures({ iam })\n * // logout 撤销请求持有的 opaque access token;过期 access 也可撤销 refresh,\n * // 不接收 userId,不要求有效 access 的 auth guard,失败仍检查 HaiResult。\n * ```\n */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n // 权限守卫先执行;额外鉴别只绑定已授权操作,不提升原有权限。\n async function sensitive<T>(context: ServContext, operation: string, run: () => Promise<HaiResult<T>>): Promise<HaiResult<T>> {\n const policy = iam.publicConfig?.reauthentication\n if (policy?.enabled) {\n const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', operation, context.request.headers.get('x-hai-reauthentication') ?? undefined)\n if (!checked.success)\n return checked\n }\n return run()\n }\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.reauthenticate')\n .auth()\n .handle(({ input, context }) => iam.auth.reauthenticate(context.accessToken ?? '', input.password, input.operation))\n\n .route('auth.login', ({ input, context }) => iam.auth.login(input, context))\n .route('auth.createCaptcha', ({ input }) => iam.auth.createCaptcha(input))\n .route('auth.loginWithOtp', ({ input, context }) => iam.auth.loginWithOtp(input, context))\n .route('auth.loginWithLdap', ({ input, context }) => iam.auth.loginWithLdap(input, context))\n .route('auth.publicConfig', () => iam.getPublicConfig())\n .route('auth.renewPassword', ({ input }) => iam.user.renewPassword(input))\n .route('auth.requestPasswordReset', ({ input }) => iam.user.requestPasswordReset(input.identifier, input))\n .route('auth.confirmPasswordReset', ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword))\n\n .route('auth.logout', ({ input, context }) => {\n // 仅撤销调用方持有的完整随机令牌;不要求 access 仍有效,也不接受 userId 撤销。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.closeSession', ({ input, context }) => {\n // 页面离开只撤销持有的会话,不清 Cookie,避免迟到响应覆盖下一文档的新登录。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier, input))\n .route('auth.register', ({ input, context }) => iam.auth.registerAndLogin(input, context))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(async ({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n const policy = await iam.getPublicConfig()\n if (!policy.success)\n return policy\n // 标准资料入口与独立资料/头像入口共用策略;混合更新优先使用资料用途。\n const operations = [\n ...(Object.keys(input).some(key => key !== 'avatarUrl')\n ? ['iam.profile.update']\n : []),\n ...(input.avatarUrl !== undefined\n ? ['iam.profile.avatar']\n : []),\n ]\n const operation = operations.find(value => policy.data.reauthentication.operations.includes(value)) ?? operations[0] ?? 'iam.profile.update'\n return sensitive(context, operation, () => iam.user.updateCurrentUser(token, input))\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, {\n include: ['roles'],\n }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.create', () => createUser(iam, input)))\n\n .route('users.invite')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.invite', () => createUser(iam, input)))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.update', () => updateUser(iam, input)))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return sensitive(context, 'iam.users.delete', () => iam.user.deleteUser(input.id))\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input, context }) => sensitive(context, 'iam.users.resetPassword', () => iam.user.adminResetPassword(input.id, input.newPassword)))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.create', () => iam.authz.createRole(input)))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input, context }) => {\n const { id, ...data } = input\n return sensitive(context, 'iam.roles.update', () => iam.authz.updateRole(id, data))\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input, context }) => sensitive(context, 'iam.roles.delete', () => iam.authz.deleteRole(input.id)))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.create', () => iam.authz.createPermission(input)))\n\n .route('permissions.setSensitiveOperations')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.sensitiveOperations', () => iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input, context }) => sensitive(context, 'iam.permissions.delete', () => iam.authz.deletePermission(input.id)))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput | IamInviteUserInput): Promise<HaiResult<User>> {\n const invited = !('password' in input)\n const registerResult = 'password' in input\n ? await iam.user.createUser(input)\n : await iam.user.inviteUser(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n if (invited) {\n // 邀请可能已经送达,不能回滚外部已发布凭据;保留账号供管理员补齐配置。\n iamFeatureLogger.error('Invited account requires configuration repair', { userId, reason })\n return\n }\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds !== undefined) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
|
|
@@ -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 {
|
|
6
|
-
import {
|
|
5
|
+
import { ServContext } from '../index.js';
|
|
6
|
+
import { AuthenticatedServContext } from '../index.js';
|
|
7
7
|
import '@h-ai/core';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -250,4 +250,5 @@ declare function createStorageProcedures(deps: StorageProcedureDeps): {
|
|
|
250
250
|
};
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
-
export {
|
|
253
|
+
export { createStorageProcedures };
|
|
254
|
+
export type { StorageAccessRequest, StorageProcedureDeps };
|
package/dist/features/storage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { validateInputOrFail } from '../chunk-
|
|
1
|
+
import { validateInputOrFail } from '../chunk-CZ6VA2EN.js';
|
|
2
2
|
import { mapHaiResult } from '../chunk-XKCLD42I.js';
|
|
3
|
-
import { implement, servM } from '../chunk-
|
|
3
|
+
import { implement, servM } from '../chunk-PLGCHDZM.js';
|
|
4
4
|
import { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract';
|
|
5
5
|
import { err, HaiCommonError, ok } from '@h-ai/core';
|
|
6
6
|
import { getStorageKeyIssue, StorageDeleteKeysSchema, STORAGE_KEY_MAX_BYTES } from '@h-ai/storage/schemas';
|
|
@@ -11,7 +11,9 @@ function buildStorageKeyValidationFailure(field, locale, messageKey, params) {
|
|
|
11
11
|
return err(
|
|
12
12
|
HaiCommonError.VALIDATION_ERROR,
|
|
13
13
|
servM("serv_validationFailed", { locale }),
|
|
14
|
-
[
|
|
14
|
+
[
|
|
15
|
+
{ field, message }
|
|
16
|
+
]
|
|
15
17
|
);
|
|
16
18
|
}
|
|
17
19
|
function validateStorageKeyOrFail(key, locale, field = "key") {
|
|
@@ -51,7 +53,11 @@ function createStorageProcedures(deps) {
|
|
|
51
53
|
const keyResult = validateStorageKeyOrFail(input.key, context.locale);
|
|
52
54
|
if (!keyResult.success)
|
|
53
55
|
return keyResult;
|
|
54
|
-
const access = await authorize({
|
|
56
|
+
const access = await authorize({
|
|
57
|
+
context,
|
|
58
|
+
operation: "download",
|
|
59
|
+
keys: [keyResult.data]
|
|
60
|
+
});
|
|
55
61
|
if (!access.success)
|
|
56
62
|
return access;
|
|
57
63
|
const expiresIn = input.expiresIn ?? 900;
|
|
@@ -64,7 +70,11 @@ function createStorageProcedures(deps) {
|
|
|
64
70
|
const keyResult = validateStorageKeyOrFail(input.key, context.locale);
|
|
65
71
|
if (!keyResult.success)
|
|
66
72
|
return keyResult;
|
|
67
|
-
const access = await authorize({
|
|
73
|
+
const access = await authorize({
|
|
74
|
+
context,
|
|
75
|
+
operation: "upload",
|
|
76
|
+
keys: [keyResult.data]
|
|
77
|
+
});
|
|
68
78
|
if (!access.success)
|
|
69
79
|
return access;
|
|
70
80
|
const expiresIn = input.expiresIn ?? 900;
|
|
@@ -82,7 +92,11 @@ function createStorageProcedures(deps) {
|
|
|
82
92
|
if (!prefixResult.success)
|
|
83
93
|
return prefixResult;
|
|
84
94
|
}
|
|
85
|
-
const access = await authorize({
|
|
95
|
+
const access = await authorize({
|
|
96
|
+
context,
|
|
97
|
+
operation: "list",
|
|
98
|
+
keys: [prefix]
|
|
99
|
+
});
|
|
86
100
|
if (!access.success)
|
|
87
101
|
return access;
|
|
88
102
|
return storage.dir.list(input);
|
|
@@ -90,7 +104,11 @@ function createStorageProcedures(deps) {
|
|
|
90
104
|
const keyResult = validateStorageKeyOrFail(input.key, context.locale);
|
|
91
105
|
if (!keyResult.success)
|
|
92
106
|
return keyResult;
|
|
93
|
-
const access = await authorize({
|
|
107
|
+
const access = await authorize({
|
|
108
|
+
context,
|
|
109
|
+
operation: "metadata",
|
|
110
|
+
keys: [keyResult.data]
|
|
111
|
+
});
|
|
94
112
|
if (!access.success)
|
|
95
113
|
return access;
|
|
96
114
|
return storage.file.head(keyResult.data);
|
|
@@ -98,7 +116,11 @@ function createStorageProcedures(deps) {
|
|
|
98
116
|
const keyResult = validateStorageKeyOrFail(input.key, context.locale);
|
|
99
117
|
if (!keyResult.success)
|
|
100
118
|
return keyResult;
|
|
101
|
-
const access = await authorize({
|
|
119
|
+
const access = await authorize({
|
|
120
|
+
context,
|
|
121
|
+
operation: "delete",
|
|
122
|
+
keys: [keyResult.data]
|
|
123
|
+
});
|
|
102
124
|
if (!access.success)
|
|
103
125
|
return access;
|
|
104
126
|
return storage.file.delete(keyResult.data);
|
|
@@ -113,7 +135,10 @@ function createStorageProcedures(deps) {
|
|
|
113
135
|
if (result.success)
|
|
114
136
|
return result;
|
|
115
137
|
const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany);
|
|
116
|
-
return err({
|
|
138
|
+
return err({
|
|
139
|
+
...result.error,
|
|
140
|
+
publicExt: progress.success ? { deleteMany: progress.data } : void 0
|
|
141
|
+
});
|
|
117
142
|
}).build();
|
|
118
143
|
}
|
|
119
144
|
function toPresignedResult(key, expiresIn, result) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/features/serv-feature-storage.ts"],"names":[],"mappings":"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","file":"storage.js","sourcesContent":["/**\n * @h-ai/serv — Storage 默认 procedures\n *\n * 基于 `@h-ai/storage` 提供开箱即用的 Storage procedures 实现:预签名 URL、列表、元数据、删除。\n *\n * 所有操作先验证路径,再调用应用提供的资源授权策略;未提供策略时拒绝访问。\n * 策略必须从已认证 session 确定用户/租户范围,不能信任客户端提供的前缀。\n * @module features/serv-feature-storage\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServContext } from '../serv-context.js'\nimport type { ServMessageKey } from '../serv-i18n.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract'\nimport { err, HaiCommonError, ok } from '@h-ai/core'\nimport { getStorageKeyIssue, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema } from '@h-ai/storage/schemas'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { validateInputOrFail } from '../serv-validation.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst storageContract = apiContract.storage\n\nfunction buildStorageKeyValidationFailure(\n field: string,\n locale: string,\n messageKey: ServMessageKey,\n params?: Record<string, string | number>,\n): HaiResult<never> {\n const message = servM(messageKey, { locale, params })\n return err(\n HaiCommonError.VALIDATION_ERROR,\n servM('serv_validationFailed', { locale }),\n [{ field, message }],\n )\n}\n\n/**\n * 对业务层再次校验存储 key 是否安全。\n *\n * 与公开 Schema 共用规则,保留业务入口防线;后续仍必须执行授权和 Provider 防逃逸。\n */\nfunction validateStorageKeyOrFail(key: string, locale: string, field: string = 'key'): HaiResult<string> {\n const issue = getStorageKeyIssue(key)\n if (issue === 'empty')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyRequired')\n if (issue === 'tooLong') {\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyTooLong', {\n max: STORAGE_KEY_MAX_BYTES,\n })\n }\n if (issue === 'characters')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyIllegalChars')\n if (issue === 'segments')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyDotSegments')\n return ok(key)\n}\n\nfunction validateStorageKeysOrFail(keys: string[], locale: string): HaiResult<string[]> {\n const parsed = validateInputOrFail(StorageDeleteKeysSchema, keys, locale)\n if (!parsed.success)\n return parsed\n for (const [index, key] of keys.entries()) {\n const keyResult = validateStorageKeyOrFail(key, locale, `keys.${index}`)\n if (!keyResult.success)\n return keyResult\n }\n return ok(keys)\n}\n\n/**\n * 存储资源授权请求;批量操作只有全部资源获准后才会调用存储。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageAccessRequest {\n /** 操作类别;策略可分别授予读取、写入或删除权限。 */\n readonly operation: 'download' | 'upload' | 'list' | 'metadata' | 'delete' | 'deleteMany'\n /** 已通过认证的可信请求上下文。 */\n readonly context: AuthenticatedServContext\n /** 单项/批量 key;list 操作仅包含请求的 prefix,未提供 prefix 时为空字符串。 */\n readonly keys: readonly string[]\n}\n\n/**\n * Storage 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageProcedureDeps {\n readonly storage: StorageFunctions\n /** 必需的资源授权策略;返回 true 才允许执行,缺失策略时默认拒绝。 */\n readonly authorize: (request: StorageAccessRequest) => boolean | Promise<boolean>\n}\n\n/**\n * 内部预签名结果投影,包含短期访问 URL 和可选失效时间。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * function safeUrl(value: PresignedResponse) {\n * return { url: value.url, key: value.key, expiresAt: value.expiresAt }\n * }\n * // URL 是授权后的短期凭据;只返回给已授权调用方,不记录到日志。\n * ```\n */\ninterface PresignedResponse {\n readonly url: string\n readonly key: string\n readonly expiresAt?: Date\n}\n\n/**\n * 创建具有认证、路径校验和资源授权的 Storage procedures。\n *\n * @param deps - 存储服务及必需的资源授权策略。\n * @returns 可挂载至 serv.createApp 的存储 router;拒绝访问返回 FORBIDDEN。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport function createStorageProcedures(deps: StorageProcedureDeps) {\n const { storage } = deps\n\n async function authorize(request: StorageAccessRequest): Promise<HaiResult<void>> {\n if (typeof deps.authorize !== 'function' || await deps.authorize(request) !== true)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: request.context.locale }))\n return ok(undefined)\n }\n\n return implement(storageContract)\n .context<ServContext>()\n\n .route('presignedUrls.createDownload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'download', keys: [keyResult.data] })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.getUrl(keyResult.data, { expiresIn }),\n )\n })\n\n .route('presignedUrls.createUpload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'upload', keys: [keyResult.data] })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n const contentType = input.contentType ?? 'application/octet-stream'\n const { key: _, ...options } = input\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.putUrl(keyResult.data, { ...options, expiresIn, contentType }),\n )\n })\n\n .route('files.list')\n .auth()\n .handle(async ({ input, context }) => {\n const prefix = input.prefix ?? ''\n if (prefix) {\n const prefixResult = validateStorageKeyOrFail(prefix, context.locale, 'prefix')\n if (!prefixResult.success)\n return prefixResult\n }\n const access = await authorize({ context, operation: 'list', keys: [prefix] })\n if (!access.success)\n return access\n return storage.dir.list(input)\n })\n\n .route('files.getMetadata')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'metadata', keys: [keyResult.data] })\n if (!access.success)\n return access\n return storage.file.head(keyResult.data)\n })\n\n .route('files.delete')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({ context, operation: 'delete', keys: [keyResult.data] })\n if (!access.success)\n return access\n return storage.file.delete(keyResult.data)\n })\n\n .route('files.deleteMany')\n .auth()\n .handle(async ({ input, context }) => {\n const keysResult = validateStorageKeysOrFail(input.keys, context.locale)\n if (!keysResult.success)\n return keysResult\n const access = await authorize({ context, operation: 'deleteMany', keys: keysResult.data })\n if (!access.success)\n return access\n const result = await storage.file.deleteMany(keysResult.data)\n if (result.success)\n return result\n const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany)\n return err({ ...result.error, publicExt: progress.success ? { deleteMany: progress.data } : undefined })\n })\n\n .build()\n}\n\nfunction toPresignedResult(\n key: string,\n expiresIn: number | undefined,\n result: HaiResult<string>,\n): HaiResult<PresignedResponse> {\n return mapHaiResult(result, url => ({\n url,\n key,\n expiresAt: expiresIn ? new Date(Date.now() + expiresIn * 1000) : undefined,\n }))\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/features/serv-feature-storage.ts"],"names":[],"mappings":"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","file":"storage.js","sourcesContent":["/**\n * @h-ai/serv — Storage 默认 procedures\n *\n * 基于 `@h-ai/storage` 提供开箱即用的 Storage procedures 实现:预签名 URL、列表、元数据、删除。\n *\n * 所有操作先验证路径,再调用应用提供的资源授权策略;未提供策略时拒绝访问。\n * 策略必须从已认证 session 确定用户/租户范围,不能信任客户端提供的前缀。\n * @module features/serv-feature-storage\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServContext } from '../serv-context.js'\nimport type { ServMessageKey } from '../serv-i18n.js'\nimport type { AuthenticatedServContext } from '../serv-router.js'\nimport { apiContract, StorageDeleteSummarySchema } from '@h-ai/api-contract'\nimport { err, HaiCommonError, ok } from '@h-ai/core'\nimport { getStorageKeyIssue, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema } from '@h-ai/storage/schemas'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { validateInputOrFail } from '../serv-validation.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst storageContract = apiContract.storage\n\nfunction buildStorageKeyValidationFailure(\n field: string,\n locale: string,\n messageKey: ServMessageKey,\n params?: Record<string, string | number>,\n): HaiResult<never> {\n const message = servM(messageKey, { locale, params })\n return err(\n HaiCommonError.VALIDATION_ERROR,\n servM('serv_validationFailed', { locale }),\n [\n { field, message },\n ],\n )\n}\n\n/**\n * 对业务层再次校验存储 key 是否安全。\n *\n * 与公开 Schema 共用规则,保留业务入口防线;后续仍必须执行授权和 Provider 防逃逸。\n */\nfunction validateStorageKeyOrFail(key: string, locale: string, field: string = 'key'): HaiResult<string> {\n const issue = getStorageKeyIssue(key)\n if (issue === 'empty')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyRequired')\n if (issue === 'tooLong') {\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyTooLong', {\n max: STORAGE_KEY_MAX_BYTES,\n })\n }\n if (issue === 'characters')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyIllegalChars')\n if (issue === 'segments')\n return buildStorageKeyValidationFailure(field, locale, 'serv_storageKeyDotSegments')\n return ok(key)\n}\n\nfunction validateStorageKeysOrFail(keys: string[], locale: string): HaiResult<string[]> {\n const parsed = validateInputOrFail(StorageDeleteKeysSchema, keys, locale)\n if (!parsed.success)\n return parsed\n for (const [index, key] of keys.entries()) {\n const keyResult = validateStorageKeyOrFail(key, locale, `keys.${index}`)\n if (!keyResult.success)\n return keyResult\n }\n return ok(keys)\n}\n\n/**\n * 存储资源授权请求;批量操作只有全部资源获准后才会调用存储。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageAccessRequest {\n /** 操作类别;策略可分别授予读取、写入或删除权限。 */\n readonly operation: 'download' | 'upload' | 'list' | 'metadata' | 'delete' | 'deleteMany'\n /** 已通过认证的可信请求上下文。 */\n readonly context: AuthenticatedServContext\n /** 单项/批量 key;list 操作仅包含请求的 prefix,未提供 prefix 时为空字符串。 */\n readonly keys: readonly string[]\n}\n\n/**\n * Storage 默认 procedures 依赖。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport interface StorageProcedureDeps {\n readonly storage: StorageFunctions\n /** 必需的资源授权策略;返回 true 才允许执行,缺失策略时默认拒绝。 */\n readonly authorize: (request: StorageAccessRequest) => boolean | Promise<boolean>\n}\n\n/**\n * 内部预签名结果投影,包含短期访问 URL 和可选失效时间。\n *\n * @example\n * ```ts\n * // 模块内部上下文:示例复用当前声明所在模块的实际绑定。\n * function safeUrl(value: PresignedResponse) {\n * return { url: value.url, key: value.key, expiresAt: value.expiresAt }\n * }\n * // URL 是授权后的短期凭据;只返回给已授权调用方,不记录到日志。\n * ```\n */\ninterface PresignedResponse {\n readonly url: string\n readonly key: string\n readonly expiresAt?: Date\n}\n\n/**\n * 创建具有认证、路径校验和资源授权的 Storage procedures。\n *\n * @param deps - 存储服务及必需的资源授权策略。\n * @returns 可挂载至 serv.createApp 的存储 router;拒绝访问返回 FORBIDDEN。\n *\n * @example\n * ```ts\n * import { storage } from '@h-ai/storage'\n * import { createStorageProcedures } from '@h-ai/serv/features/storage'\n *\n * // 宿主已初始化 Storage;用户根目录来自可信 session,含尾斜杠形成边界。\n * const procedures = createStorageProcedures({ storage, authorize: ({ context, keys }) => {\n * const root = `users/${encodeURIComponent(context.session.userId)}/`\n * return keys.length > 0 && keys.every(key => key.startsWith(root))\n * } })\n * // 空列表前缀与相邻用户名前缀均不能匹配此根目录。\n * ```\n */\nexport function createStorageProcedures(deps: StorageProcedureDeps) {\n const { storage } = deps\n\n async function authorize(request: StorageAccessRequest): Promise<HaiResult<void>> {\n if (typeof deps.authorize !== 'function' || await deps.authorize(request) !== true)\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: request.context.locale }))\n return ok(undefined)\n }\n\n return implement(storageContract)\n .context<ServContext>()\n\n .route('presignedUrls.createDownload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'download',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.getUrl(keyResult.data, { expiresIn }),\n )\n })\n\n .route('presignedUrls.createUpload')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'upload',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n const expiresIn = input.expiresIn ?? 900\n const contentType = input.contentType ?? 'application/octet-stream'\n const { key: _, ...options } = input\n return toPresignedResult(\n keyResult.data,\n expiresIn,\n await storage.presign.putUrl(keyResult.data, { ...options, expiresIn, contentType }),\n )\n })\n\n .route('files.list')\n .auth()\n .handle(async ({ input, context }) => {\n const prefix = input.prefix ?? ''\n if (prefix) {\n const prefixResult = validateStorageKeyOrFail(prefix, context.locale, 'prefix')\n if (!prefixResult.success)\n return prefixResult\n }\n const access = await authorize({\n context,\n operation: 'list',\n keys: [prefix],\n })\n if (!access.success)\n return access\n return storage.dir.list(input)\n })\n\n .route('files.getMetadata')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'metadata',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n return storage.file.head(keyResult.data)\n })\n\n .route('files.delete')\n .auth()\n .handle(async ({ input, context }) => {\n const keyResult = validateStorageKeyOrFail(input.key, context.locale)\n if (!keyResult.success)\n return keyResult\n const access = await authorize({\n context,\n operation: 'delete',\n keys: [keyResult.data],\n })\n if (!access.success)\n return access\n return storage.file.delete(keyResult.data)\n })\n\n .route('files.deleteMany')\n .auth()\n .handle(async ({ input, context }) => {\n const keysResult = validateStorageKeysOrFail(input.keys, context.locale)\n if (!keysResult.success)\n return keysResult\n const access = await authorize({ context, operation: 'deleteMany', keys: keysResult.data })\n if (!access.success)\n return access\n const result = await storage.file.deleteMany(keysResult.data)\n if (result.success)\n return result\n const progress = StorageDeleteSummarySchema.safeParse(result.error.ext?.deleteMany)\n return err({\n ...result.error,\n publicExt: progress.success\n ? { deleteMany: progress.data }\n : undefined,\n })\n })\n\n .build()\n}\n\nfunction toPresignedResult(\n key: string,\n expiresIn: number | undefined,\n result: HaiResult<string>,\n): HaiResult<PresignedResponse> {\n return mapHaiResult(result, url => ({\n url,\n key,\n expiresAt: expiresIn\n ? new Date(Date.now() + expiresIn * 1000)\n : undefined,\n }))\n}\n"]}
|