@undefineds.co/xpod 0.3.15 → 0.3.16

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.
Files changed (68) hide show
  1. package/config/local.json +5 -5
  2. package/config/xpod.json +24 -10
  3. package/dist/cli/commands/auth.d.ts +1 -0
  4. package/dist/cli/commands/auth.js +117 -37
  5. package/dist/cli/commands/auth.js.map +1 -1
  6. package/dist/cli/commands/login.js +16 -23
  7. package/dist/cli/commands/login.js.map +1 -1
  8. package/dist/cli/commands/logs.d.ts +2 -0
  9. package/dist/cli/commands/logs.js +20 -5
  10. package/dist/cli/commands/logs.js.map +1 -1
  11. package/dist/cli/commands/obj.d.ts +44 -0
  12. package/dist/cli/commands/obj.js +1059 -0
  13. package/dist/cli/commands/obj.js.map +1 -0
  14. package/dist/cli/commands/rdf.d.ts +14 -0
  15. package/dist/cli/commands/rdf.js +235 -0
  16. package/dist/cli/commands/rdf.js.map +1 -0
  17. package/dist/cli/commands/resource.d.ts +31 -0
  18. package/dist/cli/commands/resource.js +191 -0
  19. package/dist/cli/commands/resource.js.map +1 -0
  20. package/dist/cli/commands/secret.d.ts +36 -0
  21. package/dist/cli/commands/secret.js +285 -0
  22. package/dist/cli/commands/secret.js.map +1 -0
  23. package/dist/cli/commands/server.d.ts +11 -0
  24. package/dist/cli/commands/server.js +168 -0
  25. package/dist/cli/commands/server.js.map +1 -0
  26. package/dist/cli/commands/start.d.ts +1 -0
  27. package/dist/cli/commands/start.js +5 -0
  28. package/dist/cli/commands/start.js.map +1 -1
  29. package/dist/cli/commands/status.d.ts +1 -0
  30. package/dist/cli/commands/status.js +21 -6
  31. package/dist/cli/commands/status.js.map +1 -1
  32. package/dist/cli/commands/stop.d.ts +3 -0
  33. package/dist/cli/commands/stop.js +40 -6
  34. package/dist/cli/commands/stop.js.map +1 -1
  35. package/dist/cli/index.js +23 -8
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/lib/auth-context.d.ts +24 -0
  38. package/dist/cli/lib/auth-context.js +70 -0
  39. package/dist/cli/lib/auth-context.js.map +1 -0
  40. package/dist/cli/lib/output.d.ts +23 -0
  41. package/dist/cli/lib/output.js +63 -0
  42. package/dist/cli/lib/output.js.map +1 -0
  43. package/dist/cli/lib/resource.d.ts +29 -0
  44. package/dist/cli/lib/resource.js +114 -0
  45. package/dist/cli/lib/resource.js.map +1 -0
  46. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +11 -10
  47. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +13 -24
  48. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  49. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +4 -4
  50. package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +8 -4
  51. package/dist/identity/oidc/AutoDetectOidcHandler.js +10 -6
  52. package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
  53. package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +3 -3
  54. package/dist/storage/accessors/MixDataAccessor.js +3 -0
  55. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  56. package/dist/storage/quint/SqliteQuintStore.d.ts +26 -1
  57. package/dist/storage/quint/SqliteQuintStore.js +551 -318
  58. package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
  59. package/dist/storage/quint/SqliteQuintStore.jsonld +102 -2
  60. package/dist/storage/quint/schema.d.ts +76 -0
  61. package/dist/storage/quint/schema.js +13 -7
  62. package/dist/storage/quint/schema.js.map +1 -1
  63. package/dist/storage/sparql/ComunicaQuintEngine.js +16 -3
  64. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  65. package/package.json +1 -1
  66. package/dist/cli/commands/config.d.ts +0 -42
  67. package/dist/cli/commands/config.js +0 -289
  68. package/dist/cli/commands/config.js.map +0 -1
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeBaseUrl = normalizeBaseUrl;
4
+ exports.resolvePodRootFromWebId = resolvePodRootFromWebId;
5
+ exports.getStoredAuthStatus = getStoredAuthStatus;
6
+ exports.requireAuthContext = requireAuthContext;
7
+ exports.authFetch = authFetch;
8
+ const solid_auth_1 = require("./solid-auth");
9
+ const credentials_store_1 = require("./credentials-store");
10
+ const output_1 = require("./output");
11
+ function normalizeBaseUrl(url) {
12
+ return url.endsWith('/') ? url : `${url}/`;
13
+ }
14
+ function resolvePodRootFromWebId(webId) {
15
+ const webIdUrl = new URL(webId);
16
+ const path = webIdUrl.pathname;
17
+ const profileSuffix = '/profile/card';
18
+ if (path.endsWith(profileSuffix)) {
19
+ const podPath = path.slice(0, -profileSuffix.length);
20
+ return `${webIdUrl.origin}${podPath.endsWith('/') ? podPath : `${podPath}/`}`;
21
+ }
22
+ const pathParts = path.split('/').filter(Boolean);
23
+ if (pathParts.length > 0) {
24
+ return `${webIdUrl.origin}/${pathParts[0]}/`;
25
+ }
26
+ return `${webIdUrl.origin}/`;
27
+ }
28
+ function getStoredAuthStatus(urlOverride) {
29
+ const credentials = (0, credentials_store_1.loadCredentials)();
30
+ if (!credentials) {
31
+ return { authenticated: false };
32
+ }
33
+ const baseUrl = normalizeBaseUrl(urlOverride ?? credentials.url);
34
+ const podRoot = resolvePodRootFromWebId(credentials.webId);
35
+ return {
36
+ authenticated: true,
37
+ authType: credentials.authType,
38
+ baseUrl,
39
+ webId: credentials.webId,
40
+ podRoot,
41
+ };
42
+ }
43
+ async function requireAuthContext(options = {}) {
44
+ const credentials = (0, credentials_store_1.loadCredentials)();
45
+ if (!credentials) {
46
+ throw new output_1.CliCommandError('auth_required', 'No credentials found. Run `xpod auth login` first.', 2);
47
+ }
48
+ const clientCredentials = (0, credentials_store_1.getClientCredentials)(credentials);
49
+ if (!clientCredentials) {
50
+ throw new output_1.CliCommandError('auth_unsupported', 'Stored OAuth credentials are not supported for CLI resource operations yet. Run `xpod auth login` to create client credentials.', 2);
51
+ }
52
+ const baseUrl = normalizeBaseUrl(options.url ?? credentials.url);
53
+ const tokenResult = await (0, solid_auth_1.getAccessToken)(clientCredentials.clientId, clientCredentials.clientSecret, baseUrl);
54
+ if (!tokenResult) {
55
+ throw new output_1.CliCommandError('auth_failed', 'Failed to obtain an access token. Run `xpod auth login` again.', 2);
56
+ }
57
+ const podRoot = resolvePodRootFromWebId(credentials.webId);
58
+ return {
59
+ baseUrl,
60
+ webId: credentials.webId,
61
+ podRoot,
62
+ baseIri: podRoot,
63
+ accessToken: tokenResult.accessToken,
64
+ credentials,
65
+ };
66
+ }
67
+ async function authFetch(context, url, init) {
68
+ return (0, solid_auth_1.authenticatedFetch)(url, context.accessToken, init);
69
+ }
70
+ //# sourceMappingURL=auth-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-context.js","sourceRoot":"","sources":["../../../src/cli/lib/auth-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAccessToken, authenticatedFetch } from './solid-auth';\nimport {\n getClientCredentials,\n loadCredentials,\n type StoredCredentials,\n} from './credentials-store';\nimport { CliCommandError } from './output';\n\nexport interface CliAuthContext {\n baseUrl: string;\n webId: string;\n podRoot: string;\n baseIri: string;\n accessToken: string;\n credentials: StoredCredentials;\n}\n\nexport interface AuthStatus {\n authenticated: boolean;\n authType?: string;\n baseUrl?: string;\n webId?: string;\n podRoot?: string;\n}\n\nexport function normalizeBaseUrl(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\nexport function resolvePodRootFromWebId(webId: string): string {\n const webIdUrl = new URL(webId);\n const path = webIdUrl.pathname;\n const profileSuffix = '/profile/card';\n if (path.endsWith(profileSuffix)) {\n const podPath = path.slice(0, -profileSuffix.length);\n return `${webIdUrl.origin}${podPath.endsWith('/') ? podPath : `${podPath}/`}`;\n }\n\n const pathParts = path.split('/').filter(Boolean);\n if (pathParts.length > 0) {\n return `${webIdUrl.origin}/${pathParts[0]}/`;\n }\n return `${webIdUrl.origin}/`;\n}\n\nexport function getStoredAuthStatus(urlOverride?: string): AuthStatus {\n const credentials = loadCredentials();\n if (!credentials) {\n return { authenticated: false };\n }\n\n const baseUrl = normalizeBaseUrl(urlOverride ?? credentials.url);\n const podRoot = resolvePodRootFromWebId(credentials.webId);\n return {\n authenticated: true,\n authType: credentials.authType,\n baseUrl,\n webId: credentials.webId,\n podRoot,\n };\n}\n\nexport async function requireAuthContext(options: {\n url?: string;\n json?: boolean;\n} = {}): Promise<CliAuthContext> {\n const credentials = loadCredentials();\n if (!credentials) {\n throw new CliCommandError(\n 'auth_required',\n 'No credentials found. Run `xpod auth login` first.',\n 2,\n );\n }\n\n const clientCredentials = getClientCredentials(credentials);\n if (!clientCredentials) {\n throw new CliCommandError(\n 'auth_unsupported',\n 'Stored OAuth credentials are not supported for CLI resource operations yet. Run `xpod auth login` to create client credentials.',\n 2,\n );\n }\n\n const baseUrl = normalizeBaseUrl(options.url ?? credentials.url);\n const tokenResult = await getAccessToken(\n clientCredentials.clientId,\n clientCredentials.clientSecret,\n baseUrl,\n );\n if (!tokenResult) {\n throw new CliCommandError(\n 'auth_failed',\n 'Failed to obtain an access token. Run `xpod auth login` again.',\n 2,\n );\n }\n\n const podRoot = resolvePodRootFromWebId(credentials.webId);\n return {\n baseUrl,\n webId: credentials.webId,\n podRoot,\n baseIri: podRoot,\n accessToken: tokenResult.accessToken,\n credentials,\n };\n}\n\nexport async function authFetch(\n context: CliAuthContext,\n url: string,\n init?: RequestInit,\n): Promise<Response> {\n return authenticatedFetch(url, context.accessToken, init);\n}\n"]}
@@ -0,0 +1,23 @@
1
+ export interface CliEnvelope<T = unknown> {
2
+ ok: boolean;
3
+ code: string;
4
+ data?: T;
5
+ message?: string;
6
+ warnings: string[];
7
+ items?: unknown[];
8
+ }
9
+ export declare class CliCommandError extends Error {
10
+ readonly code: string;
11
+ readonly exitCode: number;
12
+ readonly data?: unknown;
13
+ constructor(code: string, message: string, exitCode?: number, data?: unknown);
14
+ }
15
+ export declare function ok<T>(data: T, code?: string, warnings?: string[]): CliEnvelope<T>;
16
+ export declare function fail(code: string, message: string, warnings?: string[], data?: unknown): CliEnvelope;
17
+ export declare function writeJson(result: CliEnvelope): void;
18
+ export declare function writeJsonItems(items: unknown[], code: string, warnings?: string[]): void;
19
+ export declare function writeJsonResult<T>(data: T, code?: string, warnings?: string[]): void;
20
+ export declare function isJsonMode(argv: {
21
+ json?: boolean;
22
+ }): boolean;
23
+ export declare function handleCliError(error: unknown, json: boolean, fallbackCode?: string): never;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliCommandError = void 0;
4
+ exports.ok = ok;
5
+ exports.fail = fail;
6
+ exports.writeJson = writeJson;
7
+ exports.writeJsonItems = writeJsonItems;
8
+ exports.writeJsonResult = writeJsonResult;
9
+ exports.isJsonMode = isJsonMode;
10
+ exports.handleCliError = handleCliError;
11
+ class CliCommandError extends Error {
12
+ constructor(code, message, exitCode = 1, data) {
13
+ super(message);
14
+ this.name = 'CliCommandError';
15
+ this.code = code;
16
+ this.exitCode = exitCode;
17
+ this.data = data;
18
+ }
19
+ }
20
+ exports.CliCommandError = CliCommandError;
21
+ function ok(data, code = 'ok', warnings = []) {
22
+ return { ok: true, code, data, warnings };
23
+ }
24
+ function fail(code, message, warnings = [], data) {
25
+ return { ok: false, code, message, warnings, ...(data === undefined ? {} : { data }) };
26
+ }
27
+ function writeJson(result) {
28
+ console.log(JSON.stringify(result, null, 2));
29
+ }
30
+ function writeJsonItems(items, code, warnings = []) {
31
+ const ok = items.every((item) => {
32
+ if (!item || typeof item !== 'object' || !('ok' in item)) {
33
+ return false;
34
+ }
35
+ return item.ok === true;
36
+ });
37
+ writeJson({ ok, code, items, warnings });
38
+ }
39
+ function writeJsonResult(data, code = 'ok', warnings = []) {
40
+ writeJson(ok(data, code, warnings));
41
+ }
42
+ function isJsonMode(argv) {
43
+ return argv.json === true;
44
+ }
45
+ function shouldForceStructuredError(error) {
46
+ return error instanceof CliCommandError &&
47
+ error.code === 'auth_required' &&
48
+ (process.env.CI === 'true' || !process.stdout.isTTY);
49
+ }
50
+ function handleCliError(error, json, fallbackCode = 'error') {
51
+ const err = error instanceof Error ? error : new Error(String(error));
52
+ if (json || shouldForceStructuredError(err)) {
53
+ if (err instanceof CliCommandError) {
54
+ writeJson(fail(err.code, err.message, [], err.data));
55
+ process.exit(err.exitCode);
56
+ }
57
+ writeJson(fail(fallbackCode, err.message));
58
+ process.exit(1);
59
+ }
60
+ console.error(err.message);
61
+ process.exit(err instanceof CliCommandError ? err.exitCode : 1);
62
+ }
63
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/cli/lib/output.ts"],"names":[],"mappings":";;;AAuBA,gBAEC;AAED,oBAEC;AAED,8BAEC;AAED,wCAQC;AAED,0CAEC;AAED,gCAEC;AAQD,wCAaC;AA/DD,MAAa,eAAgB,SAAQ,KAAK;IAKxC,YAAmB,IAAY,EAAE,OAAe,EAAE,QAAQ,GAAG,CAAC,EAAE,IAAc;QAC5E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAZD,0CAYC;AAED,SAAgB,EAAE,CAAI,IAAO,EAAE,IAAI,GAAG,IAAI,EAAE,WAAqB,EAAE;IACjE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5C,CAAC;AAED,SAAgB,IAAI,CAAC,IAAY,EAAE,OAAe,EAAE,WAAqB,EAAE,EAAE,IAAc;IACzF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,SAAgB,SAAS,CAAC,MAAmB;IAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,SAAgB,cAAc,CAAC,KAAgB,EAAE,IAAY,EAAE,WAAqB,EAAE;IACpF,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;QAC9B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACzD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAQ,IAAyB,CAAC,EAAE,KAAK,IAAI,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,SAAgB,eAAe,CAAI,IAAO,EAAE,IAAI,GAAG,IAAI,EAAE,WAAqB,EAAE;IAC9E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,UAAU,CAAC,IAAwB;IACjD,OAAO,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;AAC5B,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAY;IAC9C,OAAO,KAAK,YAAY,eAAe;QACrC,KAAK,CAAC,IAAI,KAAK,eAAe;QAC9B,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,SAAgB,cAAc,CAAC,KAAc,EAAE,IAAa,EAAE,YAAY,GAAG,OAAO;IAClF,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,IAAI,IAAI,IAAI,0BAA0B,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;YACnC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,GAAG,YAAY,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC","sourcesContent":["export interface CliEnvelope<T = unknown> {\n ok: boolean;\n code: string;\n data?: T;\n message?: string;\n warnings: string[];\n items?: unknown[];\n}\n\nexport class CliCommandError extends Error {\n public readonly code: string;\n public readonly exitCode: number;\n public readonly data?: unknown;\n\n public constructor(code: string, message: string, exitCode = 1, data?: unknown) {\n super(message);\n this.name = 'CliCommandError';\n this.code = code;\n this.exitCode = exitCode;\n this.data = data;\n }\n}\n\nexport function ok<T>(data: T, code = 'ok', warnings: string[] = []): CliEnvelope<T> {\n return { ok: true, code, data, warnings };\n}\n\nexport function fail(code: string, message: string, warnings: string[] = [], data?: unknown): CliEnvelope {\n return { ok: false, code, message, warnings, ...(data === undefined ? {} : { data }) };\n}\n\nexport function writeJson(result: CliEnvelope): void {\n console.log(JSON.stringify(result, null, 2));\n}\n\nexport function writeJsonItems(items: unknown[], code: string, warnings: string[] = []): void {\n const ok = items.every((item) => {\n if (!item || typeof item !== 'object' || !('ok' in item)) {\n return false;\n }\n return (item as { ok?: unknown }).ok === true;\n });\n writeJson({ ok, code, items, warnings });\n}\n\nexport function writeJsonResult<T>(data: T, code = 'ok', warnings: string[] = []): void {\n writeJson(ok(data, code, warnings));\n}\n\nexport function isJsonMode(argv: { json?: boolean }): boolean {\n return argv.json === true;\n}\n\nfunction shouldForceStructuredError(error: Error): boolean {\n return error instanceof CliCommandError &&\n error.code === 'auth_required' &&\n (process.env.CI === 'true' || !process.stdout.isTTY);\n}\n\nexport function handleCliError(error: unknown, json: boolean, fallbackCode = 'error'): never {\n const err = error instanceof Error ? error : new Error(String(error));\n if (json || shouldForceStructuredError(err)) {\n if (err instanceof CliCommandError) {\n writeJson(fail(err.code, err.message, [], err.data));\n process.exit(err.exitCode);\n }\n writeJson(fail(fallbackCode, err.message));\n process.exit(1);\n }\n\n console.error(err.message);\n process.exit(err instanceof CliCommandError ? err.exitCode : 1);\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import type { CliAuthContext } from './auth-context';
2
+ export interface ResourceTarget {
3
+ input: string;
4
+ resourceUrl: string;
5
+ webId: string;
6
+ podRoot: string;
7
+ baseIri: string;
8
+ }
9
+ export interface ResourceResponseData {
10
+ webId: string;
11
+ podRoot: string;
12
+ baseIri: string;
13
+ resourceUrl: string;
14
+ status: number;
15
+ statusText: string;
16
+ headers: Record<string, string>;
17
+ }
18
+ export declare function resolveResourceTarget(context: CliAuthContext, input: string): ResourceTarget;
19
+ export declare function responseHeaders(response: Response): Record<string, string>;
20
+ export declare function responseData(target: ResourceTarget, response: Response): ResourceResponseData;
21
+ export declare function ensureOk(response: Response, code: string, message: string): void;
22
+ export declare function contentTypeForPath(filePath: string): string;
23
+ export declare function readBodyFile(filePath: string): {
24
+ body: Buffer;
25
+ contentType: string;
26
+ };
27
+ export declare function fetchResource(context: CliAuthContext, target: ResourceTarget, init: RequestInit): Promise<Response>;
28
+ export declare function parseContainedResources(turtle: string, containerUrl: string): string[];
29
+ export declare function relativeToPodRoot(resourceUrl: string, podRoot: string): string;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveResourceTarget = resolveResourceTarget;
4
+ exports.responseHeaders = responseHeaders;
5
+ exports.responseData = responseData;
6
+ exports.ensureOk = ensureOk;
7
+ exports.contentTypeForPath = contentTypeForPath;
8
+ exports.readBodyFile = readBodyFile;
9
+ exports.fetchResource = fetchResource;
10
+ exports.parseContainedResources = parseContainedResources;
11
+ exports.relativeToPodRoot = relativeToPodRoot;
12
+ const fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const auth_context_1 = require("./auth-context");
15
+ const output_1 = require("./output");
16
+ function resolveResourceTarget(context, input) {
17
+ const trimmed = input.trim();
18
+ if (!trimmed) {
19
+ throw new output_1.CliCommandError('invalid_path', 'Resource path is required.');
20
+ }
21
+ const resourceUrl = /^https?:\/\//i.test(trimmed)
22
+ ? new URL(trimmed).toString()
23
+ : new URL(trimmed.replace(/^\/+/, ''), context.podRoot).toString();
24
+ return {
25
+ input,
26
+ resourceUrl,
27
+ webId: context.webId,
28
+ podRoot: context.podRoot,
29
+ baseIri: context.baseIri,
30
+ };
31
+ }
32
+ function responseHeaders(response) {
33
+ const headers = {};
34
+ response.headers.forEach((value, key) => {
35
+ const lower = key.toLowerCase();
36
+ if (lower === 'authorization' || lower === 'cookie' || lower === 'set-cookie') {
37
+ headers[key] = '[redacted]';
38
+ }
39
+ else {
40
+ headers[key] = value;
41
+ }
42
+ });
43
+ return headers;
44
+ }
45
+ function responseData(target, response) {
46
+ return {
47
+ webId: target.webId,
48
+ podRoot: target.podRoot,
49
+ baseIri: target.baseIri,
50
+ resourceUrl: target.resourceUrl,
51
+ status: response.status,
52
+ statusText: response.statusText,
53
+ headers: responseHeaders(response),
54
+ };
55
+ }
56
+ function ensureOk(response, code, message) {
57
+ if (!response.ok) {
58
+ throw new output_1.CliCommandError(code, `${message}: HTTP ${response.status} ${response.statusText}`, 1, {
59
+ status: response.status,
60
+ statusText: response.statusText,
61
+ });
62
+ }
63
+ }
64
+ function contentTypeForPath(filePath) {
65
+ switch ((0, path_1.extname)(filePath).toLowerCase()) {
66
+ case '.ttl':
67
+ return 'text/turtle';
68
+ case '.json':
69
+ case '.jsonld':
70
+ return 'application/ld+json';
71
+ case '.txt':
72
+ case '.md':
73
+ return 'text/plain';
74
+ case '.html':
75
+ return 'text/html';
76
+ case '.png':
77
+ return 'image/png';
78
+ case '.jpg':
79
+ case '.jpeg':
80
+ return 'image/jpeg';
81
+ default:
82
+ return 'application/octet-stream';
83
+ }
84
+ }
85
+ function readBodyFile(filePath) {
86
+ return {
87
+ body: (0, fs_1.readFileSync)(filePath),
88
+ contentType: contentTypeForPath(filePath),
89
+ };
90
+ }
91
+ async function fetchResource(context, target, init) {
92
+ return (0, auth_context_1.authFetch)(context, target.resourceUrl, init);
93
+ }
94
+ function parseContainedResources(turtle, containerUrl) {
95
+ const resources = new Set();
96
+ const containsPattern = /(?:ldp:contains|<http:\/\/www\.w3\.org\/ns\/ldp#contains>)\s+((?:<[^>]+>\s*,?\s*)+)/g;
97
+ let match;
98
+ while ((match = containsPattern.exec(turtle)) !== null) {
99
+ const block = match[1] ?? '';
100
+ const iriPattern = /<([^>]+)>/g;
101
+ let iriMatch;
102
+ while ((iriMatch = iriPattern.exec(block)) !== null) {
103
+ const iri = iriMatch[1];
104
+ if (iri && iri !== containerUrl) {
105
+ resources.add(new URL(iri, containerUrl).toString());
106
+ }
107
+ }
108
+ }
109
+ return Array.from(resources).sort();
110
+ }
111
+ function relativeToPodRoot(resourceUrl, podRoot) {
112
+ return resourceUrl.startsWith(podRoot) ? resourceUrl.slice(podRoot.length) : resourceUrl;
113
+ }
114
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../../../src/cli/lib/resource.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'fs';\nimport { extname } from 'path';\nimport type { CliAuthContext } from './auth-context';\nimport { authFetch } from './auth-context';\nimport { CliCommandError } from './output';\n\nexport interface ResourceTarget {\n input: string;\n resourceUrl: string;\n webId: string;\n podRoot: string;\n baseIri: string;\n}\n\nexport interface ResourceResponseData {\n webId: string;\n podRoot: string;\n baseIri: string;\n resourceUrl: string;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n}\n\nexport function resolveResourceTarget(context: CliAuthContext, input: string): ResourceTarget {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new CliCommandError('invalid_path', 'Resource path is required.');\n }\n\n const resourceUrl = /^https?:\\/\\//i.test(trimmed)\n ? new URL(trimmed).toString()\n : new URL(trimmed.replace(/^\\/+/, ''), context.podRoot).toString();\n\n return {\n input,\n resourceUrl,\n webId: context.webId,\n podRoot: context.podRoot,\n baseIri: context.baseIri,\n };\n}\n\nexport function responseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower === 'authorization' || lower === 'cookie' || lower === 'set-cookie') {\n headers[key] = '[redacted]';\n } else {\n headers[key] = value;\n }\n });\n return headers;\n}\n\nexport function responseData(target: ResourceTarget, response: Response): ResourceResponseData {\n return {\n webId: target.webId,\n podRoot: target.podRoot,\n baseIri: target.baseIri,\n resourceUrl: target.resourceUrl,\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders(response),\n };\n}\n\nexport function ensureOk(response: Response, code: string, message: string): void {\n if (!response.ok) {\n throw new CliCommandError(code, `${message}: HTTP ${response.status} ${response.statusText}`, 1, {\n status: response.status,\n statusText: response.statusText,\n });\n }\n}\n\nexport function contentTypeForPath(filePath: string): string {\n switch (extname(filePath).toLowerCase()) {\n case '.ttl':\n return 'text/turtle';\n case '.json':\n case '.jsonld':\n return 'application/ld+json';\n case '.txt':\n case '.md':\n return 'text/plain';\n case '.html':\n return 'text/html';\n case '.png':\n return 'image/png';\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg';\n default:\n return 'application/octet-stream';\n }\n}\n\nexport function readBodyFile(filePath: string): { body: Buffer; contentType: string } {\n return {\n body: readFileSync(filePath),\n contentType: contentTypeForPath(filePath),\n };\n}\n\nexport async function fetchResource(\n context: CliAuthContext,\n target: ResourceTarget,\n init: RequestInit,\n): Promise<Response> {\n return authFetch(context, target.resourceUrl, init);\n}\n\nexport function parseContainedResources(turtle: string, containerUrl: string): string[] {\n const resources = new Set<string>();\n const containsPattern = /(?:ldp:contains|<http:\\/\\/www\\.w3\\.org\\/ns\\/ldp#contains>)\\s+((?:<[^>]+>\\s*,?\\s*)+)/g;\n let match: RegExpExecArray | null;\n while ((match = containsPattern.exec(turtle)) !== null) {\n const block = match[1] ?? '';\n const iriPattern = /<([^>]+)>/g;\n let iriMatch: RegExpExecArray | null;\n while ((iriMatch = iriPattern.exec(block)) !== null) {\n const iri = iriMatch[1];\n if (iri && iri !== containerUrl) {\n resources.add(new URL(iri, containerUrl).toString());\n }\n }\n }\n return Array.from(resources).sort();\n}\n\nexport function relativeToPodRoot(resourceUrl: string, podRoot: string): string {\n return resourceUrl.startsWith(podRoot) ? resourceUrl.slice(podRoot.length) : resourceUrl;\n}\n"]}
@@ -1,18 +1,19 @@
1
1
  import { HttpHandler, type HttpHandlerInput } from '@solid/community-server';
2
2
  export interface AutoDetectIdentityProviderHandlerOptions {
3
- /** 外部 IdP 的基础 URL,如果提供则启用 SP 模式 */
3
+ /** 外部 IdP 的基础 URL,如果提供则启用 Local SP mode. */
4
4
  oidcIssuer?: string;
5
- /** 禁用时的消息 */
5
+ /** Message used when no source handler is available. */
6
6
  message?: string;
7
- /** CSS 默认的 IdentityProviderHandler,标准模式下委托给它 */
7
+ /** CSS IdentityProviderHandler that owns account, consent and WebID selection routes. */
8
8
  source?: HttpHandler;
9
9
  }
10
10
  /**
11
11
  * Auto-detect Identity Provider Handler
12
12
  *
13
- * 自动检测运行模式:
14
- * - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理
15
- * - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler
13
+ * Local SP mode still needs the local `/.account/*` surface: CSS keeps the
14
+ * OIDC interaction and the scoped WebID picker here, while token validation can
15
+ * trust the configured external issuer. Disabling this surface makes LinX fall
16
+ * back to the Cloud issuer and lets Cloud Pods leak into a Local login flow.
16
17
  */
17
18
  export declare class AutoDetectIdentityProviderHandler extends HttpHandler {
18
19
  private readonly logger;
@@ -22,14 +23,14 @@ export declare class AutoDetectIdentityProviderHandler extends HttpHandler {
22
23
  constructor(options?: AutoDetectIdentityProviderHandlerOptions);
23
24
  /**
24
25
  * 判断是否处理请求
25
- * - SP 模式:拒绝所有 IdP 请求
26
- * - 标准模式:委托给 source Handler
26
+ * - Local SP mode: delegate local account/consent routes to source Handler
27
+ * - Standard mode: delegate to source Handler
27
28
  */
28
29
  canHandle(input: HttpHandlerInput): Promise<void>;
29
30
  /**
30
31
  * 处理请求
31
- * - SP 模式:不应该到达这里
32
- * - 标准模式:委托给 source Handler
32
+ * - Local SP mode: delegate to source Handler so consent remains scoped by SP
33
+ * - Standard mode: delegate to source Handler
33
34
  */
34
35
  handle(input: HttpHandlerInput): Promise<void>;
35
36
  /**
@@ -6,28 +6,29 @@ const community_server_1 = require("@solid/community-server");
6
6
  /**
7
7
  * Auto-detect Identity Provider Handler
8
8
  *
9
- * 自动检测运行模式:
10
- * - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理
11
- * - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler
9
+ * Local SP mode still needs the local `/.account/*` surface: CSS keeps the
10
+ * OIDC interaction and the scoped WebID picker here, while token validation can
11
+ * trust the configured external issuer. Disabling this surface makes LinX fall
12
+ * back to the Cloud issuer and lets Cloud Pods leak into a Local login flow.
12
13
  */
13
14
  class AutoDetectIdentityProviderHandler extends community_server_1.HttpHandler {
14
15
  constructor(options = {}) {
15
16
  super();
16
17
  this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
17
18
  this.oidcIssuer = options.oidcIssuer;
18
- this.message = options.message ?? 'Account management handled by external IdP';
19
+ this.message = options.message ?? 'No source IdentityProviderHandler configured';
19
20
  this.source = options.source;
20
21
  if (this.oidcIssuer) {
21
- this.logger.info(`SP mode enabled: ${this.message}, external IdP: ${this.oidcIssuer}`);
22
+ this.logger.info(`Local SP mode enabled: account and consent routes stay local, external issuer: ${this.oidcIssuer}`);
22
23
  }
23
24
  else {
24
- this.logger.info('Standard mode enabled, delegating to source IdentityProviderHandler');
25
+ this.logger.info('Standard mode enabled: delegating identity routes to source IdentityProviderHandler');
25
26
  }
26
27
  }
27
28
  /**
28
29
  * 判断是否处理请求
29
- * - SP 模式:拒绝所有 IdP 请求
30
- * - 标准模式:委托给 source Handler
30
+ * - Local SP mode: delegate local account/consent routes to source Handler
31
+ * - Standard mode: delegate to source Handler
31
32
  */
32
33
  async canHandle(input) {
33
34
  const url = input.request.url ?? '';
@@ -35,36 +36,24 @@ class AutoDetectIdentityProviderHandler extends community_server_1.HttpHandler {
35
36
  if (!this.isIdpPath(url)) {
36
37
  throw new community_server_1.NotImplementedHttpError('Not an IdP request');
37
38
  }
38
- // SP 模式:接受 IdP 路径请求,在 handle 中返回 404
39
- if (this.oidcIssuer) {
40
- return;
41
- }
42
- // 标准模式:委托给 source Handler
43
39
  if (this.source) {
44
40
  await this.source.canHandle(input);
45
41
  }
46
42
  else {
47
- throw new community_server_1.NotImplementedHttpError('No source IdentityProviderHandler configured');
43
+ throw new community_server_1.NotImplementedHttpError(this.message);
48
44
  }
49
45
  }
50
46
  /**
51
47
  * 处理请求
52
- * - SP 模式:不应该到达这里
53
- * - 标准模式:委托给 source Handler
48
+ * - Local SP mode: delegate to source Handler so consent remains scoped by SP
49
+ * - Standard mode: delegate to source Handler
54
50
  */
55
51
  async handle(input) {
56
- if (this.oidcIssuer) {
57
- const { response } = input;
58
- response.writeHead(404, { 'Content-Type': 'application/json' });
59
- response.end(JSON.stringify({ error: this.message }));
60
- return;
61
- }
62
- // 标准模式:委托给 source Handler
63
52
  if (this.source) {
64
53
  await this.source.handle(input);
65
54
  }
66
55
  else {
67
- throw new community_server_1.NotImplementedHttpError('No source IdentityProviderHandler configured');
56
+ throw new community_server_1.NotImplementedHttpError(this.message);
68
57
  }
69
58
  }
70
59
  /**
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"AutoDetectIdentityProviderHandler.js","sourceRoot":"","sources":["../../../src/identity/oidc/AutoDetectIdentityProviderHandler.ts"],"names":[],"mappings":";;;AAAA,iEAAqD;AACrD,8DAIiC;AAWjC;;;;;;;GAOG;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,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,8CAA8C,CAAC;QACjF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAE7B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kFAAkF,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACxH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qFAAqF,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;IAED;;;;OAIG;IACa,KAAK,CAAC,SAAS,CAAC,KAAuB;QACrD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;QAEpC,eAAe;QACf,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,0CAAuB,CAAC,oBAAoB,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,0CAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACa,KAAK,CAAC,MAAM,CAAC,KAAuB;QAClD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,0CAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,SAAS,CAAC,GAAW;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACvC,OAAO,CACL,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;YAC5B,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC;YACjC,QAAQ,KAAK,WAAW;YACxB,QAAQ,KAAK,QAAQ;YACrB,QAAQ,KAAK,SAAS,CACvB,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,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;CACF;AA5ED,8EA4EC","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,如果提供则启用 Local SP mode. */\n oidcIssuer?: string;\n /** Message used when no source handler is available. */\n message?: string;\n /** CSS IdentityProviderHandler that owns account, consent and WebID selection routes. */\n source?: HttpHandler;\n}\n\n/**\n * Auto-detect Identity Provider Handler\n *\n * Local SP mode still needs the local `/.account/*` surface: CSS keeps the\n * OIDC interaction and the scoped WebID picker here, while token validation can\n * trust the configured external issuer. Disabling this surface makes LinX fall\n * back to the Cloud issuer and lets Cloud Pods leak into a Local login flow.\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 ?? 'No source IdentityProviderHandler configured';\n this.source = options.source;\n\n if (this.oidcIssuer) {\n this.logger.info(`Local SP mode enabled: account and consent routes stay local, external issuer: ${this.oidcIssuer}`);\n } else {\n this.logger.info('Standard mode enabled: delegating identity routes to source IdentityProviderHandler');\n }\n }\n\n /**\n * 判断是否处理请求\n * - Local SP mode: delegate local account/consent routes to source Handler\n * - Standard mode: delegate to 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 if (this.source) {\n await this.source.canHandle(input);\n } else {\n throw new NotImplementedHttpError(this.message);\n }\n }\n\n /**\n * 处理请求\n * - Local SP mode: delegate to source Handler so consent remains scoped by SP\n * - Standard mode: delegate to source Handler\n */\n public override async handle(input: HttpHandlerInput): Promise<void> {\n if (this.source) {\n await this.source.handle(input);\n } else {\n throw new NotImplementedHttpError(this.message);\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,7 +12,7 @@
12
12
  "extends": [
13
13
  "css:dist/server/HttpHandler.jsonld#HttpHandler"
14
14
  ],
15
- "comment": "Auto-detect Identity Provider Handler 自动检测运行模式: - 如果配置了 oidcIssuer -> SP 模式:禁用本地账户管理 - 如果没有配置 oidcIssuer -> 标准模式:委托给 CSS 默认 Handler",
15
+ "comment": "Auto-detect Identity Provider Handler Local SP mode still needs the local `/.account/*` surface: CSS keeps the OIDC interaction and the scoped WebID picker here, while token validation can trust the configured external issuer. Disabling this surface makes LinX fall back to the Cloud issuer and lets Cloud Pods leak into a Local login flow.",
16
16
  "parameters": [
17
17
  {
18
18
  "@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options_oidcIssuer",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  ]
27
27
  },
28
- "comment": "外部 IdP 的基础 URL,如果提供则启用 SP 模式"
28
+ "comment": "外部 IdP 的基础 URL,如果提供则启用 Local SP mode."
29
29
  },
30
30
  {
31
31
  "@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options_message",
@@ -38,7 +38,7 @@
38
38
  }
39
39
  ]
40
40
  },
41
- "comment": "禁用时的消息"
41
+ "comment": "Message used when no source handler is available."
42
42
  },
43
43
  {
44
44
  "@id": "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld#AutoDetectIdentityProviderHandler_options_source",
@@ -51,7 +51,7 @@
51
51
  }
52
52
  ]
53
53
  },
54
- "comment": "CSS 默认的 IdentityProviderHandler,标准模式下委托给它"
54
+ "comment": "CSS IdentityProviderHandler that owns account, consent and WebID selection routes."
55
55
  }
