@undefineds.co/xpod 0.3.49 → 0.3.51
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/dist/api/auth/MultiAuthenticator.js +2 -2
- package/dist/api/auth/MultiAuthenticator.js.map +1 -1
- package/dist/api/handlers/MatrixHandler.js +25 -1
- package/dist/api/handlers/MatrixHandler.js.map +1 -1
- package/dist/api/matrix/PodMatrixStore.js +11 -4
- package/dist/api/matrix/PodMatrixStore.js.map +1 -1
- package/dist/api/matrix/types.d.ts +2 -0
- package/dist/api/matrix/types.js.map +1 -1
- package/dist/api/middleware/AuthMiddleware.d.ts +1 -0
- package/dist/api/middleware/AuthMiddleware.js +13 -1
- package/dist/api/middleware/AuthMiddleware.js.map +1 -1
- package/dist/api/protocol-metadata.js +14 -4
- package/dist/api/protocol-metadata.js.map +1 -1
- package/dist/cli/lib/auth-context.js +10 -7
- package/dist/cli/lib/auth-context.js.map +1 -1
- package/dist/cli/lib/auth-helper.d.ts +8 -6
- package/dist/cli/lib/auth-helper.js +8 -6
- package/dist/cli/lib/auth-helper.js.map +1 -1
- package/dist/cli/lib/oidc-auth.d.ts +4 -0
- package/dist/cli/lib/oidc-auth.js +90 -0
- package/dist/cli/lib/oidc-auth.js.map +1 -0
- package/dist/cli/lib/oidc-session-storage.d.ts +3 -0
- package/dist/cli/lib/oidc-session-storage.js +41 -0
- package/dist/cli/lib/oidc-session-storage.js.map +1 -0
- package/dist/runtime/Proxy.d.ts +5 -0
- package/dist/runtime/Proxy.js +60 -6
- package/dist/runtime/Proxy.js.map +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.d.ts.map +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +19 -9
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js.map +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.d.ts.map +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +19 -9
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js.map +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/package.json +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthMiddleware.js","sourceRoot":"","sources":["../../../src/api/middleware/AuthMiddleware.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AAmBrD;;GAEG;AACH,MAAa,cAAc;IAKzB,YAAmB,OAA8B;QAJhC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAK3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,IAAY;QAC9B,0CAA0C;QAC1C,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO,CAAC,OAA6B,EAAE,QAAwB;QAC1E,iCAAiC;QACjC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YACnC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;YAC3D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,yBAAyB;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAE9D,
|
|
1
|
+
{"version":3,"file":"AuthMiddleware.js","sourceRoot":"","sources":["../../../src/api/middleware/AuthMiddleware.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AAmBrD;;GAEG;AACH,MAAa,cAAc;IAKzB,YAAmB,OAA8B;QAJhC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAK3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,IAAY;QAC9B,0CAA0C;QAC1C,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO,CAAC,OAA6B,EAAE,QAAwB;QAC1E,iCAAiC;QACjC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YACnC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;YAC3D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,yBAAyB;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAE9D,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,QAAQ,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,YAAY,MAAM,CAAC,OAAO,EAAE;YACjE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC7F,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,IAAI,uBAAuB,CAAC,CAAC;YACzE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,iCAAiC;QACjC,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,gBAAgB,CAAC,QAAwB,EAAE,OAAe;QAChE,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QACvD,QAAQ,CAAC,SAAS,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;QACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAEO,iBAAiB,CAAC,OAAoB;QAC5C,OAAO;YACL,IAAI,EAAG,OAAe,CAAC,IAAI;YAC3B,KAAK,EAAG,OAAe,CAAC,KAAK;YAC7B,SAAS,EAAG,OAAe,CAAC,SAAS;YACrC,QAAQ,EAAG,OAAe,CAAC,QAAQ;YACnC,SAAS,EAAG,OAAe,CAAC,SAAS;YACrC,SAAS,EAAG,OAAe,CAAC,SAAS;SACtC,CAAC;IACJ,CAAC;CACF;AAlED,wCAkEC","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { Authenticator } from '../auth/Authenticator';\nimport type { AuthContext } from '../auth/AuthContext';\n\n/**\n * Extended request with auth context\n */\nexport interface AuthenticatedRequest extends IncomingMessage {\n auth?: AuthContext;\n}\n\nexport interface AuthMiddlewareOptions {\n authenticator: Authenticator;\n /**\n * Paths that do not require authentication\n */\n publicPaths?: string[];\n}\n\n/**\n * Middleware that handles authentication for API requests\n */\nexport class AuthMiddleware {\n private readonly logger = getLoggerFor(this);\n private readonly authenticator: Authenticator;\n private readonly publicPaths: Set<string>;\n\n public constructor(options: AuthMiddlewareOptions) {\n this.authenticator = options.authenticator;\n this.publicPaths = new Set(options.publicPaths ?? []);\n }\n\n /**\n * Check if a path is public (does not require authentication)\n */\n public isPublicPath(path: string): boolean {\n // Normalize path by removing query string\n const normalizedPath = path.split('?')[0];\n return this.publicPaths.has(normalizedPath);\n }\n\n /**\n * Process the request, adding auth context if authenticated\n * Returns true if request should continue, false if response was sent\n */\n public async process(request: AuthenticatedRequest, response: ServerResponse): Promise<boolean> {\n // Check for authorization header\n if (!request.headers.authorization) {\n this.sendUnauthorized(response, 'Authentication required');\n return false;\n }\n\n // Attempt authentication\n const result = await this.authenticator.authenticate(request);\n\n this.logger.debug(\n `Auth ${request.method} ${request.url} success=${result.success}` +\n (result.error ? ` error=${result.error}` : '') +\n (result.context ? ` context=${JSON.stringify(this.safeContextForLog(result.context))}` : ''),\n );\n\n if (!result.success) {\n this.sendUnauthorized(response, result.error ?? 'Authentication failed');\n return false;\n }\n\n // Attach auth context to request\n request.auth = result.context;\n return true;\n }\n\n private sendUnauthorized(response: ServerResponse, message: string): void {\n response.statusCode = 401;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('WWW-Authenticate', 'Bearer, DPoP');\n response.end(JSON.stringify({ error: 'Unauthorized', message }));\n }\n\n private safeContextForLog(context: AuthContext): Record<string, unknown> {\n return {\n type: (context as any).type,\n webId: (context as any).webId,\n accountId: (context as any).accountId,\n clientId: (context as any).clientId,\n tokenType: (context as any).tokenType,\n viaApiKey: (context as any).viaApiKey,\n };\n }\n}\n"]}
|
|
@@ -15,11 +15,21 @@ function getProtocolMetadata(metadata, namespace) {
|
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
17
|
const protocols = metadata[PROTOCOL_METADATA_KEY];
|
|
18
|
-
if (
|
|
19
|
-
|
|
18
|
+
if (Array.isArray(protocols)) {
|
|
19
|
+
const merged = protocols.reduce((acc, entry) => {
|
|
20
|
+
if (!isRecord(entry)) {
|
|
21
|
+
return acc;
|
|
22
|
+
}
|
|
23
|
+
const namespaced = entry[namespace];
|
|
24
|
+
return isRecord(namespaced) ? { ...acc, ...namespaced } : acc;
|
|
25
|
+
}, {});
|
|
26
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
27
|
+
}
|
|
28
|
+
if (isRecord(protocols)) {
|
|
29
|
+
const namespaced = protocols[namespace];
|
|
30
|
+
return isRecord(namespaced) ? namespaced : undefined;
|
|
20
31
|
}
|
|
21
|
-
|
|
22
|
-
return isRecord(namespaced) ? namespaced : undefined;
|
|
32
|
+
return undefined;
|
|
23
33
|
}
|
|
24
34
|
function withProtocolMetadata(metadata, namespace, values) {
|
|
25
35
|
const base = metadata ?? {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-metadata.js","sourceRoot":"","sources":["../../src/api/protocol-metadata.ts"],"names":[],"mappings":";;AAcA,
|
|
1
|
+
{"version":3,"file":"protocol-metadata.js","sourceRoot":"","sources":["../../src/api/protocol-metadata.ts"],"names":[],"mappings":";;AAcA,kDA0BC;AAED,oDAwBC;AAED,sEAcC;AAhFD,MAAM,qBAAqB,GAAG,WAAW,CAAC;AAE1C,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,cAAc,CAAC,MAAwB;IAC9C,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAClE,CAAC;AACJ,CAAC;AAED,SAAgB,mBAAmB,CACjC,QAA6C,EAC7C,SAAiB;IAEjB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAmB,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YAC/D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;YACpC,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAChE,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,CAAC;IAED,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACxB,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;QACxC,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,oBAAoB,CAClC,QAA6C,EAC7C,SAAiB,EACjB,MAAwB;IAExB,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACrD,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAqB;QACjD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC5C,CAAC,CAAC,SAAS,CAAC,SAAS,CAAqB;QAC1C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEpC,OAAO;QACL,GAAG,IAAI;QACP,CAAC,qBAAqB,CAAC,EAAE;YACvB,GAAG,SAAS;YACZ,CAAC,SAAS,CAAC,EAAE;gBACX,GAAG,OAAO;gBACV,GAAG,IAAI;aACR;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAgB,6BAA6B,CAC3C,QAA6C,EAC7C,IAAuB;IAEvB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC","sourcesContent":["export type ProtocolMetadata = Record<string, unknown>;\n\nconst PROTOCOL_METADATA_KEY = 'protocols';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction definedEntries(values: ProtocolMetadata): ProtocolMetadata {\n return Object.fromEntries(\n Object.entries(values).filter(([, value]) => value !== undefined),\n );\n}\n\nexport function getProtocolMetadata(\n metadata: ProtocolMetadata | null | undefined,\n namespace: string,\n): ProtocolMetadata | undefined {\n if (!metadata) {\n return undefined;\n }\n\n const protocols = metadata[PROTOCOL_METADATA_KEY];\n if (Array.isArray(protocols)) {\n const merged = protocols.reduce<ProtocolMetadata>((acc, entry) => {\n if (!isRecord(entry)) {\n return acc;\n }\n const namespaced = entry[namespace];\n return isRecord(namespaced) ? { ...acc, ...namespaced } : acc;\n }, {});\n return Object.keys(merged).length > 0 ? merged : undefined;\n }\n\n if (isRecord(protocols)) {\n const namespaced = protocols[namespace];\n return isRecord(namespaced) ? namespaced : undefined;\n }\n\n return undefined;\n}\n\nexport function withProtocolMetadata(\n metadata: ProtocolMetadata | null | undefined,\n namespace: string,\n values: ProtocolMetadata,\n): ProtocolMetadata {\n const base = metadata ?? {};\n const protocols = isRecord(base[PROTOCOL_METADATA_KEY])\n ? base[PROTOCOL_METADATA_KEY] as ProtocolMetadata\n : {};\n const current = isRecord(protocols[namespace])\n ? protocols[namespace] as ProtocolMetadata\n : {};\n const next = definedEntries(values);\n\n return {\n ...base,\n [PROTOCOL_METADATA_KEY]: {\n ...protocols,\n [namespace]: {\n ...current,\n ...next,\n },\n },\n };\n}\n\nexport function withoutProtocolProjectionKeys(\n metadata: ProtocolMetadata | null | undefined,\n keys: readonly string[],\n): ProtocolMetadata | undefined {\n if (!metadata) {\n return undefined;\n }\n\n const result = { ...metadata };\n for (const key of keys) {\n delete result[key];\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n"]}
|
|
@@ -7,6 +7,7 @@ exports.requireAuthContext = requireAuthContext;
|
|
|
7
7
|
exports.authFetch = authFetch;
|
|
8
8
|
const solid_auth_1 = require("./solid-auth");
|
|
9
9
|
const credentials_store_1 = require("./credentials-store");
|
|
10
|
+
const oidc_auth_1 = require("./oidc-auth");
|
|
10
11
|
const output_1 = require("./output");
|
|
11
12
|
function normalizeBaseUrl(url) {
|
|
12
13
|
return url.endsWith('/') ? url : `${url}/`;
|
|
@@ -45,13 +46,15 @@ async function requireAuthContext(options = {}) {
|
|
|
45
46
|
if (!credentials) {
|
|
46
47
|
throw new output_1.CliCommandError('auth_required', 'No credentials found. Run `xpod auth login` first.', 2);
|
|
47
48
|
}
|
|
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
49
|
const baseUrl = normalizeBaseUrl(options.url ?? credentials.url);
|
|
53
|
-
const
|
|
54
|
-
|
|
50
|
+
const clientCredentials = (0, credentials_store_1.getClientCredentials)(credentials);
|
|
51
|
+
const oauthCredentials = (0, credentials_store_1.getOAuthCredentials)(credentials);
|
|
52
|
+
const accessToken = clientCredentials
|
|
53
|
+
? (await (0, solid_auth_1.getAccessToken)(clientCredentials.clientId, clientCredentials.clientSecret, baseUrl))?.accessToken
|
|
54
|
+
: oauthCredentials
|
|
55
|
+
? await (0, oidc_auth_1.getOidcAccessToken)(credentials)
|
|
56
|
+
: null;
|
|
57
|
+
if (!accessToken) {
|
|
55
58
|
throw new output_1.CliCommandError('auth_failed', 'Failed to obtain an access token. Run `xpod auth login` again.', 2);
|
|
56
59
|
}
|
|
57
60
|
const podRoot = resolvePodRootFromWebId(credentials.webId);
|
|
@@ -60,7 +63,7 @@ async function requireAuthContext(options = {}) {
|
|
|
60
63
|
webId: credentials.webId,
|
|
61
64
|
podRoot,
|
|
62
65
|
baseIri: podRoot,
|
|
63
|
-
accessToken
|
|
66
|
+
accessToken,
|
|
64
67
|
credentials,
|
|
65
68
|
};
|
|
66
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-context.js","sourceRoot":"","sources":["../../../src/cli/lib/auth-context.ts"],"names":[],"mappings":";;
|
|
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 getOAuthCredentials,\n loadCredentials,\n type StoredCredentials,\n} from './credentials-store';\nimport { getOidcAccessToken } from './oidc-auth';\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 baseUrl = normalizeBaseUrl(options.url ?? credentials.url);\n const clientCredentials = getClientCredentials(credentials);\n const oauthCredentials = getOAuthCredentials(credentials);\n const accessToken = clientCredentials\n ? (await getAccessToken(\n clientCredentials.clientId,\n clientCredentials.clientSecret,\n baseUrl,\n ))?.accessToken\n : oauthCredentials\n ? await getOidcAccessToken(credentials)\n : null;\n\n if (!accessToken) {\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,\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"]}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Legacy client-credentials authentication helper.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Descriptor/resource/rdf/secret CLI commands use `auth-context.ts`, which
|
|
5
|
+
* supports both shared Solid OAuth sessions and client credentials. Keep this
|
|
6
|
+
* helper only for legacy callers that explicitly need a Solid Node Session from
|
|
7
|
+
* client credentials.
|
|
7
8
|
*/
|
|
8
9
|
import { type PodAuth } from './solid-auth';
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* 获取 client-credentials 认证信息。
|
|
11
12
|
*
|
|
12
13
|
* 规则:
|
|
13
14
|
* 1. 只读取 shared Solid auth source:$SOLID_HOME/auth/credentials.json
|
|
14
|
-
* 2.
|
|
15
|
+
* 2. OAuth credentials intentionally return null here; use auth-context for
|
|
16
|
+
* resource operations.
|
|
15
17
|
*
|
|
16
18
|
* @returns PodAuth 或 null(如果没有保存的 credentials)
|
|
17
19
|
*/
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Legacy client-credentials authentication helper.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Descriptor/resource/rdf/secret CLI commands use `auth-context.ts`, which
|
|
6
|
+
* supports both shared Solid OAuth sessions and client credentials. Keep this
|
|
7
|
+
* helper only for legacy callers that explicitly need a Solid Node Session from
|
|
8
|
+
* client credentials.
|
|
8
9
|
*/
|
|
9
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
11
|
exports.getAuth = getAuth;
|
|
@@ -12,11 +13,12 @@ exports.requireAuth = requireAuth;
|
|
|
12
13
|
const solid_auth_1 = require("./solid-auth");
|
|
13
14
|
const credentials_store_1 = require("./credentials-store");
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* 获取 client-credentials 认证信息。
|
|
16
17
|
*
|
|
17
18
|
* 规则:
|
|
18
19
|
* 1. 只读取 shared Solid auth source:$SOLID_HOME/auth/credentials.json
|
|
19
|
-
* 2.
|
|
20
|
+
* 2. OAuth credentials intentionally return null here; use auth-context for
|
|
21
|
+
* resource operations.
|
|
20
22
|
*
|
|
21
23
|
* @returns PodAuth 或 null(如果没有保存的 credentials)
|
|
22
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-helper.js","sourceRoot":"","sources":["../../../src/cli/lib/auth-helper.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"auth-helper.js","sourceRoot":"","sources":["../../../src/cli/lib/auth-helper.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAeH,0BA8BC;AAOD,kCAUC;AA5DD,6CAA0D;AAC1D,2DAA2E;AAE3E;;;;;;;;;GASG;AACI,KAAK,UAAU,OAAO;IAC3B,MAAM,KAAK,GAAG,IAAA,mCAAe,GAAE,CAAC;IAEhC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2BAA2B;IAC3B,IAAI,CAAC,IAAA,uCAAmB,EAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC/D,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2BAA2B;IAC3B,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAY,EAC7B,KAAK,CAAC,OAAO,CAAC,QAAQ,EACtB,KAAK,CAAC,OAAO,CAAC,YAAY,EAC1B,UAAU,CACX,CAAC;QAEF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,WAAW;IAC/B,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAC;IAE7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * Legacy client-credentials authentication helper.\n *\n * Descriptor/resource/rdf/secret CLI commands use `auth-context.ts`, which\n * supports both shared Solid OAuth sessions and client credentials. Keep this\n * helper only for legacy callers that explicitly need a Solid Node Session from\n * client credentials.\n */\n\nimport { authenticate, type PodAuth } from './solid-auth';\nimport { loadCredentials, isClientCredentials } from './credentials-store';\n\n/**\n * 获取 client-credentials 认证信息。\n *\n * 规则:\n * 1. 只读取 shared Solid auth source:$SOLID_HOME/auth/credentials.json\n * 2. OAuth credentials intentionally return null here; use auth-context for\n * resource operations.\n *\n * @returns PodAuth 或 null(如果没有保存的 credentials)\n */\nexport async function getAuth(): Promise<PodAuth | null> {\n const creds = loadCredentials();\n\n if (!creds) {\n return null;\n }\n\n // 检查是否是 client credentials\n if (!isClientCredentials(creds.secrets)) {\n console.error('Saved credentials are not client credentials.');\n console.error('Please run: xpod auth create-credentials');\n return null;\n }\n\n // 使用 client credentials 认证\n try {\n const oidcIssuer = creds.url;\n const auth = await authenticate(\n creds.secrets.clientId,\n creds.secrets.clientSecret,\n oidcIssuer,\n );\n\n return auth;\n } catch (error) {\n console.error('Authentication failed:', error);\n console.error('Your credentials may be expired or invalid.');\n console.error('Please run: xpod auth create-credentials');\n return null;\n }\n}\n\n/**\n * 获取认证信息,如果失败则退出进程\n *\n * @returns PodAuth\n */\nexport async function requireAuth(): Promise<PodAuth> {\n const auth = await getAuth();\n\n if (!auth) {\n console.error('\\nNo credentials found. Please run:');\n console.error(' xpod auth create-credentials --email your@email.com');\n process.exit(1);\n }\n\n return auth;\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOidcAccessToken = getOidcAccessToken;
|
|
4
|
+
const solid_client_authn_node_1 = require("@inrupt/solid-client-authn-node");
|
|
5
|
+
const credentials_store_1 = require("./credentials-store");
|
|
6
|
+
const oidc_session_storage_1 = require("./oidc-session-storage");
|
|
7
|
+
const TOKEN_EXPIRY_SKEW_MS = 60_000;
|
|
8
|
+
async function getOidcAccessToken(credentials, options = {}) {
|
|
9
|
+
const secrets = (0, credentials_store_1.getOAuthCredentials)(credentials);
|
|
10
|
+
if (!secrets)
|
|
11
|
+
return null;
|
|
12
|
+
if (!options.forceRefresh && isUsableAccessToken(secrets)) {
|
|
13
|
+
return secrets.oidcAccessToken;
|
|
14
|
+
}
|
|
15
|
+
return refreshStoredOidcSession(credentials, secrets);
|
|
16
|
+
}
|
|
17
|
+
function isUsableAccessToken(secrets) {
|
|
18
|
+
if (!secrets.oidcAccessToken)
|
|
19
|
+
return false;
|
|
20
|
+
const expiresAt = new Date(secrets.oidcExpiresAt).getTime();
|
|
21
|
+
return Number.isFinite(expiresAt) && expiresAt > Date.now() + TOKEN_EXPIRY_SKEW_MS;
|
|
22
|
+
}
|
|
23
|
+
async function refreshStoredOidcSession(credentials, secrets) {
|
|
24
|
+
const storage = (0, oidc_session_storage_1.createOidcSessionStorage)();
|
|
25
|
+
const sessionId = await resolveStoredOidcSessionId(storage, credentials.webId, credentials.url);
|
|
26
|
+
if (!sessionId)
|
|
27
|
+
return null;
|
|
28
|
+
const session = await (0, solid_client_authn_node_1.getSessionFromStorage)(sessionId, {
|
|
29
|
+
storage,
|
|
30
|
+
refreshSession: false,
|
|
31
|
+
});
|
|
32
|
+
if (!session)
|
|
33
|
+
return null;
|
|
34
|
+
let refreshedTokenSet = null;
|
|
35
|
+
session.events.on(solid_client_authn_node_1.EVENTS.NEW_TOKENS, (tokenSet) => {
|
|
36
|
+
refreshedTokenSet = tokenSet;
|
|
37
|
+
});
|
|
38
|
+
await (0, solid_client_authn_node_1.refreshSession)(session, { storage });
|
|
39
|
+
const nextTokenSet = refreshedTokenSet;
|
|
40
|
+
if (!nextTokenSet?.accessToken)
|
|
41
|
+
return null;
|
|
42
|
+
secrets.oidcRefreshToken = nextTokenSet.refreshToken ?? secrets.oidcRefreshToken;
|
|
43
|
+
secrets.oidcAccessToken = nextTokenSet.accessToken;
|
|
44
|
+
secrets.oidcExpiresAt = nextTokenSet.expiresAt
|
|
45
|
+
? new Date(nextTokenSet.expiresAt * 1000).toISOString()
|
|
46
|
+
: secrets.oidcExpiresAt;
|
|
47
|
+
secrets.oidcClientId = nextTokenSet.clientId ?? secrets.oidcClientId;
|
|
48
|
+
(0, credentials_store_1.saveCredentials)({
|
|
49
|
+
url: credentials.url,
|
|
50
|
+
webId: session.info.webId ?? credentials.webId,
|
|
51
|
+
authType: 'oidc_oauth',
|
|
52
|
+
secrets,
|
|
53
|
+
});
|
|
54
|
+
return nextTokenSet.accessToken;
|
|
55
|
+
}
|
|
56
|
+
async function resolveStoredOidcSessionId(storage, webId, issuerUrl) {
|
|
57
|
+
const raw = await storage.get('solidClientAuthn:registeredSessions');
|
|
58
|
+
if (!raw)
|
|
59
|
+
return null;
|
|
60
|
+
let sessionIds = [];
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(raw);
|
|
63
|
+
if (Array.isArray(parsed)) {
|
|
64
|
+
sessionIds = parsed.filter((value) => typeof value === 'string');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const normalizedIssuer = issuerUrl.replace(/\/+$/u, '');
|
|
71
|
+
for (const sessionId of [...sessionIds].reverse()) {
|
|
72
|
+
const stored = await storage.get(`solidClientAuthenticationUser:${sessionId}`);
|
|
73
|
+
if (!stored)
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(stored);
|
|
77
|
+
const sessionWebId = typeof parsed.webId === 'string' ? parsed.webId : null;
|
|
78
|
+
const sessionIssuer = typeof parsed.issuer === 'string' ? parsed.issuer.replace(/\/+$/u, '') : null;
|
|
79
|
+
const sessionUsesDpop = parsed.dpop === true || parsed.dpop === 'true';
|
|
80
|
+
if (sessionWebId === webId && sessionIssuer === normalizedIssuer && !sessionUsesDpop) {
|
|
81
|
+
return sessionId;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=oidc-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-auth.js","sourceRoot":"","sources":["../../../src/cli/lib/oidc-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n EVENTS,\n getSessionFromStorage,\n refreshSession,\n type SessionTokenSet,\n} from '@inrupt/solid-client-authn-node';\nimport {\n getOAuthCredentials,\n saveCredentials,\n type OidcOAuthSecrets,\n type StoredCredentials,\n} from './credentials-store';\nimport { createOidcSessionStorage } from './oidc-session-storage';\n\nconst TOKEN_EXPIRY_SKEW_MS = 60_000;\n\nexport async function getOidcAccessToken(\n credentials: StoredCredentials,\n options: { forceRefresh?: boolean } = {},\n): Promise<string | null> {\n const secrets = getOAuthCredentials(credentials);\n if (!secrets) return null;\n\n if (!options.forceRefresh && isUsableAccessToken(secrets)) {\n return secrets.oidcAccessToken;\n }\n\n return refreshStoredOidcSession(credentials, secrets);\n}\n\nfunction isUsableAccessToken(secrets: OidcOAuthSecrets): boolean {\n if (!secrets.oidcAccessToken) return false;\n const expiresAt = new Date(secrets.oidcExpiresAt).getTime();\n return Number.isFinite(expiresAt) && expiresAt > Date.now() + TOKEN_EXPIRY_SKEW_MS;\n}\n\nasync function refreshStoredOidcSession(\n credentials: StoredCredentials,\n secrets: OidcOAuthSecrets,\n): Promise<string | null> {\n const storage = createOidcSessionStorage();\n const sessionId = await resolveStoredOidcSessionId(storage, credentials.webId, credentials.url);\n if (!sessionId) return null;\n\n const session = await getSessionFromStorage(sessionId, {\n storage,\n refreshSession: false,\n });\n if (!session) return null;\n\n let refreshedTokenSet: SessionTokenSet | null = null;\n session.events.on(EVENTS.NEW_TOKENS, (tokenSet) => {\n refreshedTokenSet = tokenSet;\n });\n\n await refreshSession(session, { storage });\n\n const nextTokenSet = refreshedTokenSet as SessionTokenSet | null;\n if (!nextTokenSet?.accessToken) return null;\n\n secrets.oidcRefreshToken = nextTokenSet.refreshToken ?? secrets.oidcRefreshToken;\n secrets.oidcAccessToken = nextTokenSet.accessToken;\n secrets.oidcExpiresAt = nextTokenSet.expiresAt\n ? new Date(nextTokenSet.expiresAt * 1000).toISOString()\n : secrets.oidcExpiresAt;\n secrets.oidcClientId = nextTokenSet.clientId ?? secrets.oidcClientId;\n\n saveCredentials({\n url: credentials.url,\n webId: session.info.webId ?? credentials.webId,\n authType: 'oidc_oauth',\n secrets,\n });\n\n return nextTokenSet.accessToken;\n}\n\nasync function resolveStoredOidcSessionId(\n storage: ReturnType<typeof createOidcSessionStorage>,\n webId: string,\n issuerUrl: string,\n): Promise<string | null> {\n const raw = await storage.get('solidClientAuthn:registeredSessions');\n if (!raw) return null;\n\n let sessionIds: string[] = [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (Array.isArray(parsed)) {\n sessionIds = parsed.filter((value): value is string => typeof value === 'string');\n }\n } catch {\n return null;\n }\n\n const normalizedIssuer = issuerUrl.replace(/\\/+$/u, '');\n for (const sessionId of [...sessionIds].reverse()) {\n const stored = await storage.get(`solidClientAuthenticationUser:${sessionId}`);\n if (!stored) continue;\n\n try {\n const parsed = JSON.parse(stored) as { webId?: string; issuer?: string; dpop?: string | boolean };\n const sessionWebId = typeof parsed.webId === 'string' ? parsed.webId : null;\n const sessionIssuer = typeof parsed.issuer === 'string' ? parsed.issuer.replace(/\\/+$/u, '') : null;\n const sessionUsesDpop = parsed.dpop === true || parsed.dpop === 'true';\n if (sessionWebId === webId && sessionIssuer === normalizedIssuer && !sessionUsesDpop) {\n return sessionId;\n }\n } catch {\n continue;\n }\n }\n\n return null;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOidcSessionStorage = createOidcSessionStorage;
|
|
4
|
+
exports.clearOidcSessionStorage = clearOidcSessionStorage;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const credentials_store_1 = require("./credentials-store");
|
|
8
|
+
function storageDir() {
|
|
9
|
+
return (0, path_1.join)((0, credentials_store_1.getSolidAuthDir)(), 'oidc-storage');
|
|
10
|
+
}
|
|
11
|
+
function keyPath(key) {
|
|
12
|
+
return (0, path_1.join)(storageDir(), encodeURIComponent(key));
|
|
13
|
+
}
|
|
14
|
+
function createOidcSessionStorage() {
|
|
15
|
+
return {
|
|
16
|
+
async get(key) {
|
|
17
|
+
const path = keyPath(key);
|
|
18
|
+
if (!(0, fs_1.existsSync)(path))
|
|
19
|
+
return undefined;
|
|
20
|
+
return (0, fs_1.readFileSync)(path, 'utf-8');
|
|
21
|
+
},
|
|
22
|
+
async set(key, value) {
|
|
23
|
+
(0, fs_1.mkdirSync)(storageDir(), { recursive: true });
|
|
24
|
+
const path = keyPath(key);
|
|
25
|
+
(0, fs_1.writeFileSync)(path, value, 'utf-8');
|
|
26
|
+
(0, fs_1.chmodSync)(path, 0o600);
|
|
27
|
+
},
|
|
28
|
+
async delete(key) {
|
|
29
|
+
const path = keyPath(key);
|
|
30
|
+
if ((0, fs_1.existsSync)(path))
|
|
31
|
+
(0, fs_1.unlinkSync)(path);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function clearOidcSessionStorage() {
|
|
36
|
+
const dir = storageDir();
|
|
37
|
+
if ((0, fs_1.existsSync)(dir)) {
|
|
38
|
+
(0, fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=oidc-session-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-session-storage.js","sourceRoot":"","sources":["../../../src/cli/lib/oidc-session-storage.ts"],"names":[],"mappings":";;AAaA,4DAkBC;AAED,0DAKC;AAtCD,2BAAuG;AACvG,+BAA4B;AAE5B,2DAAsD;AAEtD,SAAS,UAAU;IACjB,OAAO,IAAA,WAAI,EAAC,IAAA,mCAAe,GAAE,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,OAAO,IAAA,WAAI,EAAC,UAAU,EAAE,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAgB,wBAAwB;IACtC,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,GAAW;YACnB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC;YACxC,OAAO,IAAA,iBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;YAClC,IAAA,cAAS,EAAC,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAA,kBAAa,EAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACpC,IAAA,cAAS,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAW;YACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,IAAA,eAAU,EAAC,IAAI,CAAC;gBAAE,IAAA,eAAU,EAAC,IAAI,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAgB,uBAAuB;IACrC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,IAAA,eAAU,EAAC,GAAG,CAAC,EAAE,CAAC;QACpB,IAAA,WAAM,EAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport type { IStorage } from '@inrupt/solid-client-authn-node';\nimport { getSolidAuthDir } from './credentials-store';\n\nfunction storageDir(): string {\n return join(getSolidAuthDir(), 'oidc-storage');\n}\n\nfunction keyPath(key: string): string {\n return join(storageDir(), encodeURIComponent(key));\n}\n\nexport function createOidcSessionStorage(): IStorage {\n return {\n async get(key: string): Promise<string | undefined> {\n const path = keyPath(key);\n if (!existsSync(path)) return undefined;\n return readFileSync(path, 'utf-8');\n },\n async set(key: string, value: string): Promise<void> {\n mkdirSync(storageDir(), { recursive: true });\n const path = keyPath(key);\n writeFileSync(path, value, 'utf-8');\n chmodSync(path, 0o600);\n },\n async delete(key: string): Promise<void> {\n const path = keyPath(key);\n if (existsSync(path)) unlinkSync(path);\n },\n };\n}\n\nexport function clearOidcSessionStorage(): void {\n const dir = storageDir();\n if (existsSync(dir)) {\n rmSync(dir, { recursive: true, force: true });\n }\n}\n"]}
|
package/dist/runtime/Proxy.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export declare class GatewayProxy {
|
|
|
19
19
|
start(): Promise<void>;
|
|
20
20
|
stop(): Promise<void>;
|
|
21
21
|
private handleRequest;
|
|
22
|
+
private shouldRouteToApi;
|
|
23
|
+
private isApiHost;
|
|
24
|
+
private hostFromUrl;
|
|
25
|
+
private normalizeHost;
|
|
26
|
+
private firstHeaderValue;
|
|
22
27
|
private shouldInspectRootMutation;
|
|
23
28
|
private shouldRejectRootResourceMutation;
|
|
24
29
|
private writeRootMutationForbidden;
|
package/dist/runtime/Proxy.js
CHANGED
|
@@ -100,15 +100,26 @@ class GatewayProxy {
|
|
|
100
100
|
handleRequest(req, res) {
|
|
101
101
|
const url = req.url ?? '/';
|
|
102
102
|
const origin = req.headers.origin;
|
|
103
|
-
// Store
|
|
104
|
-
|
|
103
|
+
// Store public host for routing before any CSS canonical-host rewrites.
|
|
104
|
+
// External gateways pass the original domain through X-Forwarded-Host;
|
|
105
|
+
// direct/local requests use Host.
|
|
106
|
+
const originalHost = this.firstHeaderValue(req.headers['x-forwarded-host']) ?? req.headers.host;
|
|
107
|
+
const apiHost = this.isApiHost(originalHost);
|
|
105
108
|
// Set x-forwarded-proto based on CSS_BASE_URL
|
|
106
109
|
const baseUrl = this.baseUrl ?? process.env.CSS_BASE_URL ?? '';
|
|
107
110
|
if (baseUrl.startsWith('https')) {
|
|
108
111
|
req.headers['x-forwarded-proto'] = 'https';
|
|
109
112
|
}
|
|
110
|
-
//
|
|
111
|
-
|
|
113
|
+
// API subdomains are the public API boundary. Preserve the API host for API
|
|
114
|
+
// handlers (for example Matrix discovery) instead of rewriting it to the
|
|
115
|
+
// canonical CSS/WebID host.
|
|
116
|
+
if (apiHost) {
|
|
117
|
+
if (originalHost) {
|
|
118
|
+
req.headers.host = originalHost;
|
|
119
|
+
req.headers['x-forwarded-host'] = originalHost;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (baseUrl) {
|
|
112
123
|
try {
|
|
113
124
|
const parsedBaseUrl = new URL(baseUrl);
|
|
114
125
|
req.headers.host = parsedBaseUrl.host;
|
|
@@ -136,13 +147,16 @@ class GatewayProxy {
|
|
|
136
147
|
void this.handleInternalApi(req, res);
|
|
137
148
|
return;
|
|
138
149
|
}
|
|
139
|
-
// 2. API Server Routing
|
|
150
|
+
// 2. API Server Routing.
|
|
151
|
+
// Public API is selected by host (`api.<domain>`), not by adding an `/api`
|
|
152
|
+
// path prefix to the IdP/Pod host. Path-based routing remains for local/dev
|
|
153
|
+
// single-origin clients and existing legacy endpoints.
|
|
140
154
|
// 2a. Dashboard UI is served by API server under /dashboard/*
|
|
141
155
|
if ((url === '/dashboard' || url.startsWith('/dashboard/')) && this.targets.api) {
|
|
142
156
|
this.proxy.web(req, res, { target: this.toProxyTarget(this.targets.api) });
|
|
143
157
|
return;
|
|
144
158
|
}
|
|
145
|
-
if ((
|
|
159
|
+
if ((apiHost || this.shouldRouteToApi(url)) && this.targets.api) {
|
|
146
160
|
this.proxy.web(req, res, { target: this.toProxyTarget(this.targets.api) });
|
|
147
161
|
return;
|
|
148
162
|
}
|
|
@@ -164,6 +178,46 @@ class GatewayProxy {
|
|
|
164
178
|
res.end('CSS Service Not Available');
|
|
165
179
|
}
|
|
166
180
|
}
|
|
181
|
+
shouldRouteToApi(url) {
|
|
182
|
+
return url.startsWith('/v1/')
|
|
183
|
+
|| url.startsWith('/api/')
|
|
184
|
+
|| url.startsWith('/provision/')
|
|
185
|
+
|| url === '/.well-known/matrix/client'
|
|
186
|
+
|| url.startsWith('/_matrix/');
|
|
187
|
+
}
|
|
188
|
+
isApiHost(hostHeader) {
|
|
189
|
+
const host = this.normalizeHost(hostHeader);
|
|
190
|
+
if (!host) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
if (host.startsWith('api.')) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
const configuredHost = this.hostFromUrl(process.env.XPOD_CLOUD_API_ENDPOINT)
|
|
197
|
+
?? this.hostFromUrl(process.env.XPOD_PUBLIC_API_URL);
|
|
198
|
+
return Boolean(configuredHost && host === configuredHost);
|
|
199
|
+
}
|
|
200
|
+
hostFromUrl(value) {
|
|
201
|
+
if (!value) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
return new URL(value).hostname.toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
normalizeHost(hostHeader) {
|
|
212
|
+
const host = this.firstHeaderValue(hostHeader)?.split(',')[0]?.trim();
|
|
213
|
+
if (!host) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
return host.replace(/:\d+$/, '').toLowerCase();
|
|
217
|
+
}
|
|
218
|
+
firstHeaderValue(value) {
|
|
219
|
+
return Array.isArray(value) ? value[0] : value;
|
|
220
|
+
}
|
|
167
221
|
shouldInspectRootMutation(req) {
|
|
168
222
|
const method = (req.method ?? 'GET').toUpperCase();
|
|
169
223
|
if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
|