@undefineds.co/xpod 0.3.6 → 0.3.14
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/cli.json +1 -1
- package/config/cloud.json +54 -22
- package/config/local.json +56 -12
- package/config/resolver.json +10 -2
- package/config/xpod.base.json +50 -0
- package/config/xpod.json +8 -8
- package/dist/agents/config/resolve.js +10 -10
- package/dist/agents/config/resolve.js.map +1 -1
- package/dist/api/chatkit/index.d.ts +1 -1
- package/dist/api/chatkit/index.js.map +1 -1
- package/dist/api/chatkit/pod-store.d.ts +14 -11
- package/dist/api/chatkit/pod-store.js +114 -78
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js.map +1 -1
- package/dist/api/chatkit/service.js +1 -1
- package/dist/api/chatkit/service.js.map +1 -1
- package/dist/api/chatkit/types.d.ts +11 -11
- package/dist/api/chatkit/types.js +3 -3
- package/dist/api/chatkit/types.js.map +1 -1
- package/dist/api/container/cloud.js +0 -8
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/index.js +2 -1
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +0 -7
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +3 -17
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +0 -2
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/PodManagementHandler.d.ts +3 -0
- package/dist/api/handlers/PodManagementHandler.js +71 -1
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/RunHandler.js +5 -5
- package/dist/api/handlers/RunHandler.js.map +1 -1
- package/dist/api/runs/AgentRuntimeTypes.d.ts +7 -8
- package/dist/api/runs/AgentRuntimeTypes.js.map +1 -1
- package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
- package/dist/api/runs/ManagedRunWorker.d.ts +1 -1
- package/dist/api/runs/ManagedRunWorker.js +6 -6
- package/dist/api/runs/ManagedRunWorker.js.map +1 -1
- package/dist/api/runs/PiAgentRuntimeDriver.d.ts +16 -1
- package/dist/api/runs/PiAgentRuntimeDriver.js +182 -23
- package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
- package/dist/api/runs/RunStateCenter.d.ts +3 -3
- package/dist/api/runs/RunStateCenter.js +13 -13
- package/dist/api/runs/RunStateCenter.js.map +1 -1
- package/dist/api/runs/store.d.ts +4 -4
- package/dist/api/runs/store.js +2 -2
- package/dist/api/runs/store.js.map +1 -1
- package/dist/api/service/VectorStoreService.d.ts +1 -1
- package/dist/api/service/VectorStoreService.js +16 -16
- package/dist/api/service/VectorStoreService.js.map +1 -1
- package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
- package/dist/api/tasks/TaskMaterializer.d.ts +3 -3
- package/dist/api/tasks/TaskMaterializer.js +11 -11
- package/dist/api/tasks/TaskMaterializer.js.map +1 -1
- package/dist/api/tasks/TaskService.d.ts +3 -3
- package/dist/api/tasks/TaskService.js +11 -7
- package/dist/api/tasks/TaskService.js.map +1 -1
- package/dist/api/tasks/store.d.ts +10 -4
- package/dist/api/tasks/store.js +14 -4
- package/dist/api/tasks/store.js.map +1 -1
- package/dist/api/workspace/types.d.ts +3 -3
- package/dist/api/workspace/types.js +6 -6
- package/dist/api/workspace/types.js.map +1 -1
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/start.js +9 -3
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/components/components.jsonld +8 -2
- package/dist/components/context.jsonld +302 -51
- package/dist/http/search/SearchHttpHandler.js +8 -8
- package/dist/http/search/SearchHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/PodLookupRepository.d.ts +11 -1
- package/dist/identity/drizzle/PodLookupRepository.js +95 -4
- package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
- package/dist/identity/drizzle/db.js +4 -43
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +0 -5
- package/dist/identity/drizzle/schema.pg.js +2 -16
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +19 -176
- package/dist/identity/drizzle/schema.sqlite.js +2 -16
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +7 -7
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectOidcHandler.js +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +6 -6
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +37 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +211 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +158 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/main.js +8 -2
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.d.ts +3 -4
- package/dist/provision/ProvisionPodCreator.js +8 -13
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.jsonld +7 -7
- package/dist/runtime/Proxy.d.ts +0 -1
- package/dist/runtime/Proxy.js +0 -9
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts +1 -0
- package/dist/runtime/bootstrap.js +5 -2
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/runtime/css-process.d.ts +12 -4
- package/dist/runtime/css-process.js +61 -14
- package/dist/runtime/css-process.js.map +1 -1
- package/dist/runtime/oidc-issuer.d.ts +3 -2
- package/dist/runtime/oidc-issuer.js +3 -2
- package/dist/runtime/oidc-issuer.js.map +1 -1
- package/dist/runtime/runtime-types.d.ts +1 -0
- package/dist/runtime/runtime-types.js.map +1 -1
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.d.ts +21 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js +38 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js.map +1 -0
- package/dist/solidfs/LocalSolidFS.d.ts +18 -0
- package/dist/solidfs/LocalSolidFS.js +539 -0
- package/dist/solidfs/LocalSolidFS.js.map +1 -0
- package/dist/solidfs/PodSolidFsHttpClient.d.ts +16 -0
- package/dist/solidfs/PodSolidFsHttpClient.js +93 -0
- package/dist/solidfs/PodSolidFsHttpClient.js.map +1 -0
- package/dist/solidfs/PodSolidFsHydrator.d.ts +27 -0
- package/dist/solidfs/PodSolidFsHydrator.js +127 -0
- package/dist/solidfs/PodSolidFsHydrator.js.map +1 -0
- package/dist/solidfs/PodSolidFsSyncer.d.ts +21 -0
- package/dist/solidfs/PodSolidFsSyncer.js +78 -0
- package/dist/solidfs/PodSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.d.ts +22 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js +131 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/index.d.ts +7 -0
- package/dist/solidfs/index.js +24 -0
- package/dist/solidfs/index.js.map +1 -0
- package/dist/solidfs/types.d.ts +131 -0
- package/dist/solidfs/types.js +19 -0
- package/dist/solidfs/types.js.map +1 -0
- package/dist/storage/RepresentationPartialConvertingStore.js +6 -13
- package/dist/storage/RepresentationPartialConvertingStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.d.ts +4 -0
- package/dist/storage/SparqlUpdateResourceStore.js +13 -0
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.jsonld +26 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +85 -4
- package/dist/storage/accessors/MixDataAccessor.js +511 -16
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +176 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.d.ts +7 -0
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +72 -4
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.jsonld +24 -0
- package/dist/storage/quint/BaseQuintStore.d.ts +3 -0
- package/dist/storage/quint/BaseQuintStore.js +51 -27
- package/dist/storage/quint/BaseQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.d.ts +1 -0
- package/dist/storage/quint/PgQuintStore.js +50 -32
- package/dist/storage/quint/PgQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.jsonld +4 -3
- package/dist/storage/quint/SqliteQuintStore.d.ts +5 -0
- package/dist/storage/quint/SqliteQuintStore.js +100 -0
- package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
- package/dist/storage/quint/SqliteQuintStore.jsonld +20 -0
- package/dist/storage/quint/types.d.ts +16 -0
- package/dist/storage/quint/types.js.map +1 -1
- package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +55 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js +1235 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js.map +1 -0
- package/dist/storage/rdf/RdfContentTypes.d.ts +9 -0
- package/dist/storage/rdf/RdfContentTypes.js +79 -0
- package/dist/storage/rdf/RdfContentTypes.js.map +1 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +76 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js +2636 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.d.ts +98 -0
- package/dist/storage/rdf/RdfQuadIndex.js +1840 -0
- package/dist/storage/rdf/RdfQuadIndex.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.jsonld +416 -0
- package/dist/storage/rdf/RdfShadowComparator.d.ts +12 -0
- package/dist/storage/rdf/RdfShadowComparator.js +47 -0
- package/dist/storage/rdf/RdfShadowComparator.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.d.ts +147 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js +2420 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.jsonld +414 -0
- package/dist/storage/rdf/RdfTermDictionary.d.ts +27 -0
- package/dist/storage/rdf/RdfTermDictionary.js +352 -0
- package/dist/storage/rdf/RdfTermDictionary.js.map +1 -0
- package/dist/storage/rdf/RdfTermDictionary.jsonld +114 -0
- package/dist/storage/rdf/RdfTermSemantics.d.ts +6 -0
- package/dist/storage/rdf/RdfTermSemantics.js +40 -0
- package/dist/storage/rdf/RdfTermSemantics.js.map +1 -0
- package/dist/storage/rdf/RdfTextIndex.d.ts +23 -0
- package/dist/storage/rdf/RdfTextIndex.js +569 -0
- package/dist/storage/rdf/RdfTextIndex.js.map +1 -0
- package/dist/storage/rdf/RdfVectorIndex.d.ts +22 -0
- package/dist/storage/rdf/RdfVectorIndex.js +631 -0
- package/dist/storage/rdf/RdfVectorIndex.js.map +1 -0
- package/dist/storage/rdf/RdfXmlSerializer.d.ts +2 -0
- package/dist/storage/rdf/RdfXmlSerializer.js +123 -0
- package/dist/storage/rdf/RdfXmlSerializer.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.d.ts +58 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js +202 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.jsonld +308 -0
- package/dist/storage/rdf/SolidRdfEngine.d.ts +51 -0
- package/dist/storage/rdf/SolidRdfEngine.js +264 -0
- package/dist/storage/rdf/SolidRdfEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfEngine.jsonld +338 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +92 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js +477 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +257 -0
- package/dist/storage/rdf/index.d.ts +15 -0
- package/dist/storage/rdf/index.js +61 -0
- package/dist/storage/rdf/index.js.map +1 -0
- package/dist/storage/rdf/models-benchmark.d.ts +260 -0
- package/dist/storage/rdf/models-benchmark.js +1405 -0
- package/dist/storage/rdf/models-benchmark.js.map +1 -0
- package/dist/storage/rdf/types.d.ts +726 -0
- package/dist/storage/rdf/types.js +3 -0
- package/dist/storage/rdf/types.js.map +1 -0
- package/dist/storage/rdf/types.jsonld +316 -0
- package/dist/storage/vector/VectorIndexingListener.d.ts +5 -5
- package/dist/storage/vector/VectorIndexingListener.js +19 -19
- package/dist/storage/vector/VectorIndexingListener.js.map +1 -1
- package/package.json +3 -2
- package/templates/pod/acp/.acr.hbs +39 -0
- package/templates/pod/acp/README.acr +18 -0
- package/templates/pod/acp/profile/card.acr +22 -0
- package/templates/pod/base/README$.md.hbs +27 -0
- package/templates/pod/base/profile/card$.ttl.hbs +13 -0
- package/templates/pod/wac/.acl.hbs +26 -0
- package/templates/pod/wac/README.acl.hbs +14 -0
- package/templates/pod/wac/profile/card.acl.hbs +19 -0
- package/dist/api/handlers/WebIdProfileHandler.d.ts +0 -16
- package/dist/api/handlers/WebIdProfileHandler.js +0 -423
- package/dist/api/handlers/WebIdProfileHandler.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.d.ts +0 -63
- package/dist/identity/drizzle/WebIdProfileRepository.js +0 -168
- package/dist/identity/drizzle/WebIdProfileRepository.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.jsonld +0 -112
- package/dist/storage/quint/BaseQuintStore.jsonld +0 -257
|
@@ -7,18 +7,18 @@ const community_server_1 = require("@solid/community-server");
|
|
|
7
7
|
* Auto-detect Identity Provider Handler
|
|
8
8
|
*
|
|
9
9
|
* 自动检测运行模式:
|
|
10
|
-
* - 如果配置了
|
|
11
|
-
* - 如果没有配置
|
|
10
|
+
* - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理
|
|
11
|
+
* - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler
|
|
12
12
|
*/
|
|
13
13
|
class AutoDetectIdentityProviderHandler extends community_server_1.HttpHandler {
|
|
14
14
|
constructor(options = {}) {
|
|
15
15
|
super();
|
|
16
16
|
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
17
|
-
this.
|
|
17
|
+
this.oidcIssuer = options.oidcIssuer;
|
|
18
18
|
this.message = options.message ?? 'Account management handled by external IdP';
|
|
19
19
|
this.source = options.source;
|
|
20
|
-
if (this.
|
|
21
|
-
this.logger.info(`SP mode enabled: ${this.message}, external IdP: ${this.
|
|
20
|
+
if (this.oidcIssuer) {
|
|
21
|
+
this.logger.info(`SP mode enabled: ${this.message}, external IdP: ${this.oidcIssuer}`);
|
|
22
22
|
}
|
|
23
23
|
else {
|
|
24
24
|
this.logger.info('Standard mode enabled, delegating to source IdentityProviderHandler');
|
|
@@ -36,7 +36,7 @@ class AutoDetectIdentityProviderHandler extends community_server_1.HttpHandler {
|
|
|
36
36
|
throw new community_server_1.NotImplementedHttpError('Not an IdP request');
|
|
37
37
|
}
|
|
38
38
|
// SP 模式:接受 IdP 路径请求,在 handle 中返回 404
|
|
39
|
-
if (this.
|
|
39
|
+
if (this.oidcIssuer) {
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
// 标准模式:委托给 source Handler
|
|
@@ -53,7 +53,7 @@ class AutoDetectIdentityProviderHandler extends community_server_1.HttpHandler {
|
|
|
53
53
|
* - 标准模式:委托给 source Handler
|
|
54
54
|
*/
|
|
55
55
|
async handle(input) {
|
|
56
|
-
if (this.
|
|
56
|
+
if (this.oidcIssuer) {
|
|
57
57
|
const { response } = input;
|
|
58
58
|
response.writeHead(404, { 'Content-Type': 'application/json' });
|
|
59
59
|
response.end(JSON.stringify({ error: this.message }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoDetectIdentityProviderHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectIdentityProviderHandler.ts"],"names":[],"mappings":";;;AAAA,iEAAqD;AACrD,8DAIiC;AAWjC;;;;;;GAMG;AACH,MAAa,iCAAkC,SAAQ,8BAAW;IAMhE,YAAY,UAAoD,EAAE;QAChE,KAAK,EAAE,CAAC;QANO,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAO3C,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"AutoDetectIdentityProviderHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectIdentityProviderHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport {\n HttpHandler,\n type HttpHandlerInput,\n NotImplementedHttpError,\n} from '@solid/community-server';\n\nexport interface AutoDetectIdentityProviderHandlerOptions {\n /** 外部 IdP 的基础 URL,如果提供则启用 SP 模式 */\n oidcIssuer?: string;\n /** 禁用时的消息 */\n message?: string;\n /** CSS 默认的 IdentityProviderHandler,标准模式下委托给它 */\n source?: HttpHandler;\n}\n\n/**\n * Auto-detect Identity Provider Handler\n *\n * 自动检测运行模式:\n * - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理\n * - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler\n */\nexport class AutoDetectIdentityProviderHandler extends HttpHandler {\n private readonly logger = getLoggerFor(this);\n private readonly oidcIssuer?: string;\n private readonly message: string;\n private readonly source?: HttpHandler;\n\n constructor(options: AutoDetectIdentityProviderHandlerOptions = {}) {\n super();\n this.oidcIssuer = options.oidcIssuer;\n this.message = options.message ?? 'Account management handled by external IdP';\n this.source = options.source;\n\n if (this.oidcIssuer) {\n this.logger.info(`SP mode enabled: ${this.message}, external IdP: ${this.oidcIssuer}`);\n } else {\n this.logger.info('Standard mode enabled, delegating to source IdentityProviderHandler');\n }\n }\n\n /**\n * 判断是否处理请求\n * - SP 模式:拒绝所有 IdP 请求\n * - 标准模式:委托给 source Handler\n */\n public override async canHandle(input: HttpHandlerInput): Promise<void> {\n const url = input.request.url ?? '';\n\n // 检查是否是 IdP 路径\n if (!this.isIdpPath(url)) {\n throw new NotImplementedHttpError('Not an IdP request');\n }\n\n // SP 模式:接受 IdP 路径请求,在 handle 中返回 404\n if (this.oidcIssuer) {\n return;\n }\n\n // 标准模式:委托给 source Handler\n if (this.source) {\n await this.source.canHandle(input);\n } else {\n throw new NotImplementedHttpError('No source IdentityProviderHandler configured');\n }\n }\n\n /**\n * 处理请求\n * - SP 模式:不应该到达这里\n * - 标准模式:委托给 source Handler\n */\n public override async handle(input: HttpHandlerInput): Promise<void> {\n if (this.oidcIssuer) {\n const { response } = input;\n response.writeHead(404, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify({ error: this.message }));\n return;\n }\n\n // 标准模式:委托给 source Handler\n if (this.source) {\n await this.source.handle(input);\n } else {\n throw new NotImplementedHttpError('No source IdentityProviderHandler configured');\n }\n }\n\n /**\n * 检查是否是 IdP 路径\n */\n private isIdpPath(url: string): boolean {\n const pathname = this.getPathname(url);\n return (\n pathname.startsWith('/idp/') ||\n pathname.startsWith('/.account/') ||\n pathname === '/register' ||\n pathname === '/login' ||\n pathname === '/logout'\n );\n }\n\n /**\n * 从 URL 提取 pathname\n */\n private getPathname(url: string): string {\n try {\n return new URL(url, 'http://localhost').pathname;\n } catch {\n return url.split('?')[0];\n }\n }\n}\n"]}
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"extends": [
|
|
13
13
|
"css:dist/server/HttpHandler.jsonld#HttpHandler"
|
|
14
14
|
],
|
|
15
|
-
"comment": "Auto-detect Identity Provider Handler 自动检测运行模式: - 如果配置了
|
|
15
|
+
"comment": "Auto-detect Identity Provider Handler 自动检测运行模式: - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理 - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler",
|
|
16
16
|
"parameters": [
|
|
17
17
|
{
|
|
18
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#
|
|
18
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options_oidcIssuer",
|
|
19
19
|
"range": {
|
|
20
20
|
"@type": "ParameterRangeUnion",
|
|
21
21
|
"parameterRangeElements": [
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"memberFieldName": "logger"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#
|
|
64
|
-
"memberFieldName": "
|
|
63
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler__member_oidcIssuer",
|
|
64
|
+
"memberFieldName": "oidcIssuer"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler__member_message",
|
|
@@ -97,9 +97,9 @@
|
|
|
97
97
|
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options__constructorArgument",
|
|
98
98
|
"fields": [
|
|
99
99
|
{
|
|
100
|
-
"keyRaw": "
|
|
100
|
+
"keyRaw": "oidcIssuer",
|
|
101
101
|
"value": {
|
|
102
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#
|
|
102
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options_oidcIssuer"
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
105
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HttpHandler, type HttpHandlerInput } from '@solid/community-server';
|
|
2
2
|
export interface AutoDetectOidcHandlerOptions {
|
|
3
3
|
/** 外部 IdP 的基础 URL,如果提供则启用 SP 模式 */
|
|
4
|
-
|
|
4
|
+
oidcIssuer?: string;
|
|
5
5
|
/** 禁用原因说明 */
|
|
6
6
|
message?: string;
|
|
7
7
|
/** JWKS 缓存时间 (ms) */
|
|
@@ -11,14 +11,14 @@ export interface AutoDetectOidcHandlerOptions {
|
|
|
11
11
|
* Auto-detect OIDC Handler
|
|
12
12
|
*
|
|
13
13
|
* 自动检测运行模式:
|
|
14
|
-
* - 如果配置了
|
|
15
|
-
* - 如果没有配置
|
|
14
|
+
* - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS
|
|
15
|
+
* - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
|
|
16
16
|
*
|
|
17
17
|
* 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
|
|
18
18
|
*/
|
|
19
19
|
export declare class AutoDetectOidcHandler extends HttpHandler {
|
|
20
20
|
private readonly logger;
|
|
21
|
-
private readonly
|
|
21
|
+
private readonly oidcIssuer?;
|
|
22
22
|
private readonly jwksUrl?;
|
|
23
23
|
private readonly message;
|
|
24
24
|
private readonly cacheMs;
|
|
@@ -8,8 +8,8 @@ const community_server_1 = require("@solid/community-server");
|
|
|
8
8
|
* Auto-detect OIDC Handler
|
|
9
9
|
*
|
|
10
10
|
* 自动检测运行模式:
|
|
11
|
-
* - 如果配置了
|
|
12
|
-
* - 如果没有配置
|
|
11
|
+
* - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS
|
|
12
|
+
* - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
|
|
13
13
|
*
|
|
14
14
|
* 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
|
|
15
15
|
*/
|
|
@@ -17,12 +17,12 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
|
|
|
17
17
|
constructor(options = {}) {
|
|
18
18
|
super();
|
|
19
19
|
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
20
|
-
this.
|
|
21
|
-
this.jwksUrl = this.
|
|
20
|
+
this.oidcIssuer = options.oidcIssuer;
|
|
21
|
+
this.jwksUrl = this.oidcIssuer ? `${this.oidcIssuer.replace(/\/$/, '')}/.oidc/jwks` : undefined;
|
|
22
22
|
this.message = options.message ?? 'OIDC disabled in storage provider mode';
|
|
23
23
|
this.cacheMs = options.cacheMs ?? 300000; // 默认 5 分钟
|
|
24
|
-
if (this.
|
|
25
|
-
this.logger.info(`SP mode enabled, external IdP: ${this.
|
|
24
|
+
if (this.oidcIssuer) {
|
|
25
|
+
this.logger.info(`SP mode enabled, external IdP: ${this.oidcIssuer}, JWKS: ${this.jwksUrl}`);
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
28
|
this.logger.info('Standard mode enabled, OIDC requests will pass through');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoDetectOidcHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectOidcHandler.ts"],"names":[],"mappings":";;;AAAA,uCAA+B;AAC/B,iEAAqD;AACrD,8DAKiC;AAgBjC;;;;;;;;GAQG;AACH,MAAa,qBAAsB,SAAQ,8BAAW;IAQpD,YAAY,UAAwC,EAAE;QACpD,KAAK,EAAE,CAAC;QARO,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAS3C,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"AutoDetectOidcHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectOidcHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { URL } from 'node:url';\nimport { getLoggerFor } from 'global-logger-factory';\nimport {\n HttpHandler,\n type HttpHandlerInput,\n NotImplementedHttpError,\n InternalServerError,\n} from '@solid/community-server';\n\nexport interface AutoDetectOidcHandlerOptions {\n /** 外部 IdP 的基础 URL,如果提供则启用 SP 模式 */\n oidcIssuer?: string;\n /** 禁用原因说明 */\n message?: string;\n /** JWKS 缓存时间 (ms) */\n cacheMs?: number;\n}\n\ninterface JwksCache {\n keys: unknown[];\n expiresAt: number;\n}\n\n/**\n * Auto-detect OIDC Handler\n *\n * 自动检测运行模式:\n * - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS\n * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)\n *\n * 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler\n */\nexport class AutoDetectOidcHandler extends HttpHandler {\n private readonly logger = getLoggerFor(this);\n private readonly oidcIssuer?: string;\n private readonly jwksUrl?: string;\n private readonly message: string;\n private readonly cacheMs: number;\n private jwksCache?: JwksCache;\n\n constructor(options: AutoDetectOidcHandlerOptions = {}) {\n super();\n this.oidcIssuer = options.oidcIssuer;\n this.jwksUrl = this.oidcIssuer ? `${this.oidcIssuer.replace(/\\/$/, '')}/.oidc/jwks` : undefined;\n this.message = options.message ?? 'OIDC disabled in storage provider mode';\n this.cacheMs = options.cacheMs ?? 300000; // 默认 5 分钟\n\n if (this.oidcIssuer) {\n this.logger.info(`SP mode enabled, external IdP: ${this.oidcIssuer}, JWKS: ${this.jwksUrl}`);\n } else {\n this.logger.info('Standard mode enabled, OIDC requests will pass through');\n }\n }\n\n /**\n * 判断是否处理请求\n * - SP 模式:只处理 JWKS 请求,其他 OIDC 请求返回 501\n * - 标准模式:不处理任何请求(透传给 CSS 默认 Handler)\n */\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n const url = request.url ?? '';\n\n // 检查是否是 OIDC 路径\n if (!this.isOidcPath(url)) {\n throw new NotImplementedHttpError('Not an OIDC request');\n }\n\n // 标准模式:不处理,透传给 CSS 默认 Handler\n if (!this.jwksUrl) {\n throw new NotImplementedHttpError('Pass through to default OIDC handler');\n }\n\n // SP 模式:只有 JWKS 请求可以处理\n if (!this.isJwksPath(url)) {\n throw new NotImplementedHttpError(\n `External IdP mode: ${this.message}. Authentication handled by external IdP.`\n );\n }\n }\n\n /**\n * 处理请求\n * - SP 模式:代理 JWKS\n * - 标准模式:不应该到达这里\n */\n public override async handle({ response }: HttpHandlerInput): Promise<void> {\n // 标准模式:不应该到达这里\n if (!this.jwksUrl) {\n throw new InternalServerError('AutoDetectOidcHandler should not handle requests in standard mode');\n }\n\n try {\n const jwks = await this.fetchJwks();\n\n response.statusCode = 200;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('Cache-Control', `public, max-age=${Math.floor(this.cacheMs / 1000)}`);\n response.end(JSON.stringify(jwks));\n\n this.logger.debug('JWKS proxy successful');\n } catch (error) {\n this.logger.error(`JWKS proxy failed: ${(error as Error).message}`);\n throw new InternalServerError('Failed to proxy JWKS request', { cause: error });\n }\n }\n\n /**\n * 获取并缓存 JWKS\n */\n private async fetchJwks(): Promise<{ keys: unknown[] }> {\n // 检查缓存\n if (this.jwksCache && this.jwksCache.expiresAt > Date.now()) {\n this.logger.debug('Returning cached JWKS');\n return { keys: this.jwksCache.keys };\n }\n\n if (!this.jwksUrl) {\n throw new Error('External JWKS URL not configured');\n }\n\n this.logger.debug(`Fetching JWKS from ${this.jwksUrl}`);\n\n const res = await fetch(this.jwksUrl, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch JWKS: ${res.status} ${res.statusText}`);\n }\n\n const jwks = await res.json() as { keys: unknown[] };\n\n // 验证 JWKS 格式\n if (!Array.isArray(jwks.keys)) {\n throw new Error('Invalid JWKS format: missing keys array');\n }\n\n // 更新缓存\n this.jwksCache = {\n keys: jwks.keys,\n expiresAt: Date.now() + this.cacheMs,\n };\n\n this.logger.debug(`JWKS cached with ${jwks.keys.length} keys`);\n return jwks;\n }\n\n /**\n * 检查是否是 OIDC 路径\n */\n private isOidcPath(url: string): boolean {\n const pathname = this.getPathname(url);\n return (\n pathname.startsWith('/.oidc/') ||\n pathname === '/.well-known/openid-configuration' ||\n pathname === '/.well-known/oauth-authorization-server' ||\n pathname.startsWith('/idp/')\n );\n }\n\n /**\n * 检查是否是 JWKS 路径\n */\n private isJwksPath(url: string): boolean {\n const pathname = this.getPathname(url);\n return pathname === '/.oidc/jwks' || pathname === '/.oidc/jwks.json';\n }\n\n /**\n * 从 URL 提取 pathname\n */\n private getPathname(url: string): string {\n try {\n return new URL(url, 'http://localhost').pathname;\n } catch {\n // 如果解析失败,直接返回 url(可能是相对路径)\n return url.split('?')[0];\n }\n }\n}\n"]}
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"extends": [
|
|
13
13
|
"css:dist/server/HttpHandler.jsonld#HttpHandler"
|
|
14
14
|
],
|
|
15
|
-
"comment": "Auto-detect OIDC Handler 自动检测运行模式: - 如果配置了
|
|
15
|
+
"comment": "Auto-detect OIDC Handler 自动检测运行模式: - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理) 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler",
|
|
16
16
|
"parameters": [
|
|
17
17
|
{
|
|
18
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#
|
|
18
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options_oidcIssuer",
|
|
19
19
|
"range": {
|
|
20
20
|
"@type": "ParameterRangeUnion",
|
|
21
21
|
"parameterRangeElements": [
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"memberFieldName": "logger"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#
|
|
64
|
-
"memberFieldName": "
|
|
63
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_oidcIssuer",
|
|
64
|
+
"memberFieldName": "oidcIssuer"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_jwksUrl",
|
|
@@ -113,9 +113,9 @@
|
|
|
113
113
|
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options__constructorArgument",
|
|
114
114
|
"fields": [
|
|
115
115
|
{
|
|
116
|
-
"keyRaw": "
|
|
116
|
+
"keyRaw": "oidcIssuer",
|
|
117
117
|
"value": {
|
|
118
|
-
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#
|
|
118
|
+
"@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options_oidcIssuer"
|
|
119
119
|
}
|
|
120
120
|
},
|
|
121
121
|
{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { JsonInteractionHandler } from '@solid/community-server';
|
|
2
|
+
import type { JsonInteractionHandlerInput, JsonRepresentation, JsonView, ProviderFactory, WebIdStore } from '@solid/community-server';
|
|
3
|
+
import { type PodLookupResult } from '../drizzle/PodLookupRepository';
|
|
4
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
export interface ScopedPickWebIdHandlerOptions {
|
|
6
|
+
webIdStore: WebIdStore;
|
|
7
|
+
providerFactory: ProviderFactory;
|
|
8
|
+
identityDbUrl?: string;
|
|
9
|
+
podLookupRepository?: PodWebIdLookupRepository;
|
|
10
|
+
fetch?: FetchLike;
|
|
11
|
+
}
|
|
12
|
+
export interface PodWebIdLookupRepository {
|
|
13
|
+
findByWebId: (webId: string) => Promise<PodLookupResult | undefined>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* CSS-compatible WebID picker scoped to the current storage provider.
|
|
17
|
+
*
|
|
18
|
+
* The upstream handler lists every WebID linked to the IdP account. In an
|
|
19
|
+
* IDP/SP split flow that lets a Local SP login pick a Cloud Pod again, so this
|
|
20
|
+
* replacement keeps consent choices constrained by the selected SP's Pod facts.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ScopedPickWebIdHandler extends JsonInteractionHandler implements JsonView {
|
|
23
|
+
private readonly logger;
|
|
24
|
+
private readonly webIdStore;
|
|
25
|
+
private readonly providerFactory;
|
|
26
|
+
private readonly podLookupRepository?;
|
|
27
|
+
private readonly fetch;
|
|
28
|
+
constructor(options: ScopedPickWebIdHandlerOptions);
|
|
29
|
+
getView({ accountId, oidcInteraction }: JsonInteractionHandlerInput): Promise<JsonRepresentation>;
|
|
30
|
+
handle({ oidcInteraction, accountId, json }: JsonInteractionHandlerInput): Promise<never>;
|
|
31
|
+
private resolveScopedEntries;
|
|
32
|
+
private isResolvableByCurrentSp;
|
|
33
|
+
private findSpPod;
|
|
34
|
+
private resolveTargetStorage;
|
|
35
|
+
private resolveRemoteSpEntries;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopedPickWebIdHandler = void 0;
|
|
4
|
+
const yup_1 = require("yup");
|
|
5
|
+
const global_logger_factory_1 = require("global-logger-factory");
|
|
6
|
+
const community_server_1 = require("@solid/community-server");
|
|
7
|
+
const db_1 = require("../drizzle/db");
|
|
8
|
+
const PodLookupRepository_1 = require("../drizzle/PodLookupRepository");
|
|
9
|
+
const ProvisionCodeCodec_1 = require("../../provision/ProvisionCodeCodec");
|
|
10
|
+
const inSchema = (0, yup_1.object)({
|
|
11
|
+
webId: (0, yup_1.string)().trim().required(),
|
|
12
|
+
remember: (0, yup_1.boolean)().default(false),
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* CSS-compatible WebID picker scoped to the current storage provider.
|
|
16
|
+
*
|
|
17
|
+
* The upstream handler lists every WebID linked to the IdP account. In an
|
|
18
|
+
* IDP/SP split flow that lets a Local SP login pick a Cloud Pod again, so this
|
|
19
|
+
* replacement keeps consent choices constrained by the selected SP's Pod facts.
|
|
20
|
+
*/
|
|
21
|
+
class ScopedPickWebIdHandler extends community_server_1.JsonInteractionHandler {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
super();
|
|
24
|
+
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
25
|
+
this.webIdStore = options.webIdStore;
|
|
26
|
+
this.providerFactory = options.providerFactory;
|
|
27
|
+
this.podLookupRepository = options.podLookupRepository ??
|
|
28
|
+
(options.identityDbUrl ? new PodLookupRepository_1.PodLookupRepository((0, db_1.getIdentityDatabase)(options.identityDbUrl)) : undefined);
|
|
29
|
+
this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
30
|
+
}
|
|
31
|
+
async getView({ accountId, oidcInteraction }) {
|
|
32
|
+
(0, community_server_1.assertAccountId)(accountId);
|
|
33
|
+
const provider = await this.providerFactory.getProvider();
|
|
34
|
+
const description = (0, community_server_1.parseSchema)(inSchema);
|
|
35
|
+
const target = await this.resolveTargetStorage(provider, oidcInteraction);
|
|
36
|
+
const entries = await this.resolveScopedEntries(accountId, target);
|
|
37
|
+
return {
|
|
38
|
+
json: {
|
|
39
|
+
...description,
|
|
40
|
+
webIds: entries.map((entry) => entry.webId),
|
|
41
|
+
entries,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async handle({ oidcInteraction, accountId, json }) {
|
|
46
|
+
(0, community_server_1.assertOidcInteraction)(oidcInteraction);
|
|
47
|
+
(0, community_server_1.assertAccountId)(accountId);
|
|
48
|
+
const { webId, remember } = await (0, community_server_1.validateWithError)(inSchema, json);
|
|
49
|
+
const provider = await this.providerFactory.getProvider();
|
|
50
|
+
const target = await this.resolveTargetStorage(provider, oidcInteraction);
|
|
51
|
+
if (!await this.webIdStore.isLinked(webId, accountId)) {
|
|
52
|
+
this.logger.warn(`Trying to pick WebID ${webId} which does not belong to account ${accountId}`);
|
|
53
|
+
throw new community_server_1.BadRequestHttpError('WebID does not belong to this account.');
|
|
54
|
+
}
|
|
55
|
+
if (!await this.isResolvableByCurrentSp(webId, target)) {
|
|
56
|
+
this.logger.warn(`Trying to pick WebID ${webId} which does not belong to this storage provider`);
|
|
57
|
+
throw new community_server_1.BadRequestHttpError('WebID does not belong to this storage provider.');
|
|
58
|
+
}
|
|
59
|
+
await (0, community_server_1.forgetWebId)(provider, oidcInteraction);
|
|
60
|
+
const location = await (0, community_server_1.finishInteraction)(oidcInteraction, {
|
|
61
|
+
login: {
|
|
62
|
+
accountId: webId,
|
|
63
|
+
remember,
|
|
64
|
+
},
|
|
65
|
+
}, true);
|
|
66
|
+
throw new community_server_1.FoundHttpError(location);
|
|
67
|
+
}
|
|
68
|
+
async resolveScopedEntries(accountId, target) {
|
|
69
|
+
const webIds = (await this.webIdStore.findLinks(accountId)).map((link) => link.webId);
|
|
70
|
+
if (target.serviceToken) {
|
|
71
|
+
return this.resolveRemoteSpEntries(webIds, target);
|
|
72
|
+
}
|
|
73
|
+
const entries = [];
|
|
74
|
+
for (const webId of webIds) {
|
|
75
|
+
const pod = await this.findSpPod(webId, target.storageUrl);
|
|
76
|
+
if (!pod) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);
|
|
80
|
+
entries.push({
|
|
81
|
+
webId,
|
|
82
|
+
storageUrl,
|
|
83
|
+
storageMode: deriveStorageMode(webId, storageUrl),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return entries;
|
|
87
|
+
}
|
|
88
|
+
async isResolvableByCurrentSp(webId, target) {
|
|
89
|
+
if (target.serviceToken) {
|
|
90
|
+
return (await this.resolveRemoteSpEntries([webId], target)).some((entry) => entry.webId === webId);
|
|
91
|
+
}
|
|
92
|
+
return Boolean(await this.findSpPod(webId, target.storageUrl));
|
|
93
|
+
}
|
|
94
|
+
async findSpPod(webId, targetStorageUrl) {
|
|
95
|
+
if (!this.podLookupRepository) {
|
|
96
|
+
this.logger.warn('No PodLookupRepository configured; refusing to expose unscoped WebID choices');
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const pod = await this.podLookupRepository.findByWebId(webId);
|
|
101
|
+
return pod && matchesTargetStorage(pod, targetStorageUrl) ? pod : undefined;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.logger.warn(`Pod lookup unavailable for WebID ${webId}: ${error}`);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async resolveTargetStorage(provider, oidcInteraction) {
|
|
109
|
+
const provisionCode = extractProvisionCode(oidcInteraction);
|
|
110
|
+
if (!provisionCode) {
|
|
111
|
+
return { storageUrl: ensureTrailingSlash(provider.issuer) };
|
|
112
|
+
}
|
|
113
|
+
const payload = new ProvisionCodeCodec_1.ProvisionCodeCodec(provider.issuer).decode(provisionCode);
|
|
114
|
+
if (!payload) {
|
|
115
|
+
throw new community_server_1.BadRequestHttpError('Invalid or expired provisionCode.');
|
|
116
|
+
}
|
|
117
|
+
const targetUrl = payload.spDomain
|
|
118
|
+
? `https://${payload.spDomain}`
|
|
119
|
+
: payload.spUrl;
|
|
120
|
+
return {
|
|
121
|
+
storageUrl: ensureTrailingSlash(targetUrl),
|
|
122
|
+
lookupUrl: ensureTrailingSlash(payload.spUrl),
|
|
123
|
+
serviceToken: payload.serviceToken,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async resolveRemoteSpEntries(webIds, target) {
|
|
127
|
+
if (!target.lookupUrl || !target.serviceToken || webIds.length === 0) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const response = await this.fetch(new URL('/provision/webids', target.lookupUrl).toString(), {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Authorization': `Bearer ${target.serviceToken}`,
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'Accept': 'application/json',
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({ webIds }),
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
this.logger.warn(`Remote SP WebID lookup failed: HTTP ${response.status}`);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const body = await response.json().catch(() => null);
|
|
144
|
+
if (!Array.isArray(body?.entries)) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
const allowedWebIds = new Set(webIds);
|
|
148
|
+
return body.entries
|
|
149
|
+
.filter((entry) => typeof entry.webId === 'string' && allowedWebIds.has(entry.webId))
|
|
150
|
+
.filter((entry) => typeof entry.storageUrl === 'string' && matchesTargetStorage({
|
|
151
|
+
podId: '',
|
|
152
|
+
accountId: '',
|
|
153
|
+
baseUrl: entry.podUrl ?? entry.storageUrl,
|
|
154
|
+
storageUrl: entry.storageUrl,
|
|
155
|
+
}, target.storageUrl))
|
|
156
|
+
.map((entry) => ({
|
|
157
|
+
webId: entry.webId,
|
|
158
|
+
storageUrl: ensureTrailingSlash(entry.storageUrl),
|
|
159
|
+
storageMode: deriveStorageMode(entry.webId, entry.storageUrl),
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.ScopedPickWebIdHandler = ScopedPickWebIdHandler;
|
|
164
|
+
function deriveStorageMode(webId, storageUrl) {
|
|
165
|
+
const webIdRoot = deriveStorageRoot(webId);
|
|
166
|
+
const storageRoot = deriveStorageRoot(storageUrl);
|
|
167
|
+
if (!webIdRoot || !storageRoot) {
|
|
168
|
+
return 'custom';
|
|
169
|
+
}
|
|
170
|
+
return webIdRoot === storageRoot ? 'cloud' : 'local';
|
|
171
|
+
}
|
|
172
|
+
function deriveStorageRoot(url) {
|
|
173
|
+
try {
|
|
174
|
+
const parsed = new URL(url);
|
|
175
|
+
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
176
|
+
if (segments.length === 0) {
|
|
177
|
+
return ensureTrailingSlash(parsed.origin);
|
|
178
|
+
}
|
|
179
|
+
return ensureTrailingSlash(new URL(`/${segments[0]}/`, parsed.origin).toString());
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function ensureTrailingSlash(url) {
|
|
186
|
+
return url.replace(/\/+$/u, '') + '/';
|
|
187
|
+
}
|
|
188
|
+
function matchesTargetStorage(pod, targetStorageUrl) {
|
|
189
|
+
const candidateUrls = [pod.storageUrl, pod.baseUrl].filter((value) => typeof value === 'string' && value.length > 0);
|
|
190
|
+
const targetRoot = deriveStorageRoot(targetStorageUrl);
|
|
191
|
+
if (!targetRoot) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
for (const candidate of candidateUrls) {
|
|
195
|
+
const candidateRoot = deriveStorageRoot(candidate);
|
|
196
|
+
if (candidateRoot && candidateRoot === targetRoot) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
const candidateUrl = ensureTrailingSlash(candidate);
|
|
200
|
+
if (candidateUrl.startsWith(targetRoot) || targetRoot.startsWith(candidateUrl)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
function extractProvisionCode(oidcInteraction) {
|
|
207
|
+
const params = oidcInteraction?.params;
|
|
208
|
+
const value = params?.provisionCode;
|
|
209
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=ScopedPickWebIdHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScopedPickWebIdHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/ScopedPickWebIdHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { boolean, object, string } from 'yup';\nimport { getLoggerFor } from 'global-logger-factory';\nimport {\n BadRequestHttpError,\n FoundHttpError,\n JsonInteractionHandler,\n assertAccountId,\n assertOidcInteraction,\n finishInteraction,\n forgetWebId,\n parseSchema,\n validateWithError,\n} from '@solid/community-server';\nimport type {\n Json,\n JsonInteractionHandlerInput,\n JsonRepresentation,\n JsonView,\n ProviderFactory,\n WebIdStore,\n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../drizzle/db';\nimport { PodLookupRepository, type PodLookupResult } from '../drizzle/PodLookupRepository';\nimport { ProvisionCodeCodec } from '../../provision/ProvisionCodeCodec';\n\ntype FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\nconst inSchema = object({\n webId: string().trim().required(),\n remember: boolean().default(false),\n});\n\nexport interface ScopedPickWebIdHandlerOptions {\n webIdStore: WebIdStore;\n providerFactory: ProviderFactory;\n identityDbUrl?: string;\n podLookupRepository?: PodWebIdLookupRepository;\n fetch?: FetchLike;\n}\n\nexport interface PodWebIdLookupRepository {\n findByWebId: (webId: string) => Promise<PodLookupResult | undefined>;\n}\n\ninterface WebIdEntry extends Record<string, Json | undefined> {\n webId: string;\n storageUrl?: string;\n storageMode?: 'cloud' | 'local' | 'custom';\n}\n\n/**\n * CSS-compatible WebID picker scoped to the current storage provider.\n *\n * The upstream handler lists every WebID linked to the IdP account. In an\n * IDP/SP split flow that lets a Local SP login pick a Cloud Pod again, so this\n * replacement keeps consent choices constrained by the selected SP's Pod facts.\n */\nexport class ScopedPickWebIdHandler extends JsonInteractionHandler implements JsonView {\n private readonly logger = getLoggerFor(this);\n private readonly webIdStore: WebIdStore;\n private readonly providerFactory: ProviderFactory;\n private readonly podLookupRepository?: PodWebIdLookupRepository;\n private readonly fetch: FetchLike;\n\n public constructor(options: ScopedPickWebIdHandlerOptions) {\n super();\n this.webIdStore = options.webIdStore;\n this.providerFactory = options.providerFactory;\n this.podLookupRepository = options.podLookupRepository ??\n (options.identityDbUrl ? new PodLookupRepository(getIdentityDatabase(options.identityDbUrl)) : undefined);\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n public async getView({ accountId, oidcInteraction }: JsonInteractionHandlerInput): Promise<JsonRepresentation> {\n assertAccountId(accountId);\n const provider = await this.providerFactory.getProvider();\n const description = parseSchema(inSchema);\n const target = await this.resolveTargetStorage(provider, oidcInteraction);\n const entries = await this.resolveScopedEntries(accountId, target);\n\n return {\n json: {\n ...description,\n webIds: entries.map((entry) => entry.webId),\n entries,\n },\n };\n }\n\n public async handle({ oidcInteraction, accountId, json }: JsonInteractionHandlerInput): Promise<never> {\n assertOidcInteraction(oidcInteraction);\n assertAccountId(accountId);\n const { webId, remember } = await validateWithError(inSchema, json);\n const provider = await this.providerFactory.getProvider();\n const target = await this.resolveTargetStorage(provider, oidcInteraction);\n\n if (!await this.webIdStore.isLinked(webId, accountId)) {\n this.logger.warn(`Trying to pick WebID ${webId} which does not belong to account ${accountId}`);\n throw new BadRequestHttpError('WebID does not belong to this account.');\n }\n\n if (!await this.isResolvableByCurrentSp(webId, target)) {\n this.logger.warn(`Trying to pick WebID ${webId} which does not belong to this storage provider`);\n throw new BadRequestHttpError('WebID does not belong to this storage provider.');\n }\n\n await forgetWebId(provider, oidcInteraction);\n const location = await finishInteraction(oidcInteraction, {\n login: {\n accountId: webId,\n remember,\n },\n }, true);\n throw new FoundHttpError(location);\n }\n\n private async resolveScopedEntries(accountId: string, target: TargetStorage): Promise<WebIdEntry[]> {\n const webIds = (await this.webIdStore.findLinks(accountId)).map((link) => link.webId);\n if (target.serviceToken) {\n return this.resolveRemoteSpEntries(webIds, target);\n }\n\n const entries: WebIdEntry[] = [];\n for (const webId of webIds) {\n const pod = await this.findSpPod(webId, target.storageUrl);\n if (!pod) {\n continue;\n }\n const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);\n entries.push({\n webId,\n storageUrl,\n storageMode: deriveStorageMode(webId, storageUrl),\n });\n }\n return entries;\n }\n\n private async isResolvableByCurrentSp(webId: string, target: TargetStorage): Promise<boolean> {\n if (target.serviceToken) {\n return (await this.resolveRemoteSpEntries([webId], target)).some((entry) => entry.webId === webId);\n }\n return Boolean(await this.findSpPod(webId, target.storageUrl));\n }\n\n private async findSpPod(webId: string, targetStorageUrl: string): Promise<PodLookupResult | undefined> {\n if (!this.podLookupRepository) {\n this.logger.warn('No PodLookupRepository configured; refusing to expose unscoped WebID choices');\n return undefined;\n }\n\n try {\n const pod = await this.podLookupRepository.findByWebId(webId);\n return pod && matchesTargetStorage(pod, targetStorageUrl) ? pod : undefined;\n } catch (error) {\n this.logger.warn(`Pod lookup unavailable for WebID ${webId}: ${error}`);\n return undefined;\n }\n }\n\n private async resolveTargetStorage(\n provider: { issuer: string },\n oidcInteraction?: JsonInteractionHandlerInput['oidcInteraction'],\n ): Promise<TargetStorage> {\n const provisionCode = extractProvisionCode(oidcInteraction);\n if (!provisionCode) {\n return { storageUrl: ensureTrailingSlash(provider.issuer) };\n }\n\n const payload = new ProvisionCodeCodec(provider.issuer).decode(provisionCode);\n if (!payload) {\n throw new BadRequestHttpError('Invalid or expired provisionCode.');\n }\n\n const targetUrl = payload.spDomain\n ? `https://${payload.spDomain}`\n : payload.spUrl;\n return {\n storageUrl: ensureTrailingSlash(targetUrl),\n lookupUrl: ensureTrailingSlash(payload.spUrl),\n serviceToken: payload.serviceToken,\n };\n }\n\n private async resolveRemoteSpEntries(webIds: string[], target: TargetStorage): Promise<WebIdEntry[]> {\n if (!target.lookupUrl || !target.serviceToken || webIds.length === 0) {\n return [];\n }\n\n const response = await this.fetch(new URL('/provision/webids', target.lookupUrl).toString(), {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${target.serviceToken}`,\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n },\n body: JSON.stringify({ webIds }),\n });\n\n if (!response.ok) {\n this.logger.warn(`Remote SP WebID lookup failed: HTTP ${response.status}`);\n return [];\n }\n\n const body = await response.json().catch(() => null) as { entries?: RemoteSpWebIdEntry[] } | null;\n if (!Array.isArray(body?.entries)) {\n return [];\n }\n\n const allowedWebIds = new Set(webIds);\n return body.entries\n .filter((entry) => typeof entry.webId === 'string' && allowedWebIds.has(entry.webId))\n .filter((entry) => typeof entry.storageUrl === 'string' && matchesTargetStorage(\n {\n podId: '',\n accountId: '',\n baseUrl: entry.podUrl ?? entry.storageUrl,\n storageUrl: entry.storageUrl,\n },\n target.storageUrl,\n ))\n .map((entry) => ({\n webId: entry.webId,\n storageUrl: ensureTrailingSlash(entry.storageUrl),\n storageMode: deriveStorageMode(entry.webId, entry.storageUrl),\n }));\n }\n}\n\ninterface TargetStorage {\n storageUrl: string;\n lookupUrl?: string;\n serviceToken?: string;\n}\n\ninterface RemoteSpWebIdEntry {\n webId: string;\n podUrl?: string;\n storageUrl: string;\n}\n\nfunction deriveStorageMode(webId: string, storageUrl: string): 'cloud' | 'local' | 'custom' {\n const webIdRoot = deriveStorageRoot(webId);\n const storageRoot = deriveStorageRoot(storageUrl);\n if (!webIdRoot || !storageRoot) {\n return 'custom';\n }\n return webIdRoot === storageRoot ? 'cloud' : 'local';\n}\n\nfunction deriveStorageRoot(url: string): string | undefined {\n try {\n const parsed = new URL(url);\n const segments = parsed.pathname.split('/').filter(Boolean);\n if (segments.length === 0) {\n return ensureTrailingSlash(parsed.origin);\n }\n\n return ensureTrailingSlash(new URL(`/${segments[0]}/`, parsed.origin).toString());\n } catch {\n return undefined;\n }\n}\n\nfunction ensureTrailingSlash(url: string): string {\n return url.replace(/\\/+$/u, '') + '/';\n}\n\nfunction matchesTargetStorage(pod: PodLookupResult, targetStorageUrl: string): boolean {\n const candidateUrls = [pod.storageUrl, pod.baseUrl].filter((value): value is string => typeof value === 'string' && value.length > 0);\n const targetRoot = deriveStorageRoot(targetStorageUrl);\n if (!targetRoot) {\n return false;\n }\n\n for (const candidate of candidateUrls) {\n const candidateRoot = deriveStorageRoot(candidate);\n if (candidateRoot && candidateRoot === targetRoot) {\n return true;\n }\n\n const candidateUrl = ensureTrailingSlash(candidate);\n if (candidateUrl.startsWith(targetRoot) || targetRoot.startsWith(candidateUrl)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction extractProvisionCode(oidcInteraction: JsonInteractionHandlerInput['oidcInteraction']): string | undefined {\n const params = oidcInteraction?.params as Record<string, unknown> | undefined;\n const value = params?.provisionCode;\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n}\n"]}
|