@undefineds.co/xpod 0.3.43 → 0.3.44

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/local.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "@type": "Variable"
27
27
  },
28
28
  {
29
- "comment": "Auto-detect OIDC Handler - 有 oidcIssuer 时代理外部 issuer JWKS,其它 OIDC 路由保持 CSS 本地处理",
29
+ "comment": "Auto-detect OIDC pass-through - 有 oidcIssuer 时也保持本地 CSS OIDC discovery/token/JWKS 同源",
30
30
  "@id": "urn:undefineds:xpod:AutoDetectOidcHandler",
31
31
  "@type": "AutoDetectOidcHandler",
32
32
  "AutoDetectOidcHandler:_options_oidcIssuer": {
@@ -315,7 +315,7 @@
315
315
  "@id": "urn:solid-server:default:StaticAssetHandler"
316
316
  },
317
317
  {
318
- "comment": "Local 模式下代理外部 issuer JWKS;其它 OIDC 路由由 CSS 默认 OIDC handler 处理",
318
+ "comment": "Local 模式下 OIDC discovery/token/JWKS 全部由 CSS 默认 OIDC handler 处理,避免本地 token 与外部 JWKS 错配",
319
319
  "@id": "urn:undefineds:xpod:AutoDetectOidcHandler"
320
320
  },
321
321
  {
package/config/xpod.json CHANGED
@@ -52,7 +52,7 @@
52
52
  }
53
53
  },
