@undefineds.co/xpod 0.3.71-rc.9 → 0.3.71
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/ai-gateway/connect/index.d.ts +2 -0
- package/dist/api/ai-gateway/connect/index.js +87 -24
- package/dist/api/ai-gateway/connect/index.js.map +1 -1
- package/dist/api/ai-gateway/pod/HostedPodDataAccess.js +2 -2
- package/dist/api/ai-gateway/pod/HostedPodDataAccess.js.map +1 -1
- package/dist/api/container/common.js +1 -1
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/routes.js +1 -1
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/handlers/AiGatewayManagementHandler.js +28 -12
- package/dist/api/handlers/AiGatewayManagementHandler.js.map +1 -1
- package/dist/api/handlers/PodSettingsHandler.d.ts +12 -19
- package/dist/api/handlers/PodSettingsHandler.js +12 -51
- package/dist/api/handlers/PodSettingsHandler.js.map +1 -1
- package/dist/http/InternalPodDataHttpHandler.d.ts +3 -1
- package/dist/http/InternalPodDataHttpHandler.js +48 -7
- package/dist/http/InternalPodDataHttpHandler.js.map +1 -1
- package/dist/http/InternalPodDataHttpHandler.jsonld +10 -2
- package/dist/runtime/GatewayAdminProxyAuth.d.ts +1 -1
- package/dist/runtime/GatewayAdminProxyAuth.js +1 -1
- package/dist/runtime/GatewayAdminProxyAuth.js.map +1 -1
- package/dist/runtime/Proxy.d.ts +1 -0
- package/dist/runtime/Proxy.js +22 -0
- package/dist/runtime/Proxy.js.map +1 -1
- package/package.json +1 -1
- package/static/dashboard/assets/{NetworkPage-CXjiUMR1.js → NetworkPage-CsERgA2D.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-BSipfl0W.js → PaneListHeader-WJWFvTKY.js} +1 -1
- package/static/dashboard/assets/{PodPage-Bd3Xhmp-.js → PodPage-Bo_NbnK1.js} +1 -1
- package/static/dashboard/assets/{ServicesPage-CI3jWZGe.js → ServicesPage-CrNDXQlg.js} +1 -1
- package/static/dashboard/assets/{card-z5BV5hTo.js → card-Cjax171A.js} +1 -1
- package/static/dashboard/assets/{dashboard-CSrqAv2L.js → dashboard-CaE1mRUL.js} +4 -4
- package/static/dashboard/assets/{database-Ct5ZPe6L.js → database-B0sGiSz2.js} +1 -1
- package/static/dashboard/assets/{external-link-B7wD1hkm.js → external-link-C_ANDdCY.js} +1 -1
- package/static/dashboard/assets/{index-tOBaUnM1.js → index-CDoxbvUP.js} +1 -1
- package/static/dashboard/assets/{refresh-cw-CFDZPaE8.js → refresh-cw-DHzHxCme.js} +1 -1
- package/static/dashboard/assets/{services-status-context-DN1Icg4A.js → services-status-context-ukghtkFD.js} +1 -1
- package/static/dashboard/dashboard.html +1 -1
- package/static/settings/assets/ModelsPage-ClTKzRGm.js +1 -0
- package/static/settings/assets/{PaneListHeader-DMD8OJef.js → PaneListHeader-DTVY4Ig2.js} +1 -1
- package/static/settings/assets/{PodPage-DT4EMYhL.js → PodPage-L_JAOD-r.js} +1 -1
- package/static/settings/assets/{ServicesPage-CJ0ub6IH.js → ServicesPage-BwLxrSWw.js} +1 -1
- package/static/settings/assets/{SettingsPage-BkR7ZK18.js → SettingsPage-DUGHDPQx.js} +1 -1
- package/static/settings/assets/{card-Cjsia0_V.js → card-BZF0jSa_.js} +1 -1
- package/static/settings/assets/{services-status-context-CkuKmH5h.js → services-status-context-BnbZHZnu.js} +1 -1
- package/static/settings/assets/{settings-Cyyv6-SD.js → settings-BN7FyntE.js} +4 -4
- package/static/settings/assets/{workspace-layout-B47luv6r.js → workspace-layout-jzYHoh3_.js} +1 -1
- package/static/settings/settings.html +1 -1
- package/static/settings/assets/ModelsPage-03NPWzoX.js +0 -1
|
@@ -3,7 +3,8 @@ import type { ApiServer } from '../ApiServer';
|
|
|
3
3
|
import type { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';
|
|
4
4
|
import type { UsageRepository } from '../../storage/quota/UsageRepository';
|
|
5
5
|
import type { AuthContext } from '../auth/AuthContext';
|
|
6
|
-
import type {
|
|
6
|
+
import type { GatewayDeployment } from '../ai-gateway/auth/InvocationTokenCodec';
|
|
7
|
+
import type { ProviderConnectionSummary } from '../ai-gateway/connect';
|
|
7
8
|
export interface PodSettingsStatus {
|
|
8
9
|
identity: {
|
|
9
10
|
webId: string;
|
|
@@ -54,29 +55,21 @@ export interface PodSettingsHandlerOptions {
|
|
|
54
55
|
logger?: Pick<ReturnType<typeof getLoggerFor>, 'warn' | 'error'>;
|
|
55
56
|
}
|
|
56
57
|
export declare function registerPodSettingsRoutes(server: ApiServer, options: PodSettingsHandlerOptions): void;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
private readonly dbFactory;
|
|
60
|
-
constructor(internalPodAccess?: InternalPodAccessTokenProvider | undefined, dbFactory?: (input: {
|
|
58
|
+
type ProviderConnectStatusSource = {
|
|
59
|
+
listProviders(input: {
|
|
61
60
|
webId: string;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
})
|
|
61
|
+
deployment: GatewayDeployment;
|
|
62
|
+
auth?: AuthContext;
|
|
63
|
+
}): Promise<ProviderConnectionSummary[]>;
|
|
64
|
+
};
|
|
65
|
+
export declare class ProviderConnectPodAiConnectionStatusReader implements PodAiConnectionStatusReader {
|
|
66
|
+
private readonly connectService;
|
|
67
|
+
private readonly deployment;
|
|
68
|
+
constructor(connectService: ProviderConnectStatusSource, deployment: GatewayDeployment);
|
|
65
69
|
read({ webId, podUrl, auth }: {
|
|
66
70
|
webId: string;
|
|
67
71
|
podUrl?: string;
|
|
68
72
|
auth?: AuthContext;
|
|
69
73
|
}): Promise<PodAiConnectionStatus>;
|
|
70
74
|
}
|
|
71
|
-
type AiConnectionStatusDb = {
|
|
72
|
-
init?: (...resources: unknown[]) => Promise<void>;
|
|
73
|
-
select(): {
|
|
74
|
-
from(resource: unknown): {
|
|
75
|
-
where(condition: unknown): {
|
|
76
|
-
execute(): Promise<unknown[]>;
|
|
77
|
-
};
|
|
78
|
-
execute(): Promise<unknown[]>;
|
|
79
|
-
};
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
75
|
export {};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ProviderConnectPodAiConnectionStatusReader = void 0;
|
|
4
4
|
exports.registerPodSettingsRoutes = registerPodSettingsRoutes;
|
|
5
|
-
const drizzle_solid_1 = require("@undefineds.co/drizzle-solid");
|
|
6
|
-
const models_1 = require("@undefineds.co/models");
|
|
7
5
|
const global_logger_factory_1 = require("global-logger-factory");
|
|
8
6
|
function registerPodSettingsRoutes(server, options) {
|
|
9
7
|
const logger = options.logger ?? (0, global_logger_factory_1.getLoggerFor)('PodSettingsHandler');
|
|
@@ -72,33 +70,25 @@ async function readStorageStatus(usageRepo, pod) {
|
|
|
72
70
|
source: 'identity_usage',
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
|
-
class
|
|
76
|
-
constructor(
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
73
|
+
class ProviderConnectPodAiConnectionStatusReader {
|
|
74
|
+
constructor(connectService, deployment) {
|
|
75
|
+
this.connectService = connectService;
|
|
76
|
+
this.deployment = deployment;
|
|
79
77
|
}
|
|
80
78
|
async read({ webId, podUrl, auth }) {
|
|
81
|
-
const trustedFetch = await this.internalPodAccess?.getTrustedFetch(webId, auth);
|
|
82
|
-
if (!trustedFetch) {
|
|
83
|
-
return { status: 'unsupported', reason: 'not_configured' };
|
|
84
|
-
}
|
|
85
79
|
if (!podUrl) {
|
|
86
80
|
return { status: 'unsupported', reason: 'pod_not_found' };
|
|
87
81
|
}
|
|
88
82
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
...credentialRows.map((row) => row.lastUsedAt ?? row.lastRefreshAt),
|
|
95
|
-
...indexedRows.map((row) => row.indexedAt),
|
|
96
|
-
]);
|
|
83
|
+
const providers = await this.connectService.listProviders({
|
|
84
|
+
webId,
|
|
85
|
+
deployment: this.deployment,
|
|
86
|
+
auth,
|
|
87
|
+
});
|
|
97
88
|
return {
|
|
98
89
|
status: 'available',
|
|
99
90
|
containerUrl: new URL('settings/credentials.ttl', podUrl).toString(),
|
|
100
|
-
configuredProviders:
|
|
101
|
-
lastSyncAt,
|
|
91
|
+
configuredProviders: providers.filter((provider) => provider.status !== 'disconnected').length,
|
|
102
92
|
source: 'drizzle-solid',
|
|
103
93
|
};
|
|
104
94
|
}
|
|
@@ -107,36 +97,7 @@ class DrizzlePodAiConnectionStatusReader {
|
|
|
107
97
|
}
|
|
108
98
|
}
|
|
109
99
|
}
|
|
110
|
-
exports.
|
|
111
|
-
function createAiConnectionStatusDb(input) {
|
|
112
|
-
return Promise.resolve((0, drizzle_solid_1.drizzle)({
|
|
113
|
-
fetch: input.fetch,
|
|
114
|
-
info: { webId: input.webId, isLoggedIn: true },
|
|
115
|
-
}, {
|
|
116
|
-
podUrl: input.podUrl,
|
|
117
|
-
schema: {
|
|
118
|
-
credential: models_1.credentialResource,
|
|
119
|
-
indexedFile: models_1.indexedFileResource,
|
|
120
|
-
},
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
function latestIso(values) {
|
|
124
|
-
const timestamps = values
|
|
125
|
-
.map((value) => {
|
|
126
|
-
if (value instanceof Date)
|
|
127
|
-
return value.getTime();
|
|
128
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
129
|
-
const parsed = Date.parse(String(value));
|
|
130
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
131
|
-
}
|
|
132
|
-
return undefined;
|
|
133
|
-
})
|
|
134
|
-
.filter((value) => value !== undefined);
|
|
135
|
-
if (timestamps.length === 0) {
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
return new Date(Math.max(...timestamps)).toISOString();
|
|
139
|
-
}
|
|
100
|
+
exports.ProviderConnectPodAiConnectionStatusReader = ProviderConnectPodAiConnectionStatusReader;
|
|
140
101
|
function sendJson(response, status, data) {
|
|
141
102
|
response.statusCode = status;
|
|
142
103
|
response.setHeader('Content-Type', 'application/json');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PodSettingsHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodSettingsHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { drizzle, eq } from '@undefineds.co/drizzle-solid';\nimport {\n credentialResource,\n indexedFileResource,\n type CredentialRow,\n type IndexedFileRow,\n} from '@undefineds.co/models';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { PodLookupRepository, PodLookupResult } from '../../identity/drizzle/PodLookupRepository';\nimport type { UsageRepository, PodUsageRecord } from '../../storage/quota/UsageRepository';\nimport type { AuthContext } from '../auth/AuthContext';\nimport type { InternalPodAccessTokenProvider } from '../ai-gateway/pod/HostedPodDataAccess';\n\nexport interface PodSettingsStatus {\n identity: {\n webId: string;\n podUrl?: string;\n };\n storage: PodStorageStatus;\n aiConnection: PodAiConnectionStatus;\n generatedAt: string;\n}\n\nexport type PodStorageStatus =\n | {\n status: 'available';\n usage: {\n storageBytes: number;\n ingressBytes: number;\n egressBytes: number;\n };\n limits: {\n storageLimitBytes: number | null;\n bandwidthLimitBps: number | null;\n };\n source: 'identity_usage';\n }\n | {\n status: 'unsupported' | 'error';\n reason: string;\n };\n\nexport type PodAiConnectionStatus =\n | {\n status: 'available';\n containerUrl?: string;\n configuredProviders: number;\n lastSyncAt?: string;\n source: 'drizzle-solid';\n }\n | {\n status: 'unsupported' | 'error';\n reason: string;\n };\n\nexport interface PodAiConnectionStatusReader {\n read(input: { webId: string; podUrl?: string; auth?: AuthContext }): Promise<PodAiConnectionStatus>;\n}\n\nexport interface PodSettingsHandlerOptions {\n podLookupRepository: Pick<PodLookupRepository, 'findByWebId'>;\n usageRepo: Pick<UsageRepository, 'getPodUsage'>;\n aiConnectionStatusReader?: PodAiConnectionStatusReader;\n now?: () => Date;\n logger?: Pick<ReturnType<typeof getLoggerFor>, 'warn' | 'error'>;\n}\n\nexport function registerPodSettingsRoutes(server: ApiServer, options: PodSettingsHandlerOptions): void {\n const logger = options.logger ?? getLoggerFor('PodSettingsHandler');\n const now = options.now ?? (() => new Date());\n const aiConnectionStatusReader = options.aiConnectionStatusReader ?? {\n read: async () => ({ status: 'unsupported', reason: 'not_configured' }) as const,\n };\n\n server.get('/api/pod/settings/status', async (request, response) => {\n if (!request.auth || request.auth.type !== 'solid') {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const webId = request.auth.webId;\n try {\n const pod = await options.podLookupRepository.findByWebId(webId);\n const podUrl = pod?.storageUrl ?? pod?.baseUrl;\n const [storage, aiConnection] = await Promise.all([\n readStorageStatus(options.usageRepo, pod),\n aiConnectionStatusReader.read({ webId, podUrl, auth: request.auth }).catch((error: unknown) => {\n const reason = safeAiConnectionFailureReason(error);\n logger.warn(`Failed to read Pod AI Connection status: ${reason}`);\n return { status: 'error', reason } as const;\n }),\n ]);\n\n sendJson(response, 200, {\n identity: {\n webId,\n podUrl,\n },\n storage,\n aiConnection,\n generatedAt: now().toISOString(),\n } satisfies PodSettingsStatus);\n } catch (error) {\n logger.error(`Failed to read Pod settings status: ${safeLogError(error)}`);\n sendJson(response, 500, { error: 'Failed to read Pod settings status' });\n }\n });\n}\n\nasync function readStorageStatus(\n usageRepo: Pick<UsageRepository, 'getPodUsage'>,\n pod: PodLookupResult | undefined,\n): Promise<PodStorageStatus> {\n if (!pod) {\n return { status: 'unsupported', reason: 'pod_not_found' };\n }\n\n let usage: PodUsageRecord | undefined;\n try {\n usage = await usageRepo.getPodUsage(pod.podId);\n } catch {\n return { status: 'error', reason: 'usage_unavailable' };\n }\n\n if (!usage) {\n return { status: 'unsupported', reason: 'usage_not_available' };\n }\n\n return {\n status: 'available',\n usage: {\n storageBytes: usage.storageBytes,\n ingressBytes: usage.ingressBytes,\n egressBytes: usage.egressBytes,\n },\n limits: {\n storageLimitBytes: usage.storageLimitBytes ?? null,\n bandwidthLimitBps: usage.bandwidthLimitBps ?? null,\n },\n source: 'identity_usage',\n };\n}\n\nexport class DrizzlePodAiConnectionStatusReader implements PodAiConnectionStatusReader {\n public constructor(\n private readonly internalPodAccess?: InternalPodAccessTokenProvider,\n private readonly dbFactory: (input: {\n webId: string;\n podUrl: string;\n fetch: typeof fetch;\n }) => Promise<AiConnectionStatusDb> = createAiConnectionStatusDb,\n ) {}\n\n public async read({ webId, podUrl, auth }: { webId: string; podUrl?: string; auth?: AuthContext }): Promise<PodAiConnectionStatus> {\n const trustedFetch = await this.internalPodAccess?.getTrustedFetch(webId, auth);\n if (!trustedFetch) {\n return { status: 'unsupported', reason: 'not_configured' };\n }\n if (!podUrl) {\n return { status: 'unsupported', reason: 'pod_not_found' };\n }\n\n try {\n const db = await this.dbFactory({ webId, podUrl, fetch: trustedFetch });\n await db.init?.(credentialResource, indexedFileResource);\n\n const credentialRows = await db.select().from(credentialResource).where(eq(credentialResource.status, 'active')).execute() as CredentialRow[];\n const indexedRows = await db.select().from(indexedFileResource).execute() as IndexedFileRow[];\n const lastSyncAt = latestIso([\n ...credentialRows.map((row) => row.lastUsedAt ?? row.lastRefreshAt),\n ...indexedRows.map((row) => row.indexedAt),\n ]);\n\n return {\n status: 'available',\n containerUrl: new URL('settings/credentials.ttl', podUrl).toString(),\n configuredProviders: credentialRows.length,\n lastSyncAt,\n source: 'drizzle-solid',\n };\n } catch {\n return { status: 'error', reason: 'ai_connection_unavailable' };\n }\n }\n}\n\ntype AiConnectionStatusDb = {\n init?: (...resources: unknown[]) => Promise<void>;\n select(): {\n from(resource: unknown): {\n where(condition: unknown): { execute(): Promise<unknown[]> };\n execute(): Promise<unknown[]>;\n };\n };\n};\n\nfunction createAiConnectionStatusDb(input: {\n webId: string;\n podUrl: string;\n fetch: typeof fetch;\n}): Promise<AiConnectionStatusDb> {\n return Promise.resolve(drizzle({\n fetch: input.fetch,\n info: { webId: input.webId, isLoggedIn: true },\n } as any, {\n podUrl: input.podUrl,\n schema: {\n credential: credentialResource,\n indexedFile: indexedFileResource,\n },\n }) as unknown as AiConnectionStatusDb);\n}\n\nfunction latestIso(values: unknown[]): string | undefined {\n const timestamps = values\n .map((value) => {\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'string' || typeof value === 'number') {\n const parsed = Date.parse(String(value));\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n })\n .filter((value): value is number => value !== undefined);\n if (timestamps.length === 0) {\n return undefined;\n }\n return new Date(Math.max(...timestamps)).toISOString();\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction safeLogError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction safeAiConnectionFailureReason(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n for (const reason of [\n 'service_access_missing',\n 'not_configured',\n 'pod_not_found',\n 'ai_connection_unavailable',\n ]) {\n if (message.includes(reason)) {\n return reason;\n }\n }\n return 'ai_connection_unavailable';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PodSettingsHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodSettingsHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { PodLookupRepository, PodLookupResult } from '../../identity/drizzle/PodLookupRepository';\nimport type { UsageRepository, PodUsageRecord } from '../../storage/quota/UsageRepository';\nimport type { AuthContext } from '../auth/AuthContext';\nimport type { GatewayDeployment } from '../ai-gateway/auth/InvocationTokenCodec';\nimport type { ProviderConnectionSummary } from '../ai-gateway/connect';\n\nexport interface PodSettingsStatus {\n identity: {\n webId: string;\n podUrl?: string;\n };\n storage: PodStorageStatus;\n aiConnection: PodAiConnectionStatus;\n generatedAt: string;\n}\n\nexport type PodStorageStatus =\n | {\n status: 'available';\n usage: {\n storageBytes: number;\n ingressBytes: number;\n egressBytes: number;\n };\n limits: {\n storageLimitBytes: number | null;\n bandwidthLimitBps: number | null;\n };\n source: 'identity_usage';\n }\n | {\n status: 'unsupported' | 'error';\n reason: string;\n };\n\nexport type PodAiConnectionStatus =\n | {\n status: 'available';\n containerUrl?: string;\n configuredProviders: number;\n lastSyncAt?: string;\n source: 'drizzle-solid';\n }\n | {\n status: 'unsupported' | 'error';\n reason: string;\n };\n\nexport interface PodAiConnectionStatusReader {\n read(input: { webId: string; podUrl?: string; auth?: AuthContext }): Promise<PodAiConnectionStatus>;\n}\n\nexport interface PodSettingsHandlerOptions {\n podLookupRepository: Pick<PodLookupRepository, 'findByWebId'>;\n usageRepo: Pick<UsageRepository, 'getPodUsage'>;\n aiConnectionStatusReader?: PodAiConnectionStatusReader;\n now?: () => Date;\n logger?: Pick<ReturnType<typeof getLoggerFor>, 'warn' | 'error'>;\n}\n\nexport function registerPodSettingsRoutes(server: ApiServer, options: PodSettingsHandlerOptions): void {\n const logger = options.logger ?? getLoggerFor('PodSettingsHandler');\n const now = options.now ?? (() => new Date());\n const aiConnectionStatusReader = options.aiConnectionStatusReader ?? {\n read: async () => ({ status: 'unsupported', reason: 'not_configured' }) as const,\n };\n\n server.get('/api/pod/settings/status', async (request, response) => {\n if (!request.auth || request.auth.type !== 'solid') {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const webId = request.auth.webId;\n try {\n const pod = await options.podLookupRepository.findByWebId(webId);\n const podUrl = pod?.storageUrl ?? pod?.baseUrl;\n const [storage, aiConnection] = await Promise.all([\n readStorageStatus(options.usageRepo, pod),\n aiConnectionStatusReader.read({ webId, podUrl, auth: request.auth }).catch((error: unknown) => {\n const reason = safeAiConnectionFailureReason(error);\n logger.warn(`Failed to read Pod AI Connection status: ${reason}`);\n return { status: 'error', reason } as const;\n }),\n ]);\n\n sendJson(response, 200, {\n identity: {\n webId,\n podUrl,\n },\n storage,\n aiConnection,\n generatedAt: now().toISOString(),\n } satisfies PodSettingsStatus);\n } catch (error) {\n logger.error(`Failed to read Pod settings status: ${safeLogError(error)}`);\n sendJson(response, 500, { error: 'Failed to read Pod settings status' });\n }\n });\n}\n\nasync function readStorageStatus(\n usageRepo: Pick<UsageRepository, 'getPodUsage'>,\n pod: PodLookupResult | undefined,\n): Promise<PodStorageStatus> {\n if (!pod) {\n return { status: 'unsupported', reason: 'pod_not_found' };\n }\n\n let usage: PodUsageRecord | undefined;\n try {\n usage = await usageRepo.getPodUsage(pod.podId);\n } catch {\n return { status: 'error', reason: 'usage_unavailable' };\n }\n\n if (!usage) {\n return { status: 'unsupported', reason: 'usage_not_available' };\n }\n\n return {\n status: 'available',\n usage: {\n storageBytes: usage.storageBytes,\n ingressBytes: usage.ingressBytes,\n egressBytes: usage.egressBytes,\n },\n limits: {\n storageLimitBytes: usage.storageLimitBytes ?? null,\n bandwidthLimitBps: usage.bandwidthLimitBps ?? null,\n },\n source: 'identity_usage',\n };\n}\n\ntype ProviderConnectStatusSource = {\n listProviders(input: {\n webId: string;\n deployment: GatewayDeployment;\n auth?: AuthContext;\n }): Promise<ProviderConnectionSummary[]>;\n};\n\nexport class ProviderConnectPodAiConnectionStatusReader implements PodAiConnectionStatusReader {\n public constructor(\n private readonly connectService: ProviderConnectStatusSource,\n private readonly deployment: GatewayDeployment,\n ) {}\n\n public async read({ webId, podUrl, auth }: { webId: string; podUrl?: string; auth?: AuthContext }): Promise<PodAiConnectionStatus> {\n if (!podUrl) {\n return { status: 'unsupported', reason: 'pod_not_found' };\n }\n\n try {\n const providers = await this.connectService.listProviders({\n webId,\n deployment: this.deployment,\n auth,\n });\n\n return {\n status: 'available',\n containerUrl: new URL('settings/credentials.ttl', podUrl).toString(),\n configuredProviders: providers.filter((provider) => provider.status !== 'disconnected').length,\n source: 'drizzle-solid',\n };\n } catch {\n return { status: 'error', reason: 'ai_connection_unavailable' };\n }\n }\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction safeLogError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction safeAiConnectionFailureReason(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n for (const reason of [\n 'service_access_missing',\n 'not_configured',\n 'pod_not_found',\n 'ai_connection_unavailable',\n ]) {\n if (message.includes(reason)) {\n return reason;\n }\n }\n return 'ai_connection_unavailable';\n}\n"]}
|
|
@@ -18,6 +18,7 @@ export declare class InternalPodDataHttpHandler extends HttpHandler {
|
|
|
18
18
|
canHandle({ request }: HttpHandlerInput): Promise<void>;
|
|
19
19
|
handle({ request, response }: HttpHandlerInput): Promise<void>;
|
|
20
20
|
private verifyRequest;
|
|
21
|
+
private logRejectedRequest;
|
|
21
22
|
private intentMatchesRequest;
|
|
22
23
|
private isHostedOwnerResource;
|
|
23
24
|
private isAllowedAiConnectionResource;
|
|
@@ -26,7 +27,8 @@ export declare class InternalPodDataHttpHandler extends HttpHandler {
|
|
|
26
27
|
private createPatch;
|
|
27
28
|
private pipeRepresentation;
|
|
28
29
|
private writeNotFound;
|
|
29
|
-
private
|
|
30
|
+
private writeEmptyGraph;
|
|
31
|
+
private requestPathname;
|
|
30
32
|
private parseOptionalUrl;
|
|
31
33
|
private isInsideDeployment;
|
|
32
34
|
}
|
|
@@ -6,7 +6,7 @@ const global_logger_factory_1 = require("global-logger-factory");
|
|
|
6
6
|
const community_server_1 = require("@solid/community-server");
|
|
7
7
|
const AiConnectionServiceAccess_1 = require("../api/ai-gateway/service-access/AiConnectionServiceAccess");
|
|
8
8
|
const GatewayAdminProxyAuth_1 = require("../runtime/GatewayAdminProxyAuth");
|
|
9
|
-
const ALLOWED_METHODS = new Set(['GET', 'PUT', 'PATCH', 'DELETE']);
|
|
9
|
+
const ALLOWED_METHODS = new Set(['GET', 'HEAD', 'PUT', 'PATCH', 'DELETE']);
|
|
10
10
|
const DEFAULT_NONCE_TTL_MS = 120_000;
|
|
11
11
|
const DEFAULT_NONCE_MAX_ENTRIES = 10_000;
|
|
12
12
|
class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
@@ -26,12 +26,12 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
async canHandle({ request }) {
|
|
29
|
-
if (this.
|
|
29
|
+
if (this.requestPathname(request) !== this.basePath) {
|
|
30
30
|
throw new community_server_1.NotImplementedHttpError('Not an internal Pod data request.');
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
async handle({ request, response }) {
|
|
34
|
-
if (this.
|
|
34
|
+
if (this.requestPathname(request) !== this.basePath) {
|
|
35
35
|
throw new community_server_1.NotImplementedHttpError('Not an internal Pod data request.');
|
|
36
36
|
}
|
|
37
37
|
const intent = this.verifyRequest(request);
|
|
@@ -44,7 +44,12 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
46
|
if (isHttpNotFound(error)) {
|
|
47
|
-
|
|
47
|
+
if (intent.method === 'GET') {
|
|
48
|
+
this.writeEmptyGraph(response);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.writeNotFound(response);
|
|
52
|
+
}
|
|
48
53
|
return;
|
|
49
54
|
}
|
|
50
55
|
throw error;
|
|
@@ -53,9 +58,11 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
53
58
|
verifyRequest(request) {
|
|
54
59
|
const method = request.method?.toUpperCase();
|
|
55
60
|
if (!ALLOWED_METHODS.has(method ?? '')) {
|
|
61
|
+
this.logRejectedRequest('method_not_allowed');
|
|
56
62
|
return undefined;
|
|
57
63
|
}
|
|
58
64
|
if (!(0, GatewayAdminProxyAuth_1.isLoopbackRemoteAddress)(request.socket.remoteAddress)) {
|
|
65
|
+
this.logRejectedRequest('non_loopback_transport');
|
|
59
66
|
return undefined;
|
|
60
67
|
}
|
|
61
68
|
const verification = (0, GatewayAdminProxyAuth_1.verifyGatewayAdminProxyHeaders)({
|
|
@@ -65,22 +72,30 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
65
72
|
url: request.url,
|
|
66
73
|
});
|
|
67
74
|
if (!verification.valid || !verification.originalClientLoopback || !verification.intent || !verification.nonce) {
|
|
75
|
+
this.logRejectedRequest(verification.reason ?? 'invalid_runtime_marker');
|
|
68
76
|
return undefined;
|
|
69
77
|
}
|
|
70
78
|
if (!this.intentMatchesRequest(verification.intent, method)) {
|
|
79
|
+
this.logRejectedRequest('intent_mismatch');
|
|
71
80
|
return undefined;
|
|
72
81
|
}
|
|
73
82
|
if (!this.isHostedOwnerResource(verification.intent)) {
|
|
83
|
+
this.logRejectedRequest('owner_resource_outside_deployment');
|
|
74
84
|
return undefined;
|
|
75
85
|
}
|
|
76
86
|
if (!this.isAllowedAiConnectionResource(verification.intent)) {
|
|
87
|
+
this.logRejectedRequest('resource_not_allowed');
|
|
77
88
|
return undefined;
|
|
78
89
|
}
|
|
79
90
|
if (!this.seenNonces.consume(verification.nonce)) {
|
|
91
|
+
this.logRejectedRequest('nonce_rejected');
|
|
80
92
|
return undefined;
|
|
81
93
|
}
|
|
82
94
|
return verification.intent;
|
|
83
95
|
}
|
|
96
|
+
logRejectedRequest(reason) {
|
|
97
|
+
this.logger.warn(`Rejected internal Pod data request: ${reason}`);
|
|
98
|
+
}
|
|
84
99
|
intentMatchesRequest(intent, method) {
|
|
85
100
|
if (intent.method !== method) {
|
|
86
101
|
return false;
|
|
@@ -88,7 +103,7 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
88
103
|
if (intent.scopes.length === 0) {
|
|
89
104
|
return false;
|
|
90
105
|
}
|
|
91
|
-
const requiredScope = method === 'GET' ? 'ai:credentials:read' : 'ai:credentials:write';
|
|
106
|
+
const requiredScope = method === 'GET' || method === 'HEAD' ? 'ai:credentials:read' : 'ai:credentials:write';
|
|
92
107
|
return intent.scopes.some((scope) => scope === requiredScope ||
|
|
93
108
|
scope === 'ai:credentials:*' ||
|
|
94
109
|
scope === 'ai:*');
|
|
@@ -143,6 +158,16 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
143
158
|
await this.pipeRepresentation(representation, response);
|
|
144
159
|
return;
|
|
145
160
|
}
|
|
161
|
+
case 'HEAD': {
|
|
162
|
+
const representation = await this.resourceStore.getRepresentation(identifier, {
|
|
163
|
+
type: { 'text/turtle': 1, '*/*': 0.1 },
|
|
164
|
+
});
|
|
165
|
+
response.statusCode = 200;
|
|
166
|
+
response.setHeader('Content-Type', representation.metadata.contentType ?? 'text/turtle');
|
|
167
|
+
representation.data.destroy();
|
|
168
|
+
response.end();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
146
171
|
case 'PUT': {
|
|
147
172
|
await this.resourceStore.setRepresentation(identifier, this.createRepresentation(request, intent.resourceUrl));
|
|
148
173
|
response.statusCode = 204;
|
|
@@ -182,8 +207,24 @@ class InternalPodDataHttpHandler extends community_server_1.HttpHandler {
|
|
|
182
207
|
response.setHeader('Cache-Control', 'no-store');
|
|
183
208
|
response.end('Not Found');
|
|
184
209
|
}
|
|
185
|
-
|
|
186
|
-
|
|
210
|
+
writeEmptyGraph(response) {
|
|
211
|
+
response.statusCode = 200;
|
|
212
|
+
response.setHeader('Content-Type', 'text/turtle');
|
|
213
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
214
|
+
response.end();
|
|
215
|
+
}
|
|
216
|
+
requestPathname(request) {
|
|
217
|
+
const requestUrl = request.url ?? '/';
|
|
218
|
+
try {
|
|
219
|
+
// Route ownership is path-based. Do not let an untrusted or proxy-shaped
|
|
220
|
+
// Host header make URL parsing throw and silently skip this first handler.
|
|
221
|
+
return requestUrl.startsWith('/')
|
|
222
|
+
? new URL(requestUrl, 'http://xpod.internal').pathname
|
|
223
|
+
: new URL(requestUrl).pathname;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
187
228
|
}
|
|
188
229
|
parseOptionalUrl(value) {
|
|
189
230
|
if (!value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InternalPodDataHttpHandler.js","sourceRoot":"","sources":["../../src/http/InternalPodDataHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { getLoggerFor } from 'global-logger-factory';\nimport {\n BasicRepresentation,\n HttpHandler,\n NotImplementedHttpError,\n RepresentationMetadata,\n type BodyParser,\n type HttpHandlerInput,\n type HttpRequest,\n type HttpResponse,\n type Patch,\n type Representation,\n type ResourceStore,\n} from '@solid/community-server';\nimport { createAiConnectionServiceAccess } from '../api/ai-gateway/service-access/AiConnectionServiceAccess';\nimport {\n isLoopbackRemoteAddress,\n verifyGatewayAdminProxyHeaders,\n type GatewayAdminProxyIntent,\n} from '../runtime/GatewayAdminProxyAuth';\n\nexport interface InternalPodDataHttpHandlerOptions {\n resourceStore: ResourceStore;\n gatewayAdminProxyAuthSecret?: string;\n patchBodyParser?: BodyParser;\n baseUrl?: string;\n basePath?: string;\n}\n\ninterface InternalPodDataHttpHandlerNonceOptions {\n nonceTtlMs?: number;\n nonceMaxEntries?: number;\n now?: () => number;\n}\n\ntype InternalPodDataMethod = 'GET' | 'PUT' | 'PATCH' | 'DELETE';\n\nconst ALLOWED_METHODS = new Set([ 'GET', 'PUT', 'PATCH', 'DELETE' ]);\nconst DEFAULT_NONCE_TTL_MS = 120_000;\nconst DEFAULT_NONCE_MAX_ENTRIES = 10_000;\n\nexport class InternalPodDataHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly resourceStore: ResourceStore;\n private readonly gatewayAdminProxyAuthSecret?: string;\n private readonly patchBodyParser?: BodyParser;\n private readonly deploymentBaseUrl?: URL;\n private readonly basePath: string;\n private readonly seenNonces: BoundedTtlNonceCache;\n\n public constructor(options: InternalPodDataHttpHandlerOptions) {\n super();\n this.resourceStore = options.resourceStore;\n this.gatewayAdminProxyAuthSecret = options.gatewayAdminProxyAuthSecret ?? process.env.XPOD_GATEWAY_ADMIN_PROXY_AUTH_SECRET;\n this.patchBodyParser = options.patchBodyParser;\n this.deploymentBaseUrl = this.parseOptionalUrl(options.baseUrl ?? process.env.CSS_BASE_URL);\n this.basePath = options.basePath ?? '/.internal/pod-data';\n const nonceOptions = options as InternalPodDataHttpHandlerOptions & InternalPodDataHttpHandlerNonceOptions;\n this.seenNonces = new BoundedTtlNonceCache({\n ttlMs: nonceOptions.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS,\n maxEntries: nonceOptions.nonceMaxEntries ?? DEFAULT_NONCE_MAX_ENTRIES,\n now: nonceOptions.now,\n });\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (this.parseUrl(request).pathname !== this.basePath) {\n throw new NotImplementedHttpError('Not an internal Pod data request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if (this.parseUrl(request).pathname !== this.basePath) {\n throw new NotImplementedHttpError('Not an internal Pod data request.');\n }\n\n const intent = this.verifyRequest(request);\n if (!intent) {\n this.writeNotFound(response);\n return;\n }\n\n try {\n await this.delegate(request, response, intent);\n } catch (error: unknown) {\n if (isHttpNotFound(error)) {\n this.writeNotFound(response);\n return;\n }\n throw error;\n }\n }\n\n private verifyRequest(request: HttpRequest): GatewayAdminProxyIntent | undefined {\n const method = request.method?.toUpperCase();\n if (!ALLOWED_METHODS.has(method ?? '')) {\n return undefined;\n }\n if (!isLoopbackRemoteAddress(request.socket.remoteAddress)) {\n return undefined;\n }\n\n const verification = verifyGatewayAdminProxyHeaders({\n headers: request.headers,\n secret: this.gatewayAdminProxyAuthSecret,\n method,\n url: request.url,\n });\n if (!verification.valid || !verification.originalClientLoopback || !verification.intent || !verification.nonce) {\n return undefined;\n }\n if (!this.intentMatchesRequest(verification.intent, method as InternalPodDataMethod)) {\n return undefined;\n }\n if (!this.isHostedOwnerResource(verification.intent)) {\n return undefined;\n }\n if (!this.isAllowedAiConnectionResource(verification.intent)) {\n return undefined;\n }\n if (!this.seenNonces.consume(verification.nonce)) {\n return undefined;\n }\n return verification.intent;\n }\n\n private intentMatchesRequest(intent: GatewayAdminProxyIntent, method: InternalPodDataMethod): boolean {\n if (intent.method !== method) {\n return false;\n }\n if (intent.scopes.length === 0) {\n return false;\n }\n const requiredScope = method === 'GET' ? 'ai:credentials:read' : 'ai:credentials:write';\n return intent.scopes.some((scope) =>\n scope === requiredScope ||\n scope === 'ai:credentials:*' ||\n scope === 'ai:*',\n );\n }\n\n private isHostedOwnerResource(intent: GatewayAdminProxyIntent): boolean {\n if (!this.deploymentBaseUrl) {\n return false;\n }\n\n let ownerUrl: URL;\n let resourceUrl: URL;\n try {\n ownerUrl = new URL(intent.ownerWebId);\n resourceUrl = new URL(intent.resourceUrl);\n } catch {\n return false;\n }\n if (!this.isInsideDeployment(ownerUrl) || !this.isInsideDeployment(resourceUrl)) {\n return false;\n }\n\n const podRoot = hostedPodRootFromOwner(ownerUrl);\n return podRoot !== undefined &&\n resourceUrl.origin === podRoot.origin &&\n resourceUrl.pathname.startsWith(podRoot.pathname);\n }\n\n private isAllowedAiConnectionResource(intent: GatewayAdminProxyIntent): boolean {\n let resourceUrl: URL;\n try {\n resourceUrl = new URL(intent.resourceUrl);\n } catch {\n return false;\n }\n if (resourceUrl.hash || resourceUrl.search) {\n return false;\n }\n const allowed = createAiConnectionServiceAccess({\n ownerWebId: intent.ownerWebId,\n serviceWebId: intent.ownerWebId,\n }).resources.map((resource) => resource.url);\n return allowed.includes(resourceUrl.href);\n }\n\n private async delegate(\n request: HttpRequest,\n response: HttpResponse,\n intent: GatewayAdminProxyIntent,\n ): Promise<void> {\n const identifier = { path: intent.resourceUrl };\n switch (intent.method) {\n case 'GET': {\n const representation = await this.resourceStore.getRepresentation(identifier, {\n type: { 'text/turtle': 1, '*/*': 0.1 },\n });\n response.statusCode = 200;\n response.setHeader('Content-Type', representation.metadata.contentType ?? 'text/turtle');\n await this.pipeRepresentation(representation, response);\n return;\n }\n case 'PUT': {\n await this.resourceStore.setRepresentation(identifier, this.createRepresentation(request, intent.resourceUrl));\n response.statusCode = 204;\n response.end();\n return;\n }\n case 'PATCH': {\n await this.resourceStore.modifyResource(identifier, await this.createPatch(request, intent.resourceUrl));\n response.statusCode = 204;\n response.end();\n return;\n }\n case 'DELETE': {\n await this.resourceStore.deleteResource(identifier);\n response.statusCode = 204;\n response.end();\n }\n }\n }\n\n private createRepresentation(request: HttpRequest, resourceUrl: string): Representation {\n const contentType = firstHeader(request.headers['content-type']) ?? 'text/turtle';\n return new BasicRepresentation(\n request,\n new RepresentationMetadata({ path: resourceUrl }, contentType),\n );\n }\n\n private async createPatch(request: HttpRequest, resourceUrl: string): Promise<Patch> {\n if (!this.patchBodyParser) {\n throw new NotImplementedHttpError('Internal Pod data PATCH requires a PatchBodyParser.');\n }\n const contentType = firstHeader(request.headers['content-type']) ?? 'application/sparql-update';\n const metadata = new RepresentationMetadata({ path: resourceUrl }, contentType);\n return this.patchBodyParser.handleSafe({ request, metadata }) as Promise<Patch>;\n }\n\n private async pipeRepresentation(representation: Representation, response: HttpResponse): Promise<void> {\n await pipeline(representation.data as Readable, response);\n }\n\n private writeNotFound(response: HttpResponse): void {\n response.statusCode = 404;\n response.setHeader('Cache-Control', 'no-store');\n response.end('Not Found');\n }\n\n private parseUrl(request: HttpRequest): URL {\n return new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n }\n\n private parseOptionalUrl(value: string | undefined): URL | undefined {\n if (!value) {\n return undefined;\n }\n try {\n return new URL(ensureTrailingSlash(value));\n } catch {\n return undefined;\n }\n }\n\n private isInsideDeployment(url: URL): boolean {\n return this.deploymentBaseUrl !== undefined &&\n url.origin === this.deploymentBaseUrl.origin &&\n url.pathname.startsWith(this.deploymentBaseUrl.pathname);\n }\n}\n\nfunction isHttpNotFound(error: unknown): boolean {\n return typeof error === 'object' &&\n error !== null &&\n ('statusCode' in error ? error.statusCode === 404 : (error as { name?: string }).name === 'NotFoundHttpError');\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction hostedPodRootFromOwner(ownerUrl: URL): URL | undefined {\n if (ownerUrl.hash !== '#me') {\n return undefined;\n }\n if (!ownerUrl.pathname.endsWith('/profile/card')) {\n return undefined;\n }\n const podPath = ownerUrl.pathname.slice(0, -'profile/card'.length);\n if (!podPath || !podPath.endsWith('/')) {\n return undefined;\n }\n return new URL(podPath, ownerUrl.origin);\n}\n\nfunction ensureTrailingSlash(value: string): string {\n return value.endsWith('/') ? value : `${value}/`;\n}\n\nclass BoundedTtlNonceCache {\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private readonly now: () => number;\n private readonly entries = new Map<string, number>();\n\n public constructor(options: { ttlMs: number; maxEntries: number; now?: () => number }) {\n this.ttlMs = Math.max(1, options.ttlMs);\n this.maxEntries = Math.max(1, options.maxEntries);\n this.now = options.now ?? Date.now;\n }\n\n public consume(nonce: string): boolean {\n const now = this.now();\n this.pruneExpired(now);\n if (this.entries.has(nonce)) {\n return false;\n }\n if (this.entries.size >= this.maxEntries) {\n return false;\n }\n this.entries.set(nonce, now + this.ttlMs);\n return true;\n }\n\n private pruneExpired(now: number): void {\n for (const [nonce, expiresAt] of this.entries) {\n if (expiresAt > now) {\n continue;\n }\n this.entries.delete(nonce);\n }\n }\n\n}\n"]}
|
|
1
|
+
{"version":3,"file":"InternalPodDataHttpHandler.js","sourceRoot":"","sources":["../../src/http/InternalPodDataHttpHandler.ts"],"names":[],"mappings":";;;AACA,mDAAgD;AAChD,iEAAqD;AACrD,8DAYiC;AACjC,0GAA6G;AAC7G,4EAI0C;AAkB1C,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAE,CAAC,CAAC;AAC7E,MAAM,oBAAoB,GAAG,OAAO,CAAC;AACrC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEzC,MAAa,0BAA2B,SAAQ,8BAAW;IASzD,YAAmB,OAA0C;QAC3D,KAAK,EAAE,CAAC;QATS,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAU7C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,2BAA2B,GAAG,OAAO,CAAC,2BAA2B,IAAI,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC;QAC3H,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QAC/C,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC5F,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAC;QAC1D,MAAM,YAAY,GAAG,OAAqF,CAAC;QAC3G,IAAI,CAAC,UAAU,GAAG,IAAI,oBAAoB,CAAC;YACzC,KAAK,EAAE,YAAY,CAAC,UAAU,IAAI,oBAAoB;YACtD,UAAU,EAAE,YAAY,CAAC,eAAe,IAAI,yBAAyB;YACrE,GAAG,EAAE,YAAY,CAAC,GAAG;SACtB,CAAC,CAAC;IACL,CAAC;IAEe,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,EAAoB;QAC3D,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpD,MAAM,IAAI,0CAAuB,CAAC,mCAAmC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAEe,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAoB;QAClE,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpD,MAAM,IAAI,0CAAuB,CAAC,mCAAmC,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;oBAC5B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAC/B,CAAC;gBACD,OAAO;YACT,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,OAAoB;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;YAC9C,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,IAAA,+CAAuB,EAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;YAC3D,IAAI,CAAC,kBAAkB,CAAC,wBAAwB,CAAC,CAAC;YAClD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,YAAY,GAAG,IAAA,sDAA8B,EAAC;YAClD,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,IAAI,CAAC,2BAA2B;YACxC,MAAM;YACN,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC,sBAAsB,IAAI,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;YAC/G,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,MAAM,IAAI,wBAAwB,CAAC,CAAC;YACzE,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,YAAY,CAAC,MAAM,EAAE,MAA+B,CAAC,EAAE,CAAC;YACrF,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;YAC3C,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,kBAAkB,CAAC,mCAAmC,CAAC,CAAC;YAC7D,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;YAC1C,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,YAAY,CAAC,MAAM,CAAC;IAC7B,CAAC;IAEO,kBAAkB,CAAC,MAAc;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;IAEO,oBAAoB,CAAC,MAA+B,EAAE,MAA6B;QACzF,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,aAAa,GAAG,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,sBAAsB,CAAC;QAC7G,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAClC,KAAK,KAAK,aAAa;YACvB,KAAK,KAAK,kBAAkB;YAC5B,KAAK,KAAK,MAAM,CACjB,CAAC;IACJ,CAAC;IAEO,qBAAqB,CAAC,MAA+B;QAC3D,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,QAAa,CAAC;QAClB,IAAI,WAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACtC,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;YAChF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACjD,OAAO,OAAO,KAAK,SAAS;YAC1B,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;YACrC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAEO,6BAA6B,CAAC,MAA+B;QACnE,IAAI,WAAgB,CAAC;QACrB,IAAI,CAAC;YACH,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,WAAW,CAAC,IAAI,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YAC3C,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,OAAO,GAAG,IAAA,2DAA+B,EAAC;YAC9C,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,YAAY,EAAE,MAAM,CAAC,UAAU;SAChC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,QAAQ,CACpB,OAAoB,EACpB,QAAsB,EACtB,MAA+B;QAE/B,MAAM,UAAU,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAChD,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAAE;oBAC5E,IAAI,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;iBACvC,CAAC,CAAC;gBACH,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC,QAAQ,CAAC,WAAW,IAAI,aAAa,CAAC,CAAC;gBACzF,MAAM,IAAI,CAAC,kBAAkB,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAAE;oBAC5E,IAAI,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;iBACvC,CAAC,CAAC;gBACH,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC,QAAQ,CAAC,WAAW,IAAI,aAAa,CAAC,CAAC;gBACxF,cAAc,CAAC,IAAiB,CAAC,OAAO,EAAE,CAAC;gBAC5C,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,MAAM,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC/G,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;gBACzG,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;gBACpD,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,GAAG,EAAE,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,OAAoB,EAAE,WAAmB;QACpE,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,aAAa,CAAC;QAClF,OAAO,IAAI,sCAAmB,CAC5B,OAAO,EACP,IAAI,yCAAsB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,WAAW,CAAC,CAC/D,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,OAAoB,EAAE,WAAmB;QACjE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,MAAM,IAAI,0CAAuB,CAAC,qDAAqD,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,2BAA2B,CAAC;QAChG,MAAM,QAAQ,GAAG,IAAI,yCAAsB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,WAAW,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAmB,CAAC;IAClF,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,cAA8B,EAAE,QAAsB;QACrF,MAAM,IAAA,mBAAQ,EAAC,cAAc,CAAC,IAAgB,EAAE,QAAQ,CAAC,CAAC;IAC5D,CAAC;IAEO,aAAa,CAAC,QAAsB;QAC1C,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAChD,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IAEO,eAAe,CAAC,QAAsB;QAC5C,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;QAClD,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAChD,QAAQ,CAAC,GAAG,EAAE,CAAC;IACjB,CAAC;IAEO,eAAe,CAAC,OAAoB;QAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;QACtC,IAAI,CAAC;YACH,yEAAyE;YACzE,2EAA2E;YAC3E,OAAO,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC;gBAC/B,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,sBAAsB,CAAC,CAAC,QAAQ;gBACtD,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAyB;QAChD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,GAAQ;QACjC,OAAO,IAAI,CAAC,iBAAiB,KAAK,SAAS;YACzC,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,iBAAiB,CAAC,MAAM;YAC5C,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC;CACF;AAxQD,gEAwQC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ;QAC9B,KAAK,KAAK,IAAI;QACd,CAAC,YAAY,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAE,KAA2B,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC;AACnH,CAAC;AAED,SAAS,WAAW,CAAC,KAAoC;IACvD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC;AAED,SAAS,sBAAsB,CAAC,QAAa;IAC3C,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;AACnD,CAAC;AAED,MAAM,oBAAoB;IAMxB,YAAmB,OAAkE;QAFpE,YAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAGnD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAEM,OAAO,CAAC,KAAa;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,YAAY,CAAC,GAAW;QAC9B,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC9C,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;gBACpB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;CAEF","sourcesContent":["import { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { getLoggerFor } from 'global-logger-factory';\nimport {\n BasicRepresentation,\n HttpHandler,\n NotImplementedHttpError,\n RepresentationMetadata,\n type BodyParser,\n type HttpHandlerInput,\n type HttpRequest,\n type HttpResponse,\n type Patch,\n type Representation,\n type ResourceStore,\n} from '@solid/community-server';\nimport { createAiConnectionServiceAccess } from '../api/ai-gateway/service-access/AiConnectionServiceAccess';\nimport {\n isLoopbackRemoteAddress,\n verifyGatewayAdminProxyHeaders,\n type GatewayAdminProxyIntent,\n} from '../runtime/GatewayAdminProxyAuth';\n\nexport interface InternalPodDataHttpHandlerOptions {\n resourceStore: ResourceStore;\n gatewayAdminProxyAuthSecret?: string;\n patchBodyParser?: BodyParser;\n baseUrl?: string;\n basePath?: string;\n}\n\ninterface InternalPodDataHttpHandlerNonceOptions {\n nonceTtlMs?: number;\n nonceMaxEntries?: number;\n now?: () => number;\n}\n\ntype InternalPodDataMethod = 'GET' | 'HEAD' | 'PUT' | 'PATCH' | 'DELETE';\n\nconst ALLOWED_METHODS = new Set([ 'GET', 'HEAD', 'PUT', 'PATCH', 'DELETE' ]);\nconst DEFAULT_NONCE_TTL_MS = 120_000;\nconst DEFAULT_NONCE_MAX_ENTRIES = 10_000;\n\nexport class InternalPodDataHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly resourceStore: ResourceStore;\n private readonly gatewayAdminProxyAuthSecret?: string;\n private readonly patchBodyParser?: BodyParser;\n private readonly deploymentBaseUrl?: URL;\n private readonly basePath: string;\n private readonly seenNonces: BoundedTtlNonceCache;\n\n public constructor(options: InternalPodDataHttpHandlerOptions) {\n super();\n this.resourceStore = options.resourceStore;\n this.gatewayAdminProxyAuthSecret = options.gatewayAdminProxyAuthSecret ?? process.env.XPOD_GATEWAY_ADMIN_PROXY_AUTH_SECRET;\n this.patchBodyParser = options.patchBodyParser;\n this.deploymentBaseUrl = this.parseOptionalUrl(options.baseUrl ?? process.env.CSS_BASE_URL);\n this.basePath = options.basePath ?? '/.internal/pod-data';\n const nonceOptions = options as InternalPodDataHttpHandlerOptions & InternalPodDataHttpHandlerNonceOptions;\n this.seenNonces = new BoundedTtlNonceCache({\n ttlMs: nonceOptions.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS,\n maxEntries: nonceOptions.nonceMaxEntries ?? DEFAULT_NONCE_MAX_ENTRIES,\n now: nonceOptions.now,\n });\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (this.requestPathname(request) !== this.basePath) {\n throw new NotImplementedHttpError('Not an internal Pod data request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if (this.requestPathname(request) !== this.basePath) {\n throw new NotImplementedHttpError('Not an internal Pod data request.');\n }\n\n const intent = this.verifyRequest(request);\n if (!intent) {\n this.writeNotFound(response);\n return;\n }\n\n try {\n await this.delegate(request, response, intent);\n } catch (error: unknown) {\n if (isHttpNotFound(error)) {\n if (intent.method === 'GET') {\n this.writeEmptyGraph(response);\n } else {\n this.writeNotFound(response);\n }\n return;\n }\n throw error;\n }\n }\n\n private verifyRequest(request: HttpRequest): GatewayAdminProxyIntent | undefined {\n const method = request.method?.toUpperCase();\n if (!ALLOWED_METHODS.has(method ?? '')) {\n this.logRejectedRequest('method_not_allowed');\n return undefined;\n }\n if (!isLoopbackRemoteAddress(request.socket.remoteAddress)) {\n this.logRejectedRequest('non_loopback_transport');\n return undefined;\n }\n\n const verification = verifyGatewayAdminProxyHeaders({\n headers: request.headers,\n secret: this.gatewayAdminProxyAuthSecret,\n method,\n url: request.url,\n });\n if (!verification.valid || !verification.originalClientLoopback || !verification.intent || !verification.nonce) {\n this.logRejectedRequest(verification.reason ?? 'invalid_runtime_marker');\n return undefined;\n }\n if (!this.intentMatchesRequest(verification.intent, method as InternalPodDataMethod)) {\n this.logRejectedRequest('intent_mismatch');\n return undefined;\n }\n if (!this.isHostedOwnerResource(verification.intent)) {\n this.logRejectedRequest('owner_resource_outside_deployment');\n return undefined;\n }\n if (!this.isAllowedAiConnectionResource(verification.intent)) {\n this.logRejectedRequest('resource_not_allowed');\n return undefined;\n }\n if (!this.seenNonces.consume(verification.nonce)) {\n this.logRejectedRequest('nonce_rejected');\n return undefined;\n }\n return verification.intent;\n }\n\n private logRejectedRequest(reason: string): void {\n this.logger.warn(`Rejected internal Pod data request: ${reason}`);\n }\n\n private intentMatchesRequest(intent: GatewayAdminProxyIntent, method: InternalPodDataMethod): boolean {\n if (intent.method !== method) {\n return false;\n }\n if (intent.scopes.length === 0) {\n return false;\n }\n const requiredScope = method === 'GET' || method === 'HEAD' ? 'ai:credentials:read' : 'ai:credentials:write';\n return intent.scopes.some((scope) =>\n scope === requiredScope ||\n scope === 'ai:credentials:*' ||\n scope === 'ai:*',\n );\n }\n\n private isHostedOwnerResource(intent: GatewayAdminProxyIntent): boolean {\n if (!this.deploymentBaseUrl) {\n return false;\n }\n\n let ownerUrl: URL;\n let resourceUrl: URL;\n try {\n ownerUrl = new URL(intent.ownerWebId);\n resourceUrl = new URL(intent.resourceUrl);\n } catch {\n return false;\n }\n if (!this.isInsideDeployment(ownerUrl) || !this.isInsideDeployment(resourceUrl)) {\n return false;\n }\n\n const podRoot = hostedPodRootFromOwner(ownerUrl);\n return podRoot !== undefined &&\n resourceUrl.origin === podRoot.origin &&\n resourceUrl.pathname.startsWith(podRoot.pathname);\n }\n\n private isAllowedAiConnectionResource(intent: GatewayAdminProxyIntent): boolean {\n let resourceUrl: URL;\n try {\n resourceUrl = new URL(intent.resourceUrl);\n } catch {\n return false;\n }\n if (resourceUrl.hash || resourceUrl.search) {\n return false;\n }\n const allowed = createAiConnectionServiceAccess({\n ownerWebId: intent.ownerWebId,\n serviceWebId: intent.ownerWebId,\n }).resources.map((resource) => resource.url);\n return allowed.includes(resourceUrl.href);\n }\n\n private async delegate(\n request: HttpRequest,\n response: HttpResponse,\n intent: GatewayAdminProxyIntent,\n ): Promise<void> {\n const identifier = { path: intent.resourceUrl };\n switch (intent.method) {\n case 'GET': {\n const representation = await this.resourceStore.getRepresentation(identifier, {\n type: { 'text/turtle': 1, '*/*': 0.1 },\n });\n response.statusCode = 200;\n response.setHeader('Content-Type', representation.metadata.contentType ?? 'text/turtle');\n await this.pipeRepresentation(representation, response);\n return;\n }\n case 'HEAD': {\n const representation = await this.resourceStore.getRepresentation(identifier, {\n type: { 'text/turtle': 1, '*/*': 0.1 },\n });\n response.statusCode = 200;\n response.setHeader('Content-Type', representation.metadata.contentType ?? 'text/turtle');\n (representation.data as Readable).destroy();\n response.end();\n return;\n }\n case 'PUT': {\n await this.resourceStore.setRepresentation(identifier, this.createRepresentation(request, intent.resourceUrl));\n response.statusCode = 204;\n response.end();\n return;\n }\n case 'PATCH': {\n await this.resourceStore.modifyResource(identifier, await this.createPatch(request, intent.resourceUrl));\n response.statusCode = 204;\n response.end();\n return;\n }\n case 'DELETE': {\n await this.resourceStore.deleteResource(identifier);\n response.statusCode = 204;\n response.end();\n }\n }\n }\n\n private createRepresentation(request: HttpRequest, resourceUrl: string): Representation {\n const contentType = firstHeader(request.headers['content-type']) ?? 'text/turtle';\n return new BasicRepresentation(\n request,\n new RepresentationMetadata({ path: resourceUrl }, contentType),\n );\n }\n\n private async createPatch(request: HttpRequest, resourceUrl: string): Promise<Patch> {\n if (!this.patchBodyParser) {\n throw new NotImplementedHttpError('Internal Pod data PATCH requires a PatchBodyParser.');\n }\n const contentType = firstHeader(request.headers['content-type']) ?? 'application/sparql-update';\n const metadata = new RepresentationMetadata({ path: resourceUrl }, contentType);\n return this.patchBodyParser.handleSafe({ request, metadata }) as Promise<Patch>;\n }\n\n private async pipeRepresentation(representation: Representation, response: HttpResponse): Promise<void> {\n await pipeline(representation.data as Readable, response);\n }\n\n private writeNotFound(response: HttpResponse): void {\n response.statusCode = 404;\n response.setHeader('Cache-Control', 'no-store');\n response.end('Not Found');\n }\n\n private writeEmptyGraph(response: HttpResponse): void {\n response.statusCode = 200;\n response.setHeader('Content-Type', 'text/turtle');\n response.setHeader('Cache-Control', 'no-store');\n response.end();\n }\n\n private requestPathname(request: HttpRequest): string | undefined {\n const requestUrl = request.url ?? '/';\n try {\n // Route ownership is path-based. Do not let an untrusted or proxy-shaped\n // Host header make URL parsing throw and silently skip this first handler.\n return requestUrl.startsWith('/')\n ? new URL(requestUrl, 'http://xpod.internal').pathname\n : new URL(requestUrl).pathname;\n } catch {\n return undefined;\n }\n }\n\n private parseOptionalUrl(value: string | undefined): URL | undefined {\n if (!value) {\n return undefined;\n }\n try {\n return new URL(ensureTrailingSlash(value));\n } catch {\n return undefined;\n }\n }\n\n private isInsideDeployment(url: URL): boolean {\n return this.deploymentBaseUrl !== undefined &&\n url.origin === this.deploymentBaseUrl.origin &&\n url.pathname.startsWith(this.deploymentBaseUrl.pathname);\n }\n}\n\nfunction isHttpNotFound(error: unknown): boolean {\n return typeof error === 'object' &&\n error !== null &&\n ('statusCode' in error ? error.statusCode === 404 : (error as { name?: string }).name === 'NotFoundHttpError');\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction hostedPodRootFromOwner(ownerUrl: URL): URL | undefined {\n if (ownerUrl.hash !== '#me') {\n return undefined;\n }\n if (!ownerUrl.pathname.endsWith('/profile/card')) {\n return undefined;\n }\n const podPath = ownerUrl.pathname.slice(0, -'profile/card'.length);\n if (!podPath || !podPath.endsWith('/')) {\n return undefined;\n }\n return new URL(podPath, ownerUrl.origin);\n}\n\nfunction ensureTrailingSlash(value: string): string {\n return value.endsWith('/') ? value : `${value}/`;\n}\n\nclass BoundedTtlNonceCache {\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private readonly now: () => number;\n private readonly entries = new Map<string, number>();\n\n public constructor(options: { ttlMs: number; maxEntries: number; now?: () => number }) {\n this.ttlMs = Math.max(1, options.ttlMs);\n this.maxEntries = Math.max(1, options.maxEntries);\n this.now = options.now ?? Date.now;\n }\n\n public consume(nonce: string): boolean {\n const now = this.now();\n this.pruneExpired(now);\n if (this.entries.has(nonce)) {\n return false;\n }\n if (this.entries.size >= this.maxEntries) {\n return false;\n }\n this.entries.set(nonce, now + this.ttlMs);\n return true;\n }\n\n private pruneExpired(now: number): void {\n for (const [nonce, expiresAt] of this.entries) {\n if (expiresAt > now) {\n continue;\n }\n this.entries.delete(nonce);\n }\n }\n\n}\n"]}
|
|
@@ -114,6 +114,10 @@
|
|
|
114
114
|
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_verifyRequest",
|
|
115
115
|
"memberFieldName": "verifyRequest"
|
|
116
116
|
},
|
|
117
|
+
{
|
|
118
|
+
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_logRejectedRequest",
|
|
119
|
+
"memberFieldName": "logRejectedRequest"
|
|
120
|
+
},
|
|
117
121
|
{
|
|
118
122
|
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_intentMatchesRequest",
|
|
119
123
|
"memberFieldName": "intentMatchesRequest"
|
|
@@ -147,8 +151,12 @@
|
|
|
147
151
|
"memberFieldName": "writeNotFound"
|
|
148
152
|
},
|
|
149
153
|
{
|
|
150
|
-
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#
|
|
151
|
-
"memberFieldName": "
|
|
154
|
+
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_writeEmptyGraph",
|
|
155
|
+
"memberFieldName": "writeEmptyGraph"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_requestPathname",
|
|
159
|
+
"memberFieldName": "requestPathname"
|
|
152
160
|
},
|
|
153
161
|
{
|
|
154
162
|
"@id": "undefineds:dist/http/InternalPodDataHttpHandler.jsonld#InternalPodDataHttpHandler__member_parseOptionalUrl",
|
|
@@ -25,7 +25,7 @@ export interface GatewayAdminProxyMarkerVerification {
|
|
|
25
25
|
}
|
|
26
26
|
export interface GatewayAdminProxyIntent {
|
|
27
27
|
ownerWebId: string;
|
|
28
|
-
method: 'GET' | 'PUT' | 'PATCH' | 'DELETE';
|
|
28
|
+
method: 'GET' | 'HEAD' | 'PUT' | 'PATCH' | 'DELETE';
|
|
29
29
|
resourceUrl: string;
|
|
30
30
|
principalKind: 'solid-user';
|
|
31
31
|
scopes: string[];
|
|
@@ -134,7 +134,7 @@ function parseGatewayAdminProxyIntent(value) {
|
|
|
134
134
|
const parsed = JSON.parse(value);
|
|
135
135
|
if (typeof parsed.ownerWebId !== 'string' ||
|
|
136
136
|
typeof parsed.resourceUrl !== 'string' ||
|
|
137
|
-
(parsed.method !== 'GET' && parsed.method !== 'PUT' && parsed.method !== 'PATCH' && parsed.method !== 'DELETE') ||
|
|
137
|
+
(parsed.method !== 'GET' && parsed.method !== 'HEAD' && parsed.method !== 'PUT' && parsed.method !== 'PATCH' && parsed.method !== 'DELETE') ||
|
|
138
138
|
parsed.principalKind !== 'solid-user' ||
|
|
139
139
|
!Array.isArray(parsed.scopes) ||
|
|
140
140
|
!parsed.scopes.every((scope) => typeof scope === 'string')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GatewayAdminProxyAuth.js","sourceRoot":"","sources":["../../src/runtime/GatewayAdminProxyAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'node:http';\nimport { isIP } from 'node:net';\n\nexport const GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER = 'x-xpod-admin-proxy-loopback';\nexport const GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER = 'x-xpod-admin-proxy-timestamp';\nexport const GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER = 'x-xpod-admin-proxy-signature';\nexport const GATEWAY_ADMIN_PROXY_INTENT_HEADER = 'x-xpod-admin-proxy-intent';\nexport const GATEWAY_ADMIN_PROXY_NONCE_HEADER = 'x-xpod-admin-proxy-nonce';\n\nexport const GATEWAY_ADMIN_PROXY_HEADERS = [\n GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER,\n GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER,\n GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER,\n GATEWAY_ADMIN_PROXY_INTENT_HEADER,\n GATEWAY_ADMIN_PROXY_NONCE_HEADER,\n] as const;\n\nconst MAX_CLOCK_SKEW_MS = 60_000;\n\nexport function createGatewayAdminProxyAuthSecret(): string {\n return randomBytes(32).toString('base64url');\n}\n\nexport interface GatewayAdminProxyMarkerInput {\n secret: string;\n method: string | undefined;\n url: string | undefined;\n originalClientLoopback: boolean;\n issuedAt?: number;\n nonce?: string;\n intent?: GatewayAdminProxyIntent;\n}\n\nexport interface GatewayAdminProxyMarkerVerification {\n present: boolean;\n valid: boolean;\n originalClientLoopback: boolean;\n nonce?: string;\n intent?: GatewayAdminProxyIntent;\n reason?: string;\n}\n\nexport interface GatewayAdminProxyIntent {\n ownerWebId: string;\n method: 'GET' | 'PUT' | 'PATCH' | 'DELETE';\n resourceUrl: string;\n principalKind: 'solid-user';\n scopes: string[];\n}\n\nexport function createGatewayAdminProxyHeaders(input: GatewayAdminProxyMarkerInput): OutgoingHttpHeaders {\n const issuedAt = input.issuedAt ?? Date.now();\n const loopback = input.originalClientLoopback ? '1' : '0';\n const intent = input.intent ? canonicalGatewayAdminProxyIntent(input.intent) : undefined;\n return {\n [GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER]: loopback,\n [GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER]: String(issuedAt),\n ...(input.nonce ? { [GATEWAY_ADMIN_PROXY_NONCE_HEADER]: input.nonce } : {}),\n ...(intent ? { [GATEWAY_ADMIN_PROXY_INTENT_HEADER]: intent } : {}),\n [GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER]: signGatewayAdminProxyMarker({\n secret: input.secret,\n method: input.method,\n url: input.url,\n loopback,\n issuedAt,\n nonce: input.nonce,\n intent,\n }),\n };\n}\n\nexport function stripGatewayAdminProxyHeaders(headers: IncomingHttpHeaders): void {\n for (const header of GATEWAY_ADMIN_PROXY_HEADERS) {\n delete headers[header];\n }\n}\n\nexport function isLoopbackRemoteAddress(remoteAddress: string | undefined): boolean {\n if (!remoteAddress) return false;\n const normalized = remoteAddress.startsWith('::ffff:')\n ? remoteAddress.slice('::ffff:'.length)\n : remoteAddress;\n\n if (isIP(normalized) === 4) {\n return normalized.startsWith('127.');\n }\n return normalized === '::1';\n}\n\nexport function verifyGatewayAdminProxyHeaders(input: {\n headers: IncomingHttpHeaders;\n secret?: string;\n method: string | undefined;\n url: string | undefined;\n now?: number;\n}): GatewayAdminProxyMarkerVerification {\n const loopback = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER]);\n const timestamp = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER]);\n const signature = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER]);\n const nonce = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_NONCE_HEADER]);\n const intentHeader = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_INTENT_HEADER]);\n const present = loopback !== undefined || timestamp !== undefined || signature !== undefined;\n if (!present) {\n return { present: false, valid: false, originalClientLoopback: false };\n }\n if (!input.secret) {\n return { present: true, valid: false, originalClientLoopback: false, reason: 'missing_secret' };\n }\n if (loopback !== '0' && loopback !== '1') {\n return { present: true, valid: false, originalClientLoopback: false, reason: 'invalid_loopback' };\n }\n if (!timestamp || !signature) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'incomplete_marker' };\n }\n\n const issuedAt = Number.parseInt(timestamp, 10);\n if (!Number.isFinite(issuedAt)) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'invalid_timestamp' };\n }\n if (Math.abs((input.now ?? Date.now()) - issuedAt) > MAX_CLOCK_SKEW_MS) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'expired_marker' };\n }\n\n const intent = parseGatewayAdminProxyIntent(intentHeader);\n if (intentHeader !== undefined && !intent) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'invalid_intent' };\n }\n\n const expected = signGatewayAdminProxyMarker({\n secret: input.secret,\n method: input.method,\n url: input.url,\n loopback,\n issuedAt,\n nonce,\n intent: intentHeader,\n });\n if (!safeEqual(signature, expected)) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'bad_signature' };\n }\n\n return { present: true, valid: true, originalClientLoopback: loopback === '1', nonce, intent };\n}\n\nfunction signGatewayAdminProxyMarker(input: {\n secret: string;\n method: string | undefined;\n url: string | undefined;\n loopback: '0' | '1';\n issuedAt: number;\n nonce?: string;\n intent?: string;\n}): string {\n const payload = [\n 'v1',\n (input.method ?? 'GET').toUpperCase(),\n input.url ?? '/',\n input.loopback,\n String(input.issuedAt),\n input.nonce ?? '',\n input.intent ?? '',\n ].join('\\n');\n return createHmac('sha256', input.secret).update(payload).digest('base64url');\n}\n\nexport function canonicalGatewayAdminProxyIntent(intent: GatewayAdminProxyIntent): string {\n return JSON.stringify({\n ownerWebId: intent.ownerWebId,\n method: intent.method,\n resourceUrl: intent.resourceUrl,\n principalKind: intent.principalKind,\n scopes: [...intent.scopes].sort(),\n });\n}\n\nfunction parseGatewayAdminProxyIntent(value: string | undefined): GatewayAdminProxyIntent | undefined {\n if (!value) {\n return undefined;\n }\n try {\n const parsed = JSON.parse(value) as Partial<GatewayAdminProxyIntent>;\n if (typeof parsed.ownerWebId !== 'string' ||\n typeof parsed.resourceUrl !== 'string' ||\n (parsed.method !== 'GET' && parsed.method !== 'PUT' && parsed.method !== 'PATCH' && parsed.method !== 'DELETE') ||\n parsed.principalKind !== 'solid-user' ||\n !Array.isArray(parsed.scopes) ||\n !parsed.scopes.every((scope) => typeof scope === 'string')) {\n return undefined;\n }\n return {\n ownerWebId: parsed.ownerWebId,\n method: parsed.method,\n resourceUrl: parsed.resourceUrl,\n principalKind: parsed.principalKind,\n scopes: parsed.scopes,\n };\n } catch {\n return undefined;\n }\n}\n\nfunction safeEqual(actual: string, expected: string): boolean {\n const actualBuffer = Buffer.from(actual);\n const expectedBuffer = Buffer.from(expected);\n return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"GatewayAdminProxyAuth.js","sourceRoot":"","sources":["../../src/runtime/GatewayAdminProxyAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'node:http';\nimport { isIP } from 'node:net';\n\nexport const GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER = 'x-xpod-admin-proxy-loopback';\nexport const GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER = 'x-xpod-admin-proxy-timestamp';\nexport const GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER = 'x-xpod-admin-proxy-signature';\nexport const GATEWAY_ADMIN_PROXY_INTENT_HEADER = 'x-xpod-admin-proxy-intent';\nexport const GATEWAY_ADMIN_PROXY_NONCE_HEADER = 'x-xpod-admin-proxy-nonce';\n\nexport const GATEWAY_ADMIN_PROXY_HEADERS = [\n GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER,\n GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER,\n GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER,\n GATEWAY_ADMIN_PROXY_INTENT_HEADER,\n GATEWAY_ADMIN_PROXY_NONCE_HEADER,\n] as const;\n\nconst MAX_CLOCK_SKEW_MS = 60_000;\n\nexport function createGatewayAdminProxyAuthSecret(): string {\n return randomBytes(32).toString('base64url');\n}\n\nexport interface GatewayAdminProxyMarkerInput {\n secret: string;\n method: string | undefined;\n url: string | undefined;\n originalClientLoopback: boolean;\n issuedAt?: number;\n nonce?: string;\n intent?: GatewayAdminProxyIntent;\n}\n\nexport interface GatewayAdminProxyMarkerVerification {\n present: boolean;\n valid: boolean;\n originalClientLoopback: boolean;\n nonce?: string;\n intent?: GatewayAdminProxyIntent;\n reason?: string;\n}\n\nexport interface GatewayAdminProxyIntent {\n ownerWebId: string;\n method: 'GET' | 'HEAD' | 'PUT' | 'PATCH' | 'DELETE';\n resourceUrl: string;\n principalKind: 'solid-user';\n scopes: string[];\n}\n\nexport function createGatewayAdminProxyHeaders(input: GatewayAdminProxyMarkerInput): OutgoingHttpHeaders {\n const issuedAt = input.issuedAt ?? Date.now();\n const loopback = input.originalClientLoopback ? '1' : '0';\n const intent = input.intent ? canonicalGatewayAdminProxyIntent(input.intent) : undefined;\n return {\n [GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER]: loopback,\n [GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER]: String(issuedAt),\n ...(input.nonce ? { [GATEWAY_ADMIN_PROXY_NONCE_HEADER]: input.nonce } : {}),\n ...(intent ? { [GATEWAY_ADMIN_PROXY_INTENT_HEADER]: intent } : {}),\n [GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER]: signGatewayAdminProxyMarker({\n secret: input.secret,\n method: input.method,\n url: input.url,\n loopback,\n issuedAt,\n nonce: input.nonce,\n intent,\n }),\n };\n}\n\nexport function stripGatewayAdminProxyHeaders(headers: IncomingHttpHeaders): void {\n for (const header of GATEWAY_ADMIN_PROXY_HEADERS) {\n delete headers[header];\n }\n}\n\nexport function isLoopbackRemoteAddress(remoteAddress: string | undefined): boolean {\n if (!remoteAddress) return false;\n const normalized = remoteAddress.startsWith('::ffff:')\n ? remoteAddress.slice('::ffff:'.length)\n : remoteAddress;\n\n if (isIP(normalized) === 4) {\n return normalized.startsWith('127.');\n }\n return normalized === '::1';\n}\n\nexport function verifyGatewayAdminProxyHeaders(input: {\n headers: IncomingHttpHeaders;\n secret?: string;\n method: string | undefined;\n url: string | undefined;\n now?: number;\n}): GatewayAdminProxyMarkerVerification {\n const loopback = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_LOOPBACK_HEADER]);\n const timestamp = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_TIMESTAMP_HEADER]);\n const signature = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_SIGNATURE_HEADER]);\n const nonce = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_NONCE_HEADER]);\n const intentHeader = firstHeader(input.headers[GATEWAY_ADMIN_PROXY_INTENT_HEADER]);\n const present = loopback !== undefined || timestamp !== undefined || signature !== undefined;\n if (!present) {\n return { present: false, valid: false, originalClientLoopback: false };\n }\n if (!input.secret) {\n return { present: true, valid: false, originalClientLoopback: false, reason: 'missing_secret' };\n }\n if (loopback !== '0' && loopback !== '1') {\n return { present: true, valid: false, originalClientLoopback: false, reason: 'invalid_loopback' };\n }\n if (!timestamp || !signature) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'incomplete_marker' };\n }\n\n const issuedAt = Number.parseInt(timestamp, 10);\n if (!Number.isFinite(issuedAt)) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'invalid_timestamp' };\n }\n if (Math.abs((input.now ?? Date.now()) - issuedAt) > MAX_CLOCK_SKEW_MS) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'expired_marker' };\n }\n\n const intent = parseGatewayAdminProxyIntent(intentHeader);\n if (intentHeader !== undefined && !intent) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'invalid_intent' };\n }\n\n const expected = signGatewayAdminProxyMarker({\n secret: input.secret,\n method: input.method,\n url: input.url,\n loopback,\n issuedAt,\n nonce,\n intent: intentHeader,\n });\n if (!safeEqual(signature, expected)) {\n return { present: true, valid: false, originalClientLoopback: loopback === '1', reason: 'bad_signature' };\n }\n\n return { present: true, valid: true, originalClientLoopback: loopback === '1', nonce, intent };\n}\n\nfunction signGatewayAdminProxyMarker(input: {\n secret: string;\n method: string | undefined;\n url: string | undefined;\n loopback: '0' | '1';\n issuedAt: number;\n nonce?: string;\n intent?: string;\n}): string {\n const payload = [\n 'v1',\n (input.method ?? 'GET').toUpperCase(),\n input.url ?? '/',\n input.loopback,\n String(input.issuedAt),\n input.nonce ?? '',\n input.intent ?? '',\n ].join('\\n');\n return createHmac('sha256', input.secret).update(payload).digest('base64url');\n}\n\nexport function canonicalGatewayAdminProxyIntent(intent: GatewayAdminProxyIntent): string {\n return JSON.stringify({\n ownerWebId: intent.ownerWebId,\n method: intent.method,\n resourceUrl: intent.resourceUrl,\n principalKind: intent.principalKind,\n scopes: [...intent.scopes].sort(),\n });\n}\n\nfunction parseGatewayAdminProxyIntent(value: string | undefined): GatewayAdminProxyIntent | undefined {\n if (!value) {\n return undefined;\n }\n try {\n const parsed = JSON.parse(value) as Partial<GatewayAdminProxyIntent>;\n if (typeof parsed.ownerWebId !== 'string' ||\n typeof parsed.resourceUrl !== 'string' ||\n (parsed.method !== 'GET' && parsed.method !== 'HEAD' && parsed.method !== 'PUT' && parsed.method !== 'PATCH' && parsed.method !== 'DELETE') ||\n parsed.principalKind !== 'solid-user' ||\n !Array.isArray(parsed.scopes) ||\n !parsed.scopes.every((scope) => typeof scope === 'string')) {\n return undefined;\n }\n return {\n ownerWebId: parsed.ownerWebId,\n method: parsed.method,\n resourceUrl: parsed.resourceUrl,\n principalKind: parsed.principalKind,\n scopes: parsed.scopes,\n };\n } catch {\n return undefined;\n }\n}\n\nfunction safeEqual(actual: string, expected: string): boolean {\n const actualBuffer = Buffer.from(actual);\n const expectedBuffer = Buffer.from(expected);\n return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n"]}
|
package/dist/runtime/Proxy.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class GatewayProxy {
|
|
|
25
25
|
private isApiWebProductPath;
|
|
26
26
|
private shouldRouteToApi;
|
|
27
27
|
private applyInternalAdminProxyHeaders;
|
|
28
|
+
private verifiedInternalPodProxyHeaders;
|
|
28
29
|
private isApiHost;
|
|
29
30
|
private configuredApiHosts;
|
|
30
31
|
private hostsFromUrlList;
|
package/dist/runtime/Proxy.js
CHANGED
|
@@ -106,7 +106,11 @@ class GatewayProxy {
|
|
|
106
106
|
const origin = req.headers.origin;
|
|
107
107
|
const originalRemoteAddress = this.clientRemoteAddressResolver?.(req) ?? req.socket.remoteAddress;
|
|
108
108
|
const originalClientLoopback = (0, GatewayAdminProxyAuth_1.isLoopbackRemoteAddress)(originalRemoteAddress);
|
|
109
|
+
const internalPodProxyHeaders = this.verifiedInternalPodProxyHeaders(req, originalClientLoopback);
|
|
109
110
|
(0, GatewayAdminProxyAuth_1.stripGatewayAdminProxyHeaders)(req.headers);
|
|
111
|
+
if (internalPodProxyHeaders) {
|
|
112
|
+
Object.assign(req.headers, internalPodProxyHeaders);
|
|
113
|
+
}
|
|
110
114
|
// Store public host for routing before any CSS canonical-host rewrites.
|
|
111
115
|
// External gateways pass the original domain through X-Forwarded-Host;
|
|
112
116
|
// direct/local requests use Host.
|
|
@@ -212,6 +216,24 @@ class GatewayProxy {
|
|
|
212
216
|
originalClientLoopback,
|
|
213
217
|
}));
|
|
214
218
|
}
|
|
219
|
+
verifiedInternalPodProxyHeaders(req, originalClientLoopback) {
|
|
220
|
+
if (!originalClientLoopback || req.url !== '/.internal/pod-data') {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
const verification = (0, GatewayAdminProxyAuth_1.verifyGatewayAdminProxyHeaders)({
|
|
224
|
+
headers: req.headers,
|
|
225
|
+
secret: this.internalAdminAuthSecret,
|
|
226
|
+
method: req.method,
|
|
227
|
+
url: req.url,
|
|
228
|
+
});
|
|
229
|
+
if (!verification.valid || !verification.originalClientLoopback || !verification.intent || !verification.nonce) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return Object.fromEntries(GatewayAdminProxyAuth_1.GATEWAY_ADMIN_PROXY_HEADERS.flatMap((header) => {
|
|
233
|
+
const value = req.headers[header];
|
|
234
|
+
return value === undefined ? [] : [[header, value]];
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
215
237
|
isApiHost(hostHeader) {
|
|
216
238
|
const host = this.normalizeHost(hostHeader);
|
|
217
239
|
if (!host) {
|