56
56
  ],
57
57
  "memberFields": [
@@ -1,8 +1,8 @@
1
1
  import { HttpHandler, type HttpHandlerInput } from '@solid/community-server';
2
2
  export interface AutoDetectOidcHandlerOptions {
3
- /** 外部 IdP 的基础 URL,如果提供则启用 SP 模式 */
3
+ /** External OIDC issuer base URL used as the trust source for Local SP mode. */
4
4
  oidcIssuer?: string;
5
- /** 禁用原因说明 */
5
+ /** Explanation used when this handler declines non-JWKS OIDC routes. */
6
6
  message?: string;
7
7
  /** JWKS 缓存时间 (ms) */
8
8
  cacheMs?: number;
@@ -11,9 +11,13 @@ export interface AutoDetectOidcHandlerOptions {
11
11
  * Auto-detect OIDC Handler
12
12
  *
13
13
  * 自动检测运行模式:
14
- * - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS
14
+ * - 如果配置了 oidcIssuer -> Local SP 模式:只代理外部 issuer 的 JWKS
15
15
  * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
16
16
  *
17
+ * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和
18
+ * scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回
19
+ * Cloud consent 并暴露 Cloud Pod。
20
+ *
17
21
  * 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
18
22
  */
19
23
  export declare class AutoDetectOidcHandler extends HttpHandler {
@@ -26,7 +30,7 @@ export declare class AutoDetectOidcHandler extends HttpHandler {
26
30
  constructor(options?: AutoDetectOidcHandlerOptions);
27
31
  /**
28
32
  * 判断是否处理请求
29
- * - SP 模式:只处理 JWKS 请求,其他 OIDC 请求返回 501
33
+ * - Local SP 模式:只处理 JWKS 请求,其他 OIDC 请求透传给 CSS 本地 OIDC handler
30
34
  * - 标准模式:不处理任何请求(透传给 CSS 默认 Handler)
31
35
  */
32
36
  canHandle({ request }: HttpHandlerInput): Promise<void>;
@@ -8,9 +8,13 @@ const community_server_1 = require("@solid/community-server");
8
8
  * Auto-detect OIDC Handler
9
9
  *
10
10
  * 自动检测运行模式:
11
- * - 如果配置了 oidcIssuer -> SP 模式:禁用本地 OIDC,代理外部 IdP 的 JWKS
11
+ * - 如果配置了 oidcIssuer -> Local SP 模式:只代理外部 issuer 的 JWKS
12
12
  * - 如果没有配置 oidcIssuer -> 标准模式:所有 OIDC 请求透传(由 CSS 默认 Handler 处理)
13
13
  *
14
+ * 注意:Local SP 模式不能禁用本地 account/consent。OIDC 交互页面和
15
+ * scoped WebID picker 必须继续由本地 CSS 提供,否则 Local 登录会退回
16
+ * Cloud consent 并暴露 Cloud Pod。
17
+ *
14
18
  * 使用方式:在 HTTP pipeline 中替换默认的 OidcHandler
15
19
  */
16
20
  class AutoDetectOidcHandler extends community_server_1.HttpHandler {
@@ -19,10 +23,10 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
19
23
  this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
20
24
  this.oidcIssuer = options.oidcIssuer;
21
25
  this.jwksUrl = this.oidcIssuer ? `${this.oidcIssuer.replace(/\/$/, '')}/.oidc/jwks` : undefined;
22
- this.message = options.message ?? 'OIDC disabled in storage provider mode';
26
+ this.message = options.message ?? 'OIDC route handled by local CSS OIDC handler';
23
27
  this.cacheMs = options.cacheMs ?? 300000; // 默认 5 分钟
24
28
  if (this.oidcIssuer) {
25
- this.logger.info(`SP mode enabled, external IdP: ${this.oidcIssuer}, JWKS: ${this.jwksUrl}`);
29
+ this.logger.info(`Local SP mode enabled, external issuer: ${this.oidcIssuer}, JWKS: ${this.jwksUrl}`);
26
30
  }
27
31
  else {
28
32
  this.logger.info('Standard mode enabled, OIDC requests will pass through');
@@ -30,7 +34,7 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
30
34
  }
31
35
  /**
32
36
  * 判断是否处理请求
33
- * - SP 模式:只处理 JWKS 请求,其他 OIDC 请求返回 501
37
+ * - Local SP 模式:只处理 JWKS 请求,其他 OIDC 请求透传给 CSS 本地 OIDC handler
34
38
  * - 标准模式:不处理任何请求(透传给 CSS 默认 Handler)
35
39
  */
36
40
  async canHandle({ request }) {
@@ -43,9 +47,9 @@ class AutoDetectOidcHandler extends community_server_1.HttpHandler {
43
47
  if (!this.jwksUrl) {
44
48
  throw new community_server_1.NotImplementedHttpError('Pass through to default OIDC handler');
45
49
  }
46
- // SP 模式:只有 JWKS 请求可以处理
50
+ // Local SP 模式:只有 JWKS 请求由这里处理,其它 OIDC 路由交给 CSS 本地 handler
47
51
  if (!this.isJwksPath(url)) {
48
- throw new community_server_1.NotImplementedHttpError(`External IdP mode: ${this.message}. Authentication handled by external IdP.`);
52
+ throw new community_server_1.NotImplementedHttpError(`Local SP mode: ${this.message}.`);
49
53
  }
50
54
  }
51
55
  /**