@undefineds.co/xpod 0.4.3 → 0.4.5
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/config/cloud.json +1 -1
- package/config/main.json +36 -2
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
- package/dist/api/ai-gateway/connect/index.d.ts +125 -23
- package/dist/api/ai-gateway/connect/index.js +1277 -260
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
- package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
- package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
- package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
- package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
- package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
- package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
- package/dist/api/container/common.js +30 -40
- package/dist/api/container/index.js +6 -2
- package/dist/api/container/routes.js +5 -22
- package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
- package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
- package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
- package/dist/api/service/AiClientConfigurationService.js +17 -64
- package/dist/cli/commands/start.js +2 -0
- package/dist/components/components.jsonld +1 -0
- package/dist/components/context.jsonld +80 -0
- package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
- package/dist/http/InternalPodDataHttpHandler.js +13 -2
- package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
- package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
- package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
- package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/runtime/component-parameter-keys.js +18 -2
- package/dist/storage/accessors/MixDataAccessor.js +6 -2
- package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
- package/dist/storage/rdf/RdfContentTypes.js +4 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
- package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
- package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
- package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
- package/package.json +2 -2
- package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
- package/static/app/assets/inrupt-smoke.js +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +10 -10
- package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
- package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
- package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
- package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
- package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
- package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
- package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
- package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
- package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
- package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
- package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
- package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
- package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
- package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
- package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
- package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
- package/static/auth-callback/auth-callback.html +2 -2
- package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
- package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
- package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
- package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
- package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
- package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
- package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
- package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
- package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
- package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
- package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
- package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
- package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
- package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
- package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
- package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
- package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
- package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
- package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
- package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
- package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
- package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
- package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
- package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
- package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
- package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
- package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
- package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
- package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
- package/static/settings/assets/settings-vPSC2d2F.css +1 -0
- package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
- package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
- package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
- package/static/settings/settings.html +2 -2
- package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
- package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
- package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
- package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
- package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
- package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/settings/assets/settings-01yz-UDU.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProviderConnectService = exports.DeepSeekConnectAdapter = exports.
|
|
3
|
+
exports.ProviderConnectService = exports.DeepSeekConnectAdapter = exports.AuthorizationCodeConnectAdapter = exports.DeviceCodeConnectAdapter = exports.BrowserAssistedApiKeyConnectAdapter = exports.InMemoryConnectAttemptStore = exports.PodConnectedCredentialRepository = exports.LoopbackAuthorizationCallbackReceiver = exports.OpenAiSubscriptionSessionImportAdapter = exports.requireTrustedOAuthIntegration = exports.OAuthIntegrationRegistry = exports.OAuthConnectCredentialStore = void 0;
|
|
4
4
|
exports.customModelsFromMetadata = customModelsFromMetadata;
|
|
5
5
|
const node_crypto_1 = require("node:crypto");
|
|
6
6
|
const drizzle_solid_1 = require("@undefineds.co/drizzle-solid");
|
|
@@ -10,17 +10,19 @@ const ProviderRegistry_1 = require("../providers/ProviderRegistry");
|
|
|
10
10
|
const CallerPodAccess_1 = require("../auth/CallerPodAccess");
|
|
11
11
|
const PodBaseUrlResolver_1 = require("../pod/PodBaseUrlResolver");
|
|
12
12
|
const OAuthConnectAdapter_1 = require("./OAuthConnectAdapter");
|
|
13
|
-
const OAuthIntegrationRegistry_1 = require("./OAuthIntegrationRegistry");
|
|
14
13
|
const provider_http_transport_1 = require("../../service/provider-http-transport");
|
|
15
14
|
const n3_1 = require("n3");
|
|
16
15
|
var OAuthConnectAdapter_2 = require("./OAuthConnectAdapter");
|
|
17
16
|
Object.defineProperty(exports, "OAuthConnectCredentialStore", { enumerable: true, get: function () { return OAuthConnectAdapter_2.OAuthConnectCredentialStore; } });
|
|
18
|
-
var
|
|
19
|
-
Object.defineProperty(exports, "OAuthIntegrationRegistry", { enumerable: true, get: function () { return
|
|
20
|
-
Object.defineProperty(exports, "
|
|
17
|
+
var OAuthIntegrationRegistry_1 = require("./OAuthIntegrationRegistry");
|
|
18
|
+
Object.defineProperty(exports, "OAuthIntegrationRegistry", { enumerable: true, get: function () { return OAuthIntegrationRegistry_1.OAuthIntegrationRegistry; } });
|
|
19
|
+
Object.defineProperty(exports, "requireTrustedOAuthIntegration", { enumerable: true, get: function () { return OAuthIntegrationRegistry_1.requireTrustedOAuthIntegration; } });
|
|
21
20
|
var OpenAiSubscriptionSessionImportAdapter_1 = require("./OpenAiSubscriptionSessionImportAdapter");
|
|
22
21
|
Object.defineProperty(exports, "OpenAiSubscriptionSessionImportAdapter", { enumerable: true, get: function () { return OpenAiSubscriptionSessionImportAdapter_1.OpenAiSubscriptionSessionImportAdapter; } });
|
|
22
|
+
var LoopbackAuthorizationCallbackReceiver_1 = require("./LoopbackAuthorizationCallbackReceiver");
|
|
23
|
+
Object.defineProperty(exports, "LoopbackAuthorizationCallbackReceiver", { enumerable: true, get: function () { return LoopbackAuthorizationCallbackReceiver_1.LoopbackAuthorizationCallbackReceiver; } });
|
|
23
24
|
const CREDENTIAL_COLLECTION_QUERY_UNSUPPORTED = 'credential_collection_query_unsupported';
|
|
25
|
+
const credentialUpdateLocks = new Map();
|
|
24
26
|
class PodConnectedCredentialRepository {
|
|
25
27
|
constructor(options = {}) {
|
|
26
28
|
this.internalPodAccess = options.internalPodAccess;
|
|
@@ -164,6 +166,7 @@ class PodConnectedCredentialRepository {
|
|
|
164
166
|
});
|
|
165
167
|
const expectedVersionString = String(expectedVersion ?? currentVersion);
|
|
166
168
|
const updated = await updateByCredentialIdAndVersion({
|
|
169
|
+
owner: input.webId,
|
|
167
170
|
db,
|
|
168
171
|
credential,
|
|
169
172
|
credentialId: input.credentialId,
|
|
@@ -250,17 +253,20 @@ class PodConnectedCredentialRepository {
|
|
|
250
253
|
if (input.expectedVersion === undefined || currentVersion !== input.expectedVersion) {
|
|
251
254
|
return false;
|
|
252
255
|
}
|
|
253
|
-
const updated = await
|
|
254
|
-
.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
const updated = await updateByCredentialIdAndVersion({
|
|
257
|
+
owner: input.webId,
|
|
258
|
+
db,
|
|
259
|
+
credential,
|
|
260
|
+
credentialId: input.credentialId,
|
|
261
|
+
expectedVersion: String(input.expectedVersion),
|
|
262
|
+
patch: {
|
|
263
|
+
encryptedSecret: JSON.stringify(input.encryptedSecret),
|
|
264
|
+
wrappedDataKey: input.encryptedSecret.wrappedDek,
|
|
265
|
+
encryptionAlgorithm: input.encryptedSecret.algorithm,
|
|
266
|
+
keyVersion: String(currentVersion + 1),
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
return updated !== null;
|
|
264
270
|
}
|
|
265
271
|
async markReauthRequired(input) {
|
|
266
272
|
const current = await this.getActiveCredential(input);
|
|
@@ -449,7 +455,7 @@ class InMemoryConnectAttemptStore {
|
|
|
449
455
|
}
|
|
450
456
|
return attempt ? cloneAttempt(attempt) : undefined;
|
|
451
457
|
}
|
|
452
|
-
async consume(id, now) {
|
|
458
|
+
async consume(id, now, terminalStatus) {
|
|
453
459
|
this.pruneExpired(now, id);
|
|
454
460
|
const attempt = this.attempts.get(id);
|
|
455
461
|
if (!attempt) {
|
|
@@ -463,6 +469,7 @@ class InMemoryConnectAttemptStore {
|
|
|
463
469
|
throw new Error('Connect attempt expired');
|
|
464
470
|
}
|
|
465
471
|
attempt.consumedAt = new Date(now);
|
|
472
|
+
attempt.terminalStatus = terminalStatus;
|
|
466
473
|
return cloneAttempt(attempt);
|
|
467
474
|
}
|
|
468
475
|
async claimPoll(id, now) {
|
|
@@ -523,10 +530,9 @@ class InMemoryConnectAttemptStore {
|
|
|
523
530
|
}
|
|
524
531
|
}
|
|
525
532
|
exports.InMemoryConnectAttemptStore = InMemoryConnectAttemptStore;
|
|
526
|
-
class
|
|
533
|
+
class SignedConnectAttemptAdapterBase {
|
|
527
534
|
constructor(options) {
|
|
528
535
|
this.provider = normalizeProvider(options.provider);
|
|
529
|
-
this.consoleUrl = options.consoleUrl;
|
|
530
536
|
this.attempts = options.attempts;
|
|
531
537
|
this.credentialRepository = options.credentialRepository;
|
|
532
538
|
this.vault = options.vault;
|
|
@@ -535,6 +541,91 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
535
541
|
this.randomBytes = options.randomBytes ?? node_crypto_1.randomBytes;
|
|
536
542
|
this.signingSecret = options.signingSecret;
|
|
537
543
|
}
|
|
544
|
+
async createAttempt(input, expiresAt, extra = {}) {
|
|
545
|
+
const attemptWithoutSignature = {
|
|
546
|
+
id: token(this.randomBytes),
|
|
547
|
+
provider: this.provider,
|
|
548
|
+
deployment: input.deployment,
|
|
549
|
+
webId: input.webId,
|
|
550
|
+
mode: input.requestedMode,
|
|
551
|
+
offeringId: extra.offeringId ?? input.offeringId,
|
|
552
|
+
authorizationMethodId: extra.authorizationMethodId ?? input.authorizationMethodId,
|
|
553
|
+
state: token(this.randomBytes),
|
|
554
|
+
expiresAt,
|
|
555
|
+
expectedCredentialVersion: input.expectedCredentialVersion,
|
|
556
|
+
...extra,
|
|
557
|
+
};
|
|
558
|
+
const signature = signAttempt(attemptWithoutSignature, this.signingSecret);
|
|
559
|
+
return this.attempts.create({ ...attemptWithoutSignature, signature });
|
|
560
|
+
}
|
|
561
|
+
async loadAttemptForStatus(input, mode) {
|
|
562
|
+
const attempt = await this.attempts.get(input.attemptId, this.now());
|
|
563
|
+
if (!attempt) {
|
|
564
|
+
throw new Error('Connect attempt not found');
|
|
565
|
+
}
|
|
566
|
+
if (attempt.webId !== input.webId) {
|
|
567
|
+
throw new Error('Connect attempt is bound to a different WebID');
|
|
568
|
+
}
|
|
569
|
+
if (attempt.deployment !== input.deployment) {
|
|
570
|
+
throw new Error('Connect attempt is bound to a different deployment');
|
|
571
|
+
}
|
|
572
|
+
if (attempt.provider !== normalizeProvider(input.provider)) {
|
|
573
|
+
throw new Error('Connect attempt is bound to a different provider');
|
|
574
|
+
}
|
|
575
|
+
if (attempt.mode !== mode) {
|
|
576
|
+
throw new Error('Connect attempt mode mismatch');
|
|
577
|
+
}
|
|
578
|
+
if (input.offeringId && attempt.offeringId !== input.offeringId) {
|
|
579
|
+
throw new Error('Connect attempt is bound to a different offering');
|
|
580
|
+
}
|
|
581
|
+
if (attempt.state !== input.state) {
|
|
582
|
+
throw new Error('Invalid Connect attempt state');
|
|
583
|
+
}
|
|
584
|
+
if (!signatureMatches(input.signature, signAttempt(attempt, this.signingSecret))) {
|
|
585
|
+
throw new Error('Invalid Connect attempt signature');
|
|
586
|
+
}
|
|
587
|
+
return attempt;
|
|
588
|
+
}
|
|
589
|
+
async loadConsumableAttempt(input, mode) {
|
|
590
|
+
const attempt = await this.loadAttemptForStatus(input, mode);
|
|
591
|
+
if (attempt.expiresAt.getTime() <= this.now().getTime()) {
|
|
592
|
+
throw new Error('Connect attempt expired');
|
|
593
|
+
}
|
|
594
|
+
if (attempt.consumedAt) {
|
|
595
|
+
throw new Error('Connect attempt already consumed');
|
|
596
|
+
}
|
|
597
|
+
return attempt;
|
|
598
|
+
}
|
|
599
|
+
statusForAttempt(attempt) {
|
|
600
|
+
if (attempt.expiresAt.getTime() <= this.now().getTime()) {
|
|
601
|
+
return 'expired';
|
|
602
|
+
}
|
|
603
|
+
if (attempt.terminalStatus) {
|
|
604
|
+
return attempt.terminalStatus;
|
|
605
|
+
}
|
|
606
|
+
return attempt.consumedAt ? 'completed' : 'pending';
|
|
607
|
+
}
|
|
608
|
+
assertInput(input, mode) {
|
|
609
|
+
if (normalizeProvider(input.provider) !== this.provider) {
|
|
610
|
+
throw new Error('Connect provider mismatch');
|
|
611
|
+
}
|
|
612
|
+
if (input.deployment !== this.deployment) {
|
|
613
|
+
throw new Error('Connect deployment mismatch');
|
|
614
|
+
}
|
|
615
|
+
if (input.requestedMode !== mode) {
|
|
616
|
+
throw new Error('Unsupported Connect mode');
|
|
617
|
+
}
|
|
618
|
+
if (!input.webId) {
|
|
619
|
+
throw new Error('Connect WebID is required');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
class BrowserAssistedApiKeyConnectAdapter extends SignedConnectAttemptAdapterBase {
|
|
624
|
+
constructor(options) {
|
|
625
|
+
super(options);
|
|
626
|
+
this.mode = 'browserAssistedApiKey';
|
|
627
|
+
this.consoleUrl = options.consoleUrl;
|
|
628
|
+
}
|
|
538
629
|
async begin(input) {
|
|
539
630
|
this.assertInput(input, 'browserAssistedApiKey');
|
|
540
631
|
const now = this.now();
|
|
@@ -547,6 +638,7 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
547
638
|
mode: 'browserAssistedApiKey',
|
|
548
639
|
status: 'pending',
|
|
549
640
|
provider: this.provider,
|
|
641
|
+
offeringId: attempt.offeringId,
|
|
550
642
|
deployment: this.deployment,
|
|
551
643
|
attemptId: attempt.id,
|
|
552
644
|
state: attempt.state,
|
|
@@ -567,6 +659,7 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
567
659
|
});
|
|
568
660
|
const encryptedSecret = await this.vault.seal(principal(input.webId), credentialIri, this.provider, { type: 'apiKey', apiKey: input.apiKey });
|
|
569
661
|
const metadata = metadataWithoutUndefined({
|
|
662
|
+
offeringId: attempt.offeringId,
|
|
570
663
|
baseUrl: input.baseUrl,
|
|
571
664
|
health: 'unknown',
|
|
572
665
|
});
|
|
@@ -581,6 +674,7 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
581
674
|
status: 'active',
|
|
582
675
|
accountLabel: input.accountLabel,
|
|
583
676
|
expectedVersion: consumed.expectedCredentialVersion,
|
|
677
|
+
offeringId: attempt.offeringId,
|
|
584
678
|
health: 'unknown',
|
|
585
679
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
586
680
|
}, { auth: input.auth });
|
|
@@ -588,6 +682,7 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
588
682
|
mode: 'browserAssistedApiKey',
|
|
589
683
|
status: 'completed',
|
|
590
684
|
provider: this.provider,
|
|
685
|
+
offeringId: attempt.offeringId,
|
|
591
686
|
deployment: input.deployment,
|
|
592
687
|
attemptId: attempt.id,
|
|
593
688
|
credentialId: record.id,
|
|
@@ -599,6 +694,7 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
599
694
|
mode: attempt.mode,
|
|
600
695
|
status: this.statusForAttempt(attempt),
|
|
601
696
|
provider: this.provider,
|
|
697
|
+
offeringId: attempt.offeringId,
|
|
602
698
|
deployment: input.deployment,
|
|
603
699
|
attemptId: attempt.id,
|
|
604
700
|
expiresAt: attempt.expiresAt.toISOString(),
|
|
@@ -613,239 +709,556 @@ class BrowserAssistedApiKeyConnectAdapter {
|
|
|
613
709
|
auth: input.auth,
|
|
614
710
|
});
|
|
615
711
|
}
|
|
616
|
-
async createAttempt(input, expiresAt, extra = {}) {
|
|
617
|
-
const attemptWithoutSignature = {
|
|
618
|
-
id: token(this.randomBytes),
|
|
619
|
-
provider: this.provider,
|
|
620
|
-
deployment: input.deployment,
|
|
621
|
-
webId: input.webId,
|
|
622
|
-
mode: input.requestedMode,
|
|
623
|
-
state: token(this.randomBytes),
|
|
624
|
-
expiresAt,
|
|
625
|
-
expectedCredentialVersion: input.expectedCredentialVersion,
|
|
626
|
-
...extra,
|
|
627
|
-
};
|
|
628
|
-
const signature = signAttempt(attemptWithoutSignature, this.signingSecret);
|
|
629
|
-
return this.attempts.create({ ...attemptWithoutSignature, signature });
|
|
630
|
-
}
|
|
631
|
-
async loadAttemptForStatus(input, mode) {
|
|
632
|
-
const attempt = await this.attempts.get(input.attemptId, this.now());
|
|
633
|
-
if (!attempt) {
|
|
634
|
-
throw new Error('Connect attempt not found');
|
|
635
|
-
}
|
|
636
|
-
if (attempt.webId !== input.webId) {
|
|
637
|
-
throw new Error('Connect attempt is bound to a different WebID');
|
|
638
|
-
}
|
|
639
|
-
if (attempt.deployment !== input.deployment) {
|
|
640
|
-
throw new Error('Connect attempt is bound to a different deployment');
|
|
641
|
-
}
|
|
642
|
-
if (attempt.provider !== normalizeProvider(input.provider)) {
|
|
643
|
-
throw new Error('Connect attempt is bound to a different provider');
|
|
644
|
-
}
|
|
645
|
-
if (attempt.mode !== mode) {
|
|
646
|
-
throw new Error('Connect attempt mode mismatch');
|
|
647
|
-
}
|
|
648
|
-
if (attempt.state !== input.state) {
|
|
649
|
-
throw new Error('Invalid Connect attempt state');
|
|
650
|
-
}
|
|
651
|
-
if (!signatureMatches(input.signature, signAttempt(attempt, this.signingSecret))) {
|
|
652
|
-
throw new Error('Invalid Connect attempt signature');
|
|
653
|
-
}
|
|
654
|
-
return attempt;
|
|
655
|
-
}
|
|
656
|
-
async loadConsumableAttempt(input, mode) {
|
|
657
|
-
const attempt = await this.loadAttemptForStatus(input, mode);
|
|
658
|
-
if (attempt.expiresAt.getTime() <= this.now().getTime()) {
|
|
659
|
-
throw new Error('Connect attempt expired');
|
|
660
|
-
}
|
|
661
|
-
if (attempt.consumedAt) {
|
|
662
|
-
throw new Error('Connect attempt already consumed');
|
|
663
|
-
}
|
|
664
|
-
return attempt;
|
|
665
|
-
}
|
|
666
|
-
statusForAttempt(attempt) {
|
|
667
|
-
if (attempt.expiresAt.getTime() <= this.now().getTime()) {
|
|
668
|
-
return 'expired';
|
|
669
|
-
}
|
|
670
|
-
return attempt.consumedAt ? 'completed' : 'pending';
|
|
671
|
-
}
|
|
672
|
-
assertInput(input, mode) {
|
|
673
|
-
if (normalizeProvider(input.provider) !== this.provider) {
|
|
674
|
-
throw new Error('Connect provider mismatch');
|
|
675
|
-
}
|
|
676
|
-
if (input.deployment !== this.deployment) {
|
|
677
|
-
throw new Error('Connect deployment mismatch');
|
|
678
|
-
}
|
|
679
|
-
if (input.requestedMode !== mode) {
|
|
680
|
-
throw new Error('Unsupported Connect mode');
|
|
681
|
-
}
|
|
682
|
-
if (!input.webId) {
|
|
683
|
-
throw new Error('Connect WebID is required');
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
712
|
}
|
|
687
713
|
exports.BrowserAssistedApiKeyConnectAdapter = BrowserAssistedApiKeyConnectAdapter;
|
|
688
|
-
class
|
|
714
|
+
class DeviceCodeConnectAdapter extends SignedConnectAttemptAdapterBase {
|
|
689
715
|
constructor(options) {
|
|
690
716
|
super({
|
|
691
717
|
...options,
|
|
692
|
-
provider:
|
|
693
|
-
consoleUrl: 'https://kimi.moonshot.cn/device',
|
|
718
|
+
provider: options.integration.provider,
|
|
694
719
|
});
|
|
720
|
+
this.mode = 'deviceCodeOAuth';
|
|
721
|
+
this.authorizationMethodId = 'device-code';
|
|
722
|
+
this.integration = options.integration;
|
|
723
|
+
this.offeringId = options.integration.offeringId;
|
|
695
724
|
this.fetchImpl = options.fetch ?? fetch;
|
|
696
|
-
this.clientId =
|
|
697
|
-
this.
|
|
698
|
-
this.
|
|
725
|
+
this.clientId = options.integration.clientId;
|
|
726
|
+
this.protocol = options.integration.protocol;
|
|
727
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
|
|
699
728
|
this.oauthCredentials = new OAuthConnectAdapter_1.OAuthConnectCredentialStore({
|
|
700
|
-
provider:
|
|
729
|
+
provider: this.provider,
|
|
730
|
+
offeringId: this.offeringId,
|
|
701
731
|
deployment: options.deployment,
|
|
702
732
|
credentialRepository: options.credentialRepository,
|
|
703
733
|
vault: options.vault,
|
|
704
734
|
});
|
|
705
|
-
|
|
706
|
-
|
|
735
|
+
assertTrustedEndpoint(this.protocol.begin);
|
|
736
|
+
assertTrustedEndpoint(this.protocol.poll);
|
|
737
|
+
if (this.protocol.tokenExchange) {
|
|
738
|
+
assertTrustedEndpoint(this.protocol.tokenExchange);
|
|
739
|
+
}
|
|
740
|
+
if (this.protocol.refresh) {
|
|
741
|
+
assertTrustedEndpoint(this.protocol.refresh);
|
|
742
|
+
}
|
|
707
743
|
}
|
|
708
744
|
async begin(input) {
|
|
709
745
|
this.assertInput(input, 'deviceCodeOAuth');
|
|
746
|
+
this.assertOfferingInput(input.offeringId);
|
|
747
|
+
this.assertAuthorizationMethodInput(input.authorizationMethodId);
|
|
710
748
|
const now = this.now();
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
749
|
+
const usesPkce = this.protocol.begin.codec === 'oauthDeviceCodePkce' || this.protocol.poll.codec === 'oauthDeviceCodePkce';
|
|
750
|
+
const verifier = usesPkce ? token(this.randomBytes) : undefined;
|
|
751
|
+
const challenge = verifier ? codeChallenge(verifier) : undefined;
|
|
752
|
+
const response = await this.fetchImpl(this.protocol.begin.endpoint, this.providerRequest({
|
|
714
753
|
method: 'POST',
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
client_id: this.clientId,
|
|
718
|
-
code_challenge: challenge,
|
|
719
|
-
code_challenge_method: 'S256',
|
|
720
|
-
}),
|
|
721
|
-
});
|
|
754
|
+
...deviceCodeBeginRequest(this.protocol, this.clientId, challenge),
|
|
755
|
+
}));
|
|
722
756
|
const body = await safeJson(response);
|
|
723
757
|
if (!response.ok) {
|
|
724
|
-
throw new Error(`
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
758
|
+
throw new Error(`Device authorization failed: ${safeProviderError(body)}`);
|
|
759
|
+
}
|
|
760
|
+
const deviceCode = requireProtocolStringField(body, this.protocol.begin.deviceCodeField ?? ['device_code', 'device_auth_id']);
|
|
761
|
+
const userCode = requireProtocolStringField(body, this.protocol.begin.userCodeField ?? ['user_code', 'usercode']);
|
|
762
|
+
if (!stringFrom(body[this.protocol.begin.verificationUriCompleteField ?? 'verification_uri_complete'])
|
|
763
|
+
&& !stringFrom(body[this.protocol.begin.verificationUriField ?? 'verification_uri'])
|
|
764
|
+
&& !this.protocol.defaultVerificationUri) {
|
|
765
|
+
throw new Error('Provider response missing required field: verification_uri_complete');
|
|
766
|
+
}
|
|
767
|
+
const verificationUri = verifiedVerificationUri(body, this.protocol);
|
|
768
|
+
const expiresAt = deviceCodeExpiresAt(body, this.protocol, now);
|
|
769
|
+
const intervalSeconds = numberFrom(body[this.protocol.begin.intervalField ?? 'interval'], this.protocol.begin.defaultIntervalSeconds ?? 5);
|
|
770
|
+
const attempt = await this.createAttempt(input, expiresAt, {
|
|
731
771
|
mode: 'deviceCodeOAuth',
|
|
772
|
+
offeringId: this.offeringId,
|
|
773
|
+
authorizationMethodId: this.authorizationMethodId,
|
|
732
774
|
codeVerifier: verifier,
|
|
733
|
-
deviceCode
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
775
|
+
deviceCode,
|
|
776
|
+
userCode,
|
|
777
|
+
intervalSeconds,
|
|
778
|
+
currentPollIntervalSeconds: intervalSeconds,
|
|
779
|
+
nextPollAt: new Date(now.getTime() + intervalSeconds * 1000),
|
|
737
780
|
});
|
|
738
781
|
return {
|
|
739
782
|
mode: 'deviceCodeOAuth',
|
|
740
783
|
status: 'pending',
|
|
741
|
-
provider:
|
|
784
|
+
provider: this.provider,
|
|
785
|
+
offeringId: this.offeringId,
|
|
742
786
|
deployment: input.deployment,
|
|
743
787
|
attemptId: attempt.id,
|
|
744
788
|
state: attempt.state,
|
|
745
789
|
signature: attempt.signature,
|
|
746
790
|
expiresAt: attempt.expiresAt.toISOString(),
|
|
747
791
|
pkceChallenge: challenge,
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
verificationUriComplete: stringFrom(body.verification_uri_complete),
|
|
792
|
+
userCode,
|
|
793
|
+
verificationUri: verificationUri.verificationUri,
|
|
794
|
+
verificationUriComplete: verificationUri.verificationUriComplete,
|
|
752
795
|
intervalSeconds: attempt.intervalSeconds,
|
|
753
796
|
};
|
|
754
797
|
}
|
|
755
798
|
async pollDevice(input) {
|
|
756
799
|
const attempt = await this.loadConsumableAttempt(input, 'deviceCodeOAuth');
|
|
800
|
+
this.assertAttemptOffering(attempt);
|
|
757
801
|
const now = this.nowForConsume();
|
|
758
802
|
const claim = await this.attempts.claimPoll(input.attemptId, now);
|
|
759
803
|
if (!claim.claimed) {
|
|
760
804
|
return pendingResult(input, claim.attempt.lastPollStatus === 'slow_down' ? 'slow_down' : 'authorization_pending', claim.attempt.currentPollIntervalSeconds ?? claim.attempt.intervalSeconds);
|
|
761
805
|
}
|
|
762
806
|
try {
|
|
763
|
-
const response = await this.fetchImpl(this.
|
|
807
|
+
const response = await this.fetchImpl(this.protocol.poll.endpoint, this.providerRequest({
|
|
764
808
|
method: 'POST',
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
768
|
-
device_code: attempt.deviceCode ?? '',
|
|
769
|
-
client_id: this.clientId,
|
|
770
|
-
code_verifier: attempt.codeVerifier ?? '',
|
|
771
|
-
}),
|
|
772
|
-
});
|
|
809
|
+
...deviceCodePollRequest(this.protocol, this.clientId, attempt),
|
|
810
|
+
}));
|
|
773
811
|
const body = await safeJson(response);
|
|
774
812
|
if (!response.ok) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const intervalSeconds =
|
|
813
|
+
const pendingStatus = this.pendingPollStatus(response, body);
|
|
814
|
+
if (pendingStatus) {
|
|
815
|
+
const intervalSeconds = pendingStatus === 'slow_down'
|
|
778
816
|
? (attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds ?? 5) + 5
|
|
779
817
|
: (attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds ?? 5);
|
|
780
818
|
await this.attempts.updatePollSchedule(input.attemptId, {
|
|
781
819
|
intervalSeconds,
|
|
782
820
|
nextPollAt: new Date(now.getTime() + intervalSeconds * 1000),
|
|
783
|
-
lastPollStatus:
|
|
821
|
+
lastPollStatus: pendingStatus,
|
|
784
822
|
});
|
|
785
|
-
return pendingResult(input,
|
|
823
|
+
return pendingResult(input, pendingStatus, intervalSeconds, this.provider, this.offeringId);
|
|
786
824
|
}
|
|
787
825
|
if (body.error === 'expired_token') {
|
|
788
|
-
await this.attempts.consume(input.attemptId, this.nowForConsume());
|
|
789
|
-
return pendingResult(input, 'expired');
|
|
826
|
+
await this.attempts.consume(input.attemptId, this.nowForConsume(), 'expired');
|
|
827
|
+
return pendingResult(input, 'expired', undefined, this.provider, this.offeringId);
|
|
828
|
+
}
|
|
829
|
+
if (body.error === 'access_denied') {
|
|
830
|
+
await this.attempts.consume(input.attemptId, this.nowForConsume(), 'denied');
|
|
831
|
+
return pendingResult(input, 'denied', undefined, this.provider, this.offeringId);
|
|
790
832
|
}
|
|
791
|
-
throw new Error(`
|
|
833
|
+
throw new Error(`Device token failed: ${safeProviderError(body)}`);
|
|
792
834
|
}
|
|
835
|
+
const tokenBody = await this.resolveTokenBody(body, attempt);
|
|
793
836
|
await this.attempts.consume(input.attemptId, this.nowForConsume());
|
|
794
|
-
requireStringField(
|
|
795
|
-
requireStringField(
|
|
837
|
+
requireStringField(tokenBody, 'access_token');
|
|
838
|
+
requireStringField(tokenBody, 'refresh_token');
|
|
796
839
|
return {
|
|
797
840
|
mode: 'deviceCodeOAuth',
|
|
798
841
|
status: 'completed',
|
|
799
|
-
provider:
|
|
842
|
+
provider: this.provider,
|
|
843
|
+
offeringId: this.offeringId,
|
|
800
844
|
deployment: input.deployment,
|
|
801
845
|
attemptId: input.attemptId,
|
|
802
|
-
oauthCredential: oneTimeOAuthCredential(
|
|
846
|
+
oauthCredential: oneTimeOAuthCredential(tokenBody, this.now(), attempt.expectedCredentialVersion, this.integration, undefined, this.authorizationMethodId),
|
|
803
847
|
};
|
|
804
848
|
}
|
|
805
849
|
catch (error) {
|
|
806
850
|
await this.attempts.releasePollClaim(input.attemptId);
|
|
807
851
|
throw error;
|
|
808
852
|
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
|
|
853
|
+
}
|
|
854
|
+
pendingPollStatus(response, body) {
|
|
855
|
+
if (body.error === 'authorization_pending' || body.error === 'slow_down') {
|
|
856
|
+
return body.error;
|
|
857
|
+
}
|
|
858
|
+
return this.protocol.poll.pendingHttpStatuses?.includes(response.status) ? 'authorization_pending' : undefined;
|
|
859
|
+
}
|
|
860
|
+
async status(input) {
|
|
861
|
+
const attempt = await this.loadAttemptForStatus(input, 'deviceCodeOAuth');
|
|
862
|
+
this.assertAttemptOffering(attempt);
|
|
863
|
+
return {
|
|
864
|
+
mode: 'deviceCodeOAuth',
|
|
865
|
+
status: this.statusForAttempt(attempt),
|
|
866
|
+
provider: this.provider,
|
|
867
|
+
offeringId: this.offeringId,
|
|
868
|
+
deployment: input.deployment,
|
|
869
|
+
attemptId: attempt.id,
|
|
870
|
+
expiresAt: attempt.expiresAt.toISOString(),
|
|
871
|
+
intervalSeconds: attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
async cancel(input) {
|
|
875
|
+
const attempt = await this.loadConsumableAttempt(input, 'deviceCodeOAuth');
|
|
876
|
+
this.assertAttemptOffering(attempt);
|
|
877
|
+
await this.attempts.consume(input.attemptId, this.nowForConsume(), 'cancelled');
|
|
878
|
+
return pendingResult(input, 'cancelled', undefined, this.provider, this.offeringId);
|
|
879
|
+
}
|
|
880
|
+
async refresh(input, current, secret) {
|
|
881
|
+
this.assertStoredOffering(current);
|
|
882
|
+
const refreshToken = stringFrom(secret.refreshToken);
|
|
883
|
+
if (!refreshToken) {
|
|
884
|
+
return this.credentialRepository.markReauthRequired({
|
|
885
|
+
webId: input.webId,
|
|
886
|
+
provider: this.provider,
|
|
887
|
+
deployment: input.deployment,
|
|
888
|
+
reason: 'missing_refresh_token',
|
|
889
|
+
expectedVersion: current.version,
|
|
890
|
+
auth: input.auth,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
const refresh = this.protocol.refresh ?? this.protocol.poll;
|
|
894
|
+
const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, refreshToken);
|
|
895
|
+
const body = refreshResult.body;
|
|
896
|
+
if (!refreshResult.ok) {
|
|
897
|
+
return this.credentialRepository.markReauthRequired({
|
|
898
|
+
webId: input.webId,
|
|
899
|
+
provider: this.provider,
|
|
900
|
+
deployment: input.deployment,
|
|
901
|
+
reason: safeProviderError(body),
|
|
902
|
+
expectedVersion: current.version,
|
|
903
|
+
auth: input.auth,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
requireStringField(body, 'access_token');
|
|
907
|
+
return this.updateOAuthCredential(input, body, current, secret);
|
|
908
|
+
}
|
|
909
|
+
async refreshCallerOwned(input) {
|
|
910
|
+
this.assertOfferingInput(input.offeringId);
|
|
911
|
+
if (!input.refreshToken.trim())
|
|
912
|
+
throw new Error('oauth_refresh_token_required');
|
|
913
|
+
const refresh = this.protocol.refresh ?? this.protocol.poll;
|
|
914
|
+
const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, input.refreshToken);
|
|
915
|
+
const body = refreshResult.body;
|
|
916
|
+
if (!refreshResult.ok) {
|
|
917
|
+
throw new Error(`OAuth refresh failed: ${safeProviderError(body)}`);
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
mode: 'deviceCodeOAuth',
|
|
921
|
+
status: 'completed',
|
|
922
|
+
provider: this.provider,
|
|
923
|
+
offeringId: this.offeringId,
|
|
924
|
+
deployment: input.deployment,
|
|
925
|
+
credentialId: input.credentialId,
|
|
926
|
+
oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion, this.integration, input.refreshToken, this.authorizationMethodId),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
async disconnect(input) {
|
|
930
|
+
this.assertOfferingInput(input.offeringId);
|
|
931
|
+
const current = await this.findOAuthCredential(input);
|
|
932
|
+
if (!current) {
|
|
933
|
+
throw new Error('oauth_credential_not_found');
|
|
934
|
+
}
|
|
935
|
+
return this.credentialRepository.revokeCredential({
|
|
936
|
+
webId: input.webId,
|
|
937
|
+
provider: this.provider,
|
|
938
|
+
deployment: input.deployment,
|
|
939
|
+
credentialId: current.id,
|
|
940
|
+
expectedVersion: current.version,
|
|
941
|
+
auth: input.auth,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
nowForConsume() {
|
|
945
|
+
return this.now();
|
|
946
|
+
}
|
|
947
|
+
async updateOAuthCredential(input, body, current, currentSecret) {
|
|
948
|
+
const expiresAt = expiresAtFrom(body.expires_in, this.now());
|
|
949
|
+
const nextIdToken = stringFrom(body.id_token) || stringFrom(currentSecret.idToken);
|
|
950
|
+
const secret = {
|
|
951
|
+
type: 'deviceCodeOAuth',
|
|
952
|
+
accessToken: stringFrom(body.access_token),
|
|
953
|
+
refreshToken: stringFrom(body.refresh_token) || stringFrom(currentSecret.refreshToken),
|
|
954
|
+
expiresAt: expiresAt?.toISOString(),
|
|
955
|
+
scope: stringFrom(body.scope),
|
|
956
|
+
idToken: nextIdToken,
|
|
957
|
+
accountId: stringFrom(body.account_id)
|
|
958
|
+
|| jwtClaim(nextIdToken, this.protocol.accountIdClaim)
|
|
959
|
+
|| stringFrom(currentSecret.accountId),
|
|
960
|
+
};
|
|
961
|
+
return this.oauthCredentials.updateOAuthCredential({
|
|
962
|
+
current,
|
|
963
|
+
webId: input.webId,
|
|
964
|
+
deployment: input.deployment,
|
|
965
|
+
secret,
|
|
966
|
+
expiresAt,
|
|
967
|
+
auth: input.auth,
|
|
968
|
+
metadata: {
|
|
969
|
+
authoritativeSubject: decodeJwtSubject(nextIdToken),
|
|
970
|
+
},
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
async findOAuthCredential(input) {
|
|
974
|
+
const credentials = input.credentialId
|
|
975
|
+
? [
|
|
976
|
+
await this.credentialRepository.getCredentialById({
|
|
977
|
+
webId: input.webId,
|
|
978
|
+
provider: this.provider,
|
|
979
|
+
deployment: input.deployment,
|
|
980
|
+
credentialId: input.credentialId,
|
|
981
|
+
auth: input.auth,
|
|
982
|
+
}),
|
|
983
|
+
]
|
|
984
|
+
: await this.credentialRepository.listProviderCredentials({
|
|
985
|
+
webId: input.webId,
|
|
986
|
+
provider: this.provider,
|
|
987
|
+
deployment: input.deployment,
|
|
988
|
+
auth: input.auth,
|
|
989
|
+
});
|
|
990
|
+
return credentials.find((credential) => credential && this.isProviderOAuthCredential(credential));
|
|
991
|
+
}
|
|
992
|
+
async resolveTokenBody(body, attempt) {
|
|
993
|
+
if (this.protocol.tokenExchange?.codec !== 'authorizationCodeForm') {
|
|
994
|
+
return body;
|
|
995
|
+
}
|
|
996
|
+
const code = requireProtocolStringField(body, this.protocol.tokenExchange.codeField ?? ['code', 'authorization_code']);
|
|
997
|
+
const codeVerifier = stringFrom(body[this.protocol.tokenExchange.codeVerifierField ?? 'code_verifier']) || attempt.codeVerifier;
|
|
998
|
+
if (!codeVerifier) {
|
|
999
|
+
throw new Error('Provider response missing required field: code_verifier');
|
|
1000
|
+
}
|
|
1001
|
+
const response = await this.fetchImpl(this.protocol.tokenExchange.endpoint, this.providerRequest({
|
|
1002
|
+
method: 'POST',
|
|
1003
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1004
|
+
body: new URLSearchParams({
|
|
1005
|
+
grant_type: 'authorization_code',
|
|
1006
|
+
code,
|
|
1007
|
+
code_verifier: codeVerifier,
|
|
1008
|
+
client_id: this.clientId,
|
|
1009
|
+
...(this.protocol.tokenExchange.redirectUri ? { redirect_uri: this.protocol.tokenExchange.redirectUri } : {}),
|
|
1010
|
+
}),
|
|
1011
|
+
}));
|
|
1012
|
+
const tokenBody = await safeJson(response);
|
|
1013
|
+
if (!response.ok) {
|
|
1014
|
+
throw new Error(`Device token exchange failed: ${safeProviderError(tokenBody)}`);
|
|
1015
|
+
}
|
|
1016
|
+
return tokenBody;
|
|
1017
|
+
}
|
|
1018
|
+
providerRequest(init) {
|
|
1019
|
+
return {
|
|
1020
|
+
...init,
|
|
1021
|
+
redirect: 'error',
|
|
1022
|
+
signal: requestTimeoutSignal(this.requestTimeoutMs),
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
assertOfferingInput(offeringId) {
|
|
1026
|
+
if (offeringId && normalizeProvider(offeringId) !== normalizeProvider(this.offeringId)) {
|
|
1027
|
+
throw new Error('Connect offering mismatch');
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
assertAuthorizationMethodInput(authorizationMethodId) {
|
|
1031
|
+
if (authorizationMethodId && normalizeProvider(authorizationMethodId) !== this.authorizationMethodId) {
|
|
1032
|
+
throw new Error('Connect authorization method mismatch');
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
assertAttemptOffering(attempt) {
|
|
1036
|
+
if (attempt.offeringId !== this.offeringId) {
|
|
1037
|
+
throw new Error('Connect attempt is bound to a different offering');
|
|
1038
|
+
}
|
|
1039
|
+
if (attempt.authorizationMethodId !== this.authorizationMethodId) {
|
|
1040
|
+
throw new Error('Connect attempt authorization method mismatch');
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
assertStoredOffering(current) {
|
|
1044
|
+
const metadata = metadataFromRowValue(current.metadata) ?? {};
|
|
1045
|
+
if ((current.offeringId ?? stringMetadata(metadata, 'offeringId')) !== this.offeringId) {
|
|
1046
|
+
throw new Error('oauth_credential_not_found');
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
isProviderOAuthCredential(credential) {
|
|
1050
|
+
if (!credential)
|
|
1051
|
+
return false;
|
|
1052
|
+
const metadata = metadataFromRowValue(credential.metadata) ?? {};
|
|
1053
|
+
return credential.provider === this.provider
|
|
1054
|
+
&& isOAuthProviderCredential(credential)
|
|
1055
|
+
&& (credential.offeringId ?? stringMetadata(metadata, 'offeringId')) === this.offeringId;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
exports.DeviceCodeConnectAdapter = DeviceCodeConnectAdapter;
|
|
1059
|
+
class AuthorizationCodeConnectAdapter extends SignedConnectAttemptAdapterBase {
|
|
1060
|
+
constructor(options) {
|
|
1061
|
+
super({
|
|
1062
|
+
...options,
|
|
1063
|
+
provider: options.integration.provider,
|
|
1064
|
+
});
|
|
1065
|
+
this.mode = 'authorizationCodeOAuth';
|
|
1066
|
+
this.authorizationMethodId = 'browser-oauth';
|
|
1067
|
+
this.callbacks = new Map();
|
|
1068
|
+
this.integration = options.integration;
|
|
1069
|
+
this.offeringId = options.integration.offeringId;
|
|
1070
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
1071
|
+
this.clientId = options.integration.clientId;
|
|
1072
|
+
this.callbackReceiver = options.callbackReceiver;
|
|
1073
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
|
|
1074
|
+
this.oauthCredentials = new OAuthConnectAdapter_1.OAuthConnectCredentialStore({
|
|
1075
|
+
provider: this.provider,
|
|
1076
|
+
offeringId: this.offeringId,
|
|
1077
|
+
deployment: options.deployment,
|
|
1078
|
+
credentialRepository: options.credentialRepository,
|
|
1079
|
+
vault: options.vault,
|
|
1080
|
+
});
|
|
1081
|
+
assertTrustedEndpoint(this.integration.protocol.authorization);
|
|
1082
|
+
assertTrustedEndpoint(this.integration.protocol.token);
|
|
1083
|
+
if (this.integration.protocol.refresh) {
|
|
1084
|
+
assertTrustedEndpoint(this.integration.protocol.refresh);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
async begin(input) {
|
|
1088
|
+
this.assertInput(input, 'authorizationCodeOAuth');
|
|
1089
|
+
this.assertOfferingInput(input.offeringId);
|
|
1090
|
+
this.assertAuthorizationMethodInput(input.authorizationMethodId);
|
|
1091
|
+
const now = this.now();
|
|
1092
|
+
const expiresAt = new Date(now.getTime() + 15 * 60 * 1000);
|
|
1093
|
+
const verifier = token(this.randomBytes);
|
|
1094
|
+
const challenge = codeChallenge(verifier);
|
|
1095
|
+
const attempt = await this.createAttempt(input, expiresAt, {
|
|
1096
|
+
mode: 'authorizationCodeOAuth',
|
|
1097
|
+
offeringId: this.offeringId,
|
|
1098
|
+
authorizationMethodId: this.authorizationMethodId,
|
|
1099
|
+
codeVerifier: verifier,
|
|
1100
|
+
});
|
|
1101
|
+
let registration;
|
|
1102
|
+
try {
|
|
1103
|
+
registration = await this.callbackReceiver.register({
|
|
1104
|
+
redirectUris: this.integration.protocol.authorization.redirectUris,
|
|
1105
|
+
state: attempt.state,
|
|
1106
|
+
expiresAt,
|
|
1107
|
+
onCallback: async (result) => {
|
|
1108
|
+
const context = this.callbacks.get(attempt.id);
|
|
1109
|
+
if (!context)
|
|
1110
|
+
return;
|
|
1111
|
+
context.authorizationCode = result.code;
|
|
1112
|
+
context.error = result.error;
|
|
1113
|
+
},
|
|
1114
|
+
});
|
|
1115
|
+
if (!this.integration.protocol.authorization.redirectUris.includes(registration.redirectUri)) {
|
|
1116
|
+
registration.close();
|
|
1117
|
+
await this.attempts.consume(attempt.id, this.now(), 'cancelled');
|
|
1118
|
+
throw new Error('Authorization callback redirect URI is not allowlisted');
|
|
1119
|
+
}
|
|
1120
|
+
const expiresTimer = setTimeout(() => this.closeCallback(attempt.id), Math.max(0, expiresAt.getTime() - this.now().getTime()));
|
|
1121
|
+
expiresTimer.unref();
|
|
1122
|
+
this.callbacks.set(attempt.id, {
|
|
1123
|
+
redirectUri: registration.redirectUri,
|
|
1124
|
+
close: registration.close,
|
|
1125
|
+
expiresTimer,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
catch (error) {
|
|
1129
|
+
registration?.close();
|
|
1130
|
+
this.callbacks.delete(attempt.id);
|
|
1131
|
+
try {
|
|
1132
|
+
await this.attempts.consume(attempt.id, this.now(), 'cancelled');
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// Attempt cleanup is best-effort when registration fails after expiry.
|
|
1136
|
+
}
|
|
1137
|
+
throw error;
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
mode: 'authorizationCodeOAuth',
|
|
1141
|
+
status: 'pending',
|
|
1142
|
+
provider: this.provider,
|
|
1143
|
+
offeringId: this.offeringId,
|
|
1144
|
+
deployment: input.deployment,
|
|
1145
|
+
attemptId: attempt.id,
|
|
1146
|
+
state: attempt.state,
|
|
1147
|
+
signature: attempt.signature,
|
|
1148
|
+
expiresAt: attempt.expiresAt.toISOString(),
|
|
1149
|
+
pkceChallenge: challenge,
|
|
1150
|
+
authorizationUrl: this.authorizationUrl(attempt, challenge, registration.redirectUri),
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
async pollDevice(input) {
|
|
1154
|
+
const attempt = await this.loadAuthorizationAttempt(input, true);
|
|
1155
|
+
this.assertAttemptOffering(attempt);
|
|
1156
|
+
const context = this.callbacks.get(input.attemptId);
|
|
1157
|
+
if (!context) {
|
|
1158
|
+
throw new Error('Authorization callback listener not found');
|
|
1159
|
+
}
|
|
1160
|
+
if (context.error) {
|
|
1161
|
+
this.closeCallback(input.attemptId);
|
|
1162
|
+
await this.attempts.consume(input.attemptId, this.now(), 'denied');
|
|
1163
|
+
return oauthPendingResult(input, 'denied', this.provider, this.offeringId, 'authorizationCodeOAuth');
|
|
1164
|
+
}
|
|
1165
|
+
if (!context.authorizationCode) {
|
|
1166
|
+
return oauthPendingResult(input, 'authorization_pending', this.provider, this.offeringId, 'authorizationCodeOAuth');
|
|
1167
|
+
}
|
|
1168
|
+
const now = this.now();
|
|
1169
|
+
const claim = await this.attempts.claimPoll(input.attemptId, now);
|
|
1170
|
+
if (!claim.claimed) {
|
|
1171
|
+
return oauthPendingResult(input, 'authorization_pending', this.provider, this.offeringId, 'authorizationCodeOAuth');
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
const body = await this.exchangeAuthorizationCode(context.authorizationCode, attempt.codeVerifier, context.redirectUri);
|
|
1175
|
+
const oauthCredential = oneTimeOAuthCredential(body, this.now(), attempt.expectedCredentialVersion, this.integration, undefined, this.authorizationMethodId);
|
|
1176
|
+
await this.attempts.consume(input.attemptId, this.now());
|
|
1177
|
+
this.closeCallback(input.attemptId);
|
|
1178
|
+
return {
|
|
1179
|
+
mode: 'authorizationCodeOAuth',
|
|
1180
|
+
status: 'completed',
|
|
1181
|
+
provider: this.provider,
|
|
1182
|
+
offeringId: this.offeringId,
|
|
1183
|
+
deployment: input.deployment,
|
|
1184
|
+
attemptId: input.attemptId,
|
|
1185
|
+
oauthCredential,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
this.closeCallback(input.attemptId);
|
|
1190
|
+
try {
|
|
1191
|
+
await this.attempts.consume(input.attemptId, this.now(), 'denied');
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
await this.attempts.releasePollClaim(input.attemptId);
|
|
1195
|
+
}
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async status(input) {
|
|
1200
|
+
const attempt = await this.loadAuthorizationAttempt(input, false);
|
|
1201
|
+
this.assertAttemptOffering(attempt);
|
|
1202
|
+
return {
|
|
1203
|
+
mode: 'authorizationCodeOAuth',
|
|
1204
|
+
status: this.statusForAttempt(attempt),
|
|
1205
|
+
provider: this.provider,
|
|
1206
|
+
offeringId: this.offeringId,
|
|
1207
|
+
deployment: input.deployment,
|
|
1208
|
+
attemptId: attempt.id,
|
|
1209
|
+
expiresAt: attempt.expiresAt.toISOString(),
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
async cancel(input) {
|
|
1213
|
+
const attempt = await this.loadAuthorizationAttempt(input, true);
|
|
1214
|
+
this.assertAttemptOffering(attempt);
|
|
1215
|
+
this.closeCallback(input.attemptId);
|
|
1216
|
+
await this.attempts.consume(input.attemptId, this.now(), 'cancelled');
|
|
1217
|
+
return oauthPendingResult(input, 'cancelled', this.provider, this.offeringId, 'authorizationCodeOAuth');
|
|
1218
|
+
}
|
|
1219
|
+
async refreshCallerOwned(input) {
|
|
1220
|
+
this.assertOfferingInput(input.offeringId);
|
|
1221
|
+
if (input.authorizationMethodId) {
|
|
1222
|
+
this.assertAuthorizationMethodInput(input.authorizationMethodId);
|
|
1223
|
+
}
|
|
1224
|
+
if (!input.refreshToken.trim())
|
|
1225
|
+
throw new Error('oauth_refresh_token_required');
|
|
1226
|
+
const refresh = this.integration.protocol.refresh ?? this.integration.protocol.token;
|
|
1227
|
+
const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, input.refreshToken);
|
|
1228
|
+
const body = refreshResult.body;
|
|
1229
|
+
if (!refreshResult.ok) {
|
|
1230
|
+
throw new Error(`OAuth refresh failed: ${safeProviderError(body)}`);
|
|
1231
|
+
}
|
|
812
1232
|
return {
|
|
813
|
-
mode: '
|
|
814
|
-
status:
|
|
815
|
-
provider:
|
|
1233
|
+
mode: 'authorizationCodeOAuth',
|
|
1234
|
+
status: 'completed',
|
|
1235
|
+
provider: this.provider,
|
|
1236
|
+
offeringId: this.offeringId,
|
|
816
1237
|
deployment: input.deployment,
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
deviceCode: attempt.deviceCode,
|
|
820
|
-
intervalSeconds: attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds,
|
|
1238
|
+
credentialId: input.credentialId,
|
|
1239
|
+
oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion, this.integration, input.refreshToken, this.authorizationMethodId),
|
|
821
1240
|
};
|
|
822
1241
|
}
|
|
823
1242
|
async refresh(input, current, secret) {
|
|
1243
|
+
this.assertStoredOffering(current);
|
|
824
1244
|
const refreshToken = stringFrom(secret.refreshToken);
|
|
825
1245
|
if (!refreshToken) {
|
|
826
1246
|
return this.credentialRepository.markReauthRequired({
|
|
827
1247
|
webId: input.webId,
|
|
828
|
-
provider:
|
|
1248
|
+
provider: this.provider,
|
|
829
1249
|
deployment: input.deployment,
|
|
830
1250
|
reason: 'missing_refresh_token',
|
|
831
1251
|
expectedVersion: current.version,
|
|
832
1252
|
auth: input.auth,
|
|
833
1253
|
});
|
|
834
1254
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
grant_type: 'refresh_token',
|
|
840
|
-
refresh_token: refreshToken,
|
|
841
|
-
client_id: this.clientId,
|
|
842
|
-
}),
|
|
843
|
-
});
|
|
844
|
-
const body = await safeJson(response);
|
|
845
|
-
if (!response.ok) {
|
|
1255
|
+
const refresh = this.integration.protocol.refresh ?? this.integration.protocol.token;
|
|
1256
|
+
const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, refreshToken);
|
|
1257
|
+
const body = refreshResult.body;
|
|
1258
|
+
if (!refreshResult.ok) {
|
|
846
1259
|
return this.credentialRepository.markReauthRequired({
|
|
847
1260
|
webId: input.webId,
|
|
848
|
-
provider:
|
|
1261
|
+
provider: this.provider,
|
|
849
1262
|
deployment: input.deployment,
|
|
850
1263
|
reason: safeProviderError(body),
|
|
851
1264
|
expectedVersion: current.version,
|
|
@@ -853,60 +1266,74 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
|
|
|
853
1266
|
});
|
|
854
1267
|
}
|
|
855
1268
|
requireStringField(body, 'access_token');
|
|
856
|
-
|
|
857
|
-
return this.updateOAuthCredential(input, body, current);
|
|
858
|
-
}
|
|
859
|
-
async refreshCallerOwned(input) {
|
|
860
|
-
if (!input.refreshToken.trim())
|
|
861
|
-
throw new Error('oauth_refresh_token_required');
|
|
862
|
-
const response = await this.fetchImpl(this.tokenEndpoint, {
|
|
863
|
-
method: 'POST',
|
|
864
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
865
|
-
body: new URLSearchParams({
|
|
866
|
-
grant_type: 'refresh_token',
|
|
867
|
-
refresh_token: input.refreshToken,
|
|
868
|
-
client_id: this.clientId,
|
|
869
|
-
}),
|
|
870
|
-
});
|
|
871
|
-
const body = await safeJson(response);
|
|
872
|
-
if (!response.ok) {
|
|
873
|
-
throw new Error(`Kimi OAuth refresh failed: ${safeProviderError(body)}`);
|
|
874
|
-
}
|
|
875
|
-
return {
|
|
876
|
-
mode: 'deviceCodeOAuth',
|
|
877
|
-
status: 'completed',
|
|
878
|
-
provider: 'kimi',
|
|
879
|
-
deployment: input.deployment,
|
|
880
|
-
credentialId: input.credentialId,
|
|
881
|
-
oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion),
|
|
882
|
-
};
|
|
1269
|
+
return this.updateOAuthCredential(input, body, current, secret);
|
|
883
1270
|
}
|
|
884
1271
|
async disconnect(input) {
|
|
1272
|
+
this.assertOfferingInput(input.offeringId);
|
|
885
1273
|
const current = await this.findOAuthCredential(input);
|
|
886
1274
|
if (!current) {
|
|
887
1275
|
throw new Error('oauth_credential_not_found');
|
|
888
1276
|
}
|
|
889
1277
|
return this.credentialRepository.revokeCredential({
|
|
890
1278
|
webId: input.webId,
|
|
891
|
-
provider:
|
|
1279
|
+
provider: this.provider,
|
|
892
1280
|
deployment: input.deployment,
|
|
893
1281
|
credentialId: current.id,
|
|
894
1282
|
expectedVersion: current.version,
|
|
895
1283
|
auth: input.auth,
|
|
896
1284
|
});
|
|
897
1285
|
}
|
|
898
|
-
|
|
899
|
-
|
|
1286
|
+
authorizationUrl(attempt, challenge, redirectUri) {
|
|
1287
|
+
const url = new URL(this.integration.protocol.authorization.endpoint);
|
|
1288
|
+
url.searchParams.set('response_type', this.integration.protocol.authorization.responseType ?? 'code');
|
|
1289
|
+
url.searchParams.set('client_id', this.clientId);
|
|
1290
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
1291
|
+
url.searchParams.set('state', attempt.state);
|
|
1292
|
+
url.searchParams.set('code_challenge', challenge);
|
|
1293
|
+
url.searchParams.set('code_challenge_method', this.integration.protocol.authorization.codeChallengeMethod ?? 'S256');
|
|
1294
|
+
const scopes = this.integration.protocol.authorization.scopes ?? [];
|
|
1295
|
+
if (scopes.length > 0) {
|
|
1296
|
+
url.searchParams.set('scope', scopes.join(' '));
|
|
1297
|
+
}
|
|
1298
|
+
for (const [key, value] of Object.entries(this.integration.protocol.authorization.extraParams ?? {})) {
|
|
1299
|
+
url.searchParams.set(key, value);
|
|
1300
|
+
}
|
|
1301
|
+
return url.toString();
|
|
1302
|
+
}
|
|
1303
|
+
async exchangeAuthorizationCode(code, codeVerifier, redirectUri) {
|
|
1304
|
+
if (!codeVerifier) {
|
|
1305
|
+
throw new Error('Authorization code verifier is missing');
|
|
1306
|
+
}
|
|
1307
|
+
const response = await this.fetchImpl(this.integration.protocol.token.endpoint, this.providerRequest({
|
|
1308
|
+
method: 'POST',
|
|
1309
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, this.integration.protocol.token.headers),
|
|
1310
|
+
body: new URLSearchParams({
|
|
1311
|
+
grant_type: 'authorization_code',
|
|
1312
|
+
code,
|
|
1313
|
+
code_verifier: codeVerifier,
|
|
1314
|
+
client_id: this.clientId,
|
|
1315
|
+
redirect_uri: redirectUri,
|
|
1316
|
+
}),
|
|
1317
|
+
}));
|
|
1318
|
+
const body = await safeJson(response);
|
|
1319
|
+
if (!response.ok) {
|
|
1320
|
+
throw new Error(`Authorization code token exchange failed: ${safeProviderError(body)}`);
|
|
1321
|
+
}
|
|
1322
|
+
return body;
|
|
900
1323
|
}
|
|
901
|
-
async updateOAuthCredential(input, body, current) {
|
|
1324
|
+
async updateOAuthCredential(input, body, current, currentSecret) {
|
|
902
1325
|
const expiresAt = expiresAtFrom(body.expires_in, this.now());
|
|
1326
|
+
const nextIdToken = stringFrom(body.id_token) || stringFrom(currentSecret.idToken);
|
|
903
1327
|
const secret = {
|
|
904
1328
|
type: 'deviceCodeOAuth',
|
|
905
1329
|
accessToken: stringFrom(body.access_token),
|
|
906
|
-
refreshToken: stringFrom(body.refresh_token),
|
|
1330
|
+
refreshToken: stringFrom(body.refresh_token) || stringFrom(currentSecret.refreshToken),
|
|
907
1331
|
expiresAt: expiresAt?.toISOString(),
|
|
908
1332
|
scope: stringFrom(body.scope),
|
|
909
|
-
idToken:
|
|
1333
|
+
idToken: nextIdToken,
|
|
1334
|
+
accountId: stringFrom(body.account_id)
|
|
1335
|
+
|| jwtClaim(nextIdToken, this.integration.protocol.accountIdClaim)
|
|
1336
|
+
|| stringFrom(currentSecret.accountId),
|
|
910
1337
|
};
|
|
911
1338
|
return this.oauthCredentials.updateOAuthCredential({
|
|
912
1339
|
current,
|
|
@@ -916,7 +1343,8 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
|
|
|
916
1343
|
expiresAt,
|
|
917
1344
|
auth: input.auth,
|
|
918
1345
|
metadata: {
|
|
919
|
-
authoritativeSubject: decodeJwtSubject(
|
|
1346
|
+
authoritativeSubject: decodeJwtSubject(nextIdToken),
|
|
1347
|
+
authorizationMethodId: this.authorizationMethodId,
|
|
920
1348
|
},
|
|
921
1349
|
});
|
|
922
1350
|
}
|
|
@@ -925,7 +1353,7 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
|
|
|
925
1353
|
? [
|
|
926
1354
|
await this.credentialRepository.getCredentialById({
|
|
927
1355
|
webId: input.webId,
|
|
928
|
-
provider:
|
|
1356
|
+
provider: this.provider,
|
|
929
1357
|
deployment: input.deployment,
|
|
930
1358
|
credentialId: input.credentialId,
|
|
931
1359
|
auth: input.auth,
|
|
@@ -933,14 +1361,74 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
|
|
|
933
1361
|
]
|
|
934
1362
|
: await this.credentialRepository.listProviderCredentials({
|
|
935
1363
|
webId: input.webId,
|
|
936
|
-
provider:
|
|
1364
|
+
provider: this.provider,
|
|
937
1365
|
deployment: input.deployment,
|
|
938
1366
|
auth: input.auth,
|
|
939
1367
|
});
|
|
940
|
-
return credentials.find((credential) => credential &&
|
|
1368
|
+
return credentials.find((credential) => credential && this.isProviderOAuthCredential(credential));
|
|
1369
|
+
}
|
|
1370
|
+
providerRequest(init) {
|
|
1371
|
+
return {
|
|
1372
|
+
...init,
|
|
1373
|
+
redirect: 'error',
|
|
1374
|
+
signal: requestTimeoutSignal(this.requestTimeoutMs),
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
assertOfferingInput(offeringId) {
|
|
1378
|
+
if (offeringId && normalizeProvider(offeringId) !== normalizeProvider(this.offeringId)) {
|
|
1379
|
+
throw new Error('Connect offering mismatch');
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
assertAuthorizationMethodInput(authorizationMethodId) {
|
|
1383
|
+
if (authorizationMethodId && normalizeProvider(authorizationMethodId) !== this.authorizationMethodId) {
|
|
1384
|
+
throw new Error('Connect authorization method mismatch');
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
assertAttemptOffering(attempt) {
|
|
1388
|
+
if (attempt.offeringId !== this.offeringId) {
|
|
1389
|
+
throw new Error('Connect attempt is bound to a different offering');
|
|
1390
|
+
}
|
|
1391
|
+
if (attempt.authorizationMethodId !== this.authorizationMethodId) {
|
|
1392
|
+
throw new Error('Connect attempt authorization method mismatch');
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
assertStoredOffering(current) {
|
|
1396
|
+
const metadata = metadataFromRowValue(current.metadata) ?? {};
|
|
1397
|
+
if ((current.offeringId ?? stringMetadata(metadata, 'offeringId')) !== this.offeringId) {
|
|
1398
|
+
throw new Error('oauth_credential_not_found');
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
isProviderOAuthCredential(credential) {
|
|
1402
|
+
if (!credential)
|
|
1403
|
+
return false;
|
|
1404
|
+
const metadata = metadataFromRowValue(credential.metadata) ?? {};
|
|
1405
|
+
return credential.provider === this.provider
|
|
1406
|
+
&& isOAuthProviderCredential(credential)
|
|
1407
|
+
&& (credential.offeringId ?? stringMetadata(metadata, 'offeringId')) === this.offeringId;
|
|
1408
|
+
}
|
|
1409
|
+
async loadAuthorizationAttempt(input, consumable) {
|
|
1410
|
+
try {
|
|
1411
|
+
return consumable
|
|
1412
|
+
? await this.loadConsumableAttempt(input, 'authorizationCodeOAuth')
|
|
1413
|
+
: await this.loadAttemptForStatus(input, 'authorizationCodeOAuth');
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
if (isTerminalAttemptError(error)) {
|
|
1417
|
+
this.closeCallback(input.attemptId);
|
|
1418
|
+
}
|
|
1419
|
+
throw error;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
closeCallback(attemptId) {
|
|
1423
|
+
const context = this.callbacks.get(attemptId);
|
|
1424
|
+
if (!context)
|
|
1425
|
+
return;
|
|
1426
|
+
clearTimeout(context.expiresTimer);
|
|
1427
|
+
context.close();
|
|
1428
|
+
this.callbacks.delete(attemptId);
|
|
941
1429
|
}
|
|
942
1430
|
}
|
|
943
|
-
exports.
|
|
1431
|
+
exports.AuthorizationCodeConnectAdapter = AuthorizationCodeConnectAdapter;
|
|
944
1432
|
class DeepSeekConnectAdapter {
|
|
945
1433
|
constructor() {
|
|
946
1434
|
this.provider = 'deepseek';
|
|
@@ -968,7 +1456,7 @@ class ProviderConnectService {
|
|
|
968
1456
|
this.credentialRepository = options.credentialRepository;
|
|
969
1457
|
this.vault = options.vault;
|
|
970
1458
|
for (const adapter of options.adapters) {
|
|
971
|
-
this.adapters.set(
|
|
1459
|
+
this.adapters.set(connectAdapterKey(adapter.provider, adapter.offeringId, adapter.mode), adapter);
|
|
972
1460
|
}
|
|
973
1461
|
for (const importer of options.localSessionImporters ?? []) {
|
|
974
1462
|
this.localSessionImporters.set(localSessionImporterKey(importer.provider, importer.offeringId), importer);
|
|
@@ -976,7 +1464,8 @@ class ProviderConnectService {
|
|
|
976
1464
|
}
|
|
977
1465
|
begin(input) {
|
|
978
1466
|
const descriptor = this.registry.requireProvider(input.provider);
|
|
979
|
-
|
|
1467
|
+
const offeringId = this.requireConnectOffering(input.provider, input.offeringId, input.requestedMode);
|
|
1468
|
+
if (descriptor.connect?.mode !== input.requestedMode && !this.offeringSupportsConnectMode(input.provider, offeringId, input.requestedMode)) {
|
|
980
1469
|
throw new Error('Requested Connect mode does not match provider capability');
|
|
981
1470
|
}
|
|
982
1471
|
if (descriptor.connect?.configured === false) {
|
|
@@ -987,12 +1476,36 @@ class ProviderConnectService {
|
|
|
987
1476
|
mode: descriptor.connect.mode,
|
|
988
1477
|
status: 'unsupported',
|
|
989
1478
|
provider: normalizeProvider(input.provider),
|
|
1479
|
+
offeringId,
|
|
990
1480
|
deployment: input.deployment,
|
|
991
1481
|
apiKeyManagementSupported: descriptor.connect.apiKeyManagementSupported,
|
|
992
1482
|
message,
|
|
993
1483
|
});
|
|
994
1484
|
}
|
|
995
|
-
return this.requireAdapter(input.provider).begin(
|
|
1485
|
+
return this.requireAdapter(input.provider, offeringId, input.requestedMode).begin({
|
|
1486
|
+
...input,
|
|
1487
|
+
offeringId,
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
getAuthorizationMethods() {
|
|
1491
|
+
return this.registry.listProducts().flatMap((product) => product.offerings.map((offering) => {
|
|
1492
|
+
const authorizationMethods = (offering.authorizationMethods ?? []).map((method) => {
|
|
1493
|
+
const unavailableReason = this.authorizationMethodUnavailableReason(product.id, offering.id, method);
|
|
1494
|
+
return metadataWithoutUndefined({
|
|
1495
|
+
...method,
|
|
1496
|
+
lifecycle: unavailableReason ? 'unavailable' : method.lifecycle,
|
|
1497
|
+
reason: unavailableReason ?? method.reason,
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
return {
|
|
1501
|
+
provider: product.id,
|
|
1502
|
+
offeringId: offering.id,
|
|
1503
|
+
authorizationMethods,
|
|
1504
|
+
...(product.id === 'custom' ? {} : {
|
|
1505
|
+
endpoints: offering.endpoints.map((endpoint) => ({ ...endpoint })),
|
|
1506
|
+
}),
|
|
1507
|
+
};
|
|
1508
|
+
}));
|
|
996
1509
|
}
|
|
997
1510
|
async listProviders(input) {
|
|
998
1511
|
return Promise.all(this.registry.listProviders().map(async (descriptor) => {
|
|
@@ -1100,11 +1613,30 @@ class ProviderConnectService {
|
|
|
1100
1613
|
return publicPoolCredentialSummary(created);
|
|
1101
1614
|
}
|
|
1102
1615
|
async createLocalCredential(input) {
|
|
1616
|
+
if (!this.credentialRepository || !this.vault)
|
|
1617
|
+
throw new Error('credential_pool_not_configured');
|
|
1618
|
+
const key = JSON.stringify([input.webId, input.deployment, normalizeProvider(input.provider),
|
|
1619
|
+
this.requireLocalOffering(normalizeProvider(input.provider), input.offeringId)]);
|
|
1620
|
+
const previous = ProviderConnectService.localImportLocks.get(key) ?? Promise.resolve();
|
|
1621
|
+
let release;
|
|
1622
|
+
const pending = new Promise((resolve) => { release = resolve; });
|
|
1623
|
+
ProviderConnectService.localImportLocks.set(key, pending);
|
|
1624
|
+
await previous;
|
|
1625
|
+
try {
|
|
1626
|
+
return await this.importLocalCredential(input);
|
|
1627
|
+
}
|
|
1628
|
+
finally {
|
|
1629
|
+
release();
|
|
1630
|
+
if (ProviderConnectService.localImportLocks.get(key) === pending) {
|
|
1631
|
+
ProviderConnectService.localImportLocks.delete(key);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
async importLocalCredential(input) {
|
|
1103
1636
|
if (!this.credentialRepository || !this.vault)
|
|
1104
1637
|
throw new Error('credential_pool_not_configured');
|
|
1105
1638
|
const provider = normalizeProvider(input.provider);
|
|
1106
1639
|
const offeringId = this.requireLocalOffering(provider, input.offeringId);
|
|
1107
|
-
const { credentialId, credentialIri } = createPoolCredentialLocator(input.webId, input.deployment, provider);
|
|
1108
1640
|
const importer = this.localSessionImporters.get(localSessionImporterKey(provider, offeringId));
|
|
1109
1641
|
const offering = this.registry.getOffering(provider, offeringId);
|
|
1110
1642
|
if (offering?.kind === 'oauth-subscription' && !importer) {
|
|
@@ -1115,7 +1647,79 @@ class ProviderConnectService {
|
|
|
1115
1647
|
});
|
|
1116
1648
|
}
|
|
1117
1649
|
const imported = await importer?.importSession({ deployment: input.deployment });
|
|
1118
|
-
|
|
1650
|
+
if (imported) {
|
|
1651
|
+
const candidates = (await this.credentialRepository.listProviderCredentials({ ...input, provider }))
|
|
1652
|
+
.sort((left, right) => (right.expiresAt?.getTime() ?? 0) - (left.expiresAt?.getTime() ?? 0)
|
|
1653
|
+
|| (right.version ?? 0) - (left.version ?? 0));
|
|
1654
|
+
let refreshFailure;
|
|
1655
|
+
for (const existing of candidates) {
|
|
1656
|
+
if (existing.status !== 'active' || existing.offeringId !== offeringId)
|
|
1657
|
+
continue;
|
|
1658
|
+
let secret;
|
|
1659
|
+
try {
|
|
1660
|
+
secret = await this.vault.open({ webId: input.webId }, existing.credentialIri, provider, existing.encryptedSecret);
|
|
1661
|
+
}
|
|
1662
|
+
catch {
|
|
1663
|
+
// An unreadable credential cannot establish session identity.
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
if (!sameImportedSession(provider, secret, imported.secret))
|
|
1667
|
+
continue;
|
|
1668
|
+
const importedExpired = (dateFrom(imported.secret.expiresAt)?.getTime() ?? Infinity) <= Date.now();
|
|
1669
|
+
const existingExpiresAt = dateFrom(secret.expiresAt)?.getTime() ?? existing.expiresAt?.getTime() ?? 0;
|
|
1670
|
+
const existingIsNewer = existingExpiresAt > (dateFrom(imported.secret.expiresAt)?.getTime() ?? 0);
|
|
1671
|
+
const isPreviousImport = typeof secret.importedSessionFingerprint === 'string'
|
|
1672
|
+
&& secret.importedSessionFingerprint === importedSessionFingerprint(imported.secret);
|
|
1673
|
+
let nextSecret;
|
|
1674
|
+
try {
|
|
1675
|
+
nextSecret = await this.refreshImportedSecret(importedExpired && (existingIsNewer || isPreviousImport) ? secret : imported.secret, { ...input, provider, offeringId, credentialId: existing.id, expectedVersion: existing.version ?? 0 });
|
|
1676
|
+
}
|
|
1677
|
+
catch (error) {
|
|
1678
|
+
if (!(error instanceof Error) || error.message !== 'local_session_reauth_required')
|
|
1679
|
+
throw error;
|
|
1680
|
+
refreshFailure = error;
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
nextSecret.importedSessionFingerprint = importedSessionFingerprint(imported.secret);
|
|
1684
|
+
const health = existing.enabled === false ? 'disabled' : 'healthy';
|
|
1685
|
+
const updated = await this.credentialRepository.updateCredential({
|
|
1686
|
+
...input,
|
|
1687
|
+
provider,
|
|
1688
|
+
credentialId: existing.id,
|
|
1689
|
+
expectedVersion: existing.version,
|
|
1690
|
+
patch: {
|
|
1691
|
+
encryptedSecret: await this.vault.seal({ webId: input.webId }, existing.credentialIri, provider, nextSecret),
|
|
1692
|
+
authMode: imported.credentialAuthMode ?? 'local',
|
|
1693
|
+
expiresAt: dateFrom(nextSecret.expiresAt),
|
|
1694
|
+
scopes: scopeListFromSecret(nextSecret),
|
|
1695
|
+
reauthRequired: false,
|
|
1696
|
+
health,
|
|
1697
|
+
metadata: metadataWithoutUndefined({
|
|
1698
|
+
...imported.metadata,
|
|
1699
|
+
...existing.metadata,
|
|
1700
|
+
offeringId,
|
|
1701
|
+
accountId: nextSecret.accountId,
|
|
1702
|
+
authoritativeSubject: nextSecret.accountSubject,
|
|
1703
|
+
health,
|
|
1704
|
+
}),
|
|
1705
|
+
},
|
|
1706
|
+
});
|
|
1707
|
+
if (!updated)
|
|
1708
|
+
throw new Error('credential_version_conflict');
|
|
1709
|
+
return publicPoolCredentialSummary(updated);
|
|
1710
|
+
}
|
|
1711
|
+
if (refreshFailure)
|
|
1712
|
+
throw refreshFailure;
|
|
1713
|
+
}
|
|
1714
|
+
const { credentialId, credentialIri } = createPoolCredentialLocator(input.webId, input.deployment, provider);
|
|
1715
|
+
const nextSecret = imported
|
|
1716
|
+
? await this.refreshImportedSecret(imported.secret, {
|
|
1717
|
+
...input, provider, offeringId, credentialId, expectedVersion: 0,
|
|
1718
|
+
})
|
|
1719
|
+
: { type: 'local' };
|
|
1720
|
+
if (imported)
|
|
1721
|
+
nextSecret.importedSessionFingerprint = importedSessionFingerprint(imported.secret);
|
|
1722
|
+
const encryptedSecret = await this.vault.seal({ webId: input.webId }, credentialIri, provider, nextSecret);
|
|
1119
1723
|
const created = await this.credentialRepository.createCredential({
|
|
1120
1724
|
id: credentialId,
|
|
1121
1725
|
credentialIri,
|
|
@@ -1130,8 +1734,12 @@ class ProviderConnectService {
|
|
|
1130
1734
|
priority: input.priority ?? 100,
|
|
1131
1735
|
enabled: true,
|
|
1132
1736
|
health: 'healthy',
|
|
1737
|
+
expiresAt: dateFrom(nextSecret.expiresAt),
|
|
1738
|
+
scopes: scopeListFromSecret(nextSecret),
|
|
1133
1739
|
metadata: metadataWithoutUndefined({
|
|
1134
1740
|
...imported?.metadata,
|
|
1741
|
+
accountId: nextSecret.accountId,
|
|
1742
|
+
authoritativeSubject: nextSecret.accountSubject,
|
|
1135
1743
|
offeringId,
|
|
1136
1744
|
priority: input.priority ?? 100,
|
|
1137
1745
|
enabled: true,
|
|
@@ -1141,6 +1749,31 @@ class ProviderConnectService {
|
|
|
1141
1749
|
}, { auth: input.auth });
|
|
1142
1750
|
return publicPoolCredentialSummary(created);
|
|
1143
1751
|
}
|
|
1752
|
+
async refreshImportedSecret(secret, input) {
|
|
1753
|
+
if ((dateFrom(secret.expiresAt)?.getTime() ?? Infinity) > Date.now()) {
|
|
1754
|
+
return { ...secret, ...metadataWithoutUndefined({ ...kimiAccountIdentityHint(input.provider, secret.accessToken) }) };
|
|
1755
|
+
}
|
|
1756
|
+
if (typeof secret.refreshToken !== 'string' || !secret.refreshToken) {
|
|
1757
|
+
throw new Error('local_session_missing_refresh_token');
|
|
1758
|
+
}
|
|
1759
|
+
let refreshed;
|
|
1760
|
+
try {
|
|
1761
|
+
refreshed = await this.refreshCallerOwned({ ...input, refreshToken: secret.refreshToken });
|
|
1762
|
+
}
|
|
1763
|
+
catch (error) {
|
|
1764
|
+
const message = error instanceof Error ? error.message : '';
|
|
1765
|
+
throw new Error(/^OAuth refresh failed: (?:invalid_grant|invalid_token)$/u.test(message)
|
|
1766
|
+
? 'local_session_reauth_required' : 'local_session_refresh_failed');
|
|
1767
|
+
}
|
|
1768
|
+
if (refreshed.status !== 'completed' || !refreshed.oauthCredential?.accessToken) {
|
|
1769
|
+
throw new Error('local_session_refresh_failed');
|
|
1770
|
+
}
|
|
1771
|
+
const result = { ...secret, ...metadataWithoutUndefined({ ...refreshed.oauthCredential }) };
|
|
1772
|
+
if ((dateFrom(result.expiresAt)?.getTime() ?? Infinity) <= Date.now()) {
|
|
1773
|
+
throw new Error('local_session_refresh_failed');
|
|
1774
|
+
}
|
|
1775
|
+
return { ...result, ...metadataWithoutUndefined({ ...kimiAccountIdentityHint(input.provider, result.accessToken) }) };
|
|
1776
|
+
}
|
|
1144
1777
|
async updateCredential(input) {
|
|
1145
1778
|
if (!this.credentialRepository) {
|
|
1146
1779
|
throw new Error('credential_pool_not_configured');
|
|
@@ -1252,21 +1885,24 @@ class ProviderConnectService {
|
|
|
1252
1885
|
return offering.id;
|
|
1253
1886
|
}
|
|
1254
1887
|
completeApiKey(input) {
|
|
1255
|
-
const adapter = this.requireAdapter(input.provider);
|
|
1888
|
+
const adapter = this.requireAdapter(input.provider, input.offeringId, 'browserAssistedApiKey');
|
|
1256
1889
|
if (!adapter.completeApiKey) {
|
|
1257
1890
|
throw new Error('Provider does not support API key Connect completion');
|
|
1258
1891
|
}
|
|
1259
1892
|
return adapter.completeApiKey(input);
|
|
1260
1893
|
}
|
|
1261
|
-
pollDevice(input) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1894
|
+
async pollDevice(input) {
|
|
1895
|
+
if (input.mode) {
|
|
1896
|
+
const adapter = this.requireAdapter(input.provider, input.offeringId, input.mode);
|
|
1897
|
+
if (!adapter.pollDevice) {
|
|
1898
|
+
throw new Error('Provider does not support OAuth polling');
|
|
1899
|
+
}
|
|
1900
|
+
return adapter.pollDevice(input);
|
|
1265
1901
|
}
|
|
1266
|
-
return
|
|
1902
|
+
return this.runAttemptOperation(input, 'pollDevice', 'Provider does not support OAuth polling');
|
|
1267
1903
|
}
|
|
1268
|
-
status(input) {
|
|
1269
|
-
const adapter = this.
|
|
1904
|
+
async status(input) {
|
|
1905
|
+
const adapter = await this.requireAdapterForAttemptStatus(input);
|
|
1270
1906
|
if (!adapter.status) {
|
|
1271
1907
|
throw new Error('Provider does not support Connect status');
|
|
1272
1908
|
}
|
|
@@ -1276,17 +1912,13 @@ class ProviderConnectService {
|
|
|
1276
1912
|
return this.refreshWithRetry(input, 2);
|
|
1277
1913
|
}
|
|
1278
1914
|
refreshCallerOwned(input) {
|
|
1279
|
-
const adapter = this.
|
|
1915
|
+
const adapter = this.requireOAuthRefreshAdapter(input, 'refreshCallerOwned');
|
|
1280
1916
|
if (!adapter.refreshCallerOwned) {
|
|
1281
1917
|
throw new Error('Provider does not support caller-owned OAuth refresh');
|
|
1282
1918
|
}
|
|
1283
1919
|
return adapter.refreshCallerOwned(input);
|
|
1284
1920
|
}
|
|
1285
1921
|
async refreshWithRetry(input, remainingAttempts) {
|
|
1286
|
-
const adapter = this.requireAdapter(input.provider);
|
|
1287
|
-
if (!adapter.refresh) {
|
|
1288
|
-
throw new Error('Provider does not support refresh');
|
|
1289
|
-
}
|
|
1290
1922
|
if (!this.credentialRepository || !this.vault) {
|
|
1291
1923
|
throw new Error('CredentialVault and PodCredentialRepository are required for provider refresh');
|
|
1292
1924
|
}
|
|
@@ -1294,6 +1926,10 @@ class ProviderConnectService {
|
|
|
1294
1926
|
if (!current) {
|
|
1295
1927
|
throw new Error('oauth_credential_not_found');
|
|
1296
1928
|
}
|
|
1929
|
+
const adapter = this.requireOAuthRefreshAdapter(input, 'refresh', current);
|
|
1930
|
+
if (!adapter.refresh) {
|
|
1931
|
+
throw new Error('Provider does not support refresh');
|
|
1932
|
+
}
|
|
1297
1933
|
const secret = await this.vault.open({ webId: input.webId }, current.credentialIri, normalizeProvider(input.provider), current.encryptedSecret);
|
|
1298
1934
|
try {
|
|
1299
1935
|
return await adapter.refresh(input, current, secret);
|
|
@@ -1309,12 +1945,64 @@ class ProviderConnectService {
|
|
|
1309
1945
|
throw error;
|
|
1310
1946
|
}
|
|
1311
1947
|
}
|
|
1312
|
-
disconnect(input) {
|
|
1313
|
-
const adapter = this.
|
|
1948
|
+
async disconnect(input) {
|
|
1949
|
+
const adapter = await this.requireAdapterForDisconnect(input);
|
|
1314
1950
|
if (!adapter.disconnect) {
|
|
1315
1951
|
throw new Error('Provider does not support disconnect');
|
|
1316
1952
|
}
|
|
1317
|
-
return adapter.disconnect(
|
|
1953
|
+
return adapter.disconnect({
|
|
1954
|
+
...input,
|
|
1955
|
+
offeringId: input.offeringId ?? adapter.offeringId,
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
async cancel(input) {
|
|
1959
|
+
if (input.mode) {
|
|
1960
|
+
const adapter = this.requireAdapter(input.provider, input.offeringId, input.mode);
|
|
1961
|
+
if (!adapter.cancel) {
|
|
1962
|
+
throw new Error('Provider does not support Connect cancellation');
|
|
1963
|
+
}
|
|
1964
|
+
return adapter.cancel(input);
|
|
1965
|
+
}
|
|
1966
|
+
return this.runAttemptOperation(input, 'cancel', 'Provider does not support Connect cancellation');
|
|
1967
|
+
}
|
|
1968
|
+
async runAttemptOperation(input, operation, unsupportedMessage) {
|
|
1969
|
+
const candidates = this.findAdapterCandidates(input.provider, input.offeringId).filter((adapter) => adapter[operation]);
|
|
1970
|
+
let deferredError;
|
|
1971
|
+
for (const adapter of candidates) {
|
|
1972
|
+
try {
|
|
1973
|
+
return await adapter[operation](input);
|
|
1974
|
+
}
|
|
1975
|
+
catch (error) {
|
|
1976
|
+
if (!isAttemptBindingError(error)) {
|
|
1977
|
+
throw error;
|
|
1978
|
+
}
|
|
1979
|
+
deferredError ??= error;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (deferredError)
|
|
1983
|
+
throw deferredError;
|
|
1984
|
+
throw new Error(unsupportedMessage);
|
|
1985
|
+
}
|
|
1986
|
+
requireOAuthRefreshAdapter(input, operation, current) {
|
|
1987
|
+
if (input.mode) {
|
|
1988
|
+
return this.requireAdapter(input.provider, input.offeringId, input.mode);
|
|
1989
|
+
}
|
|
1990
|
+
const authorizationMethodId = input.authorizationMethodId ?? credentialAuthorizationMethodId(current);
|
|
1991
|
+
if (authorizationMethodId) {
|
|
1992
|
+
const adapter = this.findAdapterCandidates(input.provider, input.offeringId)
|
|
1993
|
+
.find((candidate) => candidate[operation] && candidate.authorizationMethodId === authorizationMethodId);
|
|
1994
|
+
if (adapter)
|
|
1995
|
+
return adapter;
|
|
1996
|
+
}
|
|
1997
|
+
if (operation === 'refreshCallerOwned' || current?.authMode === 'deviceCodeOAuth') {
|
|
1998
|
+
return this.requireAdapter(input.provider, input.offeringId ?? credentialOfferingId(current), 'deviceCodeOAuth');
|
|
1999
|
+
}
|
|
2000
|
+
const candidates = this.findAdapterCandidates(input.provider, input.offeringId)
|
|
2001
|
+
.filter((adapter) => adapter[operation] && (adapter.mode === 'deviceCodeOAuth' || adapter.mode === 'authorizationCodeOAuth'));
|
|
2002
|
+
if (candidates.length === 1) {
|
|
2003
|
+
return candidates[0];
|
|
2004
|
+
}
|
|
2005
|
+
return this.requireAdapter(input.provider, input.offeringId, 'deviceCodeOAuth');
|
|
1318
2006
|
}
|
|
1319
2007
|
async getRefreshCredential(input) {
|
|
1320
2008
|
if (!this.credentialRepository) {
|
|
@@ -1326,18 +2014,109 @@ class ProviderConnectService {
|
|
|
1326
2014
|
credentialId: input.credentialId,
|
|
1327
2015
|
})
|
|
1328
2016
|
: (await this.credentialRepository.listProviderCredentials(input))
|
|
1329
|
-
.find(isOAuthProviderCredential);
|
|
1330
|
-
return credential && isOAuthProviderCredential(credential) ? credential : undefined;
|
|
2017
|
+
.find((record) => isOAuthProviderCredential(record, input.offeringId));
|
|
2018
|
+
return credential && isOAuthProviderCredential(credential, input.offeringId) ? credential : undefined;
|
|
1331
2019
|
}
|
|
1332
|
-
requireAdapter(provider) {
|
|
1333
|
-
const adapter = this.
|
|
2020
|
+
requireAdapter(provider, offeringId, mode) {
|
|
2021
|
+
const adapter = this.findAdapter(provider, offeringId, mode);
|
|
1334
2022
|
if (!adapter) {
|
|
1335
2023
|
throw new Error(`No Connect adapter registered for ${provider}`);
|
|
1336
2024
|
}
|
|
1337
2025
|
return adapter;
|
|
1338
2026
|
}
|
|
2027
|
+
async requireAdapterForAttemptStatus(input) {
|
|
2028
|
+
const candidates = this.findAdapterCandidates(input.provider, input.offeringId).filter((adapter) => adapter.status);
|
|
2029
|
+
let deferredError;
|
|
2030
|
+
for (const adapter of candidates) {
|
|
2031
|
+
try {
|
|
2032
|
+
await adapter.status(input);
|
|
2033
|
+
return adapter;
|
|
2034
|
+
}
|
|
2035
|
+
catch (error) {
|
|
2036
|
+
if (!isAttemptBindingError(error)) {
|
|
2037
|
+
throw error;
|
|
2038
|
+
}
|
|
2039
|
+
deferredError ??= error;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
if (deferredError)
|
|
2043
|
+
throw deferredError;
|
|
2044
|
+
throw new Error(`No Connect adapter registered for ${input.provider}`);
|
|
2045
|
+
}
|
|
2046
|
+
async requireAdapterForDisconnect(input) {
|
|
2047
|
+
if (input.credentialId && this.credentialRepository) {
|
|
2048
|
+
const credential = await this.credentialRepository.getCredentialById({
|
|
2049
|
+
webId: input.webId,
|
|
2050
|
+
provider: normalizeProvider(input.provider),
|
|
2051
|
+
deployment: input.deployment,
|
|
2052
|
+
credentialId: input.credentialId,
|
|
2053
|
+
auth: input.auth,
|
|
2054
|
+
});
|
|
2055
|
+
if (credential) {
|
|
2056
|
+
const mode = credential.authMode === 'apiKey'
|
|
2057
|
+
? 'browserAssistedApiKey'
|
|
2058
|
+
: credential.authMode === 'deviceCodeOAuth'
|
|
2059
|
+
? 'deviceCodeOAuth'
|
|
2060
|
+
: undefined;
|
|
2061
|
+
return this.requireAdapter(input.provider, input.offeringId ?? credentialOfferingId(credential), mode);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return this.requireAdapter(input.provider, input.offeringId);
|
|
2065
|
+
}
|
|
2066
|
+
findAdapter(provider, offeringId, mode) {
|
|
2067
|
+
const exact = this.adapters.get(connectAdapterKey(provider, offeringId, mode));
|
|
2068
|
+
if (exact)
|
|
2069
|
+
return exact;
|
|
2070
|
+
const candidates = this.findAdapterCandidates(provider, offeringId, mode);
|
|
2071
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
2072
|
+
}
|
|
2073
|
+
findAdapterCandidates(provider, offeringId, mode) {
|
|
2074
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
2075
|
+
const normalizedOffering = offeringId ? normalizeProvider(offeringId) : undefined;
|
|
2076
|
+
return [...this.adapters.values()].filter((adapter) => {
|
|
2077
|
+
if (normalizeProvider(adapter.provider) !== normalizedProvider)
|
|
2078
|
+
return false;
|
|
2079
|
+
if (mode && adapter.mode && adapter.mode !== mode)
|
|
2080
|
+
return false;
|
|
2081
|
+
if ((mode === 'deviceCodeOAuth' || mode === 'authorizationCodeOAuth') && !adapter.mode)
|
|
2082
|
+
return false;
|
|
2083
|
+
if (!normalizedOffering)
|
|
2084
|
+
return true;
|
|
2085
|
+
if (normalizeProvider(adapter.offeringId ?? '') === normalizedOffering)
|
|
2086
|
+
return true;
|
|
2087
|
+
return !adapter.offeringId
|
|
2088
|
+
&& (!adapter.mode || adapter.mode === 'browserAssistedApiKey')
|
|
2089
|
+
&& (mode === undefined || mode === 'browserAssistedApiKey');
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
requireConnectOffering(provider, offeringId, mode) {
|
|
2093
|
+
if (offeringId)
|
|
2094
|
+
return offeringId;
|
|
2095
|
+
const product = this.registry.getProduct(provider);
|
|
2096
|
+
const candidates = product?.offerings.filter((offering) => this.offeringSupportsConnectMode(provider, offering.id, mode)) ?? [];
|
|
2097
|
+
return candidates.length === 1 ? candidates[0].id : undefined;
|
|
2098
|
+
}
|
|
2099
|
+
offeringSupportsConnectMode(provider, offeringId, mode) {
|
|
2100
|
+
if (!offeringId)
|
|
2101
|
+
return false;
|
|
2102
|
+
const offering = this.registry.getOffering(provider, offeringId);
|
|
2103
|
+
return Boolean(offering?.authorizationMethods?.some((method) => method.connectMode === mode && method.lifecycle === 'active'));
|
|
2104
|
+
}
|
|
2105
|
+
authorizationMethodUnavailableReason(provider, offeringId, method) {
|
|
2106
|
+
if (method.lifecycle === 'unavailable') {
|
|
2107
|
+
return method.reason ?? 'authorization_method_unavailable';
|
|
2108
|
+
}
|
|
2109
|
+
if (method.connectMode && !this.findAdapter(provider, offeringId, method.connectMode)) {
|
|
2110
|
+
return 'authorization_adapter_unavailable';
|
|
2111
|
+
}
|
|
2112
|
+
if (method.id === 'local-session-import' && !this.localSessionImporters.has(localSessionImporterKey(provider, offeringId))) {
|
|
2113
|
+
return 'local_session_importer_unavailable';
|
|
2114
|
+
}
|
|
2115
|
+
return undefined;
|
|
2116
|
+
}
|
|
1339
2117
|
}
|
|
1340
2118
|
exports.ProviderConnectService = ProviderConnectService;
|
|
2119
|
+
ProviderConnectService.localImportLocks = new Map();
|
|
1341
2120
|
function token(randomBytes) {
|
|
1342
2121
|
return randomBytes(32).toString('base64url');
|
|
1343
2122
|
}
|
|
@@ -1348,6 +2127,7 @@ function publicOfferingSummary(offering) {
|
|
|
1348
2127
|
kind: offering.kind,
|
|
1349
2128
|
lifecycle: offering.lifecycle,
|
|
1350
2129
|
authModes: offering.authModes,
|
|
2130
|
+
authorizationMethods: offering.authorizationMethods,
|
|
1351
2131
|
runtimeProviderIds: offering.runtimeProviderIds,
|
|
1352
2132
|
productLabel: offering.productLabel,
|
|
1353
2133
|
credentialPrefixHints: offering.credentialPrefixHints,
|
|
@@ -1382,18 +2162,23 @@ function publicPoolCredentialSummary(record) {
|
|
|
1382
2162
|
function publicAuthMode(authMode) {
|
|
1383
2163
|
return authMode === 'deviceCodeOAuth' ? 'deviceCode' : authMode;
|
|
1384
2164
|
}
|
|
1385
|
-
function isOAuthProviderCredential(record) {
|
|
2165
|
+
function isOAuthProviderCredential(record, offeringId) {
|
|
1386
2166
|
return record.status === 'active'
|
|
1387
2167
|
&& record.authMode === 'deviceCodeOAuth'
|
|
1388
|
-
&& credentialOfferingId(record) ===
|
|
1389
|
-
}
|
|
1390
|
-
function isKimiOAuthCredential(record) {
|
|
1391
|
-
return normalizeProvider(record.provider) === 'kimi' && isOAuthProviderCredential(record);
|
|
2168
|
+
&& (!offeringId || credentialOfferingId(record) === offeringId);
|
|
1392
2169
|
}
|
|
1393
2170
|
function credentialOfferingId(record) {
|
|
2171
|
+
if (!record)
|
|
2172
|
+
return undefined;
|
|
1394
2173
|
const metadata = metadataFromRowValue(record.metadata) ?? {};
|
|
1395
2174
|
return record.offeringId ?? stringMetadata(metadata, 'offeringId');
|
|
1396
2175
|
}
|
|
2176
|
+
function credentialAuthorizationMethodId(record) {
|
|
2177
|
+
if (!record)
|
|
2178
|
+
return undefined;
|
|
2179
|
+
const metadata = metadataFromRowValue(record.metadata) ?? {};
|
|
2180
|
+
return stringMetadata(metadata, 'authorizationMethodId');
|
|
2181
|
+
}
|
|
1397
2182
|
function publicCredentialHealth(record) {
|
|
1398
2183
|
const metadata = metadataFromRowValue(record.metadata) ?? {};
|
|
1399
2184
|
const health = record.health ?? stringMetadata(metadata, 'health');
|
|
@@ -1470,6 +2255,54 @@ function modelIdsFromMetadata(metadata) {
|
|
|
1470
2255
|
}
|
|
1471
2256
|
return [...ids];
|
|
1472
2257
|
}
|
|
2258
|
+
function importedSessionFingerprint(secret) {
|
|
2259
|
+
if (typeof secret.accessToken !== 'string' || !secret.accessToken)
|
|
2260
|
+
return undefined;
|
|
2261
|
+
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify([
|
|
2262
|
+
secret.accountId, secret.accountSubject, secret.accessToken, secret.refreshToken,
|
|
2263
|
+
])).digest('hex');
|
|
2264
|
+
}
|
|
2265
|
+
function kimiAccountIdentityHint(provider, accessToken) {
|
|
2266
|
+
if (provider !== 'kimi' || typeof accessToken !== 'string' || jwtClaim(accessToken, 'iss') !== 'kimi-auth') {
|
|
2267
|
+
return undefined;
|
|
2268
|
+
}
|
|
2269
|
+
// These unverified claims are comparison hints within an already authorized owner's
|
|
2270
|
+
// credential pool. They must never authenticate a caller or grant Pod access.
|
|
2271
|
+
const accountId = jwtClaim(accessToken, 'user_id');
|
|
2272
|
+
const accountSubject = jwtClaim(accessToken, 'sub');
|
|
2273
|
+
return {
|
|
2274
|
+
accountId: accountId ? `kimi-auth:${accountId}` : undefined,
|
|
2275
|
+
accountSubject: accountSubject ? `kimi-auth:${accountSubject}` : undefined,
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function sameImportedSession(provider, existing, imported) {
|
|
2279
|
+
const identityFields = ['accountId', 'accountSubject'];
|
|
2280
|
+
const hasValue = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
2281
|
+
if (provider === 'kimi') {
|
|
2282
|
+
const existingIssuer = jwtClaim(stringFrom(existing.accessToken), 'iss');
|
|
2283
|
+
const importedIssuer = jwtClaim(stringFrom(imported.accessToken), 'iss');
|
|
2284
|
+
if (existingIssuer && importedIssuer && existingIssuer !== importedIssuer)
|
|
2285
|
+
return false;
|
|
2286
|
+
}
|
|
2287
|
+
// A shared token must not override an explicit account identity conflict.
|
|
2288
|
+
if (identityFields.some((field) => hasValue(existing[field]) && hasValue(imported[field])
|
|
2289
|
+
&& existing[field] !== imported[field]))
|
|
2290
|
+
return false;
|
|
2291
|
+
const existingIdentity = kimiAccountIdentityHint(provider, existing.accessToken);
|
|
2292
|
+
const importedIdentity = kimiAccountIdentityHint(provider, imported.accessToken);
|
|
2293
|
+
if (existingIdentity && importedIdentity) {
|
|
2294
|
+
if (identityFields.some((field) => hasValue(existingIdentity[field]) && hasValue(importedIdentity[field])
|
|
2295
|
+
&& existingIdentity[field] !== importedIdentity[field]))
|
|
2296
|
+
return false;
|
|
2297
|
+
if (identityFields.some((field) => hasValue(existingIdentity[field])
|
|
2298
|
+
&& existingIdentity[field] === importedIdentity[field]))
|
|
2299
|
+
return true;
|
|
2300
|
+
}
|
|
2301
|
+
if (existing.importedSessionFingerprint === importedSessionFingerprint(imported)
|
|
2302
|
+
&& typeof existing.importedSessionFingerprint === 'string')
|
|
2303
|
+
return true;
|
|
2304
|
+
return [...identityFields, 'accessToken', 'refreshToken'].some((field) => hasValue(existing[field]) && existing[field] === imported[field]);
|
|
2305
|
+
}
|
|
1473
2306
|
function createPoolCredentialLocator(webId, deployment, provider) {
|
|
1474
2307
|
const key = `${deployment}-${provider}-${(0, node_crypto_1.randomUUID)()}`;
|
|
1475
2308
|
return {
|
|
@@ -2075,6 +2908,8 @@ function signAttempt(attempt, secret) {
|
|
|
2075
2908
|
deployment: attempt.deployment,
|
|
2076
2909
|
webId: attempt.webId,
|
|
2077
2910
|
mode: attempt.mode,
|
|
2911
|
+
offeringId: attempt.offeringId,
|
|
2912
|
+
authorizationMethodId: attempt.authorizationMethodId,
|
|
2078
2913
|
state: attempt.state,
|
|
2079
2914
|
expiresAt: attempt.expiresAt.toISOString(),
|
|
2080
2915
|
}))
|
|
@@ -2219,7 +3054,14 @@ function stringFrom(value) {
|
|
|
2219
3054
|
return typeof value === 'string' ? value : '';
|
|
2220
3055
|
}
|
|
2221
3056
|
function numberFrom(value, fallback) {
|
|
2222
|
-
|
|
3057
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
3058
|
+
return value;
|
|
3059
|
+
}
|
|
3060
|
+
if (typeof value === 'string' && value.trim()) {
|
|
3061
|
+
const parsed = Number(value);
|
|
3062
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
3063
|
+
}
|
|
3064
|
+
return fallback;
|
|
2223
3065
|
}
|
|
2224
3066
|
function expiresAtFrom(expiresIn, now) {
|
|
2225
3067
|
if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn)) {
|
|
@@ -2227,16 +3069,26 @@ function expiresAtFrom(expiresIn, now) {
|
|
|
2227
3069
|
}
|
|
2228
3070
|
return new Date(now.getTime() + expiresIn * 1000);
|
|
2229
3071
|
}
|
|
2230
|
-
function oneTimeOAuthCredential(body, now, expectedVersion) {
|
|
3072
|
+
function oneTimeOAuthCredential(body, now, expectedVersion, integration, fallbackRefreshToken, authorizationMethodId) {
|
|
2231
3073
|
requireStringField(body, 'access_token');
|
|
2232
|
-
|
|
3074
|
+
const refreshToken = stringFrom(body.refresh_token) || fallbackRefreshToken;
|
|
3075
|
+
if (!refreshToken) {
|
|
3076
|
+
throw new Error('Provider response missing required field: refresh_token');
|
|
3077
|
+
}
|
|
3078
|
+
const identityHint = kimiAccountIdentityHint(integration?.provider, body.access_token);
|
|
2233
3079
|
return {
|
|
2234
3080
|
accessToken: stringFrom(body.access_token),
|
|
2235
|
-
refreshToken
|
|
3081
|
+
refreshToken,
|
|
2236
3082
|
expiresAt: expiresAtFrom(body.expires_in, now)?.toISOString(),
|
|
2237
3083
|
scope: stringFrom(body.scope),
|
|
2238
3084
|
idToken: stringFrom(body.id_token),
|
|
2239
|
-
accountSubject: decodeJwtSubject(stringFrom(body.id_token)),
|
|
3085
|
+
accountSubject: identityHint?.accountSubject ?? decodeJwtSubject(stringFrom(body.id_token)),
|
|
3086
|
+
accountLabel: integration?.accountLabel,
|
|
3087
|
+
accountId: integration?.accountId
|
|
3088
|
+
?? identityHint?.accountId
|
|
3089
|
+
?? (stringFrom(body.account_id) || jwtClaim(stringFrom(body.id_token), integration?.protocol.accountIdClaim)),
|
|
3090
|
+
offeringId: integration?.offeringId,
|
|
3091
|
+
authorizationMethodId,
|
|
2240
3092
|
expectedVersion,
|
|
2241
3093
|
};
|
|
2242
3094
|
}
|
|
@@ -2261,26 +3113,153 @@ const SAFE_PROVIDER_ERROR_CODES = new Set([
|
|
|
2261
3113
|
'invalid_grant',
|
|
2262
3114
|
'invalid_client',
|
|
2263
3115
|
]);
|
|
2264
|
-
function pendingResult(input, status, intervalSeconds) {
|
|
3116
|
+
function pendingResult(input, status, intervalSeconds, provider = normalizeProvider(input.provider), offeringId = input.offeringId) {
|
|
3117
|
+
return oauthPendingResult(input, status, provider, offeringId, 'deviceCodeOAuth', intervalSeconds);
|
|
3118
|
+
}
|
|
3119
|
+
function oauthPendingResult(input, status, provider, offeringId, mode, intervalSeconds) {
|
|
2265
3120
|
return {
|
|
2266
|
-
mode
|
|
3121
|
+
mode,
|
|
2267
3122
|
status,
|
|
2268
|
-
provider
|
|
3123
|
+
provider,
|
|
3124
|
+
offeringId,
|
|
2269
3125
|
deployment: input.deployment,
|
|
2270
3126
|
attemptId: input.attemptId,
|
|
2271
3127
|
intervalSeconds,
|
|
2272
3128
|
};
|
|
2273
3129
|
}
|
|
2274
|
-
function
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
3130
|
+
function connectAdapterKey(provider, offeringId, mode) {
|
|
3131
|
+
return `${normalizeProvider(provider)}:${normalizeProvider(offeringId ?? '')}:${mode ?? ''}`;
|
|
3132
|
+
}
|
|
3133
|
+
function isAttemptBindingError(error) {
|
|
3134
|
+
if (!(error instanceof Error))
|
|
3135
|
+
return false;
|
|
3136
|
+
return error.message === 'Connect attempt not found'
|
|
3137
|
+
|| error.message.startsWith('Connect attempt is bound to a different ')
|
|
3138
|
+
|| error.message === 'Connect attempt mode mismatch';
|
|
3139
|
+
}
|
|
3140
|
+
function isTerminalAttemptError(error) {
|
|
3141
|
+
if (!(error instanceof Error))
|
|
3142
|
+
return false;
|
|
3143
|
+
return error.message === 'Connect attempt not found'
|
|
3144
|
+
|| error.message === 'Connect attempt expired'
|
|
3145
|
+
|| error.message === 'Connect attempt already consumed';
|
|
3146
|
+
}
|
|
3147
|
+
function assertTrustedEndpoint(descriptor) {
|
|
3148
|
+
const url = new URL(descriptor.endpoint);
|
|
3149
|
+
if (url.protocol !== 'https:'
|
|
2278
3150
|
|| url.search
|
|
2279
3151
|
|| url.hash
|
|
2280
3152
|
|| url.username
|
|
2281
3153
|
|| url.password) {
|
|
2282
|
-
throw new Error('
|
|
3154
|
+
throw new Error('Device Connect endpoint is not allowlisted');
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
function deviceCodeBeginRequest(protocol, clientId, challenge) {
|
|
3158
|
+
if (protocol.begin.codec === 'oauthDeviceCodePkce' && !challenge) {
|
|
3159
|
+
throw new Error('Device authorization PKCE challenge is required');
|
|
3160
|
+
}
|
|
3161
|
+
if (protocol.begin.codec === 'oauthDeviceCode' || protocol.begin.codec === 'oauthDeviceCodePkce') {
|
|
3162
|
+
return {
|
|
3163
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, protocol.begin.headers),
|
|
3164
|
+
body: new URLSearchParams(metadataWithoutUndefined({
|
|
3165
|
+
client_id: clientId,
|
|
3166
|
+
code_challenge: protocol.begin.codec === 'oauthDeviceCodePkce' ? challenge : undefined,
|
|
3167
|
+
code_challenge_method: protocol.begin.codec === 'oauthDeviceCodePkce' ? 'S256' : undefined,
|
|
3168
|
+
})),
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
return {
|
|
3172
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/json' }, protocol.begin.headers),
|
|
3173
|
+
body: JSON.stringify({ client_id: clientId }),
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
function deviceCodePollRequest(protocol, clientId, attempt) {
|
|
3177
|
+
if (protocol.poll.codec === 'oauthDeviceCode' || protocol.poll.codec === 'oauthDeviceCodePkce') {
|
|
3178
|
+
return {
|
|
3179
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, protocol.poll.headers),
|
|
3180
|
+
body: new URLSearchParams(metadataWithoutUndefined({
|
|
3181
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
3182
|
+
device_code: attempt.deviceCode ?? '',
|
|
3183
|
+
client_id: clientId,
|
|
3184
|
+
code_verifier: protocol.poll.codec === 'oauthDeviceCodePkce' ? attempt.codeVerifier ?? '' : undefined,
|
|
3185
|
+
})),
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
3188
|
+
return {
|
|
3189
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/json' }, protocol.poll.headers),
|
|
3190
|
+
body: JSON.stringify(metadataWithoutUndefined({
|
|
3191
|
+
[deviceCodePollField(protocol)]: attempt.deviceCode ?? '',
|
|
3192
|
+
user_code: attempt.userCode,
|
|
3193
|
+
})),
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
function deviceCodePollField(protocol) {
|
|
3197
|
+
return protocol.poll.deviceCodeField
|
|
3198
|
+
?? protocol.begin.deviceCodeField
|
|
3199
|
+
?? (protocol.poll.codec === 'deviceCodeJson' ? 'device_auth_id' : 'device_code');
|
|
3200
|
+
}
|
|
3201
|
+
async function executeOAuthRefreshRequest(fetchImpl, request, refresh, clientId, refreshToken) {
|
|
3202
|
+
const response = await fetchImpl(refresh.endpoint, request({
|
|
3203
|
+
method: 'POST',
|
|
3204
|
+
headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, refresh.headers),
|
|
3205
|
+
body: new URLSearchParams(metadataWithoutUndefined({
|
|
3206
|
+
grant_type: 'refresh_token',
|
|
3207
|
+
refresh_token: refreshToken,
|
|
3208
|
+
client_id: clientId,
|
|
3209
|
+
})),
|
|
3210
|
+
}));
|
|
3211
|
+
return { ok: response.ok, body: await safeJson(response) };
|
|
3212
|
+
}
|
|
3213
|
+
function withProtocolHeaders(base, extra) {
|
|
3214
|
+
return { ...base, ...(extra ?? {}) };
|
|
3215
|
+
}
|
|
3216
|
+
function verifiedVerificationUri(body, protocol) {
|
|
3217
|
+
const verificationUri = stringFrom(body[protocol.begin.verificationUriField ?? 'verification_uri'])
|
|
3218
|
+
|| protocol.defaultVerificationUri;
|
|
3219
|
+
const verificationUriComplete = stringFrom(body[protocol.begin.verificationUriCompleteField ?? 'verification_uri_complete'])
|
|
3220
|
+
|| verificationUri;
|
|
3221
|
+
const origins = protocol.verificationUriOrigins ?? [];
|
|
3222
|
+
for (const candidate of [verificationUri, verificationUriComplete]) {
|
|
3223
|
+
if (!candidate || origins.length === 0)
|
|
3224
|
+
continue;
|
|
3225
|
+
const url = new URL(candidate);
|
|
3226
|
+
if (!origins.includes(url.origin)) {
|
|
3227
|
+
throw new Error('Provider verification URI is not allowlisted');
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return { verificationUri, verificationUriComplete };
|
|
3231
|
+
}
|
|
3232
|
+
function deviceCodeExpiresAt(body, protocol, now) {
|
|
3233
|
+
const expiresAtField = protocol.begin.expiresAtField;
|
|
3234
|
+
const expiresAt = expiresAtField ? absoluteExpiresAtFrom(body[expiresAtField]) : undefined;
|
|
3235
|
+
if (expiresAt)
|
|
3236
|
+
return expiresAt;
|
|
3237
|
+
const expiresIn = numberFrom(body[protocol.begin.expiresInField ?? 'expires_in'], protocol.begin.defaultExpiresInSeconds ?? 300);
|
|
3238
|
+
return new Date(now.getTime() + expiresIn * 1000);
|
|
3239
|
+
}
|
|
3240
|
+
function absoluteExpiresAtFrom(value) {
|
|
3241
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
3242
|
+
return new Date(value * 1000);
|
|
3243
|
+
}
|
|
3244
|
+
if (typeof value === 'string' && value.trim()) {
|
|
3245
|
+
const asNumber = Number(value);
|
|
3246
|
+
const parsed = Number.isFinite(asNumber) ? new Date(asNumber * 1000) : new Date(value);
|
|
3247
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
3248
|
+
}
|
|
3249
|
+
return undefined;
|
|
3250
|
+
}
|
|
3251
|
+
function requireProtocolStringField(body, field) {
|
|
3252
|
+
const fields = Array.isArray(field) ? field : [field];
|
|
3253
|
+
for (const candidate of fields) {
|
|
3254
|
+
const value = stringFrom(body[candidate]);
|
|
3255
|
+
if (value)
|
|
3256
|
+
return value;
|
|
2283
3257
|
}
|
|
3258
|
+
throw new Error(`Provider response missing required field: ${fields[0]}`);
|
|
3259
|
+
}
|
|
3260
|
+
function requestTimeoutSignal(timeoutMs) {
|
|
3261
|
+
const timeoutFactory = AbortSignal.timeout;
|
|
3262
|
+
return typeof timeoutFactory === 'function' ? timeoutFactory(timeoutMs) : undefined;
|
|
2284
3263
|
}
|
|
2285
3264
|
function requireStringField(body, field) {
|
|
2286
3265
|
if (typeof body[field] !== 'string' || !body[field].trim()) {
|
|
@@ -2303,6 +3282,34 @@ function decodeJwtSubject(idToken) {
|
|
|
2303
3282
|
return undefined;
|
|
2304
3283
|
}
|
|
2305
3284
|
}
|
|
3285
|
+
function jwtClaim(idToken, claim) {
|
|
3286
|
+
if (!claim)
|
|
3287
|
+
return undefined;
|
|
3288
|
+
const payload = idToken.split('.')[1];
|
|
3289
|
+
if (!payload)
|
|
3290
|
+
return undefined;
|
|
3291
|
+
try {
|
|
3292
|
+
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
3293
|
+
const path = Array.isArray(claim) ? claim : [claim];
|
|
3294
|
+
let value = decoded;
|
|
3295
|
+
for (const segment of path) {
|
|
3296
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
3297
|
+
return undefined;
|
|
3298
|
+
value = value[segment];
|
|
3299
|
+
}
|
|
3300
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
3301
|
+
}
|
|
3302
|
+
catch {
|
|
3303
|
+
return undefined;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function scopeListFromSecret(secret) {
|
|
3307
|
+
const scope = stringFrom(secret?.scope);
|
|
3308
|
+
if (!scope)
|
|
3309
|
+
return undefined;
|
|
3310
|
+
const scopes = scope.split(/\s+/u).map((value) => value.trim()).filter(Boolean);
|
|
3311
|
+
return scopes.length > 0 ? scopes : undefined;
|
|
3312
|
+
}
|
|
2306
3313
|
function defaultOfferingFor(provider, authMode) {
|
|
2307
3314
|
const normalized = normalizeProvider(provider);
|
|
2308
3315
|
const product = providerProductFor(normalized);
|
|
@@ -2352,16 +3359,26 @@ function offeringMatchesAuthMode(offeringAuthModes, authMode) {
|
|
|
2352
3359
|
return offeringAuthModes.includes('oauth') || offeringAuthModes.includes('deviceCode');
|
|
2353
3360
|
}
|
|
2354
3361
|
async function updateByCredentialIdAndVersion(params) {
|
|
2355
|
-
const { db, credential, credentialId, expectedVersion, patch } = params;
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
3362
|
+
const { owner, db, credential, credentialId, expectedVersion, patch } = params;
|
|
3363
|
+
const key = JSON.stringify([owner, credentialId]);
|
|
3364
|
+
const previous = credentialUpdateLocks.get(key) ?? Promise.resolve();
|
|
3365
|
+
let release;
|
|
3366
|
+
const pending = new Promise((resolve) => { release = resolve; });
|
|
3367
|
+
credentialUpdateLocks.set(key, pending);
|
|
3368
|
+
await previous;
|
|
3369
|
+
try {
|
|
3370
|
+
// The exact API does not accept a version predicate. Serialize local writers
|
|
3371
|
+
// and recheck the exact row immediately before updating it.
|
|
3372
|
+
const current = await db.findById(credential, credentialId);
|
|
3373
|
+
if (!current || String(versionFromRow(current)) !== expectedVersion)
|
|
3374
|
+
return null;
|
|
3375
|
+
const { id: _id, '@id': _iri, ...values } = patch;
|
|
3376
|
+
return await db.updateById(credential, credentialId, values);
|
|
3377
|
+
}
|
|
3378
|
+
finally {
|
|
3379
|
+
release();
|
|
3380
|
+
if (credentialUpdateLocks.get(key) === pending)
|
|
3381
|
+
credentialUpdateLocks.delete(key);
|
|
3382
|
+
}
|
|
2366
3383
|
}
|
|
2367
3384
|
//# sourceMappingURL=index.js.map
|