54
54
  {
55
- "comment": "Auto-detect OIDC Handler - 有 oidcIssuer 时代理外部 issuer JWKS,其它 OIDC 路由保持 CSS 本地处理",
55
+ "comment": "Auto-detect OIDC pass-through - 有 oidcIssuer 时也保持本地 CSS OIDC discovery/token/JWKS 同源",
56
56
  "@id": "urn:undefineds:xpod:AutoDetectOidcHandler",
57
57
  "@type": "AutoDetectOidcHandler",
58
58
  "options_oidcIssuer": {
@@ -272,7 +272,7 @@
272
272
  { "@id": "urn:undefineds:xpod:AppStaticAssetHandler" },
273
273
  { "@id": "urn:solid-server:default:StaticAssetHandler" },
274
274
  {
275
- "comment": "Local 模式下代理外部 issuer JWKS;其它 OIDC 路由由 CSS 默认 OIDC handler 处理",
275
+ "comment": "Local 模式下 OIDC discovery/token/JWKS 全部由 CSS 默认 OIDC handler 处理,避免本地 token 与外部 JWKS 错配",
276
276
  "@id": "urn:undefineds:xpod:AutoDetectOidcHandler"
277
277
  },
278
278
  { "@id": "urn:solid-server:default:OidcHandler" },
@@ -1,57 +1,44 @@
1
1
  import { HttpHandler, type HttpHandlerInput } from '@solid/community-server';
2
2
  export interface AutoDetectOidcHandlerOptions {
3
- /** External OIDC issuer base URL used as the trust source for Local SP mode. */
3
+ /** External account authority used by Local SP mode. It must not provide local OIDC JWKS. */
4
4
  oidcIssuer?: string;
5
- /** Explanation used when this handler declines non-JWKS OIDC routes. */
5
+ /** Explanation used when this handler declines OIDC routes. */
6
6
  message?: string;
7
- /** JWKS 缓存时间 (ms) */
7
+ /** @deprecated Local OIDC routes must pass through to CSS; this value is ignored. */
8
8
  cacheMs?: number;
9
9
  }
10
10
  /**
11
11
  * Auto-detect OIDC Handler
12
12
  *
13
13
  * 自动检测运行模式:
14
- * - 如果配置了 oidcIssuer -> Local SP 模式:只代理外部 issuer JWKS
14
+ * - 如果配置了 oidcIssuer -> Local SP 模式:OIDC discovery/token/JWKS 仍全部由本地 CSS 处理
15
15
  * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
16
16
  *
17
17
  * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和
18
- * scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回
19
- * Cloud consent 并暴露 Cloud Pod。
18
+ * scoped WebID picker 必须继续由本地 CSS 提供。Cloud 只作为账号密码校验和
19
+ * Cloud WebID/profile 权威;本地 CSS 颁发的 token 必须由本地 JWKS 验证。
20
20
  *
21
21
  * 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
22
22
  */
23
23
  export declare class AutoDetectOidcHandler extends HttpHandler {
24
24
  private readonly logger;
25
25
  private readonly oidcIssuer?;
26
- private readonly jwksUrl?;
27
26
  private readonly message;
28
- private readonly cacheMs;
29
- private jwksCache?;
30
27
  constructor(options?: AutoDetectOidcHandlerOptions);
31
28
  /**
32
29
  * 判断是否处理请求
33
- * - Local SP 模式:只处理 JWKS 请求,其他 OIDC 请求透传给 CSS 本地 OIDC handler
30
+ * - Local SP 模式:所有 OIDC 请求透传给 CSS 本地 OIDC handler
34
31
  * - 标准模式:不处理任何请求(透传给 CSS 默认 Handler)
35
32
  */
36
33
  canHandle({ request }: HttpHandlerInput): Promise<void>;
37
34
  /**
38
- * 处理请求
39
- * - SP 模式:代理 JWKS
40
- * - 标准模式:不应该到达这里
35
+ * 处理请求:该 handler 只用于显式透传,不应实际处理 OIDC 请求。
41
36
  */
42
- handle({ response }: HttpHandlerInput): Promise<void>;
43
- /**
44
- * 获取并缓存 JWKS
45
- */
46
- private fetchJwks;
37
+ handle(): Promise<void>;
47
38
  /**
48
39
  * 检查是否是 OIDC 路径
49
40
  */
50
41
  private isOidcPath;
51
- /**
52
- * 检查是否是 JWKS 路径
53
- */
54
- private isJwksPath;
55
42
  /**
56
43
  * 从 URL 提取 pathname
57
44
  */
@@ -1,19 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutoDetectOidcHandler = void 0;
4
- const node_url_1 = require("node:url");
5
4
  const global_logger_factory_1 = require("global-logger-factory");
6
5
  const community_server_1 = require("@solid/community-server");
7
6
  /**
8
7
  * Auto-detect OIDC Handler
9
8
  *
10
9
  * 自动检测运行模式:
11
- * - 如果配置了 oidcIssuer -> Local SP 模式:只代理外部 issuer JWKS
10
+ * - 如果配置了 oidcIssuer -> Local SP 模式:OIDC discovery/token/JWKS 仍全部由本地 CSS 处理
12
11
  * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
13
12
  *
14
13
  * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和
15
- * scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回
16
- * Cloud consent 并暴露 Cloud Pod。
14
+ * scoped WebID picker 必须继续由本地 CSS 提供。Cloud 只作为账号密码校验和
15
+ * Cloud WebID/profile 权威;本地 CSS 颁发的 token 必须由本地 JWKS 验证。
17
16
  *
18
17
  * 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
19
18
  */
@@ -22,11 +21,9 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
22
21
  super();
23
22
  this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
24
23
  this.oidcIssuer = options.oidcIssuer;
25
- this.jwksUrl = this.oidcIssuer ? `${this.oidcIssuer.replace(/\/$/, '')}/.oidc/jwks` : undefined;
26
24
  this.message = options.message ?? 'OIDC route handled by local CSS OIDC handler';
27
- this.cacheMs = options.cacheMs ?? 300000; // 默认 5 分钟
28
25
  if (this.oidcIssuer) {
29
- this.logger.info(`Local SP mode enabled, external issuer: ${this.oidcIssuer}, JWKS: ${this.jwksUrl}`);
26
+ this.logger.info(`Local SP mode enabled, account issuer: ${this.oidcIssuer}; OIDC routes pass through to local CSS`);
30
27
  }
31
28
  else {
32
29
  this.logger.info('Standard mode enabled, OIDC requests will pass through');
@@ -34,7 +31,7 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
34
31
  }
35
32
  /**
36
33
  * 判断是否处理请求
37
- * - Local SP 模式:只处理 JWKS 请求,其他 OIDC 请求透传给 CSS 本地 OIDC handler
34
+ * - Local SP 模式:所有 OIDC 请求透传给 CSS 本地 OIDC handler
38
35
  * - 标准模式:不处理任何请求(透传给 CSS 默认 Handler)
39
36
  */
40
37
  async canHandle({ request }) {
@@ -43,69 +40,13 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
43
40
  if (!this.isOidcPath(url)) {
44
41
  throw new community_server_1.NotImplementedHttpError('Not an OIDC request');
45
42
  }
46
- // 标准模式:不处理,透传给 CSS 默认 Handler
47
- if (!this.jwksUrl) {
48
- throw new community_server_1.NotImplementedHttpError('Pass through to default OIDC handler');
49
- }
50
- // Local SP 模式:只有 JWKS 请求由这里处理,其它 OIDC 路由交给 CSS 本地 handler
51
- if (!this.isJwksPath(url)) {
52
- throw new community_server_1.NotImplementedHttpError(`Local SP mode: ${this.message}.`);
53
- }
43
+ throw new community_server_1.NotImplementedHttpError(this.message);
54
44
  }
55
45
  /**
56
- * 处理请求
57
- * - SP 模式:代理 JWKS
58
- * - 标准模式:不应该到达这里
46
+ * 处理请求:该 handler 只用于显式透传,不应实际处理 OIDC 请求。
59
47
  */
60
- async handle({ response }) {
61
- // 标准模式:不应该到达这里
62
- if (!this.jwksUrl) {
63
- throw new community_server_1.InternalServerError('AutoDetectOidcHandler should not handle requests in standard mode');
64
- }
65
- try {
66
- const jwks = await this.fetchJwks();
67
- response.statusCode = 200;
68
- response.setHeader('Content-Type', 'application/json');
69
- response.setHeader('Cache-Control', `public, max-age=${Math.floor(this.cacheMs / 1000)}`);
70
- response.end(JSON.stringify(jwks));
71
- this.logger.debug('JWKS proxy successful');
72
- }
73
- catch (error) {
74
- this.logger.error(`JWKS proxy failed: ${error.message}`);
75
- throw new community_server_1.InternalServerError('Failed to proxy JWKS request', { cause: error });
76
- }
77
- }
78
- /**
79
- * 获取并缓存 JWKS
80
- */
81
- async fetchJwks() {
82
- // 检查缓存
83
- if (this.jwksCache && this.jwksCache.expiresAt > Date.now()) {
84
- this.logger.debug('Returning cached JWKS');
85
- return { keys: this.jwksCache.keys };
86
- }
87
- if (!this.jwksUrl) {
88
- throw new Error('External JWKS URL not configured');
89
- }
90
- this.logger.debug(`Fetching JWKS from ${this.jwksUrl}`);
91
- const res = await fetch(this.jwksUrl, {
92
- headers: { Accept: 'application/json' },
93
- });
94
- if (!res.ok) {
95
- throw new Error(`Failed to fetch JWKS: ${res.status} ${res.statusText}`);
96
- }
97
- const jwks = await res.json();
98
- // 验证 JWKS 格式
99
- if (!Array.isArray(jwks.keys)) {
100
- throw new Error('Invalid JWKS format: missing keys array');
101
- }
102
- // 更新缓存
103
- this.jwksCache = {
104
- keys: jwks.keys,
105
- expiresAt: Date.now() + this.cacheMs,
106
- };
107
- this.logger.debug(`JWKS cached with ${jwks.keys.length} keys`);
108
- return jwks;
48
+ async handle() {
49
+ throw new community_server_1.NotImplementedHttpError(this.message);
109
50
  }
110
51
  /**
111
52
  * 检查是否是 OIDC 路径
@@ -117,19 +58,12 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
117
58
  pathname === '/.well-known/oauth-authorization-server' ||
118
59
  pathname.startsWith('/idp/'));
119
60
  }
120
- /**
121
- * 检查是否是 JWKS 路径
122
- */
123
- isJwksPath(url) {
124
- const pathname = this.getPathname(url);
125
- return pathname === '/.oidc/jwks' || pathname === '/.oidc/jwks.json';
126
- }
127
61
  /**
128
62
  * 从 URL 提取 pathname
129
63
  */
130
64
  getPathname(url) {
131
65
  try {
132
- return new node_url_1.URL(url, 'http://localhost').pathname;
66
+ return new URL(url, 'http://localhost').pathname;
133
67
  }
134
68
  catch {
135
69
  // 如果解析失败,直接返回 url(可能是相对路径)
@@ -1 +1 @@
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 /** External OIDC issuer base URL used as the trust source for Local SP mode. */\n oidcIssuer?: string;\n /** Explanation used when this handler declines non-JWKS OIDC routes. */\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 -> Local SP 模式:只代理外部 issuer JWKS\n * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)\n *\n * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和\n * scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回\n * Cloud consent 并暴露 Cloud Pod。\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 route handled by local CSS OIDC handler';\n this.cacheMs = options.cacheMs ?? 300000; // 默认 5 分钟\n\n if (this.oidcIssuer) {\n this.logger.info(`Local SP mode enabled, external issuer: ${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 * - Local SP 模式:只处理 JWKS 请求,其他 OIDC 请求透传给 CSS 本地 OIDC handler\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 // Local SP 模式:只有 JWKS 请求由这里处理,其它 OIDC 路由交给 CSS 本地 handler\n if (!this.isJwksPath(url)) {\n throw new NotImplementedHttpError(\n `Local SP mode: ${this.message}.`\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"]}
1
+ {"version":3,"file":"AutoDetectOidcHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectOidcHandler.ts"],"names":[],"mappings":";;;AAAA,iEAAqD;AACrD,8DAIiC;AAWjC;;;;;;;;;;;;GAYG;AACH,MAAa,qBAAsB,SAAQ,8BAAW;IAKpD,YAAY,UAAwC,EAAE;QACpD,KAAK,EAAE,CAAC;QALO,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAM3C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,8CAA8C,CAAC;QAEjF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,IAAI,CAAC,UAAU,yCAAyC,CAAC,CAAC;QACvH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED;;;;OAIG;IACa,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,EAAoB;QAC3D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;QAE9B,gBAAgB;QAChB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,0CAAuB,CAAC,qBAAqB,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,IAAI,0CAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACa,KAAK,CAAC,MAAM;QAC1B,MAAM,IAAI,0CAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACK,UAAU,CAAC,GAAW;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACvC,OAAO,CACL,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC;YAC9B,QAAQ,KAAK,mCAAmC;YAChD,QAAQ,KAAK,yCAAyC;YACtD,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAC7B,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,WAAW,CAAC,GAAW;QAC7B,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;YAC3B,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;CACF;AAhED,sDAgEC","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport {\n HttpHandler,\n type HttpHandlerInput,\n NotImplementedHttpError,\n} from '@solid/community-server';\n\nexport interface AutoDetectOidcHandlerOptions {\n /** External account authority used by Local SP mode. It must not provide local OIDC JWKS. */\n oidcIssuer?: string;\n /** Explanation used when this handler declines OIDC routes. */\n message?: string;\n /** @deprecated Local OIDC routes must pass through to CSS; this value is ignored. */\n cacheMs?: number;\n}\n\n/**\n * Auto-detect OIDC Handler\n *\n * 自动检测运行模式:\n * - 如果配置了 oidcIssuer -> Local SP 模式:OIDC discovery/token/JWKS 仍全部由本地 CSS 处理\n * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)\n *\n * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和\n * scoped WebID picker 必须继续由本地 CSS 提供。Cloud 只作为账号密码校验和\n * Cloud WebID/profile 权威;本地 CSS 颁发的 token 必须由本地 JWKS 验证。\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 message: string;\n\n constructor(options: AutoDetectOidcHandlerOptions = {}) {\n super();\n this.oidcIssuer = options.oidcIssuer;\n this.message = options.message ?? 'OIDC route handled by local CSS OIDC handler';\n\n if (this.oidcIssuer) {\n this.logger.info(`Local SP mode enabled, account issuer: ${this.oidcIssuer}; OIDC routes pass through to local CSS`);\n } else {\n this.logger.info('Standard mode enabled, OIDC requests will pass through');\n }\n }\n\n /**\n * 判断是否处理请求\n * - Local SP 模式:所有 OIDC 请求透传给 CSS 本地 OIDC handler\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 throw new NotImplementedHttpError(this.message);\n }\n\n /**\n * 处理请求:该 handler 只用于显式透传,不应实际处理 OIDC 请求。\n */\n public override async handle(): Promise<void> {\n throw new NotImplementedHttpError(this.message);\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 * 从 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,7 +12,7 @@
12
12
  "extends": [
13
13
  "css:dist/server/HttpHandler.jsonld#HttpHandler"
14
14
  ],
15
- "comment": "Auto-detect OIDC Handler 自动检测运行模式: - 如果配置了 oidcIssuer -> Local SP 模式:只代理外部 issuer JWKS - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理) 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和 scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回 Cloud consent 并暴露 Cloud Pod。 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler",
15
+ "comment": "Auto-detect OIDC Handler 自动检测运行模式: - 如果配置了 oidcIssuer -> Local SP 模式:OIDC discovery/token/JWKS 仍全部由本地 CSS 处理 - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理) 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和 scoped WebID picker 必须继续由本地 CSS 提供。Cloud 只作为账号密码校验和 Cloud WebID/profile 权威;本地 CSS 颁发的 token 必须由本地 JWKS 验证。 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler",
16
16
  "parameters": [
17
17
  {
18
18
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options_oidcIssuer",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  ]
27
27
  },
28
- "comment": "External OIDC issuer base URL used as the trust source for Local SP mode."
28
+ "comment": "External account authority used by Local SP mode. It must not provide local OIDC JWKS."
29
29
  },
30
30
  {
31
31
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options_message",
@@ -38,7 +38,7 @@
38
38
  }
39
39
  ]
40
40
  },
41
- "comment": "Explanation used when this handler declines non-JWKS OIDC routes."
41
+ "comment": "Explanation used when this handler declines OIDC routes."
42
42
  },
43
43
  {
44
44
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler_options_cacheMs",
@@ -50,8 +50,7 @@
50
50
  "@type": "ParameterRangeUndefined"
51
51
  }
52
52
  ]
53
- },
54
- "comment": "JWKS 缓存时间 (ms)"
53
+ }
55
54
  }
