@lark-project/openclaw-lark-project 2026.3.131
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/LICENSE +21 -0
- package/README.md +80 -0
- package/README.zh.md +80 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +7 -0
- package/dist/skills/feishu-bitable/SKILL.md +248 -0
- package/dist/skills/feishu-bitable/references/examples.md +813 -0
- package/dist/skills/feishu-bitable/references/field-properties.md +763 -0
- package/dist/skills/feishu-bitable/references/record-values.md +911 -0
- package/dist/skills/feishu-calendar/SKILL.md +244 -0
- package/dist/skills/feishu-channel-rules/SKILL.md +18 -0
- package/dist/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/dist/skills/feishu-create-doc/SKILL.md +719 -0
- package/dist/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/dist/skills/feishu-im-read/SKILL.md +163 -0
- package/dist/skills/feishu-project/SKILL.md +122 -0
- package/dist/skills/feishu-task/SKILL.md +293 -0
- package/dist/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/dist/skills/feishu-update-doc/SKILL.md +285 -0
- package/dist/src/card/builder.js +293 -0
- package/dist/src/card/builder.js.map +7 -0
- package/dist/src/card/cardkit.js +126 -0
- package/dist/src/card/cardkit.js.map +7 -0
- package/dist/src/card/flush-controller.js +107 -0
- package/dist/src/card/flush-controller.js.map +7 -0
- package/dist/src/card/markdown-style.js +57 -0
- package/dist/src/card/markdown-style.js.map +7 -0
- package/dist/src/card/reply-dispatcher-types.js +39 -0
- package/dist/src/card/reply-dispatcher-types.js.map +7 -0
- package/dist/src/card/reply-dispatcher.js +245 -0
- package/dist/src/card/reply-dispatcher.js.map +7 -0
- package/dist/src/card/reply-mode.js +29 -0
- package/dist/src/card/reply-mode.js.map +7 -0
- package/dist/src/card/streaming-card-controller.js +653 -0
- package/dist/src/card/streaming-card-controller.js.map +7 -0
- package/dist/src/card/unavailable-guard.js +76 -0
- package/dist/src/card/unavailable-guard.js.map +7 -0
- package/dist/src/channel/abort-detect.js +79 -0
- package/dist/src/channel/abort-detect.js.map +7 -0
- package/dist/src/channel/chat-queue.js +50 -0
- package/dist/src/channel/chat-queue.js.map +7 -0
- package/dist/src/channel/config-adapter.js +89 -0
- package/dist/src/channel/config-adapter.js.map +7 -0
- package/dist/src/channel/directory.js +133 -0
- package/dist/src/channel/directory.js.map +7 -0
- package/dist/src/channel/event-handlers.js +175 -0
- package/dist/src/channel/event-handlers.js.map +7 -0
- package/dist/src/channel/monitor.js +108 -0
- package/dist/src/channel/monitor.js.map +7 -0
- package/dist/src/channel/onboarding-config.js +76 -0
- package/dist/src/channel/onboarding-config.js.map +7 -0
- package/dist/src/channel/onboarding-migrate.js +55 -0
- package/dist/src/channel/onboarding-migrate.js.map +7 -0
- package/dist/src/channel/onboarding.js +285 -0
- package/dist/src/channel/onboarding.js.map +7 -0
- package/dist/src/channel/plugin.js +260 -0
- package/dist/src/channel/plugin.js.map +7 -0
- package/dist/src/channel/probe.js +14 -0
- package/dist/src/channel/probe.js.map +7 -0
- package/dist/src/channel/types.js +1 -0
- package/dist/src/channel/types.js.map +7 -0
- package/dist/src/commands/auth.js +73 -0
- package/dist/src/commands/auth.js.map +7 -0
- package/dist/src/commands/diagnose.js +658 -0
- package/dist/src/commands/diagnose.js.map +7 -0
- package/dist/src/commands/doctor.js +327 -0
- package/dist/src/commands/doctor.js.map +7 -0
- package/dist/src/commands/index.js +124 -0
- package/dist/src/commands/index.js.map +7 -0
- package/dist/src/core/accounts.js +129 -0
- package/dist/src/core/accounts.js.map +7 -0
- package/dist/src/core/agent-config.js +60 -0
- package/dist/src/core/agent-config.js.map +7 -0
- package/dist/src/core/api-error.js +55 -0
- package/dist/src/core/api-error.js.map +7 -0
- package/dist/src/core/app-owner-fallback.js +17 -0
- package/dist/src/core/app-owner-fallback.js.map +7 -0
- package/dist/src/core/app-scope-checker.js +95 -0
- package/dist/src/core/app-scope-checker.js.map +7 -0
- package/dist/src/core/auth-errors.js +120 -0
- package/dist/src/core/auth-errors.js.map +7 -0
- package/dist/src/core/chat-info-cache.js +102 -0
- package/dist/src/core/chat-info-cache.js.map +7 -0
- package/dist/src/core/config-schema.js +150 -0
- package/dist/src/core/config-schema.js.map +7 -0
- package/dist/src/core/device-flow.js +174 -0
- package/dist/src/core/device-flow.js.map +7 -0
- package/dist/src/core/feishu-fetch.js +12 -0
- package/dist/src/core/feishu-fetch.js.map +7 -0
- package/dist/src/core/footer-config.js +16 -0
- package/dist/src/core/footer-config.js.map +7 -0
- package/dist/src/core/lark-client.js +322 -0
- package/dist/src/core/lark-client.js.map +7 -0
- package/dist/src/core/lark-logger.js +92 -0
- package/dist/src/core/lark-logger.js.map +7 -0
- package/dist/src/core/lark-ticket.js +18 -0
- package/dist/src/core/lark-ticket.js.map +7 -0
- package/dist/src/core/message-unavailable.js +119 -0
- package/dist/src/core/message-unavailable.js.map +7 -0
- package/dist/src/core/owner-policy.js +25 -0
- package/dist/src/core/owner-policy.js.map +7 -0
- package/dist/src/core/permission-url.js +37 -0
- package/dist/src/core/permission-url.js.map +7 -0
- package/dist/src/core/project-auth.js +177 -0
- package/dist/src/core/project-auth.js.map +7 -0
- package/dist/src/core/project-oauth-flow.js +124 -0
- package/dist/src/core/project-oauth-flow.js.map +7 -0
- package/dist/src/core/project-token-store.js +172 -0
- package/dist/src/core/project-token-store.js.map +7 -0
- package/dist/src/core/raw-request.js +45 -0
- package/dist/src/core/raw-request.js.map +7 -0
- package/dist/src/core/scope-manager.js +62 -0
- package/dist/src/core/scope-manager.js.map +7 -0
- package/dist/src/core/security-check.js +118 -0
- package/dist/src/core/security-check.js.map +7 -0
- package/dist/src/core/shutdown-hooks.js +37 -0
- package/dist/src/core/shutdown-hooks.js.map +7 -0
- package/dist/src/core/targets.js +55 -0
- package/dist/src/core/targets.js.map +7 -0
- package/dist/src/core/token-store.js +215 -0
- package/dist/src/core/token-store.js.map +7 -0
- package/dist/src/core/tool-client.js +335 -0
- package/dist/src/core/tool-client.js.map +7 -0
- package/dist/src/core/tool-scopes.js +207 -0
- package/dist/src/core/tool-scopes.js.map +7 -0
- package/dist/src/core/tools-config.js +57 -0
- package/dist/src/core/tools-config.js.map +7 -0
- package/dist/src/core/types.js +1 -0
- package/dist/src/core/types.js.map +7 -0
- package/dist/src/core/uat-client.js +124 -0
- package/dist/src/core/uat-client.js.map +7 -0
- package/dist/src/core/version.js +27 -0
- package/dist/src/core/version.js.map +7 -0
- package/dist/src/messaging/converters/audio.js +19 -0
- package/dist/src/messaging/converters/audio.js.map +7 -0
- package/dist/src/messaging/converters/calendar.js +46 -0
- package/dist/src/messaging/converters/calendar.js.map +7 -0
- package/dist/src/messaging/converters/content-converter.js +61 -0
- package/dist/src/messaging/converters/content-converter.js.map +7 -0
- package/dist/src/messaging/converters/file.js +18 -0
- package/dist/src/messaging/converters/file.js.map +7 -0
- package/dist/src/messaging/converters/folder.js +18 -0
- package/dist/src/messaging/converters/folder.js.map +7 -0
- package/dist/src/messaging/converters/hongbao.js +14 -0
- package/dist/src/messaging/converters/hongbao.js.map +7 -0
- package/dist/src/messaging/converters/image.js +16 -0
- package/dist/src/messaging/converters/image.js.map +7 -0
- package/dist/src/messaging/converters/index.js +48 -0
- package/dist/src/messaging/converters/index.js.map +7 -0
- package/dist/src/messaging/converters/interactive/card-converter.js +1040 -0
- package/dist/src/messaging/converters/interactive/card-converter.js.map +7 -0
- package/dist/src/messaging/converters/interactive/card-utils.js +36 -0
- package/dist/src/messaging/converters/interactive/card-utils.js.map +7 -0
- package/dist/src/messaging/converters/interactive/index.js +19 -0
- package/dist/src/messaging/converters/interactive/index.js.map +7 -0
- package/dist/src/messaging/converters/interactive/legacy.js +53 -0
- package/dist/src/messaging/converters/interactive/legacy.js.map +7 -0
- package/dist/src/messaging/converters/interactive/types.js +23 -0
- package/dist/src/messaging/converters/interactive/types.js.map +7 -0
- package/dist/src/messaging/converters/location.js +17 -0
- package/dist/src/messaging/converters/location.js.map +7 -0
- package/dist/src/messaging/converters/merge-forward.js +143 -0
- package/dist/src/messaging/converters/merge-forward.js.map +7 -0
- package/dist/src/messaging/converters/post.js +113 -0
- package/dist/src/messaging/converters/post.js.map +7 -0
- package/dist/src/messaging/converters/share.js +22 -0
- package/dist/src/messaging/converters/share.js.map +7 -0
- package/dist/src/messaging/converters/sticker.js +16 -0
- package/dist/src/messaging/converters/sticker.js.map +7 -0
- package/dist/src/messaging/converters/system.js +25 -0
- package/dist/src/messaging/converters/system.js.map +7 -0
- package/dist/src/messaging/converters/text.js +12 -0
- package/dist/src/messaging/converters/text.js.map +7 -0
- package/dist/src/messaging/converters/todo.js +37 -0
- package/dist/src/messaging/converters/todo.js.map +7 -0
- package/dist/src/messaging/converters/types.js +1 -0
- package/dist/src/messaging/converters/types.js.map +7 -0
- package/dist/src/messaging/converters/unknown.js +13 -0
- package/dist/src/messaging/converters/unknown.js.map +7 -0
- package/dist/src/messaging/converters/utils.js +35 -0
- package/dist/src/messaging/converters/utils.js.map +7 -0
- package/dist/src/messaging/converters/video-chat.js +21 -0
- package/dist/src/messaging/converters/video-chat.js.map +7 -0
- package/dist/src/messaging/converters/video.js +30 -0
- package/dist/src/messaging/converters/video.js.map +7 -0
- package/dist/src/messaging/converters/vote.js +24 -0
- package/dist/src/messaging/converters/vote.js.map +7 -0
- package/dist/src/messaging/inbound/dedup.js +82 -0
- package/dist/src/messaging/inbound/dedup.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-builders.js +98 -0
- package/dist/src/messaging/inbound/dispatch-builders.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-commands.js +94 -0
- package/dist/src/messaging/inbound/dispatch-commands.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-context.js +96 -0
- package/dist/src/messaging/inbound/dispatch-context.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch.js +150 -0
- package/dist/src/messaging/inbound/dispatch.js.map +7 -0
- package/dist/src/messaging/inbound/enrich.js +137 -0
- package/dist/src/messaging/inbound/enrich.js.map +7 -0
- package/dist/src/messaging/inbound/gate-effects.js +28 -0
- package/dist/src/messaging/inbound/gate-effects.js.map +7 -0
- package/dist/src/messaging/inbound/gate.js +163 -0
- package/dist/src/messaging/inbound/gate.js.map +7 -0
- package/dist/src/messaging/inbound/handler.js +132 -0
- package/dist/src/messaging/inbound/handler.js.map +7 -0
- package/dist/src/messaging/inbound/media-resolver.js +70 -0
- package/dist/src/messaging/inbound/media-resolver.js.map +7 -0
- package/dist/src/messaging/inbound/mention.js +50 -0
- package/dist/src/messaging/inbound/mention.js.map +7 -0
- package/dist/src/messaging/inbound/parse-io.js +41 -0
- package/dist/src/messaging/inbound/parse-io.js.map +7 -0
- package/dist/src/messaging/inbound/parse.js +79 -0
- package/dist/src/messaging/inbound/parse.js.map +7 -0
- package/dist/src/messaging/inbound/permission.js +30 -0
- package/dist/src/messaging/inbound/permission.js.map +7 -0
- package/dist/src/messaging/inbound/policy.js +83 -0
- package/dist/src/messaging/inbound/policy.js.map +7 -0
- package/dist/src/messaging/inbound/reaction-handler.js +162 -0
- package/dist/src/messaging/inbound/reaction-handler.js.map +7 -0
- package/dist/src/messaging/inbound/user-name-cache.js +172 -0
- package/dist/src/messaging/inbound/user-name-cache.js.map +7 -0
- package/dist/src/messaging/outbound/actions.js +239 -0
- package/dist/src/messaging/outbound/actions.js.map +7 -0
- package/dist/src/messaging/outbound/chat-manage.js +74 -0
- package/dist/src/messaging/outbound/chat-manage.js.map +7 -0
- package/dist/src/messaging/outbound/deliver.js +162 -0
- package/dist/src/messaging/outbound/deliver.js.map +7 -0
- package/dist/src/messaging/outbound/fetch.js +7 -0
- package/dist/src/messaging/outbound/fetch.js.map +7 -0
- package/dist/src/messaging/outbound/forward.js +31 -0
- package/dist/src/messaging/outbound/forward.js.map +7 -0
- package/dist/src/messaging/outbound/media-url-utils.js +101 -0
- package/dist/src/messaging/outbound/media-url-utils.js.map +7 -0
- package/dist/src/messaging/outbound/media.js +463 -0
- package/dist/src/messaging/outbound/media.js.map +7 -0
- package/dist/src/messaging/outbound/outbound.js +95 -0
- package/dist/src/messaging/outbound/outbound.js.map +7 -0
- package/dist/src/messaging/outbound/reactions.js +312 -0
- package/dist/src/messaging/outbound/reactions.js.map +7 -0
- package/dist/src/messaging/outbound/send.js +194 -0
- package/dist/src/messaging/outbound/send.js.map +7 -0
- package/dist/src/messaging/outbound/typing.js +77 -0
- package/dist/src/messaging/outbound/typing.js.map +7 -0
- package/dist/src/messaging/shared/message-lookup.js +84 -0
- package/dist/src/messaging/shared/message-lookup.js.map +7 -0
- package/dist/src/messaging/types.js +1 -0
- package/dist/src/messaging/types.js.map +7 -0
- package/dist/src/tools/auto-auth.js +714 -0
- package/dist/src/tools/auto-auth.js.map +7 -0
- package/dist/src/tools/helpers.js +133 -0
- package/dist/src/tools/helpers.js.map +7 -0
- package/dist/src/tools/mcp/doc/create.js +35 -0
- package/dist/src/tools/mcp/doc/create.js.map +7 -0
- package/dist/src/tools/mcp/doc/fetch.js +33 -0
- package/dist/src/tools/mcp/doc/fetch.js.map +7 -0
- package/dist/src/tools/mcp/doc/index.js +32 -0
- package/dist/src/tools/mcp/doc/index.js.map +7 -0
- package/dist/src/tools/mcp/doc/update.js +61 -0
- package/dist/src/tools/mcp/doc/update.js.map +7 -0
- package/dist/src/tools/mcp/project/endpoint.js +25 -0
- package/dist/src/tools/mcp/project/endpoint.js.map +7 -0
- package/dist/src/tools/mcp/project/index.js +27 -0
- package/dist/src/tools/mcp/project/index.js.map +7 -0
- package/dist/src/tools/mcp/project/tools.js +579 -0
- package/dist/src/tools/mcp/project/tools.js.map +7 -0
- package/dist/src/tools/mcp/shared.js +170 -0
- package/dist/src/tools/mcp/shared.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-field.js +244 -0
- package/dist/src/tools/oapi/bitable/app-table-field.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-record.js +501 -0
- package/dist/src/tools/oapi/bitable/app-table-record.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-view.js +226 -0
- package/dist/src/tools/oapi/bitable/app-table-view.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table.js +278 -0
- package/dist/src/tools/oapi/bitable/app-table.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app.js +200 -0
- package/dist/src/tools/oapi/bitable/app.js.map +7 -0
- package/dist/src/tools/oapi/bitable/index.js +13 -0
- package/dist/src/tools/oapi/bitable/index.js.map +7 -0
- package/dist/src/tools/oapi/calendar/calendar.js +131 -0
- package/dist/src/tools/oapi/calendar/calendar.js.map +7 -0
- package/dist/src/tools/oapi/calendar/event-attendee.js +301 -0
- package/dist/src/tools/oapi/calendar/event-attendee.js.map +7 -0
- package/dist/src/tools/oapi/calendar/event.js +834 -0
- package/dist/src/tools/oapi/calendar/event.js.map +7 -0
- package/dist/src/tools/oapi/calendar/freebusy.js +111 -0
- package/dist/src/tools/oapi/calendar/freebusy.js.map +7 -0
- package/dist/src/tools/oapi/calendar/index.js +11 -0
- package/dist/src/tools/oapi/calendar/index.js.map +7 -0
- package/dist/src/tools/oapi/chat/chat.js +132 -0
- package/dist/src/tools/oapi/chat/chat.js.map +7 -0
- package/dist/src/tools/oapi/chat/index.js +11 -0
- package/dist/src/tools/oapi/chat/index.js.map +7 -0
- package/dist/src/tools/oapi/chat/members.js +83 -0
- package/dist/src/tools/oapi/chat/members.js.map +7 -0
- package/dist/src/tools/oapi/common/get-user.js +95 -0
- package/dist/src/tools/oapi/common/get-user.js.map +7 -0
- package/dist/src/tools/oapi/common/index.js +7 -0
- package/dist/src/tools/oapi/common/index.js.map +7 -0
- package/dist/src/tools/oapi/common/search-user.js +67 -0
- package/dist/src/tools/oapi/common/search-user.js.map +7 -0
- package/dist/src/tools/oapi/drive/doc-comments.js +310 -0
- package/dist/src/tools/oapi/drive/doc-comments.js.map +7 -0
- package/dist/src/tools/oapi/drive/doc-media.js +314 -0
- package/dist/src/tools/oapi/drive/doc-media.js.map +7 -0
- package/dist/src/tools/oapi/drive/file.js +548 -0
- package/dist/src/tools/oapi/drive/file.js.map +7 -0
- package/dist/src/tools/oapi/drive/index.js +29 -0
- package/dist/src/tools/oapi/drive/index.js.map +7 -0
- package/dist/src/tools/oapi/helpers.js +199 -0
- package/dist/src/tools/oapi/helpers.js.map +7 -0
- package/dist/src/tools/oapi/im/format-messages.js +128 -0
- package/dist/src/tools/oapi/im/format-messages.js.map +7 -0
- package/dist/src/tools/oapi/im/index.js +15 -0
- package/dist/src/tools/oapi/im/index.js.map +7 -0
- package/dist/src/tools/oapi/im/message-read.js +404 -0
- package/dist/src/tools/oapi/im/message-read.js.map +7 -0
- package/dist/src/tools/oapi/im/message.js +179 -0
- package/dist/src/tools/oapi/im/message.js.map +7 -0
- package/dist/src/tools/oapi/im/resource.js +126 -0
- package/dist/src/tools/oapi/im/resource.js.map +7 -0
- package/dist/src/tools/oapi/im/time-utils.js +169 -0
- package/dist/src/tools/oapi/im/time-utils.js.map +7 -0
- package/dist/src/tools/oapi/im/user-name-uat.js +103 -0
- package/dist/src/tools/oapi/im/user-name-uat.js.map +7 -0
- package/dist/src/tools/oapi/index.js +56 -0
- package/dist/src/tools/oapi/index.js.map +7 -0
- package/dist/src/tools/oapi/sdk-types.js +1 -0
- package/dist/src/tools/oapi/sdk-types.js.map +7 -0
- package/dist/src/tools/oapi/search/doc-search.js +215 -0
- package/dist/src/tools/oapi/search/doc-search.js.map +7 -0
- package/dist/src/tools/oapi/search/index.js +25 -0
- package/dist/src/tools/oapi/search/index.js.map +7 -0
- package/dist/src/tools/oapi/sheets/index.js +25 -0
- package/dist/src/tools/oapi/sheets/index.js.map +7 -0
- package/dist/src/tools/oapi/sheets/sheet.js +652 -0
- package/dist/src/tools/oapi/sheets/sheet.js.map +7 -0
- package/dist/src/tools/oapi/task/comment.js +151 -0
- package/dist/src/tools/oapi/task/comment.js.map +7 -0
- package/dist/src/tools/oapi/task/index.js +11 -0
- package/dist/src/tools/oapi/task/index.js.map +7 -0
- package/dist/src/tools/oapi/task/subtask.js +175 -0
- package/dist/src/tools/oapi/task/subtask.js.map +7 -0
- package/dist/src/tools/oapi/task/task.js +405 -0
- package/dist/src/tools/oapi/task/task.js.map +7 -0
- package/dist/src/tools/oapi/task/tasklist.js +366 -0
- package/dist/src/tools/oapi/task/tasklist.js.map +7 -0
- package/dist/src/tools/oapi/wiki/index.js +27 -0
- package/dist/src/tools/oapi/wiki/index.js.map +7 -0
- package/dist/src/tools/oapi/wiki/space-node.js +311 -0
- package/dist/src/tools/oapi/wiki/space-node.js.map +7 -0
- package/dist/src/tools/oapi/wiki/space.js +148 -0
- package/dist/src/tools/oapi/wiki/space.js.map +7 -0
- package/dist/src/tools/oauth-batch-auth.js +125 -0
- package/dist/src/tools/oauth-batch-auth.js.map +7 -0
- package/dist/src/tools/oauth-cards.js +269 -0
- package/dist/src/tools/oauth-cards.js.map +7 -0
- package/dist/src/tools/oauth.js +538 -0
- package/dist/src/tools/oauth.js.map +7 -0
- package/dist/src/tools/onboarding-auth.js +101 -0
- package/dist/src/tools/onboarding-auth.js.map +7 -0
- package/dist/src/tools/project-oauth.js +305 -0
- package/dist/src/tools/project-oauth.js.map +7 -0
- package/dist/src/tools/tat/im/index.js +9 -0
- package/dist/src/tools/tat/im/index.js.map +7 -0
- package/dist/src/tools/tat/im/resource.js +123 -0
- package/dist/src/tools/tat/im/resource.js.map +7 -0
- package/package.json +64 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { MESSAGE_TERMINAL_CODES } from "./auth-errors";
|
|
2
|
+
import { extractLarkApiCode } from "./api-error";
|
|
3
|
+
import { normalizeMessageId } from "./targets";
|
|
4
|
+
const UNAVAILABLE_CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
5
|
+
const MAX_CACHE_SIZE_BEFORE_PRUNE = 512;
|
|
6
|
+
const unavailableMessageCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function pruneExpired(nowMs = Date.now()) {
|
|
8
|
+
for (const [messageId, state] of unavailableMessageCache) {
|
|
9
|
+
if (nowMs - state.markedAtMs > UNAVAILABLE_CACHE_TTL_MS) {
|
|
10
|
+
unavailableMessageCache.delete(messageId);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isTerminalMessageApiCode(code) {
|
|
15
|
+
return typeof code === "number" && MESSAGE_TERMINAL_CODES.has(code);
|
|
16
|
+
}
|
|
17
|
+
function markMessageUnavailable(params) {
|
|
18
|
+
const normalizedId = normalizeMessageId(params.messageId);
|
|
19
|
+
if (!normalizedId) return;
|
|
20
|
+
if (unavailableMessageCache.size >= MAX_CACHE_SIZE_BEFORE_PRUNE) {
|
|
21
|
+
pruneExpired();
|
|
22
|
+
}
|
|
23
|
+
unavailableMessageCache.set(normalizedId, {
|
|
24
|
+
apiCode: params.apiCode,
|
|
25
|
+
operation: params.operation,
|
|
26
|
+
markedAtMs: Date.now()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function getMessageUnavailableState(messageId) {
|
|
30
|
+
const normalizedId = normalizeMessageId(messageId);
|
|
31
|
+
if (!normalizedId) return void 0;
|
|
32
|
+
const state = unavailableMessageCache.get(normalizedId);
|
|
33
|
+
if (!state) return void 0;
|
|
34
|
+
if (Date.now() - state.markedAtMs > UNAVAILABLE_CACHE_TTL_MS) {
|
|
35
|
+
unavailableMessageCache.delete(normalizedId);
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
function isMessageUnavailable(messageId) {
|
|
41
|
+
return !!getMessageUnavailableState(messageId);
|
|
42
|
+
}
|
|
43
|
+
function markMessageUnavailableFromError(params) {
|
|
44
|
+
const normalizedId = normalizeMessageId(params.messageId);
|
|
45
|
+
if (!normalizedId) return void 0;
|
|
46
|
+
const code = extractLarkApiCode(params.error);
|
|
47
|
+
if (!isTerminalMessageApiCode(code)) return void 0;
|
|
48
|
+
markMessageUnavailable({
|
|
49
|
+
messageId: normalizedId,
|
|
50
|
+
apiCode: code,
|
|
51
|
+
operation: params.operation
|
|
52
|
+
});
|
|
53
|
+
return code;
|
|
54
|
+
}
|
|
55
|
+
class MessageUnavailableError extends Error {
|
|
56
|
+
messageId;
|
|
57
|
+
apiCode;
|
|
58
|
+
operation;
|
|
59
|
+
constructor(params) {
|
|
60
|
+
const operationText = params.operation ? `, op=${params.operation}` : "";
|
|
61
|
+
super(
|
|
62
|
+
`[feishu-message-unavailable] message ${params.messageId} unavailable (code=${params.apiCode}${operationText})`
|
|
63
|
+
);
|
|
64
|
+
this.name = "MessageUnavailableError";
|
|
65
|
+
this.messageId = params.messageId;
|
|
66
|
+
this.apiCode = params.apiCode;
|
|
67
|
+
this.operation = params.operation;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function isMessageUnavailableError(error) {
|
|
71
|
+
return error instanceof MessageUnavailableError || typeof error === "object" && error !== null && error.name === "MessageUnavailableError";
|
|
72
|
+
}
|
|
73
|
+
function assertMessageAvailable(messageId, operation) {
|
|
74
|
+
const normalizedId = normalizeMessageId(messageId);
|
|
75
|
+
if (!normalizedId) return;
|
|
76
|
+
const state = getMessageUnavailableState(normalizedId);
|
|
77
|
+
if (!state) return;
|
|
78
|
+
throw new MessageUnavailableError({
|
|
79
|
+
messageId: normalizedId,
|
|
80
|
+
apiCode: state.apiCode,
|
|
81
|
+
operation: operation ?? state.operation
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function runWithMessageUnavailableGuard(params) {
|
|
85
|
+
const normalizedId = normalizeMessageId(params.messageId);
|
|
86
|
+
if (!normalizedId) {
|
|
87
|
+
return params.fn();
|
|
88
|
+
}
|
|
89
|
+
assertMessageAvailable(normalizedId, params.operation);
|
|
90
|
+
try {
|
|
91
|
+
return await params.fn();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const code = markMessageUnavailableFromError({
|
|
94
|
+
messageId: normalizedId,
|
|
95
|
+
error,
|
|
96
|
+
operation: params.operation
|
|
97
|
+
});
|
|
98
|
+
if (code) {
|
|
99
|
+
throw new MessageUnavailableError({
|
|
100
|
+
messageId: normalizedId,
|
|
101
|
+
apiCode: code,
|
|
102
|
+
operation: params.operation
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
MessageUnavailableError,
|
|
110
|
+
assertMessageAvailable,
|
|
111
|
+
getMessageUnavailableState,
|
|
112
|
+
isMessageUnavailable,
|
|
113
|
+
isMessageUnavailableError,
|
|
114
|
+
isTerminalMessageApiCode,
|
|
115
|
+
markMessageUnavailable,
|
|
116
|
+
markMessageUnavailableFromError,
|
|
117
|
+
runWithMessageUnavailableGuard
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=message-unavailable.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/message-unavailable.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u6D88\u606F\u4E0D\u53EF\u7528\uFF08\u5DF2\u64A4\u56DE/\u5DF2\u5220\u9664\uFF09\u72B6\u6001\u7BA1\u7406\u3002\n *\n * \u76EE\u6807\uFF1A\n * 1) \u5F53\u547D\u4E2D\u98DE\u4E66\u7EC8\u6B62\u9519\u8BEF\u7801\uFF08230011/231003\uFF09\u65F6\uFF0C\u6309 message_id \u6807\u8BB0\u4E0D\u53EF\u7528\uFF1B\n * 2) \u540E\u7EED\u9488\u5BF9\u8BE5 message_id \u7684 API \u8C03\u7528\u76F4\u63A5\u77ED\u8DEF\uFF0C\u907F\u514D\u6301\u7EED\u62A5\u9519\u5237\u5C4F\u3002\n */\n\nimport { LARK_ERROR, MESSAGE_TERMINAL_CODES } from './auth-errors';\nimport { extractLarkApiCode } from './api-error';\nimport { normalizeMessageId } from './targets';\n\nexport type TerminalMessageApiCode = typeof LARK_ERROR.MESSAGE_RECALLED | typeof LARK_ERROR.MESSAGE_DELETED;\n\nexport interface MessageUnavailableState {\n apiCode: TerminalMessageApiCode;\n markedAtMs: number;\n operation?: string;\n}\n\nconst UNAVAILABLE_CACHE_TTL_MS = 30 * 60 * 1000;\nconst MAX_CACHE_SIZE_BEFORE_PRUNE = 512;\n\nconst unavailableMessageCache = new Map<string, MessageUnavailableState>();\n\nfunction pruneExpired(nowMs = Date.now()): void {\n for (const [messageId, state] of unavailableMessageCache) {\n if (nowMs - state.markedAtMs > UNAVAILABLE_CACHE_TTL_MS) {\n unavailableMessageCache.delete(messageId);\n }\n }\n}\n\nexport function isTerminalMessageApiCode(code: unknown): code is TerminalMessageApiCode {\n return typeof code === 'number' && MESSAGE_TERMINAL_CODES.has(code);\n}\n\nexport function markMessageUnavailable(params: {\n messageId: string;\n apiCode: TerminalMessageApiCode;\n operation?: string;\n}): void {\n const normalizedId = normalizeMessageId(params.messageId);\n if (!normalizedId) return;\n\n if (unavailableMessageCache.size >= MAX_CACHE_SIZE_BEFORE_PRUNE) {\n pruneExpired();\n }\n\n unavailableMessageCache.set(normalizedId, {\n apiCode: params.apiCode,\n operation: params.operation,\n markedAtMs: Date.now(),\n });\n}\n\nexport function getMessageUnavailableState(messageId: string | undefined): MessageUnavailableState | undefined {\n const normalizedId = normalizeMessageId(messageId);\n if (!normalizedId) return undefined;\n\n const state = unavailableMessageCache.get(normalizedId);\n if (!state) return undefined;\n\n if (Date.now() - state.markedAtMs > UNAVAILABLE_CACHE_TTL_MS) {\n unavailableMessageCache.delete(normalizedId);\n return undefined;\n }\n\n return state;\n}\n\nexport function isMessageUnavailable(messageId: string | undefined): boolean {\n return !!getMessageUnavailableState(messageId);\n}\n\nexport function markMessageUnavailableFromError(params: {\n messageId: string | undefined;\n error: unknown;\n operation?: string;\n}): TerminalMessageApiCode | undefined {\n const normalizedId = normalizeMessageId(params.messageId);\n if (!normalizedId) return undefined;\n\n const code = extractLarkApiCode(params.error);\n if (!isTerminalMessageApiCode(code)) return undefined;\n\n markMessageUnavailable({\n messageId: normalizedId,\n apiCode: code,\n operation: params.operation,\n });\n return code;\n}\n\nexport class MessageUnavailableError extends Error {\n readonly messageId: string;\n readonly apiCode: TerminalMessageApiCode;\n readonly operation?: string;\n\n constructor(params: { messageId: string; apiCode: TerminalMessageApiCode; operation?: string }) {\n const operationText = params.operation ? `, op=${params.operation}` : '';\n super(\n `[feishu-message-unavailable] message ${params.messageId} unavailable (code=${params.apiCode}${operationText})`,\n );\n this.name = 'MessageUnavailableError';\n this.messageId = params.messageId;\n this.apiCode = params.apiCode;\n this.operation = params.operation;\n }\n}\n\nexport function isMessageUnavailableError(error: unknown): error is MessageUnavailableError {\n return (\n error instanceof MessageUnavailableError ||\n (typeof error === 'object' && error !== null && (error as { name?: string }).name === 'MessageUnavailableError')\n );\n}\n\nexport function assertMessageAvailable(messageId: string | undefined, operation?: string): void {\n const normalizedId = normalizeMessageId(messageId);\n if (!normalizedId) return;\n\n const state = getMessageUnavailableState(normalizedId);\n if (!state) return;\n\n throw new MessageUnavailableError({\n messageId: normalizedId,\n apiCode: state.apiCode,\n operation: operation ?? state.operation,\n });\n}\n\n/**\n * \u9488\u5BF9 message_id \u7684\u7EDF\u4E00\u4FDD\u62A4\uFF1A\n * - \u8C03\u7528\u524D\u68C0\u67E5\u662F\u5426\u5DF2\u6807\u8BB0\u4E0D\u53EF\u7528\uFF1B\n * - \u8C03\u7528\u62A5\u9519\u540E\u8BC6\u522B 230011/231003 \u5E76\u6807\u8BB0\uFF1B\n * - \u547D\u4E2D\u65F6\u629B\u51FA MessageUnavailableError \u4F9B\u4E0A\u6E38\u5FEB\u901F\u7EC8\u6B62\u6D41\u7A0B\u3002\n */\nexport async function runWithMessageUnavailableGuard<T>(params: {\n messageId: string | undefined;\n operation: string;\n fn: () => Promise<T>;\n}): Promise<T> {\n const normalizedId = normalizeMessageId(params.messageId);\n if (!normalizedId) {\n return params.fn();\n }\n\n assertMessageAvailable(normalizedId, params.operation);\n\n try {\n return await params.fn();\n } catch (error) {\n const code = markMessageUnavailableFromError({\n messageId: normalizedId,\n error,\n operation: params.operation,\n });\n if (code) {\n throw new MessageUnavailableError({\n messageId: normalizedId,\n apiCode: code,\n operation: params.operation,\n });\n }\n throw error;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getAppOwnerFallback } from "./app-owner-fallback";
|
|
2
|
+
class OwnerAccessDeniedError extends Error {
|
|
3
|
+
userOpenId;
|
|
4
|
+
appOwnerId;
|
|
5
|
+
constructor(userOpenId, appOwnerId) {
|
|
6
|
+
super("Permission denied: Only the app owner is authorized to use this feature.");
|
|
7
|
+
this.name = "OwnerAccessDeniedError";
|
|
8
|
+
this.userOpenId = userOpenId;
|
|
9
|
+
this.appOwnerId = appOwnerId;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function assertOwnerAccessStrict(account, sdk, userOpenId) {
|
|
13
|
+
const ownerOpenId = await getAppOwnerFallback(account, sdk);
|
|
14
|
+
if (!ownerOpenId) {
|
|
15
|
+
throw new OwnerAccessDeniedError(userOpenId, "unknown");
|
|
16
|
+
}
|
|
17
|
+
if (ownerOpenId !== userOpenId) {
|
|
18
|
+
throw new OwnerAccessDeniedError(userOpenId, ownerOpenId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
OwnerAccessDeniedError,
|
|
23
|
+
assertOwnerAccessStrict
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=owner-policy.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/owner-policy.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * owner-policy.ts \u2014 \u5E94\u7528 Owner \u8BBF\u95EE\u63A7\u5236\u7B56\u7565\u3002\n *\n * \u4ECE uat-client.ts \u8FC1\u79FB owner \u68C0\u67E5\u903B\u8F91\u5230\u72EC\u7ACB policy \u5C42\u3002\n * \u63D0\u4F9B fail-close \u7B56\u7565\uFF08\u5B89\u5168\u4F18\u5148\uFF1A\u6388\u6743\u53D1\u8D77\u8DEF\u5F84\uFF09\u3002\n */\n\nimport type { ConfiguredLarkAccount } from './types';\nimport { getAppOwnerFallback } from './app-owner-fallback';\n\n// ---------------------------------------------------------------------------\n// Error class\n// ---------------------------------------------------------------------------\n\n/**\n * \u975E\u5E94\u7528 owner \u5C1D\u8BD5\u6267\u884C owner-only \u64CD\u4F5C\u65F6\u629B\u51FA\u3002\n *\n * \u6CE8\u610F\uFF1A`appOwnerId` \u4EC5\u7528\u4E8E\u5185\u90E8\u65E5\u5FD7\uFF0C\u4E0D\u5E94\u5E8F\u5217\u5316\u5230\u7528\u6237\u53EF\u89C1\u7684\u54CD\u5E94\u4E2D\uFF0C\n * \u4EE5\u907F\u514D\u6CC4\u9732 owner \u7684 open_id\u3002\n */\nexport class OwnerAccessDeniedError extends Error {\n readonly userOpenId: string;\n readonly appOwnerId: string;\n\n constructor(userOpenId: string, appOwnerId: string) {\n super('Permission denied: Only the app owner is authorized to use this feature.');\n this.name = 'OwnerAccessDeniedError';\n this.userOpenId = userOpenId;\n this.appOwnerId = appOwnerId;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Policy functions\n// ---------------------------------------------------------------------------\n\n/**\n * \u6821\u9A8C\u7528\u6237\u662F\u5426\u4E3A\u5E94\u7528 owner\uFF08fail-close \u7248\u672C\uFF09\u3002\n *\n * - \u83B7\u53D6 owner \u5931\u8D25\u65F6 \u2192 \u62D2\u7EDD\uFF08\u5B89\u5168\u4F18\u5148\uFF09\n * - owner \u4E0D\u5339\u914D\u65F6 \u2192 \u62D2\u7EDD\n *\n * \u9002\u7528\u4E8E\uFF1A`executeAuthorize`\uFF08OAuth \u6388\u6743\u53D1\u8D77\uFF09\u3001`commands/auth.ts`\uFF08\u6279\u91CF\u6388\u6743\uFF09\u7B49\n * \u8D4B\u4E88\u5B9E\u8D28\u6027\u6743\u9650\u7684\u5165\u53E3\u3002\n */\nexport async function assertOwnerAccessStrict(\n account: ConfiguredLarkAccount,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n sdk: any,\n userOpenId: string,\n): Promise<void> {\n const ownerOpenId = await getAppOwnerFallback(account, sdk);\n\n if (!ownerOpenId) {\n throw new OwnerAccessDeniedError(userOpenId, 'unknown');\n }\n\n if (ownerOpenId !== userOpenId) {\n throw new OwnerAccessDeniedError(userOpenId, ownerOpenId);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAWA,SAAS,2BAA2B;AAY7B,MAAM,+BAA+B,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EAET,YAAY,YAAoB,YAAoB;AAClD,UAAM,0EAA0E;AAChF,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,aAAa;AAAA,EACpB;AACF;AAeA,eAAsB,wBACpB,SAEA,KACA,YACe;AACf,QAAM,cAAc,MAAM,oBAAoB,SAAS,GAAG;AAE1D,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,uBAAuB,YAAY,SAAS;AAAA,EACxD;AAEA,MAAI,gBAAgB,YAAY;AAC9B,UAAM,IAAI,uBAAuB,YAAY,WAAW;AAAA,EAC1D;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function getPermissionPriority(scope) {
|
|
2
|
+
const lowerScope = scope.toLowerCase();
|
|
3
|
+
const hasRead = lowerScope.includes("read");
|
|
4
|
+
const hasWrite = lowerScope.includes("write");
|
|
5
|
+
if (hasRead && !hasWrite) return 1;
|
|
6
|
+
if (hasWrite && !hasRead) return 2;
|
|
7
|
+
return 3;
|
|
8
|
+
}
|
|
9
|
+
function extractHighestPriorityScope(scopeList) {
|
|
10
|
+
return scopeList.split(",").sort((a, b) => getPermissionPriority(a) - getPermissionPriority(b))[0] ?? "";
|
|
11
|
+
}
|
|
12
|
+
function extractPermissionGrantUrl(msg) {
|
|
13
|
+
const urlMatch = msg.match(/https:\/\/[^\s]+\/app\/[^\s]+/);
|
|
14
|
+
if (!urlMatch?.[0]) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(urlMatch[0]);
|
|
19
|
+
const scopeListParam = url.searchParams.get("q") ?? "";
|
|
20
|
+
const firstScope = extractHighestPriorityScope(scopeListParam);
|
|
21
|
+
if (firstScope) {
|
|
22
|
+
url.searchParams.set("q", firstScope);
|
|
23
|
+
}
|
|
24
|
+
return url.href;
|
|
25
|
+
} catch {
|
|
26
|
+
return urlMatch[0];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function extractPermissionScopes(msg) {
|
|
30
|
+
const scopeMatch = msg.match(/\[([^\]]+)\]/);
|
|
31
|
+
return scopeMatch?.[1] ?? "unknown";
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
extractPermissionGrantUrl,
|
|
35
|
+
extractPermissionScopes
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=permission-url.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/permission-url.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Permission URL extraction utilities.\n *\n * Shared functions for extracting and processing permission grant URLs\n * from Feishu API error messages.\n */\n\n// ---------------------------------------------------------------------------\n// Permission priority\n// ---------------------------------------------------------------------------\n\n/**\n * Permission priority for sorting.\n * Lower number = higher priority.\n * - read: 1 (highest)\n * - write: 2\n * - other / both read+write: 3 (lowest)\n */\nfunction getPermissionPriority(scope: string): number {\n const lowerScope = scope.toLowerCase();\n const hasRead = lowerScope.includes('read');\n const hasWrite = lowerScope.includes('write');\n\n if (hasRead && !hasWrite) return 1;\n if (hasWrite && !hasRead) return 2;\n return 3;\n}\n\n/**\n * Extract the highest-priority permission from a scope list.\n * Returns the permission with the lowest priority number (read > write > other).\n */\nfunction extractHighestPriorityScope(scopeList: string): string {\n return scopeList.split(',').sort((a, b) => getPermissionPriority(a) - getPermissionPriority(b))[0] ?? '';\n}\n\n// ---------------------------------------------------------------------------\n// Permission URL extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract permission grant URL from a Feishu error message and optimize it\n * by keeping only the highest-priority permission.\n *\n * @param msg - The error message containing the grant URL\n * @returns The optimized grant URL with single permission, or empty string if not found\n */\nexport function extractPermissionGrantUrl(msg: string): string {\n const urlMatch = msg.match(/https:\\/\\/[^\\s]+\\/app\\/[^\\s]+/);\n if (!urlMatch?.[0]) {\n return '';\n }\n\n try {\n const url = new URL(urlMatch[0]);\n const scopeListParam = url.searchParams.get('q') ?? '';\n const firstScope = extractHighestPriorityScope(scopeListParam);\n if (firstScope) {\n url.searchParams.set('q', firstScope);\n }\n return url.href;\n } catch {\n return urlMatch[0];\n }\n}\n\n/**\n * Extract permission scopes from a Feishu error message.\n * Looks for scopes in the format [scope1,scope2,...]\n */\nexport function extractPermissionScopes(msg: string): string {\n const scopeMatch = msg.match(/\\[([^\\]]+)\\]/);\n return scopeMatch?.[1] ?? 'unknown';\n}\n"],
|
|
5
|
+
"mappings": "AAqBA,SAAS,sBAAsB,OAAuB;AACpD,QAAM,aAAa,MAAM,YAAY;AACrC,QAAM,UAAU,WAAW,SAAS,MAAM;AAC1C,QAAM,WAAW,WAAW,SAAS,OAAO;AAE5C,MAAI,WAAW,CAAC,SAAU,QAAO;AACjC,MAAI,YAAY,CAAC,QAAS,QAAO;AACjC,SAAO;AACT;AAMA,SAAS,4BAA4B,WAA2B;AAC9D,SAAO,UAAU,MAAM,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,sBAAsB,CAAC,IAAI,sBAAsB,CAAC,CAAC,EAAE,CAAC,KAAK;AACxG;AAaO,SAAS,0BAA0B,KAAqB;AAC7D,QAAM,WAAW,IAAI,MAAM,+BAA+B;AAC1D,MAAI,CAAC,WAAW,CAAC,GAAG;AAClB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,SAAS,CAAC,CAAC;AAC/B,UAAM,iBAAiB,IAAI,aAAa,IAAI,GAAG,KAAK;AACpD,UAAM,aAAa,4BAA4B,cAAc;AAC7D,QAAI,YAAY;AACd,UAAI,aAAa,IAAI,KAAK,UAAU;AAAA,IACtC;AACA,WAAO,IAAI;AAAA,EACb,QAAQ;AACN,WAAO,SAAS,CAAC;AAAA,EACnB;AACF;AAMO,SAAS,wBAAwB,KAAqB;AAC3D,QAAM,aAAa,IAAI,MAAM,cAAc;AAC3C,SAAO,aAAa,CAAC,KAAK;AAC5B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { larkLogger } from "./lark-logger";
|
|
4
|
+
import {
|
|
5
|
+
discoverOAuthMetadata,
|
|
6
|
+
registerOAuthClient,
|
|
7
|
+
generatePKCE,
|
|
8
|
+
buildAuthorizationUrl,
|
|
9
|
+
exchangeCodeForTokens,
|
|
10
|
+
extractCodeFromCallbackUrl,
|
|
11
|
+
refreshAccessToken
|
|
12
|
+
} from "./project-oauth-flow";
|
|
13
|
+
import { getProjectMcpEndpoint } from "../tools/mcp/project/endpoint";
|
|
14
|
+
const log = larkLogger("core/project-auth");
|
|
15
|
+
const CALLBACK_HOST = "127.0.0.1";
|
|
16
|
+
const CALLBACK_PATH = "/callback";
|
|
17
|
+
async function prepareAuthSession(mcpEndpoint, redirectUri) {
|
|
18
|
+
const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();
|
|
19
|
+
log.info(`discovering OAuth metadata from ${endpoint}`);
|
|
20
|
+
const metadata = await discoverOAuthMetadata(endpoint);
|
|
21
|
+
const callbackUri = redirectUri ?? `http://${CALLBACK_HOST}:0${CALLBACK_PATH}`;
|
|
22
|
+
log.info("registering OAuth client via dynamic registration");
|
|
23
|
+
const client = await registerOAuthClient(
|
|
24
|
+
metadata.registration_endpoint,
|
|
25
|
+
callbackUri
|
|
26
|
+
);
|
|
27
|
+
const pkce = generatePKCE();
|
|
28
|
+
const state = randomUUID();
|
|
29
|
+
const authorizationUrl = buildAuthorizationUrl({
|
|
30
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
31
|
+
clientId: client.client_id,
|
|
32
|
+
redirectUri: callbackUri,
|
|
33
|
+
codeChallenge: pkce.codeChallenge,
|
|
34
|
+
state
|
|
35
|
+
});
|
|
36
|
+
return { metadata, client, pkce, state, redirectUri: callbackUri, authorizationUrl };
|
|
37
|
+
}
|
|
38
|
+
async function startLocalAuthFlow(mcpEndpoint, signal) {
|
|
39
|
+
const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();
|
|
40
|
+
log.info(`discovering OAuth metadata from ${endpoint}`);
|
|
41
|
+
const metadata = await discoverOAuthMetadata(endpoint);
|
|
42
|
+
const server = http.createServer();
|
|
43
|
+
const port = await new Promise((resolve, reject) => {
|
|
44
|
+
server.listen(0, CALLBACK_HOST, () => {
|
|
45
|
+
const addr = server.address();
|
|
46
|
+
if (addr && typeof addr === "object") {
|
|
47
|
+
resolve(addr.port);
|
|
48
|
+
} else {
|
|
49
|
+
reject(new Error("Failed to determine callback port"));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.once("error", reject);
|
|
53
|
+
});
|
|
54
|
+
const redirectUri = `http://${CALLBACK_HOST}:${port}${CALLBACK_PATH}`;
|
|
55
|
+
const client = await registerOAuthClient(
|
|
56
|
+
metadata.registration_endpoint,
|
|
57
|
+
redirectUri
|
|
58
|
+
);
|
|
59
|
+
const pkce = generatePKCE();
|
|
60
|
+
const state = randomUUID();
|
|
61
|
+
const authorizationUrl = buildAuthorizationUrl({
|
|
62
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
63
|
+
clientId: client.client_id,
|
|
64
|
+
redirectUri,
|
|
65
|
+
codeChallenge: pkce.codeChallenge,
|
|
66
|
+
state
|
|
67
|
+
});
|
|
68
|
+
const session = {
|
|
69
|
+
metadata,
|
|
70
|
+
client,
|
|
71
|
+
pkce,
|
|
72
|
+
state,
|
|
73
|
+
redirectUri,
|
|
74
|
+
authorizationUrl
|
|
75
|
+
};
|
|
76
|
+
const waitForCode = () => new Promise((resolve, reject) => {
|
|
77
|
+
const onAbort = () => {
|
|
78
|
+
reject(new Error("Authorization cancelled"));
|
|
79
|
+
cleanup();
|
|
80
|
+
};
|
|
81
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
signal?.removeEventListener("abort", onAbort);
|
|
84
|
+
};
|
|
85
|
+
server.on("request", async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const reqUrl = new URL(req.url ?? "", `http://${CALLBACK_HOST}:${port}`);
|
|
88
|
+
if (reqUrl.pathname !== CALLBACK_PATH) {
|
|
89
|
+
res.statusCode = 404;
|
|
90
|
+
res.end("Not found");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const { code, state: receivedState } = extractCodeFromCallbackUrl(reqUrl.toString());
|
|
94
|
+
if (receivedState && receivedState !== session.state) {
|
|
95
|
+
res.statusCode = 400;
|
|
96
|
+
res.setHeader("Content-Type", "text/html");
|
|
97
|
+
res.end("<html><body><h1>Authorization failed</h1><p>Invalid state</p></body></html>");
|
|
98
|
+
reject(new Error("OAuth state mismatch"));
|
|
99
|
+
cleanup();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
res.statusCode = 200;
|
|
103
|
+
res.setHeader("Content-Type", "text/html");
|
|
104
|
+
res.end("<html><body><h1>Authorization successful</h1><p>You can close this tab.</p></body></html>");
|
|
105
|
+
const tokens = await exchangeCodeForTokens({
|
|
106
|
+
tokenEndpoint: session.metadata.token_endpoint,
|
|
107
|
+
code,
|
|
108
|
+
codeVerifier: session.pkce.codeVerifier,
|
|
109
|
+
clientId: session.client.client_id,
|
|
110
|
+
redirectUri: session.redirectUri
|
|
111
|
+
});
|
|
112
|
+
resolve({ tokens, clientId: session.client.client_id });
|
|
113
|
+
cleanup();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
res.statusCode = 500;
|
|
116
|
+
res.end("Internal error");
|
|
117
|
+
reject(err);
|
|
118
|
+
cleanup();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
const close = async () => {
|
|
123
|
+
await new Promise((resolve) => {
|
|
124
|
+
server.close(() => resolve());
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
return { session, waitForCode, port, close };
|
|
128
|
+
}
|
|
129
|
+
async function prepareRemoteAuth(mcpEndpoint) {
|
|
130
|
+
const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();
|
|
131
|
+
const metadata = await discoverOAuthMetadata(endpoint);
|
|
132
|
+
const redirectUri = `http://${CALLBACK_HOST}:3456${CALLBACK_PATH}`;
|
|
133
|
+
const client = await registerOAuthClient(
|
|
134
|
+
metadata.registration_endpoint,
|
|
135
|
+
redirectUri
|
|
136
|
+
);
|
|
137
|
+
const pkce = generatePKCE();
|
|
138
|
+
const state = randomUUID();
|
|
139
|
+
const authorizationUrl = buildAuthorizationUrl({
|
|
140
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
141
|
+
clientId: client.client_id,
|
|
142
|
+
redirectUri,
|
|
143
|
+
codeChallenge: pkce.codeChallenge,
|
|
144
|
+
state
|
|
145
|
+
});
|
|
146
|
+
return { metadata, client, pkce, state, redirectUri, authorizationUrl };
|
|
147
|
+
}
|
|
148
|
+
async function completeRemoteAuth(session, callbackUrl) {
|
|
149
|
+
const { code, state: receivedState } = extractCodeFromCallbackUrl(callbackUrl);
|
|
150
|
+
if (receivedState && receivedState !== session.state) {
|
|
151
|
+
throw new Error("OAuth state mismatch \u2014 \u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743");
|
|
152
|
+
}
|
|
153
|
+
const tokens = await exchangeCodeForTokens({
|
|
154
|
+
tokenEndpoint: session.metadata.token_endpoint,
|
|
155
|
+
code,
|
|
156
|
+
codeVerifier: session.pkce.codeVerifier,
|
|
157
|
+
clientId: session.client.client_id,
|
|
158
|
+
redirectUri: session.redirectUri
|
|
159
|
+
});
|
|
160
|
+
return { tokens, clientId: session.client.client_id };
|
|
161
|
+
}
|
|
162
|
+
async function refreshProjectToken(mcpEndpoint, refreshToken, clientId) {
|
|
163
|
+
const metadata = await discoverOAuthMetadata(mcpEndpoint);
|
|
164
|
+
return refreshAccessToken({
|
|
165
|
+
tokenEndpoint: metadata.token_endpoint,
|
|
166
|
+
refreshToken,
|
|
167
|
+
clientId
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
completeRemoteAuth,
|
|
172
|
+
prepareAuthSession,
|
|
173
|
+
prepareRemoteAuth,
|
|
174
|
+
refreshProjectToken,
|
|
175
|
+
startLocalAuthFlow
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=project-auth.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/project-auth.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u7F16\u6392\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u4F9D\u8D56\u98DE\u4E66\u5F00\u653E\u5E73\u53F0 appId/appSecret\uFF0C\u4E5F\u4E0D\u8D70 Device Flow\u3002\n *\n * \u652F\u6301\u4E24\u79CD\u8FD0\u884C\u6A21\u5F0F\uFF1A\n * - \u672C\u5730\u6A21\u5F0F\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\uFF1A\u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u5B8C\u6210\u6388\u6743\n * - \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\uFF1A\u751F\u6210\u6388\u6743\u94FE\u63A5\uFF0C\u7528\u6237\u5728\u5916\u90E8\u6D4F\u89C8\u5668\u5B8C\u6210\u540E\u56DE\u4F20 URL\n */\n\nimport http from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\nimport {\n discoverOAuthMetadata,\n registerOAuthClient,\n generatePKCE,\n buildAuthorizationUrl,\n exchangeCodeForTokens,\n extractCodeFromCallbackUrl,\n refreshAccessToken,\n type OAuthMetadata,\n type OAuthClientInfo,\n type OAuthTokens,\n type PKCEPair,\n} from './project-oauth-flow';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\n\nconst log = larkLogger('core/project-auth');\n\nconst CALLBACK_HOST = '127.0.0.1';\nconst CALLBACK_PATH = '/callback';\n\n// ---------------------------------------------------------------------------\n// Pending authorization session \u2014 \u5171\u4EAB\u72B6\u6001\uFF0C\u4F9B\u672C\u5730\u56DE\u8C03\u548C\u8FDC\u7A0B\u624B\u52A8\u4E24\u79CD\u6A21\u5F0F\u4F7F\u7528\n// ---------------------------------------------------------------------------\n\nexport interface ProjectAuthSession {\n metadata: OAuthMetadata;\n client: OAuthClientInfo;\n pkce: PKCEPair;\n state: string;\n redirectUri: string;\n authorizationUrl: string;\n}\n\n// ---------------------------------------------------------------------------\n// \u516C\u5171\u6B65\u9AA4\uFF1A\u53D1\u73B0 + \u6CE8\u518C + PKCE + \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport async function prepareAuthSession(\n mcpEndpoint?: string,\n redirectUri?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n const callbackUri = redirectUri ?? `http://${CALLBACK_HOST}:0${CALLBACK_PATH}`;\n\n log.info('registering OAuth client via dynamic registration');\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n callbackUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri: callbackUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n return { metadata, client, pkce, state, redirectUri: callbackUri, authorizationUrl };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 1\uFF1A\u672C\u5730\u56DE\u8C03\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\nexport interface LocalAuthResult {\n tokens: OAuthTokens;\n clientId: string;\n}\n\n/**\n * \u542F\u52A8\u672C\u5730 HTTP \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u7B49\u5F85\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5E26\u56DE authorization code\u3002\n *\n * \u6D41\u7A0B\uFF1A\n * 1. prepareAuthSession\uFF08\u52A8\u6001\u6CE8\u518C\u65F6\u7528\u5B9E\u9645\u7AEF\u53E3\u7684 redirect_uri\uFF09\n * 2. \u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\n * 3. \u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u8C03\u7528\u65B9\u8D1F\u8D23\uFF09\n * 4. \u7B49\u5F85\u56DE\u8C03 \u2192 \u63D0\u53D6 code \u2192 \u6362 token\n * 5. \u5173\u95ED\u670D\u52A1\u5668\n */\nexport async function startLocalAuthFlow(\n mcpEndpoint?: string,\n signal?: AbortSignal,\n): Promise<{\n session: ProjectAuthSession;\n waitForCode: () => Promise<LocalAuthResult>;\n port: number;\n close: () => Promise<void>;\n}> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u5148\u542F\u52A8\u670D\u52A1\u5668\u62FF\u5230\u5B9E\u9645\u7AEF\u53E3\uFF0C\u518D\u7528\u8BE5\u7AEF\u53E3\u6CE8\u518C\u5BA2\u6237\u7AEF\n const server = http.createServer();\n const port = await new Promise<number>((resolve, reject) => {\n server.listen(0, CALLBACK_HOST, () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') {\n resolve(addr.port);\n } else {\n reject(new Error('Failed to determine callback port'));\n }\n });\n server.once('error', reject);\n });\n\n const redirectUri = `http://${CALLBACK_HOST}:${port}${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n const session: ProjectAuthSession = {\n metadata,\n client,\n pkce,\n state,\n redirectUri,\n authorizationUrl,\n };\n\n // \u7B49\u5F85\u56DE\u8C03\u7684 Promise\n const waitForCode = (): Promise<LocalAuthResult> =>\n new Promise<LocalAuthResult>((resolve, reject) => {\n const onAbort = () => {\n reject(new Error('Authorization cancelled'));\n cleanup();\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const cleanup = () => {\n signal?.removeEventListener('abort', onAbort);\n };\n\n server.on('request', async (req, res) => {\n try {\n const reqUrl = new URL(req.url ?? '', `http://${CALLBACK_HOST}:${port}`);\n if (reqUrl.pathname !== CALLBACK_PATH) {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n\n const { code, state: receivedState } = extractCodeFromCallbackUrl(reqUrl.toString());\n\n if (receivedState && receivedState !== session.state) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization failed</h1><p>Invalid state</p></body></html>');\n reject(new Error('OAuth state mismatch'));\n cleanup();\n return;\n }\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization successful</h1><p>You can close this tab.</p></body></html>');\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n resolve({ tokens, clientId: session.client.client_id });\n cleanup();\n } catch (err) {\n res.statusCode = 500;\n res.end('Internal error');\n reject(err);\n cleanup();\n }\n });\n });\n\n const close = async () => {\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n };\n\n return { session, waitForCode, port, close };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 2\uFF1A\u8FDC\u7A0B\u624B\u52A8\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\n/**\n * \u4E3A\u8FDC\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\u573A\u666F\u51C6\u5907\u6388\u6743\u3002\n *\n * \u8FD4\u56DE session \u548C authorizationUrl\uFF0C\u8C03\u7528\u65B9\u5C06 URL \u53D1\u9001\u7ED9\u7528\u6237\u3002\n * \u7528\u6237\u5728\u672C\u5730\u6D4F\u89C8\u5668\u5B8C\u6210\u6388\u6743\u540E\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5230 127.0.0.1\uFF08\u4E0D\u53EF\u8FBE\uFF09\uFF0C\n * \u7528\u6237\u4ECE\u5730\u5740\u680F\u590D\u5236\u5B8C\u6574 URL \u56DE\u4F20\u3002\u8C03\u7528\u65B9\u7528 completeRemoteAuth \u5B8C\u6210\u6362 token\u3002\n */\nexport async function prepareRemoteAuth(\n mcpEndpoint?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u8FDC\u7A0B\u6A21\u5F0F\u4F7F\u7528\u56FA\u5B9A\u7AEF\u53E3\u7684 redirect_uri\uFF08\u7528\u6237\u9700\u8981\u624B\u52A8\u590D\u5236 URL\uFF09\n const redirectUri = `http://${CALLBACK_HOST}:3456${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n return { metadata, client, pkce, state, redirectUri, authorizationUrl };\n}\n\n/**\n * \u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL \u540E\uFF0C\u63D0\u53D6 code \u5E76\u6362 token\u3002\n */\nexport async function completeRemoteAuth(\n session: ProjectAuthSession,\n callbackUrl: string,\n): Promise<LocalAuthResult> {\n const { code, state: receivedState } = extractCodeFromCallbackUrl(callbackUrl);\n\n if (receivedState && receivedState !== session.state) {\n throw new Error('OAuth state mismatch \u2014 \u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743');\n }\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n return { tokens, clientId: session.client.client_id };\n}\n\n// ---------------------------------------------------------------------------\n// Token \u5237\u65B0\uFF08\u4E24\u79CD\u6A21\u5F0F\u5171\u7528\uFF09\n// ---------------------------------------------------------------------------\n\nexport async function refreshProjectToken(\n mcpEndpoint: string,\n refreshToken: string,\n clientId: string,\n): Promise<OAuthTokens> {\n const metadata = await discoverOAuthMetadata(mcpEndpoint);\n return refreshAccessToken({\n tokenEndpoint: metadata.token_endpoint,\n refreshToken,\n clientId,\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
2
|
+
import { larkLogger } from "./lark-logger";
|
|
3
|
+
const log = larkLogger("core/project-oauth-flow");
|
|
4
|
+
async function discoverOAuthMetadata(serverUrl) {
|
|
5
|
+
const base = serverUrl.replace(/\/+$/, "");
|
|
6
|
+
const origin = new URL(base).origin;
|
|
7
|
+
const candidates = [
|
|
8
|
+
`${base}/.well-known/oauth-authorization-server`,
|
|
9
|
+
`${origin}/.well-known/oauth-authorization-server`
|
|
10
|
+
];
|
|
11
|
+
for (const url of candidates) {
|
|
12
|
+
const res = await fetch(url);
|
|
13
|
+
if (res.ok) {
|
|
14
|
+
const data = await res.json();
|
|
15
|
+
log.info(`OAuth metadata discovered from ${url}`);
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Failed to discover OAuth metadata from ${serverUrl} (tried ${candidates.join(", ")})`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
async function registerOAuthClient(registrationEndpoint, redirectUri, clientName) {
|
|
24
|
+
const body = {
|
|
25
|
+
client_name: clientName ?? "openclaw-feishu-project",
|
|
26
|
+
redirect_uris: [redirectUri],
|
|
27
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
28
|
+
response_types: ["code"],
|
|
29
|
+
token_endpoint_auth_method: "none"
|
|
30
|
+
};
|
|
31
|
+
const res = await fetch(registrationEndpoint, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify(body)
|
|
35
|
+
});
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`OAuth client registration failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);
|
|
39
|
+
}
|
|
40
|
+
const data = JSON.parse(text);
|
|
41
|
+
log.info(`OAuth client registered: client_id=${data.client_id}`);
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
function generatePKCE() {
|
|
45
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
46
|
+
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
47
|
+
return { codeVerifier, codeChallenge };
|
|
48
|
+
}
|
|
49
|
+
function buildAuthorizationUrl(params) {
|
|
50
|
+
const url = new URL(params.authorizationEndpoint);
|
|
51
|
+
url.searchParams.set("response_type", "code");
|
|
52
|
+
url.searchParams.set("client_id", params.clientId);
|
|
53
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
54
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
55
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
56
|
+
url.searchParams.set("state", params.state);
|
|
57
|
+
if (params.scope) {
|
|
58
|
+
url.searchParams.set("scope", params.scope);
|
|
59
|
+
}
|
|
60
|
+
return url.toString();
|
|
61
|
+
}
|
|
62
|
+
async function exchangeCodeForTokens(params) {
|
|
63
|
+
const body = new URLSearchParams({
|
|
64
|
+
grant_type: "authorization_code",
|
|
65
|
+
code: params.code,
|
|
66
|
+
redirect_uri: params.redirectUri,
|
|
67
|
+
client_id: params.clientId,
|
|
68
|
+
code_verifier: params.codeVerifier
|
|
69
|
+
});
|
|
70
|
+
const res = await fetch(params.tokenEndpoint, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
73
|
+
body: body.toString()
|
|
74
|
+
});
|
|
75
|
+
const text = await res.text();
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(`Token exchange failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);
|
|
78
|
+
}
|
|
79
|
+
const data = JSON.parse(text);
|
|
80
|
+
log.info("Authorization code exchanged for tokens");
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
async function refreshAccessToken(params) {
|
|
84
|
+
const body = new URLSearchParams({
|
|
85
|
+
grant_type: "refresh_token",
|
|
86
|
+
refresh_token: params.refreshToken,
|
|
87
|
+
client_id: params.clientId
|
|
88
|
+
});
|
|
89
|
+
const res = await fetch(params.tokenEndpoint, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
92
|
+
body: body.toString()
|
|
93
|
+
});
|
|
94
|
+
const text = await res.text();
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new Error(`Token refresh failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);
|
|
97
|
+
}
|
|
98
|
+
const data = JSON.parse(text);
|
|
99
|
+
log.info("Access token refreshed");
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
function extractCodeFromCallbackUrl(callbackUrl) {
|
|
103
|
+
const url = new URL(callbackUrl);
|
|
104
|
+
const code = url.searchParams.get("code");
|
|
105
|
+
const error = url.searchParams.get("error");
|
|
106
|
+
if (error) {
|
|
107
|
+
const desc = url.searchParams.get("error_description") ?? error;
|
|
108
|
+
throw new Error(`OAuth authorization denied: ${desc}`);
|
|
109
|
+
}
|
|
110
|
+
if (!code) {
|
|
111
|
+
throw new Error("Callback URL missing authorization code");
|
|
112
|
+
}
|
|
113
|
+
return { code, state: url.searchParams.get("state") };
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
buildAuthorizationUrl,
|
|
117
|
+
discoverOAuthMetadata,
|
|
118
|
+
exchangeCodeForTokens,
|
|
119
|
+
extractCodeFromCallbackUrl,
|
|
120
|
+
generatePKCE,
|
|
121
|
+
refreshAccessToken,
|
|
122
|
+
registerOAuthClient
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=project-oauth-flow.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/project-oauth-flow.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP OAuth \u539F\u8BED\n *\n * \u5B9E\u73B0\u6807\u51C6 MCP OAuth \u6D41\u7A0B\uFF1A\n * 1. \u53D1\u73B0 OAuth \u7AEF\u70B9 (RFC 8414)\n * 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C (RFC 7591)\n * 3. PKCE (RFC 7636)\n * 4. Authorization Code \u6362 token\n * 5. Refresh token\n *\n * \u4E0D\u4F9D\u8D56\u4EFB\u4F55\u7B2C\u4E09\u65B9 OAuth \u5E93\uFF0C\u4EC5\u4F7F\u7528 Node 18+ \u5185\u7F6E fetch + crypto\u3002\n */\n\nimport { randomBytes, createHash } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/project-oauth-flow');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthMetadata {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n registration_endpoint: string;\n response_types_supported: string[];\n grant_types_supported: string[];\n code_challenge_methods_supported: string[];\n scopes_supported: string[];\n}\n\nexport interface OAuthClientInfo {\n client_id: string;\n client_secret?: string;\n redirect_uris: string[];\n client_name: string;\n}\n\nexport interface PKCEPair {\n codeVerifier: string;\n codeChallenge: string;\n}\n\nexport interface OAuthTokens {\n access_token: string;\n refresh_token?: string;\n token_type: string;\n expires_in?: number;\n scope?: string;\n}\n\n// ---------------------------------------------------------------------------\n// 1. \u53D1\u73B0 OAuth \u7AEF\u70B9\n// ---------------------------------------------------------------------------\n\nexport async function discoverOAuthMetadata(serverUrl: string): Promise<OAuthMetadata> {\n const base = serverUrl.replace(/\\/+$/, '');\n // MCP \u89C4\u8303\uFF1A\u5148\u5C1D\u8BD5 path-aware\uFF0C\u518D\u56DE\u9000 root\n const origin = new URL(base).origin;\n const candidates = [\n `${base}/.well-known/oauth-authorization-server`,\n `${origin}/.well-known/oauth-authorization-server`,\n ];\n\n for (const url of candidates) {\n const res = await fetch(url);\n if (res.ok) {\n const data = (await res.json()) as OAuthMetadata;\n log.info(`OAuth metadata discovered from ${url}`);\n return data;\n }\n }\n\n throw new Error(\n `Failed to discover OAuth metadata from ${serverUrl} (tried ${candidates.join(', ')})`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\n// ---------------------------------------------------------------------------\n\nexport async function registerOAuthClient(\n registrationEndpoint: string,\n redirectUri: string,\n clientName?: string,\n): Promise<OAuthClientInfo> {\n const body = {\n client_name: clientName ?? 'openclaw-feishu-project',\n redirect_uris: [redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n };\n\n const res = await fetch(registrationEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`OAuth client registration failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthClientInfo;\n log.info(`OAuth client registered: client_id=${data.client_id}`);\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 3. PKCE\n// ---------------------------------------------------------------------------\n\nexport function generatePKCE(): PKCEPair {\n const codeVerifier = randomBytes(32).toString('base64url');\n const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');\n return { codeVerifier, codeChallenge };\n}\n\n// ---------------------------------------------------------------------------\n// 4. \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport function buildAuthorizationUrl(params: {\n authorizationEndpoint: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n state: string;\n scope?: string;\n}): string {\n const url = new URL(params.authorizationEndpoint);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', params.clientId);\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('code_challenge', params.codeChallenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', params.state);\n if (params.scope) {\n url.searchParams.set('scope', params.scope);\n }\n return url.toString();\n}\n\n// ---------------------------------------------------------------------------\n// 5. Authorization Code \u2192 Token\n// ---------------------------------------------------------------------------\n\nexport async function exchangeCodeForTokens(params: {\n tokenEndpoint: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n redirectUri: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: params.clientId,\n code_verifier: params.codeVerifier,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token exchange failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Authorization code exchanged for tokens');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 6. Refresh Token\n// ---------------------------------------------------------------------------\n\nexport async function refreshAccessToken(params: {\n tokenEndpoint: string;\n refreshToken: string;\n clientId: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: params.refreshToken,\n client_id: params.clientId,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token refresh failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Access token refreshed');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 7. \u4ECE\u56DE\u8C03 URL \u4E2D\u63D0\u53D6 code \u548C state\n// ---------------------------------------------------------------------------\n\nexport function extractCodeFromCallbackUrl(\n callbackUrl: string,\n): { code: string; state: string | null } {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n throw new Error(`OAuth authorization denied: ${desc}`);\n }\n if (!code) {\n throw new Error('Callback URL missing authorization code');\n }\n return { code, state: url.searchParams.get('state') };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|