@xopcai/xopc 0.0.231 → 0.0.232
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.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/schema/config-schema.d.ts +3 -3
- package/dist/extensions/telegram/src/config-schema.d.ts +4 -4
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/config-schema.d.ts +3 -3
- package/dist/gateway/static/root/assets/{activity-store-M0nAY5XB.js → activity-store-VXCWz43N.js} +1 -1
- package/dist/gateway/static/root/assets/agent-avatar-dicebear-BXRaZYW8.js +1 -0
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-dR7HMaRX.js +1 -0
- package/dist/gateway/static/root/assets/agents-Bileycwe.js +1 -0
- package/dist/gateway/static/root/assets/api-wGPgL8xY.js +1 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-DP7YVRPn.js → app-management-settings-panel-BJfClfPx.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-CVQ5n7y6.js → appearance-settings-C_R0vUAu.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-DSCia0DT.js → apps-page-CBow8pBI.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-7_DXaMTP.js → archive-plugin-DnFHkSHE.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-mmr_mHRr.js → attachment-load-DZrEwytM.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-DfY-T6Ga.js → automations-page-DpkKajui.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BBntVWgv.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CO9QMEh2.js → binary-plugins-Ckc_Csue.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CE14CVOy.js → block-editor-DAC6w-iV.js} +4 -4
- package/dist/gateway/static/root/assets/browser-settings-page-DrsMd-Po.js +8 -0
- package/dist/gateway/static/root/assets/browser-workflow-inputs-BGd7R8GO.js +1 -0
- package/dist/gateway/static/root/assets/{browser-workflows-page-1wgXyoSO.js → browser-workflows-page-DUfj7ntM.js} +2 -2
- package/dist/gateway/static/root/assets/capabilities-settings-panel-97gkGeSV.js +7 -0
- package/dist/gateway/static/root/assets/channels-settings-CuBsz8UI.js +1 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-C5ABr73b.js → channels-status-swr-CfboECLt.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-CdRW6Euc.js → chat-terminal-dock-CA9fndbZ.js} +2 -2
- package/dist/gateway/static/root/assets/{code-editor-dFzqyDXS.js → code-editor-D4_k-6QN.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-SuL7td7Z.js +2 -0
- package/dist/gateway/static/root/assets/cron-expression-CqPu7HYJ.js +2 -0
- package/dist/gateway/static/root/assets/date-picker-C8dJsCgk.js +1 -0
- package/dist/gateway/static/root/assets/dependency-picker-Iz2wYimy.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-CMO-J3cG.js → desktop-pet-C3h40FmG.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-MmOR4A3u.js → desktop-pet-settings-YfeVVzLe.js} +1 -1
- package/dist/gateway/static/root/assets/dist-DqPZM2MH2.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-DdH0dG2_.js → extension-debug-page-CChgztz_.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-Clg-KktN.js → extension-page-DF6ct6_y.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-jgBsjn0F.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-config-api-DEHnF4Wm.js → gateway-config-api-CVajuIGL.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-config-swr-BGQn8duh.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-BbssNGid.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-B_zrtccP.js → gateway-startup-retry-D2Rnh9fX.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-CJtXu8iD.js +1 -0
- package/dist/gateway/static/root/assets/home-page-Be1o1AXQ.js +1 -0
- package/dist/gateway/static/root/assets/html-workspace-editor-C3DKAkMc.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-YKP2me5Z.js → image-generation-api-D7W2p5_8.js} +1 -1
- package/dist/gateway/static/root/assets/index-B2OJSXX9.js +70 -0
- package/dist/gateway/static/root/assets/index-rHjAElxG.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BvpEeHnm.js → keyboard-shortcuts-settings-BvHYyNus.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-COfelgk7.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-Bzvn99eH.js → local-apps-page-Cb8t2wWM.js} +1 -1
- package/dist/gateway/static/root/assets/locale-store-DLwVxS3V.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-DA8oDjeB.js +2 -0
- package/dist/gateway/static/root/assets/management-settings-B8Jie4-J.js +1 -0
- package/dist/gateway/static/root/assets/markdown-editor-nDLmA_LF.js +2 -0
- package/dist/gateway/static/root/assets/markdown-split-BD1r4o2i.js +1 -0
- package/dist/gateway/static/root/assets/{markdown-view-RxNHwlNo.js → markdown-view-B-BHEcNd.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D8I7OGnT.js → media-plugins-BCev8fkW.js} +1 -1
- package/dist/gateway/static/root/assets/messages-Dp1OxSKt.js +3 -0
- package/dist/gateway/static/root/assets/mobile-device-api-DACoHhhk.js +1 -0
- package/dist/gateway/static/root/assets/{models-hub-cache-0RjEwafI.js → models-hub-cache-C8zjqmIv.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-api-EpgG-OQc.js → notes-api-DEdNKw0I.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-CCvjGMhN.js → notes-page-BBTLjJwa.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-DICgUGPv.js +4 -0
- package/dist/gateway/static/root/assets/{oauth-api-BwNLwbI8.js → oauth-api-BNCO2YK5.js} +1 -1
- package/dist/gateway/static/root/assets/popover-select-3E24E65N.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-MmMswmOU.js → product-open-page-fEBbRekH.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CqdkznYJ.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-dHDXpMCg.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-BFZrVYfy.js → registry-api-Cvr4Qm7N.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-2feZudJw.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Dx6o7sLu.js → runtime-tools-settings-panel-BT5B74IH.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-Cdd9c4vt.js → schema-form-C4tB34S5.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CO2JCFdy.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-DxMVaqdn.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-D0YxMnhm.js → settings-form-section-D1oXhkgI.js} +1 -1
- package/dist/gateway/static/root/assets/settings-loading-skeleton-CldfX6FJ.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-c3LJnbvE.js +1 -0
- package/dist/gateway/static/root/assets/settings-shell-layer-context-DCp6ykjQ.js +1 -0
- package/dist/gateway/static/root/assets/setup-status-panel-DZNHb6A5.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DiqBn-oE.js → share-preview-page-DptCK_mk.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-DakZ2AGN.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BOiNkWJC.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CZwhWiRl.js → skills-page-OHBeBtSg.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-kBGr8opZ.js → skills-page.utils-B9WZbOLC.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CPBs6lWi.js → system-settings-panel-Dj_DpOGw.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-D5Nbe3C8.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-Qgvo0Ga7.js → theme-store-Cpd3Gd5R.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-C4YcEiP9.js → use-autosave-DCl5Yq7N.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-CAPSUK8B.js +1 -0
- package/dist/gateway/static/root/assets/{vendor-codemirror-DRr7hFaD.js → vendor-codemirror--gTq3YAI.js} +16 -16
- package/dist/gateway/static/root/assets/voice-api-key-field-BB_ZQV8F.js +1 -0
- package/dist/gateway/static/root/assets/voice-config-api-BNiYmpl5.js +1 -0
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CKIdPEen.js → work-discovery-overlay-BrIdtdum.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BK44Vm5W.js → workflow-run-setup-panel-hKh-uCLo.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-ahDvypGO.js +5 -0
- package/dist/gateway/static/root/index.html +16 -16
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +1 -2
- package/dist/src/agent/agent-manager.js +29 -44
- package/dist/src/agent/background-review/run-background-review.js +10 -15
- package/dist/src/agent/embedded/run-for-session.js +1 -1
- package/dist/src/agent/external-tools/mcp-provider.js +3 -13
- package/dist/src/agent/image/generation/runtime.js +1 -1
- package/dist/src/agent/image/tool-model-config.js +13 -14
- package/dist/src/agent/mcp/bundle-mcp-policy.d.ts +0 -9
- package/dist/src/agent/mcp/bundle-mcp-policy.js +1 -13
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
- package/dist/src/agent/mcp/index.js +2 -2
- package/dist/src/agent/media-generation/runtime-shared.d.ts +1 -1
- package/dist/src/agent/media-generation/runtime-shared.js +1 -1
- package/dist/src/agent/memory/create-memory-manager.js +1 -1
- package/dist/src/agent/memory/manager.d.ts +1 -1
- package/dist/src/{agent-runtime/memory-runtime.d.ts → agent/memory/runtime.d.ts} +1 -1
- package/dist/src/{agent-runtime/memory-runtime.js → agent/memory/runtime.js} +2 -3
- package/dist/src/agent/orchestration/agent-orchestrator.d.ts +0 -3
- package/dist/src/agent/orchestration/agent-orchestrator.js +1 -3
- package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +2 -2
- package/dist/src/agent/service.js +0 -1
- package/dist/src/agent/starter-agents.js +1 -8
- package/dist/src/agent/tools/executor.d.ts +1 -1
- package/dist/src/agent/tools/workflow-tool.js +1 -5
- package/dist/src/agent-config/index.d.ts +2 -0
- package/dist/src/agent-config/index.js +11 -0
- package/dist/src/agent-config/resolver.d.ts +11 -0
- package/dist/src/agent-config/resolver.js +113 -0
- package/dist/src/agent-config/schema.d.ts +491 -0
- package/dist/src/agent-config/schema.js +172 -0
- package/dist/src/chat-commands/builtins/config.js +2 -2
- package/dist/src/chat-commands/builtins/review.js +1 -1
- package/dist/src/chat-commands/config-paths.js +1 -1
- package/dist/src/cli/commands/agents.js +2 -1
- package/dist/src/cli/commands/config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +12 -4
- package/dist/src/cli/commands/image.js +3 -4
- package/dist/src/cli/commands/models.js +9 -3
- package/dist/src/cli/commands/onboard/model.js +12 -11
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/cli/commands/tunnel.js +3 -18
- package/dist/src/cli/utils/init-workspace-core.d.ts +1 -1
- package/dist/src/cli/utils/init-workspace-core.js +1 -1
- package/dist/src/commands/agents.config.d.ts +1 -3
- package/dist/src/commands/agents.config.js +9 -26
- package/dist/src/config/agent-model-intents.d.ts +10 -0
- package/dist/src/config/agent-model-intents.js +39 -0
- package/dist/src/config/agent-profile.d.ts +7 -9
- package/dist/src/config/agent-profile.js +30 -28
- package/dist/src/config/default-model.d.ts +1 -11
- package/dist/src/config/default-model.js +3 -11
- package/dist/src/config/index.js +3 -3
- package/dist/src/config/integration.d.ts +1 -1
- package/dist/src/config/reload.js +1 -1
- package/dist/src/config/rules.js +3 -3
- package/dist/src/config/schema.d.ts +187 -452
- package/dist/src/config/schema.js +53 -131
- package/dist/src/extensions/activation-context.js +7 -11
- package/dist/src/gateway/agent-builtin-tools.d.ts +250 -6
- package/dist/src/gateway/agent-builtin-tools.js +284 -46
- package/dist/src/gateway/agents-admin.d.ts +20 -62
- package/dist/src/gateway/agents-admin.js +36 -228
- package/dist/src/gateway/device-routes.d.ts +3 -0
- package/dist/src/gateway/device-routes.js +47 -0
- package/dist/src/gateway/global-defaults-admin.d.ts +6 -20
- package/dist/src/gateway/global-defaults-admin.js +20 -120
- package/dist/src/gateway/hono/app.js +4 -2
- package/dist/src/gateway/hono/lib/config-payload.d.ts +54 -137
- package/dist/src/gateway/hono/lib/config-payload.js +2 -4
- package/dist/src/gateway/hono/middleware/auth.js +33 -1
- package/dist/src/gateway/hono/middleware/scopes.d.ts +2 -12
- package/dist/src/gateway/hono/middleware/scopes.js +9 -18
- package/dist/src/gateway/hono/routes/agents.js +64 -100
- package/dist/src/gateway/hono/routes/connectors.js +1 -1
- package/dist/src/gateway/hono/routes/device-push.d.ts +2 -0
- package/dist/src/gateway/hono/routes/{mobile.js → device-push.js} +40 -18
- package/dist/src/gateway/hono/routes/devices.d.ts +4 -0
- package/dist/src/gateway/hono/routes/devices.js +214 -0
- package/dist/src/gateway/hono/routes/files.js +5 -1
- package/dist/src/gateway/hono/routes/global-defaults.js +5 -9
- package/dist/src/gateway/hono/routes/index.js +4 -2
- package/dist/src/gateway/hono/routes/lazy-bundles.js +4 -20
- package/dist/src/gateway/hono/routes/models.js +2 -2
- package/dist/src/gateway/hono/routes/realtime.js +8 -1
- package/dist/src/gateway/hono/routes/status.js +9 -16
- package/dist/src/gateway/hono/routes/tunnel.d.ts +0 -2
- package/dist/src/gateway/hono/routes/tunnel.js +10 -199
- package/dist/src/gateway/image-generation-setup.d.ts +2 -2
- package/dist/src/gateway/image-generation-setup.js +8 -7
- package/dist/src/gateway/public-url.d.ts +1 -1
- package/dist/src/gateway/security/gateway-principal.d.ts +11 -0
- package/dist/src/gateway/security/gateway-principal.js +12 -0
- package/dist/src/gateway/security/gateway-scopes.d.ts +7 -0
- package/dist/src/gateway/security/gateway-scopes.js +66 -0
- package/dist/src/gateway/security/index.d.ts +2 -1
- package/dist/src/gateway/security/index.js +3 -2
- package/dist/src/gateway/service.js +7 -7
- package/dist/src/gateway/xopc-cloud-capability-setup.js +19 -21
- package/dist/src/migrations/index.d.ts +0 -1
- package/dist/src/migrations/index.js +1 -2
- package/dist/src/migrations/runner.js +1 -2
- package/dist/src/notifications/device-store.d.ts +1 -1
- package/dist/src/notifications/device-store.js +11 -11
- package/dist/src/notifications/store.js +3 -3
- package/dist/src/providers/index.js +1 -1
- package/dist/src/providers/model-reference-auditor.js +14 -9
- package/dist/src/realtime/runtime.d.ts +2 -0
- package/dist/src/realtime/runtime.js +27 -3
- package/dist/src/realtime/tickets.d.ts +6 -1
- package/dist/src/realtime/tickets.js +2 -1
- package/dist/src/remote-access/exposure-manager.js +3 -3
- package/dist/src/share/share-url.js +2 -2
- package/dist/src/storage/sqlite/device-access-repository.d.ts +54 -0
- package/dist/src/storage/sqlite/device-access-repository.js +194 -0
- package/dist/src/storage/sqlite/device-pairing-repository.d.ts +22 -0
- package/dist/src/storage/sqlite/device-pairing-repository.js +86 -0
- package/dist/src/storage/sqlite/gateway-identity-repository.d.ts +10 -0
- package/dist/src/storage/sqlite/gateway-identity-repository.js +50 -0
- package/dist/src/storage/sqlite/index.d.ts +3 -0
- package/dist/src/storage/sqlite/index.js +4 -1
- package/dist/src/storage/sqlite/migrations/138_device_access.sql +96 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tunnel/consent.d.ts +1 -1
- package/dist/src/tunnel/consent.js +1 -1
- package/dist/src/tunnel/gateway-lifecycle.js +1 -1
- package/dist/src/tunnel/index.d.ts +1 -4
- package/dist/src/tunnel/index.js +3 -5
- package/dist/src/tunnel/tunnel-audit.d.ts +1 -1
- package/dist/src/tunnel/tunnel-config.js +1 -1
- package/dist/src/tunnel/tunnel-service.d.ts +2 -3
- package/dist/src/tunnel/tunnel-service.js +1 -20
- package/dist/src/tunnel/tunnel-types.d.ts +0 -6
- package/dist/src/workflows/draft/workflow-draft-service.js +2 -2
- package/dist/src/workflows/service/workflow-run-service.js +3 -3
- package/package.json +3 -3
- package/dist/gateway/static/root/assets/agent-avatar-dicebear-CBqUnlvL.js +0 -1
- package/dist/gateway/static/root/assets/agents-7YO6znTT.js +0 -68
- package/dist/gateway/static/root/assets/api-BywYxpaS.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-BpvD54D6.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CeESA4Oj.js +0 -8
- package/dist/gateway/static/root/assets/browser-workflow-inputs-8NC2qDRF.js +0 -1
- package/dist/gateway/static/root/assets/capabilities-settings-panel-B_OXTojB.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-api-o0y9WgAI.js +0 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-D5nZecxV.js +0 -3
- package/dist/gateway/static/root/assets/channels-settings-BzmnWx6X.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-38FsOnR0.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DX6OkwFQ.js +0 -2
- package/dist/gateway/static/root/assets/cron-expression-DYSMZsms.js +0 -2
- package/dist/gateway/static/root/assets/date-picker-DGCfX4tK.js +0 -1
- package/dist/gateway/static/root/assets/dependency-picker-DHDa4ht1.js +0 -1
- package/dist/gateway/static/root/assets/directory-picker-path-field-B9qoxDcn.js +0 -1
- package/dist/gateway/static/root/assets/dist-J_ySZvz0.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-eP3fJ2M6.js +0 -1
- package/dist/gateway/static/root/assets/file-code-corner-byQA1VfD.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-swr-BWGjnmDl.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-DKkEXXj9.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DlBPNubY.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BnOrLRfs.js +0 -1
- package/dist/gateway/static/root/assets/html-workspace-editor-bXK5tq64.js +0 -1
- package/dist/gateway/static/root/assets/index-0xQO5B_Q.js +0 -70
- package/dist/gateway/static/root/assets/index-CCyIdGuQ.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-Ch1rdUo8.js +0 -2
- package/dist/gateway/static/root/assets/locale-store-CXhjfe4q.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-zxx2ExlY.js +0 -2
- package/dist/gateway/static/root/assets/management-settings-BQaeyOYm.js +0 -1
- package/dist/gateway/static/root/assets/markdown-editor-TWGaF7Xa.js +0 -2
- package/dist/gateway/static/root/assets/markdown-split-D9tmIiEm.js +0 -1
- package/dist/gateway/static/root/assets/messages-BMdD0LWk.js +0 -3
- package/dist/gateway/static/root/assets/notes-workbench-9cgh77VH.js +0 -4
- package/dist/gateway/static/root/assets/popover-select-CccGri5Y.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-hf6pmH4p.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-By2U-Wdh.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CUcpp7CQ.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-ByI82xS1.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BCoI5IJ8.js +0 -1
- package/dist/gateway/static/root/assets/settings-loading-skeleton-y53gEKsF.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-BoE_tToG.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BQmyb5KC.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-Bf3VpZ-R.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CXHlbuGS.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-Bo0KSZ2v.js +0 -1
- package/dist/gateway/static/root/assets/typed-models-lib-q5-Vfqd8.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-Cnj7gMX2.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-BML8zCwZ.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-CB98MTwG.js +0 -5
- package/dist/src/agent-manifest/index.d.ts +0 -5
- package/dist/src/agent-manifest/index.js +0 -10
- package/dist/src/agent-manifest/preset-chain.d.ts +0 -10
- package/dist/src/agent-manifest/preset-chain.js +0 -30
- package/dist/src/agent-manifest/prompt.d.ts +0 -2
- package/dist/src/agent-manifest/prompt.js +0 -49
- package/dist/src/agent-manifest/resolver.d.ts +0 -32
- package/dist/src/agent-manifest/resolver.js +0 -114
- package/dist/src/agent-manifest/schema.d.ts +0 -695
- package/dist/src/agent-manifest/schema.js +0 -226
- package/dist/src/agent-manifest/validator.d.ts +0 -25
- package/dist/src/agent-manifest/validator.js +0 -74
- package/dist/src/agent-runtime/boundary-guard.d.ts +0 -12
- package/dist/src/agent-runtime/boundary-guard.js +0 -21
- package/dist/src/agent-runtime/index.d.ts +0 -7
- package/dist/src/agent-runtime/index.js +0 -8
- package/dist/src/agent-runtime/model-router.d.ts +0 -11
- package/dist/src/agent-runtime/model-router.js +0 -14
- package/dist/src/agent-runtime/run-trace.d.ts +0 -27
- package/dist/src/agent-runtime/run-trace.js +0 -51
- package/dist/src/agent-runtime/runtime-profile.d.ts +0 -26
- package/dist/src/agent-runtime/runtime-profile.js +0 -42
- package/dist/src/agent-runtime/tool-registry.d.ts +0 -28
- package/dist/src/agent-runtime/tool-registry.js +0 -50
- package/dist/src/agent-runtime/workflow-runtime.d.ts +0 -31
- package/dist/src/agent-runtime/workflow-runtime.js +0 -32
- package/dist/src/config/agent-typed-models.d.ts +0 -13
- package/dist/src/config/agent-typed-models.js +0 -48
- package/dist/src/gateway/capability-presets-admin.d.ts +0 -88
- package/dist/src/gateway/capability-presets-admin.js +0 -303
- package/dist/src/gateway/hono/lib/agent-model.d.ts +0 -35
- package/dist/src/gateway/hono/lib/agent-model.js +0 -97
- package/dist/src/gateway/hono/routes/capability-presets.d.ts +0 -3
- package/dist/src/gateway/hono/routes/capability-presets.js +0 -187
- package/dist/src/gateway/hono/routes/mobile.d.ts +0 -3
- package/dist/src/gateway/security/operator-scopes.d.ts +0 -37
- package/dist/src/gateway/security/operator-scopes.js +0 -147
- package/dist/src/migrations/config/default-capability-preset.d.ts +0 -10
- package/dist/src/migrations/config/default-capability-preset.js +0 -133
- package/dist/src/tunnel/enable-lan-pairing.d.ts +0 -8
- package/dist/src/tunnel/enable-lan-pairing.js +0 -41
- package/dist/src/tunnel/pair-context.d.ts +0 -36
- package/dist/src/tunnel/pair-context.js +0 -77
- package/dist/src/tunnel/pair-url.d.ts +0 -43
- package/dist/src/tunnel/pair-url.js +0 -117
- package/dist/src/tunnel/pairing-rate-limit.d.ts +0 -12
- package/dist/src/tunnel/pairing-rate-limit.js +0 -28
- package/dist/src/tunnel/pairing.d.ts +0 -21
- package/dist/src/tunnel/pairing.js +0 -103
- package/dist/src/tunnel/tunnel-qr.d.ts +0 -9
- package/dist/src/tunnel/tunnel-qr.js +0 -29
|
@@ -41,11 +41,11 @@ function deviceFromRow(row) {
|
|
|
41
41
|
}
|
|
42
42
|
function registerNotificationDevice(input) {
|
|
43
43
|
const now = Date.now();
|
|
44
|
-
const preferences = input.preferences ? normalizePreferences(input.preferences) : getNotificationDevice(input.
|
|
44
|
+
const preferences = input.preferences ? normalizePreferences(input.preferences) : getNotificationDevice(input.deviceId)?.preferences ?? normalizePreferences();
|
|
45
45
|
runSqliteWriteTransaction((db) => {
|
|
46
|
-
db.prepare(`DELETE FROM
|
|
47
|
-
WHERE
|
|
48
|
-
db.prepare(`INSERT INTO
|
|
46
|
+
db.prepare(`DELETE FROM device_push_endpoints
|
|
47
|
+
WHERE push_token = ? AND device_id <> ?`).run(input.pushToken, input.deviceId);
|
|
48
|
+
db.prepare(`INSERT INTO device_push_endpoints (
|
|
49
49
|
device_id, platform, push_token, enabled, permissions, preferences_json,
|
|
50
50
|
locale, app_version, lease_expires_at, last_seen_at, created_at, updated_at
|
|
51
51
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -59,16 +59,16 @@ function registerNotificationDevice(input) {
|
|
|
59
59
|
app_version = excluded.app_version,
|
|
60
60
|
lease_expires_at = excluded.lease_expires_at,
|
|
61
61
|
last_seen_at = excluded.last_seen_at,
|
|
62
|
-
updated_at = excluded.updated_at`).run(input.
|
|
62
|
+
updated_at = excluded.updated_at`).run(input.deviceId, input.platform, input.pushToken, input.permissions === "granted" ? 1 : 0, input.permissions, JSON.stringify(preferences), input.locale, input.appVersion?.trim() || null, now + DEVICE_LEASE_MS, now, now, now);
|
|
63
63
|
});
|
|
64
|
-
return getNotificationDevice(input.
|
|
64
|
+
return getNotificationDevice(input.deviceId);
|
|
65
65
|
}
|
|
66
66
|
function getNotificationDevice(deviceId) {
|
|
67
|
-
const row = getSqliteDatabase().prepare("SELECT * FROM
|
|
67
|
+
const row = getSqliteDatabase().prepare("SELECT * FROM device_push_endpoints WHERE device_id = ?").get(deviceId);
|
|
68
68
|
return row ? deviceFromRow(row) : null;
|
|
69
69
|
}
|
|
70
70
|
function listDeliverableNotificationDevices(now = Date.now()) {
|
|
71
|
-
return getSqliteDatabase().prepare(`SELECT * FROM
|
|
71
|
+
return getSqliteDatabase().prepare(`SELECT * FROM device_push_endpoints
|
|
72
72
|
WHERE enabled = 1 AND permissions = 'granted' AND lease_expires_at > ?
|
|
73
73
|
ORDER BY updated_at DESC`).all(now).map(deviceFromRow);
|
|
74
74
|
}
|
|
@@ -80,16 +80,16 @@ function updateNotificationDevicePreferences(deviceId, patch) {
|
|
|
80
80
|
...patch
|
|
81
81
|
});
|
|
82
82
|
runSqliteWriteTransaction((db) => {
|
|
83
|
-
db.prepare("UPDATE
|
|
83
|
+
db.prepare("UPDATE device_push_endpoints SET preferences_json = ?, updated_at = ? WHERE device_id = ?").run(JSON.stringify(preferences), Date.now(), deviceId);
|
|
84
84
|
});
|
|
85
85
|
return getNotificationDevice(deviceId);
|
|
86
86
|
}
|
|
87
87
|
function removeNotificationDevice(deviceId) {
|
|
88
|
-
return runSqliteWriteTransaction((db) => db.prepare("DELETE FROM
|
|
88
|
+
return runSqliteWriteTransaction((db) => db.prepare("DELETE FROM device_push_endpoints WHERE device_id = ?").run(deviceId).changes > 0);
|
|
89
89
|
}
|
|
90
90
|
function disableNotificationDeviceForPushToken(pushToken) {
|
|
91
91
|
runSqliteWriteTransaction((db) => {
|
|
92
|
-
db.prepare("UPDATE
|
|
92
|
+
db.prepare("UPDATE device_push_endpoints SET enabled = 0, updated_at = ? WHERE push_token = ?").run(Date.now(), pushToken);
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
//#endregion
|
|
@@ -88,7 +88,7 @@ const DELIVERY_JOIN = `
|
|
|
88
88
|
n.push_token, n.locale
|
|
89
89
|
FROM notification_deliveries d
|
|
90
90
|
JOIN notification_events e ON e.event_id = d.event_id
|
|
91
|
-
JOIN
|
|
91
|
+
JOIN device_push_endpoints n ON n.device_id = d.device_id
|
|
92
92
|
`;
|
|
93
93
|
function listDueNotificationDeliveries(status, now = Date.now(), limit = 100) {
|
|
94
94
|
return getSqliteDatabase().prepare(`${DELIVERY_JOIN}
|
|
@@ -135,7 +135,7 @@ function notificationDeliveryMetrics() {
|
|
|
135
135
|
SUM(CASE WHEN enabled = 1 AND permissions = 'granted' AND lease_expires_at > ? THEN 1 ELSE 0 END) AS deliverable,
|
|
136
136
|
SUM(CASE WHEN enabled = 1 AND lease_expires_at <= ? THEN 1 ELSE 0 END) AS expired,
|
|
137
137
|
SUM(CASE WHEN enabled = 0 OR permissions <> 'granted' THEN 1 ELSE 0 END) AS disabled
|
|
138
|
-
FROM
|
|
138
|
+
FROM device_push_endpoints`).get(now, now);
|
|
139
139
|
const oldest = getSqliteDatabase().prepare(`SELECT MIN(next_attempt_at) AS value FROM notification_deliveries
|
|
140
140
|
WHERE status IN ('pending', 'accepted')`).get();
|
|
141
141
|
const latestError = getSqliteDatabase().prepare(`SELECT event_id, device_id, last_error, updated_at
|
|
@@ -166,7 +166,7 @@ function expireUndeliverableNotificationDeliveries(now = Date.now()) {
|
|
|
166
166
|
SET status = 'dead', last_error = 'Device registration is disabled or expired', updated_at = ?
|
|
167
167
|
WHERE status IN ('pending', 'accepted')
|
|
168
168
|
AND EXISTS (
|
|
169
|
-
SELECT 1 FROM
|
|
169
|
+
SELECT 1 FROM device_push_endpoints n
|
|
170
170
|
WHERE n.device_id = notification_deliveries.device_id
|
|
171
171
|
AND (n.enabled = 0 OR n.permissions <> 'granted' OR n.lease_expires_at <= ?)
|
|
172
172
|
)`).run(now, now).changes));
|
|
@@ -90,7 +90,7 @@ function getApiKeySync(provider) {
|
|
|
90
90
|
*/
|
|
91
91
|
function resolveModel(ref) {
|
|
92
92
|
const trimmedRef = typeof ref === "string" ? ref.trim() : "";
|
|
93
|
-
if (!trimmedRef) throw new Error("No default model configured. Choose a model in onboarding or set agents.
|
|
93
|
+
if (!trimmedRef) throw new Error("No default model configured. Choose a model in onboarding or set agents.defaults.models.chat.primary.");
|
|
94
94
|
const customModel = getModelRegistry().resolve(trimmedRef);
|
|
95
95
|
if (customModel) return normalizeProviderModel(customModel);
|
|
96
96
|
const qualifiedRef = splitProviderModelRef(trimmedRef);
|
|
@@ -24,19 +24,24 @@ function collectToolModel(out, value, location, kind = "text") {
|
|
|
24
24
|
function collectModelPolicy(out, value, location) {
|
|
25
25
|
const policy = asRecord(value);
|
|
26
26
|
if (!policy) return;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
addRef(out, role.model, `${location}.roles.${roleId}.model`);
|
|
32
|
-
if (Array.isArray(role.fallbacks)) role.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.roles.${roleId}.fallbacks[${index}]`));
|
|
27
|
+
const chat = asRecord(policy.chat);
|
|
28
|
+
if (chat) {
|
|
29
|
+
addRef(out, chat.primary, `${location}.chat.primary`);
|
|
30
|
+
if (Array.isArray(chat.fallbacks)) chat.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.chat.fallbacks[${index}]`));
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const intents = asRecord(policy.intents);
|
|
33
|
+
for (const [intent, routeValue] of Object.entries(intents ?? {})) {
|
|
34
|
+
const route = asRecord(routeValue);
|
|
35
|
+
if (!route) continue;
|
|
36
|
+
addRef(out, route.primary, `${location}.intents.${intent}.primary`);
|
|
37
|
+
if (Array.isArray(route.fallbacks)) route.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.intents.${intent}.fallbacks[${index}]`));
|
|
38
|
+
}
|
|
39
|
+
collectToolModel(out, policy.imageUnderstanding, `${location}.imageUnderstanding`);
|
|
40
|
+
collectToolModel(out, policy.imageGeneration, `${location}.imageGeneration`, "image-generation");
|
|
36
41
|
}
|
|
37
42
|
function collectReferences(config, sessionConfigs) {
|
|
38
43
|
const out = [];
|
|
39
|
-
|
|
44
|
+
collectModelPolicy(out, config.agents.defaults.models, "agents.defaults.models");
|
|
40
45
|
for (const agent of config.agents.list) collectModelPolicy(out, agent.models, `agents.list.${agent.id}.models`);
|
|
41
46
|
for (const [sessionKey, sessionConfig] of sessionConfigs) addRef(out, sessionConfig.modelOverride, `sessions.${sessionKey}.modelOverride`);
|
|
42
47
|
return out;
|
|
@@ -10,10 +10,12 @@ export declare class RealtimeRuntime {
|
|
|
10
10
|
private readonly preauthBudget;
|
|
11
11
|
private readonly wss;
|
|
12
12
|
private readonly topicCleanupTimers;
|
|
13
|
+
private readonly socketsByPrincipal;
|
|
13
14
|
private closed;
|
|
14
15
|
constructor(endpointTools?: EndpointToolRuntime);
|
|
15
16
|
handleUpgrade(req: IncomingMessage, socket: Socket, head: Buffer): boolean;
|
|
16
17
|
close(): void;
|
|
18
|
+
disconnectPrincipal(principalId: string): void;
|
|
17
19
|
completeTopic(topic: string, ttlMs?: number): void;
|
|
18
20
|
private handleConnection;
|
|
19
21
|
private rawDataToText;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { hasGatewayScope } from "../gateway/security/gateway-scopes.js";
|
|
3
4
|
import { REALTIME_HEARTBEAT_INTERVAL_MS, REALTIME_HEARTBEAT_TIMEOUT_MS, REALTIME_HELLO_TIMEOUT_MS, REALTIME_MAX_CLIENT_FRAME_BYTES, parseClientRealtimeJsonFrame, parseClientRealtimeMessage } from "../../packages/realtime-protocol/src/index.js";
|
|
4
5
|
import { createPreauthConnectionBudget } from "../gateway/security/preauth-connection-budget.js";
|
|
5
6
|
import { RealtimeBroker } from "./broker.js";
|
|
@@ -24,8 +25,13 @@ function serverMessage(kind, payload) {
|
|
|
24
25
|
payload
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
|
-
function isAuthorizedTopic(topic) {
|
|
28
|
-
|
|
28
|
+
function isAuthorizedTopic(topic, scopes) {
|
|
29
|
+
if (topic === "gateway") return hasGatewayScope(scopes, "gateway.status");
|
|
30
|
+
if (topic === "logs") return hasGatewayScope(scopes, "gateway.admin");
|
|
31
|
+
if (topic === "sessions" || topic.startsWith("session:")) return hasGatewayScope(scopes, "sessions.read");
|
|
32
|
+
if (topic.startsWith("run:")) return hasGatewayScope(scopes, "agents.run");
|
|
33
|
+
if (topic.startsWith("workflow:")) return hasGatewayScope(scopes, "automations.read");
|
|
34
|
+
return false;
|
|
29
35
|
}
|
|
30
36
|
var RealtimeRuntime = class {
|
|
31
37
|
broker = new RealtimeBroker(void 0, (error, topic) => {
|
|
@@ -42,6 +48,7 @@ var RealtimeRuntime = class {
|
|
|
42
48
|
perMessageDeflate: false
|
|
43
49
|
});
|
|
44
50
|
topicCleanupTimers = /* @__PURE__ */ new Map();
|
|
51
|
+
socketsByPrincipal = /* @__PURE__ */ new Map();
|
|
45
52
|
closed = false;
|
|
46
53
|
constructor(endpointTools) {
|
|
47
54
|
this.endpointTools = endpointTools;
|
|
@@ -80,6 +87,10 @@ var RealtimeRuntime = class {
|
|
|
80
87
|
for (const timer of this.topicCleanupTimers.values()) clearTimeout(timer);
|
|
81
88
|
this.topicCleanupTimers.clear();
|
|
82
89
|
this.wss.close();
|
|
90
|
+
this.socketsByPrincipal.clear();
|
|
91
|
+
}
|
|
92
|
+
disconnectPrincipal(principalId) {
|
|
93
|
+
for (const socket of this.socketsByPrincipal.get(principalId) ?? []) socket.close(4403, "Device access revoked");
|
|
83
94
|
}
|
|
84
95
|
completeTopic(topic, ttlMs = 5 * 6e4) {
|
|
85
96
|
const existing = this.topicCleanupTimers.get(topic);
|
|
@@ -97,6 +108,8 @@ var RealtimeRuntime = class {
|
|
|
97
108
|
let lastSeenAt = Date.now();
|
|
98
109
|
let connectionId;
|
|
99
110
|
let endpointId;
|
|
111
|
+
let principalId;
|
|
112
|
+
let grantedScopes = [];
|
|
100
113
|
let budgetHeld = true;
|
|
101
114
|
const releaseBudget = () => {
|
|
102
115
|
if (!budgetHeld) return;
|
|
@@ -115,7 +128,7 @@ var RealtimeRuntime = class {
|
|
|
115
128
|
};
|
|
116
129
|
const subscribe = (requested) => {
|
|
117
130
|
const authorized = requested.filter((item) => {
|
|
118
|
-
if (isAuthorizedTopic(item.topic)) return true;
|
|
131
|
+
if (isAuthorizedTopic(item.topic, grantedScopes)) return true;
|
|
119
132
|
sendError("FORBIDDEN_TOPIC", `Topic is not available: ${item.topic}`);
|
|
120
133
|
return false;
|
|
121
134
|
});
|
|
@@ -172,6 +185,11 @@ var RealtimeRuntime = class {
|
|
|
172
185
|
return;
|
|
173
186
|
}
|
|
174
187
|
authenticated = true;
|
|
188
|
+
principalId = claim.principalId;
|
|
189
|
+
grantedScopes = claim.scopes;
|
|
190
|
+
const principalSockets = this.socketsByPrincipal.get(principalId) ?? /* @__PURE__ */ new Set();
|
|
191
|
+
principalSockets.add(socket);
|
|
192
|
+
this.socketsByPrincipal.set(principalId, principalSockets);
|
|
175
193
|
connectionId = crypto.randomUUID();
|
|
176
194
|
let endpointReady;
|
|
177
195
|
if (message.payload.endpoint) {
|
|
@@ -215,6 +233,7 @@ var RealtimeRuntime = class {
|
|
|
215
233
|
subscribe(message.payload.subscriptions);
|
|
216
234
|
log.info({
|
|
217
235
|
connectionId,
|
|
236
|
+
principalId,
|
|
218
237
|
clientId: claim.clientId,
|
|
219
238
|
clientKind: claim.clientKind
|
|
220
239
|
}, "Realtime client connected");
|
|
@@ -249,6 +268,11 @@ var RealtimeRuntime = class {
|
|
|
249
268
|
for (const handle of subscriptions.values()) handle.unsubscribe();
|
|
250
269
|
subscriptions.clear();
|
|
251
270
|
if (endpointId && connectionId) this.endpointTools?.remove(endpointId, connectionId);
|
|
271
|
+
if (principalId) {
|
|
272
|
+
const principalSockets = this.socketsByPrincipal.get(principalId);
|
|
273
|
+
principalSockets?.delete(socket);
|
|
274
|
+
if (principalSockets?.size === 0) this.socketsByPrincipal.delete(principalId);
|
|
275
|
+
}
|
|
252
276
|
if (connectionId) {
|
|
253
277
|
const reason = reasonBuffer.toString();
|
|
254
278
|
log.info({
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { RealtimeClientKind } from '@xopcai/realtime-protocol';
|
|
2
|
+
import type { GatewayScope } from '../gateway/security/gateway-scopes.js';
|
|
2
3
|
export interface RealtimeTicketClaim {
|
|
3
4
|
clientId: string;
|
|
4
5
|
clientKind: RealtimeClientKind;
|
|
6
|
+
principalId: string;
|
|
7
|
+
deviceId?: string;
|
|
8
|
+
accessSessionId?: string;
|
|
9
|
+
scopes: readonly GatewayScope[];
|
|
5
10
|
expiresAt: number;
|
|
6
11
|
}
|
|
7
12
|
export interface IssuedRealtimeTicket extends RealtimeTicketClaim {
|
|
@@ -9,7 +14,7 @@ export interface IssuedRealtimeTicket extends RealtimeTicketClaim {
|
|
|
9
14
|
}
|
|
10
15
|
export declare class RealtimeTicketStore {
|
|
11
16
|
private readonly claims;
|
|
12
|
-
issue(clientId: string, clientKind: RealtimeClientKind, now?: number): IssuedRealtimeTicket;
|
|
17
|
+
issue(clientId: string, clientKind: RealtimeClientKind, principal: Omit<RealtimeTicketClaim, 'clientId' | 'clientKind' | 'expiresAt'>, now?: number): IssuedRealtimeTicket;
|
|
13
18
|
consume(ticket: string, clientId: string, clientKind: RealtimeClientKind, now?: number): RealtimeTicketClaim | undefined;
|
|
14
19
|
private prune;
|
|
15
20
|
}
|
|
@@ -7,13 +7,14 @@ function ticketKey(ticket) {
|
|
|
7
7
|
}
|
|
8
8
|
var RealtimeTicketStore = class {
|
|
9
9
|
claims = /* @__PURE__ */ new Map();
|
|
10
|
-
issue(clientId, clientKind, now = Date.now()) {
|
|
10
|
+
issue(clientId, clientKind, principal, now = Date.now()) {
|
|
11
11
|
this.prune(now);
|
|
12
12
|
if (this.claims.size >= MAX_OUTSTANDING_TICKETS) throw new Error("Too many outstanding realtime tickets");
|
|
13
13
|
const ticket = crypto.randomBytes(32).toString("base64url");
|
|
14
14
|
const claim = {
|
|
15
15
|
clientId,
|
|
16
16
|
clientKind,
|
|
17
|
+
...principal,
|
|
17
18
|
expiresAt: now + TICKET_TTL_MS
|
|
18
19
|
};
|
|
19
20
|
this.claims.set(ticketKey(ticket), claim);
|
|
@@ -2,11 +2,11 @@ import { createLogger } from "../utils/logger/index.js";
|
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
3
|
import { isTailscaleInstalled } from "../infra/tailscale.js";
|
|
4
4
|
import { resolveGatewayBindMode } from "../config/gateway-bind.js";
|
|
5
|
-
import { getTunnelConsentState } from "../tunnel/consent.js";
|
|
6
|
-
import { getTunnelService } from "../tunnel/tunnel-service.js";
|
|
7
|
-
import { maybeAutoStartTunnelFromConfig } from "../tunnel/gateway-lifecycle.js";
|
|
8
5
|
import { collectExposureConflicts } from "./exposure-guards.js";
|
|
9
6
|
import { getTailscaleExposureState, maybeStartTailscaleFromConfig, stopTailscaleExposure } from "../gateway/tailscale-lifecycle.js";
|
|
7
|
+
import { getTunnelService } from "../tunnel/tunnel-service.js";
|
|
8
|
+
import { getTunnelConsentState } from "../tunnel/consent.js";
|
|
9
|
+
import { maybeAutoStartTunnelFromConfig } from "../tunnel/gateway-lifecycle.js";
|
|
10
10
|
//#region src/remote-access/exposure-manager.ts
|
|
11
11
|
init_logger();
|
|
12
12
|
const log = createLogger("ExposureManager");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { enumerateLanGatewayCandidates } from "../gateway/host.js";
|
|
1
2
|
import { loadTunnelState } from "../tunnel/tunnel-state.js";
|
|
2
|
-
import { resolveLanGatewayUrl } from "../tunnel/tunnel-qr.js";
|
|
3
3
|
//#region src/share/share-url.ts
|
|
4
4
|
function resolveShareUrl(token, ctx) {
|
|
5
5
|
const tunnelState = loadTunnelState();
|
|
@@ -32,7 +32,7 @@ function resolveShareUrl(token, ctx) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
function buildLanUrl(host, port, path) {
|
|
35
|
-
const lanBase =
|
|
35
|
+
const lanBase = isLoopbackHost(host) ? null : enumerateLanGatewayCandidates(port).find((candidate) => candidate.address === host)?.url ?? `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
|
|
36
36
|
if (!lanBase) return null;
|
|
37
37
|
return `${lanBase.replace(/\/+$/, "")}${path}`;
|
|
38
38
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type { GatewayScope } from '../../gateway/security/gateway-scopes.js';
|
|
3
|
+
export type DevicePlatform = 'ios' | 'android';
|
|
4
|
+
export type DevicePublicKeyJwk = crypto.webcrypto.JsonWebKey;
|
|
5
|
+
export type DeviceRecord = {
|
|
6
|
+
id: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
platform: DevicePlatform;
|
|
9
|
+
publicKeyJwk: DevicePublicKeyJwk;
|
|
10
|
+
scopes: GatewayScope[];
|
|
11
|
+
createdAt: number;
|
|
12
|
+
lastSeenAt?: number;
|
|
13
|
+
revokedAt?: number;
|
|
14
|
+
};
|
|
15
|
+
export type DeviceAccessIdentity = {
|
|
16
|
+
deviceId: string;
|
|
17
|
+
accessSessionId: string;
|
|
18
|
+
scopes: GatewayScope[];
|
|
19
|
+
};
|
|
20
|
+
export type DeviceTokenPair = {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
accessTokenExpiresAt: number;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
refreshTokenExpiresAt: number;
|
|
25
|
+
};
|
|
26
|
+
export declare function createDevice(input: {
|
|
27
|
+
id?: string;
|
|
28
|
+
displayName: string;
|
|
29
|
+
platform: DevicePlatform;
|
|
30
|
+
publicKeyJwk: DevicePublicKeyJwk;
|
|
31
|
+
scopes: readonly GatewayScope[];
|
|
32
|
+
now?: number;
|
|
33
|
+
}): DeviceRecord;
|
|
34
|
+
export declare function getDevice(deviceId: string): DeviceRecord | undefined;
|
|
35
|
+
export declare function listDevices(): DeviceRecord[];
|
|
36
|
+
export declare function issueDeviceTokenPair(deviceId: string, now?: number): DeviceTokenPair;
|
|
37
|
+
export declare function authenticateDeviceAccessToken(token: string, now?: number): DeviceAccessIdentity | undefined;
|
|
38
|
+
export declare function buildRefreshProofMessage(input: {
|
|
39
|
+
credentialId: string;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
nonce: string;
|
|
42
|
+
requestId: string;
|
|
43
|
+
nextRefreshToken: string;
|
|
44
|
+
}): string;
|
|
45
|
+
export declare function rotateDeviceRefreshToken(input: {
|
|
46
|
+
refreshToken: string;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
nonce: string;
|
|
49
|
+
requestId: string;
|
|
50
|
+
nextRefreshToken: string;
|
|
51
|
+
signature: string;
|
|
52
|
+
now?: number;
|
|
53
|
+
}): DeviceTokenPair;
|
|
54
|
+
export declare function revokeDevice(deviceId: string, now?: number): boolean;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "./transaction.js";
|
|
2
|
+
import { parseGatewayScopes } from "../../gateway/security/gateway-scopes.js";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
//#region src/storage/sqlite/device-access-repository.ts
|
|
5
|
+
const ACCESS_TOKEN_TTL_MS = 900 * 1e3;
|
|
6
|
+
const REFRESH_TOKEN_TTL_MS = 2160 * 60 * 60 * 1e3;
|
|
7
|
+
const REFRESH_PROOF_MAX_AGE_MS = 300 * 1e3;
|
|
8
|
+
const ACCESS_TOKEN_PREFIX = "xopc_at_";
|
|
9
|
+
const REFRESH_TOKEN_PREFIX = "xopc_rt_";
|
|
10
|
+
function tokenHash(token) {
|
|
11
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
function buildToken(prefix, id) {
|
|
14
|
+
return `${prefix}${id}_${crypto.randomBytes(32).toString("base64url")}`;
|
|
15
|
+
}
|
|
16
|
+
function parseToken(token, prefix) {
|
|
17
|
+
if (!token.startsWith(prefix)) return void 0;
|
|
18
|
+
const separator = token.indexOf("_", prefix.length);
|
|
19
|
+
if (separator <= prefix.length || separator === token.length - 1) return void 0;
|
|
20
|
+
return { id: token.slice(prefix.length, separator) };
|
|
21
|
+
}
|
|
22
|
+
function deviceFromRow(row) {
|
|
23
|
+
return {
|
|
24
|
+
id: row.device_id,
|
|
25
|
+
displayName: row.display_name,
|
|
26
|
+
platform: row.platform,
|
|
27
|
+
publicKeyJwk: JSON.parse(row.public_key_jwk),
|
|
28
|
+
scopes: parseGatewayScopes(row.scopes_json),
|
|
29
|
+
createdAt: row.created_at,
|
|
30
|
+
...row.last_seen_at === null ? {} : { lastSeenAt: row.last_seen_at },
|
|
31
|
+
...row.revoked_at === null ? {} : { revokedAt: row.revoked_at }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createDevice(input) {
|
|
35
|
+
const id = input.id ?? crypto.randomUUID();
|
|
36
|
+
const createdAt = input.now ?? Date.now();
|
|
37
|
+
getSqliteDatabase().prepare(`
|
|
38
|
+
INSERT INTO devices (
|
|
39
|
+
device_id, display_name, platform, public_key_jwk, scopes_json, created_at
|
|
40
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
41
|
+
`).run(id, input.displayName, input.platform, JSON.stringify(input.publicKeyJwk), JSON.stringify([...new Set(input.scopes)]), createdAt);
|
|
42
|
+
return getDevice(id);
|
|
43
|
+
}
|
|
44
|
+
function getDevice(deviceId) {
|
|
45
|
+
const row = getSqliteDatabase().prepare("SELECT * FROM devices WHERE device_id = ?").get(deviceId);
|
|
46
|
+
return row ? deviceFromRow(row) : void 0;
|
|
47
|
+
}
|
|
48
|
+
function listDevices() {
|
|
49
|
+
return getSqliteDatabase().prepare("SELECT * FROM devices ORDER BY created_at DESC").all().map(deviceFromRow);
|
|
50
|
+
}
|
|
51
|
+
function issueAccessToken(deviceId, now) {
|
|
52
|
+
const accessSessionId = crypto.randomUUID();
|
|
53
|
+
const accessToken = buildToken(ACCESS_TOKEN_PREFIX, accessSessionId);
|
|
54
|
+
const accessTokenExpiresAt = now + ACCESS_TOKEN_TTL_MS;
|
|
55
|
+
getSqliteDatabase().prepare(`
|
|
56
|
+
INSERT INTO device_access_sessions (
|
|
57
|
+
session_id, device_id, token_hash, expires_at, created_at
|
|
58
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
59
|
+
`).run(accessSessionId, deviceId, tokenHash(accessToken), accessTokenExpiresAt, now);
|
|
60
|
+
return {
|
|
61
|
+
accessToken,
|
|
62
|
+
accessTokenExpiresAt
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function issueDeviceTokenPair(deviceId, now = Date.now()) {
|
|
66
|
+
const device = getDevice(deviceId);
|
|
67
|
+
if (!device || device.revokedAt !== void 0) throw new Error("Device is unavailable");
|
|
68
|
+
const refreshCredentialId = crypto.randomUUID();
|
|
69
|
+
const refreshToken = buildToken(REFRESH_TOKEN_PREFIX, refreshCredentialId);
|
|
70
|
+
const refreshTokenExpiresAt = now + REFRESH_TOKEN_TTL_MS;
|
|
71
|
+
return {
|
|
72
|
+
...runSqliteWriteTransaction((db) => {
|
|
73
|
+
const issuedAccess = issueAccessToken(deviceId, now);
|
|
74
|
+
db.prepare(`
|
|
75
|
+
INSERT INTO device_refresh_credentials (
|
|
76
|
+
credential_id, device_id, token_hash, expires_at, created_at
|
|
77
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
78
|
+
`).run(refreshCredentialId, deviceId, tokenHash(refreshToken), refreshTokenExpiresAt, now);
|
|
79
|
+
return issuedAccess;
|
|
80
|
+
}),
|
|
81
|
+
refreshToken,
|
|
82
|
+
refreshTokenExpiresAt
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function authenticateDeviceAccessToken(token, now = Date.now()) {
|
|
86
|
+
const parsed = parseToken(token, ACCESS_TOKEN_PREFIX);
|
|
87
|
+
if (!parsed) return void 0;
|
|
88
|
+
const row = getSqliteDatabase().prepare(`
|
|
89
|
+
SELECT a.session_id, a.device_id, a.token_hash, a.expires_at, a.revoked_at,
|
|
90
|
+
d.scopes_json, d.revoked_at AS device_revoked_at
|
|
91
|
+
FROM device_access_sessions a
|
|
92
|
+
JOIN devices d ON d.device_id = a.device_id
|
|
93
|
+
WHERE a.session_id = ?
|
|
94
|
+
`).get(parsed.id);
|
|
95
|
+
if (!row || row.expires_at <= now || row.revoked_at !== null || row.device_revoked_at !== null || !crypto.timingSafeEqual(Buffer.from(row.token_hash, "hex"), Buffer.from(tokenHash(token), "hex"))) return;
|
|
96
|
+
getSqliteDatabase().prepare("UPDATE devices SET last_seen_at = ? WHERE device_id = ?").run(now, row.device_id);
|
|
97
|
+
return {
|
|
98
|
+
deviceId: row.device_id,
|
|
99
|
+
accessSessionId: row.session_id,
|
|
100
|
+
scopes: parseGatewayScopes(row.scopes_json)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function buildRefreshProofMessage(input) {
|
|
104
|
+
return `xopc-device-refresh-v2\n${input.credentialId}\n${input.timestamp}\n${input.nonce}\n${input.requestId}\n${input.nextRefreshToken}`;
|
|
105
|
+
}
|
|
106
|
+
function verifyRefreshProof(publicKeyJwk, message, signature) {
|
|
107
|
+
try {
|
|
108
|
+
const publicKey = crypto.createPublicKey({
|
|
109
|
+
key: publicKeyJwk,
|
|
110
|
+
format: "jwk"
|
|
111
|
+
});
|
|
112
|
+
return crypto.verify("sha256", Buffer.from(message), {
|
|
113
|
+
key: publicKey,
|
|
114
|
+
dsaEncoding: "ieee-p1363"
|
|
115
|
+
}, Buffer.from(signature, "base64url"));
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function rotateDeviceRefreshToken(input) {
|
|
121
|
+
const now = input.now ?? Date.now();
|
|
122
|
+
const parsed = parseToken(input.refreshToken, REFRESH_TOKEN_PREFIX);
|
|
123
|
+
const nextParsed = parseToken(input.nextRefreshToken, REFRESH_TOKEN_PREFIX);
|
|
124
|
+
if (!parsed || !nextParsed || parsed.id === nextParsed.id) throw new Error("Invalid refresh credential");
|
|
125
|
+
if (Math.abs(now - input.timestamp) > REFRESH_PROOF_MAX_AGE_MS) throw new Error("Refresh proof expired");
|
|
126
|
+
const result = runSqliteWriteTransaction((db) => {
|
|
127
|
+
const credential = db.prepare(`
|
|
128
|
+
SELECT credential_id, device_id, token_hash, expires_at, replaced_by,
|
|
129
|
+
rotation_request_id, revoked_at
|
|
130
|
+
FROM device_refresh_credentials WHERE credential_id = ?
|
|
131
|
+
`).get(parsed.id);
|
|
132
|
+
const suppliedHash = tokenHash(input.refreshToken);
|
|
133
|
+
if (!credential || credential.token_hash !== suppliedHash) throw new Error("Invalid refresh credential");
|
|
134
|
+
const deviceRow = db.prepare("SELECT * FROM devices WHERE device_id = ?").get(credential.device_id);
|
|
135
|
+
if (!deviceRow || deviceRow.revoked_at !== null) throw new Error("Device is unavailable");
|
|
136
|
+
const message = buildRefreshProofMessage({
|
|
137
|
+
credentialId: credential.credential_id,
|
|
138
|
+
timestamp: input.timestamp,
|
|
139
|
+
nonce: input.nonce,
|
|
140
|
+
requestId: input.requestId,
|
|
141
|
+
nextRefreshToken: input.nextRefreshToken
|
|
142
|
+
});
|
|
143
|
+
if (!verifyRefreshProof(JSON.parse(deviceRow.public_key_jwk), message, input.signature)) throw new Error("Invalid refresh proof");
|
|
144
|
+
if (credential.replaced_by === nextParsed.id && credential.rotation_request_id === input.requestId) {
|
|
145
|
+
const nextCredential = db.prepare(`
|
|
146
|
+
SELECT credential_id, device_id, token_hash, expires_at, replaced_by,
|
|
147
|
+
rotation_request_id, revoked_at
|
|
148
|
+
FROM device_refresh_credentials WHERE credential_id = ?
|
|
149
|
+
`).get(nextParsed.id);
|
|
150
|
+
if (nextCredential?.token_hash === tokenHash(input.nextRefreshToken)) return {
|
|
151
|
+
...issueAccessToken(credential.device_id, now),
|
|
152
|
+
refreshToken: input.nextRefreshToken,
|
|
153
|
+
refreshTokenExpiresAt: nextCredential.expires_at
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (credential.replaced_by !== null || credential.revoked_at !== null) {
|
|
157
|
+
db.prepare("UPDATE devices SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
|
|
158
|
+
db.prepare("UPDATE device_access_sessions SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
|
|
159
|
+
db.prepare("UPDATE device_refresh_credentials SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
|
|
160
|
+
return "reused";
|
|
161
|
+
}
|
|
162
|
+
if (credential.expires_at <= now) throw new Error("Refresh credential expired");
|
|
163
|
+
const access = issueAccessToken(credential.device_id, now);
|
|
164
|
+
const refreshTokenExpiresAt = now + REFRESH_TOKEN_TTL_MS;
|
|
165
|
+
db.prepare(`
|
|
166
|
+
INSERT INTO device_refresh_credentials (
|
|
167
|
+
credential_id, device_id, token_hash, expires_at, created_at
|
|
168
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
169
|
+
`).run(nextParsed.id, credential.device_id, tokenHash(input.nextRefreshToken), refreshTokenExpiresAt, now);
|
|
170
|
+
db.prepare(`
|
|
171
|
+
UPDATE device_refresh_credentials
|
|
172
|
+
SET last_used_at = ?, replaced_by = ?, rotation_request_id = ?, revoked_at = ?
|
|
173
|
+
WHERE credential_id = ?
|
|
174
|
+
`).run(now, nextParsed.id, input.requestId, now, credential.credential_id);
|
|
175
|
+
return {
|
|
176
|
+
...access,
|
|
177
|
+
refreshToken: input.nextRefreshToken,
|
|
178
|
+
refreshTokenExpiresAt
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
if (result === "reused") throw new Error("Refresh credential reuse detected");
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
function revokeDevice(deviceId, now = Date.now()) {
|
|
185
|
+
return runSqliteWriteTransaction((db) => {
|
|
186
|
+
const changed = db.prepare("UPDATE devices SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId).changes > 0;
|
|
187
|
+
db.prepare("UPDATE device_access_sessions SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId);
|
|
188
|
+
db.prepare("UPDATE device_refresh_credentials SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId);
|
|
189
|
+
db.prepare("DELETE FROM device_push_endpoints WHERE device_id = ?").run(deviceId);
|
|
190
|
+
return changed;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
export { authenticateDeviceAccessToken, buildRefreshProofMessage, createDevice, getDevice, issueDeviceTokenPair, listDevices, revokeDevice, rotateDeviceRefreshToken };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type DeviceRoute = {
|
|
2
|
+
id: string;
|
|
3
|
+
kind: 'xopc-secure-link' | 'tailscale' | 'custom-https';
|
|
4
|
+
url: string;
|
|
5
|
+
};
|
|
6
|
+
export type DevicePairingSetup = {
|
|
7
|
+
id: string;
|
|
8
|
+
token: string;
|
|
9
|
+
routes: DeviceRoute[];
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
};
|
|
12
|
+
export type PairingConsumeResult = {
|
|
13
|
+
ok: true;
|
|
14
|
+
pairingId: string;
|
|
15
|
+
routes: DeviceRoute[];
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
reason: 'invalid' | 'expired' | 'consumed';
|
|
19
|
+
};
|
|
20
|
+
export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now?: number): DevicePairingSetup;
|
|
21
|
+
export declare function consumeDevicePairingToken(token: string, now?: number): PairingConsumeResult;
|
|
22
|
+
export declare function isDevicePairingSetupActive(pairingId: string, now?: number): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "./transaction.js";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
//#region src/storage/sqlite/device-pairing-repository.ts
|
|
4
|
+
const PAIRING_TOKEN_PREFIX = "xopc_pair_";
|
|
5
|
+
const PAIRING_TTL_MS = 600 * 1e3;
|
|
6
|
+
const PAIRING_ATTEMPTS = 5;
|
|
7
|
+
function hashToken(token) {
|
|
8
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
function hashesMatch(left, right) {
|
|
11
|
+
return crypto.timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
12
|
+
}
|
|
13
|
+
function tokenId(token) {
|
|
14
|
+
if (!token.startsWith(PAIRING_TOKEN_PREFIX)) return void 0;
|
|
15
|
+
const separator = token.indexOf("_", 10);
|
|
16
|
+
if (separator <= 10 || separator === token.length - 1) return void 0;
|
|
17
|
+
return token.slice(10, separator);
|
|
18
|
+
}
|
|
19
|
+
function createDevicePairingSetup(routes, now = Date.now()) {
|
|
20
|
+
if (routes.length === 0) throw new Error("No secure mobile route is available");
|
|
21
|
+
const id = crypto.randomUUID();
|
|
22
|
+
const token = `${PAIRING_TOKEN_PREFIX}${id}_${crypto.randomBytes(32).toString("base64url")}`;
|
|
23
|
+
const expiresAt = now + PAIRING_TTL_MS;
|
|
24
|
+
runSqliteWriteTransaction((db) => {
|
|
25
|
+
db.prepare("DELETE FROM device_pairing_sessions WHERE expires_at <= ? OR consumed_at IS NOT NULL").run(now);
|
|
26
|
+
db.prepare(`
|
|
27
|
+
INSERT INTO device_pairing_sessions (
|
|
28
|
+
pairing_id, secret_hash, routes_json, expires_at, attempts_remaining, created_at
|
|
29
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
30
|
+
`).run(id, hashToken(token), JSON.stringify(routes), expiresAt, PAIRING_ATTEMPTS, now);
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
token,
|
|
35
|
+
routes: [...routes],
|
|
36
|
+
expiresAt
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function consumeDevicePairingToken(token, now = Date.now()) {
|
|
40
|
+
const id = tokenId(token);
|
|
41
|
+
if (!id) return {
|
|
42
|
+
ok: false,
|
|
43
|
+
reason: "invalid"
|
|
44
|
+
};
|
|
45
|
+
return runSqliteWriteTransaction((db) => {
|
|
46
|
+
const row = db.prepare("SELECT * FROM device_pairing_sessions WHERE pairing_id = ?").get(id);
|
|
47
|
+
if (!row || row.attempts_remaining <= 0) return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: "invalid"
|
|
50
|
+
};
|
|
51
|
+
if (row.consumed_at !== null) return {
|
|
52
|
+
ok: false,
|
|
53
|
+
reason: "consumed"
|
|
54
|
+
};
|
|
55
|
+
if (row.expires_at <= now) return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "expired"
|
|
58
|
+
};
|
|
59
|
+
if (!hashesMatch(row.secret_hash, hashToken(token))) {
|
|
60
|
+
db.prepare(`
|
|
61
|
+
UPDATE device_pairing_sessions
|
|
62
|
+
SET attempts_remaining = attempts_remaining - 1
|
|
63
|
+
WHERE pairing_id = ? AND attempts_remaining > 0
|
|
64
|
+
`).run(id);
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: "invalid"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
db.prepare("UPDATE device_pairing_sessions SET consumed_at = ? WHERE pairing_id = ?").run(now, id);
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
pairingId: id,
|
|
74
|
+
routes: JSON.parse(row.routes_json)
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function isDevicePairingSetupActive(pairingId, now = Date.now()) {
|
|
79
|
+
const row = getSqliteDatabase().prepare(`
|
|
80
|
+
SELECT 1 FROM device_pairing_sessions
|
|
81
|
+
WHERE pairing_id = ? AND expires_at > ? AND consumed_at IS NULL AND attempts_remaining > 0
|
|
82
|
+
`).get(pairingId, now);
|
|
83
|
+
return Boolean(row);
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { consumeDevicePairingToken, createDevicePairingSetup, isDevicePairingSetupActive };
|