56
55
  ],
57
56
  "memberFields": [
@@ -63,22 +62,10 @@
63
62
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_oidcIssuer",
64
63
  "memberFieldName": "oidcIssuer"
65
64
  },
66
- {
67
- "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_jwksUrl",
68
- "memberFieldName": "jwksUrl"
69
- },
70
65
  {
71
66
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_message",
72
67
  "memberFieldName": "message"
73
68
  },
74
- {
75
- "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_cacheMs",
76
- "memberFieldName": "cacheMs"
77
- },
78
- {
79
- "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_jwksCache",
80
- "memberFieldName": "jwksCache"
81
- },
82
69
  {
83
70
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_constructor",
84
71
  "memberFieldName": "constructor"
@@ -91,18 +78,10 @@
91
78
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_handle",
92
79
  "memberFieldName": "handle"
93
80
  },
94
- {
95
- "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_fetchJwks",
96
- "memberFieldName": "fetchJwks"
97
- },
98
81
  {
99
82
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_isOidcPath",
100
83
  "memberFieldName": "isOidcPath"
101
84
  },
102
- {
103
- "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_isJwksPath",
104
- "memberFieldName": "isJwksPath"
105
- },
106
85
  {
107
86
  "@id": "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld#AutoDetectOidcHandler__member_getPathname",
108
87
  "memberFieldName": "getPathname"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",