@undefineds.co/xpod 0.3.31 → 0.3.32
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/AuthContext.d.ts +3 -2
- package/dist/api/auth/AuthContext.js +2 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +2 -12
- package/dist/api/auth/ClientCredentialsAuthenticator.js +4 -4
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +2 -2
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -1
- package/dist/api/container/business-token.d.ts +1 -1
- package/dist/api/container/business-token.js +5 -1
- package/dist/api/container/business-token.js.map +1 -1
- package/dist/api/container/common.js +14 -10
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/routes.js +16 -3
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +2 -4
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/ChatHandler.d.ts +1 -1
- package/dist/api/handlers/ChatHandler.js +1 -1
- package/dist/api/handlers/ChatHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.js +3 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
- package/dist/api/handlers/PodManagementHandler.d.ts +2 -0
- package/dist/api/handlers/PodManagementHandler.js +114 -12
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +27 -0
- package/dist/api/handlers/ProvisionHandler.js +339 -32
- package/dist/api/handlers/ProvisionHandler.js.map +1 -1
- package/dist/api/handlers/QuotaHandler.js +0 -12
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/index.d.ts +0 -1
- package/dist/api/handlers/index.js +0 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/runtime.js +3 -3
- package/dist/api/runtime.js.map +1 -1
- package/dist/components/context.jsonld +12 -0
- package/dist/edge/EdgeNodeAgent.d.ts +1 -1
- package/dist/edge/EdgeNodeAgent.js +1 -1
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -0
- package/dist/edge/EdgeNodeDnsCoordinator.js +9 -3
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +4 -0
- package/dist/edge/EdgeNodeHealthProbeService.d.ts +3 -0
- package/dist/edge/EdgeNodeHealthProbeService.js +22 -2
- package/dist/edge/EdgeNodeHealthProbeService.js.map +1 -1
- package/dist/edge/EdgeNodeHealthProbeService.jsonld +12 -0
- package/dist/http/ClusterIngressRouter.js +6 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +6 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/EdgeNodeDirectDebugHttpHandler.d.ts +2 -0
- package/dist/http/EdgeNodeDirectDebugHttpHandler.js +18 -3
- package/dist/http/EdgeNodeDirectDebugHttpHandler.js.map +1 -1
- package/dist/http/EdgeNodeDirectDebugHttpHandler.jsonld +8 -0
- package/dist/http/EdgeNodeProxyHttpHandler.js +6 -2
- package/dist/http/EdgeNodeProxyHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +2 -2
- package/dist/http/cluster/PodMigrationHttpHandler.js +2 -2
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/http/quota/QuotaAdminHttpHandler.js +27 -21
- package/dist/http/quota/QuotaAdminHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/AccountRepository.d.ts +4 -22
- package/dist/identity/drizzle/AccountRepository.js +9 -113
- package/dist/identity/drizzle/AccountRepository.js.map +1 -1
- package/dist/identity/drizzle/AccountRoleRepository.d.ts +5 -5
- package/dist/identity/drizzle/AccountRoleRepository.js +204 -97
- package/dist/identity/drizzle/AccountRoleRepository.js.map +1 -1
- package/dist/identity/drizzle/DdnsRepository.d.ts +5 -20
- package/dist/identity/drizzle/DdnsRepository.js +13 -49
- package/dist/identity/drizzle/DdnsRepository.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +13 -6
- package/dist/identity/drizzle/EdgeNodeRepository.js +167 -66
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/PodLookupRepository.d.ts +7 -36
- package/dist/identity/drizzle/PodLookupRepository.js +103 -126
- package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +13 -1
- package/dist/identity/drizzle/ServiceTokenRepository.js +7 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -1
- package/dist/identity/drizzle/db.d.ts +2 -1
- package/dist/identity/drizzle/db.js +173 -297
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +3 -11
- package/dist/identity/drizzle/schema.pg.js +10 -45
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +88 -531
- package/dist/identity/drizzle/schema.sqlite.js +13 -46
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +3 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +18 -6
- package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +22 -0
- package/dist/provision/ProvisionCodeCodec.js +10 -1
- package/dist/provision/ProvisionCodeCodec.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.d.ts +8 -2
- package/dist/provision/ProvisionPodCreator.js +134 -41
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.jsonld +38 -3
- package/dist/quota/DrizzleQuotaService.d.ts +0 -4
- package/dist/quota/DrizzleQuotaService.js +1 -21
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +0 -16
- package/dist/quota/NoopQuotaService.d.ts +0 -4
- package/dist/quota/NoopQuotaService.js +0 -8
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +0 -16
- package/dist/quota/QuotaService.d.ts +0 -4
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +0 -16
- package/dist/service/EdgeNodeSignalClient.d.ts +0 -2
- package/dist/service/EdgeNodeSignalClient.js +0 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -1
- package/dist/service/PodMigrationService.d.ts +2 -2
- package/dist/service/PodMigrationService.js +4 -4
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/setup/LocalSetupServiceTokenRepository.d.ts +22 -0
- package/dist/setup/LocalSetupServiceTokenRepository.js +68 -0
- package/dist/setup/LocalSetupServiceTokenRepository.js.map +1 -0
- package/dist/storage/quota/PerAccountQuotaStrategy.js +2 -2
- package/dist/storage/quota/PerAccountQuotaStrategy.js.map +1 -1
- package/dist/storage/quota/UsageRepository.d.ts +10 -32
- package/dist/storage/quota/UsageRepository.js +84 -281
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +1 -1
- package/dist/subdomain/SubdomainService.js +1 -1
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +1 -1
- package/package.json +1 -1
- package/dist/api/handlers/ApiKeyHandler.d.ts +0 -15
- package/dist/api/handlers/ApiKeyHandler.js +0 -153
- package/dist/api/handlers/ApiKeyHandler.js.map +0 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +0 -51
- package/dist/api/store/DrizzleClientCredentialsStore.js +0 -115
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeDirectDebugHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeDirectDebugHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeDirectDebugHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n skipPrefixes?: string[];\n nodeRepository?: EdgeNodeRepository;\n}\n\nexport class EdgeNodeDirectDebugHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly nodeRepo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly skipPrefixes: string[];\n\n public constructor(options: EdgeNodeDirectDebugHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.nodeRepo = options.nodeRepository ?? new EdgeNodeRepository(db as any);\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.skipPrefixes = options.skipPrefixes ?? [ '/admin', '/api', '/.internal' ];\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge node redirection disabled.');\n }\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const target = await this.resolve(url.pathname, url);\n if (!target) {\n throw new NotImplementedHttpError('Edge node endpoint not configured.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const resolved = await this.resolve(url.pathname, url);\n if (!resolved) {\n throw new NotImplementedHttpError('No edge node registered for this resource.');\n }\n\n this.logger.debug(`Redirecting ${url.pathname} to edge node ${resolved.nodeId}`);\n try {\n response.statusCode = 307;\n response.setHeader('Location', resolved.target.toString());\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('X-Xpod-Edge-Node', resolved.nodeId);\n response.end();\n } catch (error: unknown) {\n throw new InternalServerError('Failed to write edge node redirect.', { cause: error });\n }\n }\n\n private async resolve(pathname: string, original: URL): Promise<{ nodeId: string; target: URL } | undefined> {\n const record = await this.nodeRepo.findNodeByResourcePath(pathname);\n if (!record) {\n return undefined;\n }\n \n // Only handle direct mode nodes - proxy mode traffic should go through L4 SNI proxy\n if (record.accessMode === 'proxy') {\n this.logger.warn(`Request reached cluster for proxy-mode node ${record.nodeId}, should be handled by L4 SNI proxy`);\n return undefined;\n }\n \n const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl);\n if (!target) {\n return undefined;\n }\n return { nodeId: record.nodeId, target };\n }\n\n private shouldSkip(pathname: string): boolean {\n return this.skipPrefixes.some((prefix) => pathname.startsWith(prefix));\n }\n\n private resolveTarget(metadata: Record<string, unknown>, original: URL, podBaseUrl: string): URL | undefined {\n const baseUrl = this.extractUrl(metadata.publicAddress) ?? this.extractUrl(metadata.baseUrl);\n if (!baseUrl) {\n return undefined;\n }\n const podBase = this.extractUrl(podBaseUrl);\n if (!podBase) {\n return undefined;\n }\n const podPath = podBase.pathname.endsWith('/') ? podBase.pathname : `${podBase.pathname}/`;\n if (!original.pathname.startsWith(podPath)) {\n return undefined;\n }\n const relativePath = original.pathname.slice(podPath.length);\n const targetBase = baseUrl.href.endsWith('/') ? baseUrl.href : `${baseUrl.href}/`;\n const target = new URL(relativePath, targetBase);\n target.search = original.search;\n target.hash = original.hash;\n return target;\n }\n\n private extractUrl(value: unknown): URL | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n try {\n return new URL(trimmed);\n } catch {\n return undefined;\n }\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeDirectDebugHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeDirectDebugHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeDirectDebugHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n skipPrefixes?: string[];\n nodeRepository?: EdgeNodeRepository;\n}\n\nexport class EdgeNodeDirectDebugHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly nodeRepo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly skipPrefixes: string[];\n\n public constructor(options: EdgeNodeDirectDebugHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.nodeRepo = options.nodeRepository ?? new EdgeNodeRepository(db as any);\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.skipPrefixes = options.skipPrefixes ?? [ '/admin', '/api', '/.internal' ];\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge node redirection disabled.');\n }\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const target = await this.resolve(url.pathname, url);\n if (!target) {\n throw new NotImplementedHttpError('Edge node endpoint not configured.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const resolved = await this.resolve(url.pathname, url);\n if (!resolved) {\n throw new NotImplementedHttpError('No edge node registered for this resource.');\n }\n\n this.logger.debug(`Redirecting ${url.pathname} to edge node ${resolved.nodeId}`);\n try {\n response.statusCode = 307;\n response.setHeader('Location', resolved.target.toString());\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('X-Xpod-Edge-Node', resolved.nodeId);\n response.end();\n } catch (error: unknown) {\n throw new InternalServerError('Failed to write edge node redirect.', { cause: error });\n }\n }\n\n private async resolve(pathname: string, original: URL): Promise<{ nodeId: string; target: URL } | undefined> {\n const record = await this.nodeRepo.findNodeByResourcePath(pathname);\n if (!record) {\n return undefined;\n }\n \n // Only handle direct mode nodes - proxy mode traffic should go through L4 SNI proxy\n if (record.accessMode === 'proxy') {\n this.logger.warn(`Request reached cluster for proxy-mode node ${record.nodeId}, should be handled by L4 SNI proxy`);\n return undefined;\n }\n \n const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl, record.publicUrl);\n if (!target) {\n return undefined;\n }\n return { nodeId: record.nodeId, target };\n }\n\n private shouldSkip(pathname: string): boolean {\n return this.skipPrefixes.some((prefix) => pathname.startsWith(prefix));\n }\n\n private resolveTarget(metadata: Record<string, unknown>, original: URL, podBaseUrl: string, publicUrl?: string): URL | undefined {\n const baseUrl = this.extractUrl(publicUrl)\n ?? this.extractUrl(this.extractTunnelEntrypoint(metadata.tunnel))\n ?? this.extractUrl(this.extractManagedTunnelEndpoint(metadata.managedTunnel))\n ?? this.extractUrl(metadata.baseUrl);\n if (!baseUrl) {\n return undefined;\n }\n const podBase = this.extractUrl(podBaseUrl);\n if (!podBase) {\n return undefined;\n }\n const podPath = podBase.pathname.endsWith('/') ? podBase.pathname : `${podBase.pathname}/`;\n if (!original.pathname.startsWith(podPath)) {\n return undefined;\n }\n const relativePath = original.pathname.slice(podPath.length);\n const targetBase = baseUrl.href.endsWith('/') ? baseUrl.href : `${baseUrl.href}/`;\n const target = new URL(relativePath, targetBase);\n target.search = original.search;\n target.hash = original.hash;\n return target;\n }\n\n private extractTunnelEntrypoint(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n return (value as Record<string, unknown>).entrypoint;\n }\n\n private extractManagedTunnelEndpoint(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n return (value as Record<string, unknown>).endpoint;\n }\n\n private extractUrl(value: unknown): URL | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n try {\n return new URL(trimmed);\n } catch {\n return undefined;\n }\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n}\n"]}
|
|
@@ -102,6 +102,14 @@
|
|
|
102
102
|
"@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_resolveTarget",
|
|
103
103
|
"memberFieldName": "resolveTarget"
|
|
104
104
|
},
|
|
105
|
+
{
|
|
106
|
+
"@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractTunnelEntrypoint",
|
|
107
|
+
"memberFieldName": "extractTunnelEntrypoint"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractManagedTunnelEndpoint",
|
|
111
|
+
"memberFieldName": "extractManagedTunnelEndpoint"
|
|
112
|
+
},
|
|
105
113
|
{
|
|
106
114
|
"@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractUrl",
|
|
107
115
|
"memberFieldName": "extractUrl"
|
|
@@ -99,8 +99,12 @@ class EdgeNodeProxyHttpHandler extends community_server_1.HttpHandler {
|
|
|
99
99
|
return entrypoint;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const managedTunnel = metadata?.managedTunnel;
|
|
103
|
+
if (managedTunnel && typeof managedTunnel === 'object') {
|
|
104
|
+
const endpoint = managedTunnel.endpoint;
|
|
105
|
+
if (typeof endpoint === 'string' && endpoint.trim().length > 0) {
|
|
106
|
+
return endpoint;
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
return undefined;
|
|
106
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeProxyHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeProxyHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n BadRequestHttpError,\n InternalServerError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeProxyHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n fetchImpl?: any;\n}\n\ninterface ProxyNode {\n nodeId: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport class EdgeNodeProxyHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly fetchImpl: any;\n\n public constructor(options: EdgeNodeProxyHttpHandlerOptions) {\n super();\n this.repo = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge proxy disabled.');\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if ((request.method ?? 'GET').toUpperCase() === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n if ((request.method ?? 'GET').toUpperCase() === 'CONNECT') {\n throw new MethodNotAllowedHttpError([ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS' ]);\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new BadRequestHttpError('Host header required.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n await this.forwardRequest(node, request, response);\n }\n\n private async forwardRequest(node: ProxyNode, request: IncomingMessage, response: HttpResponse): Promise<void> {\n const metadata = node.metadata ?? undefined;\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError('Edge node tunnel is not ready.');\n }\n\n const upstreamBase = new URL(upstream);\n const originalUrl = this.parseUrl(request);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readBody(request);\n const headers = this.buildHeaders(request, originalUrl, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${(error as Error).message}`);\n throw new InternalServerError('Failed to proxy edge node request.', { cause: error });\n }\n\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Edge-Node', node.nodeId);\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return;\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Edge proxy stream error: ${(error as Error).message}`);\n response.destroy(error as Error);\n });\n readable.pipe(response);\n }\n\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n if (typeof metadata?.publicAddress === 'string') {\n return metadata.publicAddress;\n }\n return undefined;\n }\n\n private buildHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n for (const [ name, value ] of Object.entries(request.headers)) {\n if (value === undefined) {\n continue;\n }\n if (name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n headers.set('host', upstream.host);\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n return headers;\n }\n\n private readBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if ([ 'GET', 'HEAD' ].includes(method)) {\n return Promise.resolve(undefined);\n }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n private extractHostname(request: IncomingMessage): string | undefined {\n const hostHeader = request.headers.host ?? request.headers.Host;\n if (typeof hostHeader !== 'string') {\n return undefined;\n }\n const hostname = hostHeader.split(':')[0];\n return hostname.trim().toLowerCase();\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS');\n response.end();\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeProxyHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeProxyHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n BadRequestHttpError,\n InternalServerError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeProxyHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n fetchImpl?: any;\n}\n\ninterface ProxyNode {\n nodeId: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport class EdgeNodeProxyHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly fetchImpl: any;\n\n public constructor(options: EdgeNodeProxyHttpHandlerOptions) {\n super();\n this.repo = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge proxy disabled.');\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if ((request.method ?? 'GET').toUpperCase() === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n if ((request.method ?? 'GET').toUpperCase() === 'CONNECT') {\n throw new MethodNotAllowedHttpError([ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS' ]);\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new BadRequestHttpError('Host header required.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n await this.forwardRequest(node, request, response);\n }\n\n private async forwardRequest(node: ProxyNode, request: IncomingMessage, response: HttpResponse): Promise<void> {\n const metadata = node.metadata ?? undefined;\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError('Edge node tunnel is not ready.');\n }\n\n const upstreamBase = new URL(upstream);\n const originalUrl = this.parseUrl(request);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readBody(request);\n const headers = this.buildHeaders(request, originalUrl, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${(error as Error).message}`);\n throw new InternalServerError('Failed to proxy edge node request.', { cause: error });\n }\n\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Edge-Node', node.nodeId);\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return;\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Edge proxy stream error: ${(error as Error).message}`);\n response.destroy(error as Error);\n });\n readable.pipe(response);\n }\n\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n const managedTunnel = metadata?.managedTunnel;\n if (managedTunnel && typeof managedTunnel === 'object') {\n const endpoint = (managedTunnel as Record<string, unknown>).endpoint;\n if (typeof endpoint === 'string' && endpoint.trim().length > 0) {\n return endpoint;\n }\n }\n return undefined;\n }\n\n private buildHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n for (const [ name, value ] of Object.entries(request.headers)) {\n if (value === undefined) {\n continue;\n }\n if (name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n headers.set('host', upstream.host);\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n return headers;\n }\n\n private readBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if ([ 'GET', 'HEAD' ].includes(method)) {\n return Promise.resolve(undefined);\n }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n private extractHostname(request: IncomingMessage): string | undefined {\n const hostHeader = request.headers.host ?? request.headers.Host;\n if (typeof hostHeader !== 'string') {\n return undefined;\n }\n const hostname = hostHeader.split(':')[0];\n return hostname.trim().toLowerCase();\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS');\n response.end();\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -9,7 +9,7 @@ interface PodMigrationHttpHandlerOptions {
|
|
|
9
9
|
/**
|
|
10
10
|
* HTTP Handler for Pod migration operations.
|
|
11
11
|
*
|
|
12
|
-
* Migration is now instant - it only updates the
|
|
12
|
+
* Migration is now instant - it only updates the Pod routing assignment.
|
|
13
13
|
* Binary files are accessed via presigned URL redirect (302) from object storage.
|
|
14
14
|
*
|
|
15
15
|
* Endpoints:
|
|
@@ -38,7 +38,7 @@ export declare class PodMigrationHttpHandler extends HttpHandler {
|
|
|
38
38
|
private handleGetPod;
|
|
39
39
|
/**
|
|
40
40
|
* Migrate pod to target node.
|
|
41
|
-
* This is instant - only updates
|
|
41
|
+
* This is instant - only updates routing ownership. Binary files use cross-region fallback.
|
|
42
42
|
*/
|
|
43
43
|
private handleMigrate;
|
|
44
44
|
private matchesPath;
|
|
@@ -11,7 +11,7 @@ const PodMigrationService_1 = require("../../service/PodMigrationService");
|
|
|
11
11
|
/**
|
|
12
12
|
* HTTP Handler for Pod migration operations.
|
|
13
13
|
*
|
|
14
|
-
* Migration is now instant - it only updates the
|
|
14
|
+
* Migration is now instant - it only updates the Pod routing assignment.
|
|
15
15
|
* Binary files are accessed via presigned URL redirect (302) from object storage.
|
|
16
16
|
*
|
|
17
17
|
* Endpoints:
|
|
@@ -115,7 +115,7 @@ class PodMigrationHttpHandler extends community_server_1.HttpHandler {
|
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Migrate pod to target node.
|
|
118
|
-
* This is instant - only updates
|
|
118
|
+
* This is instant - only updates routing ownership. Binary files use cross-region fallback.
|
|
119
119
|
*/
|
|
120
120
|
async handleMigrate(podId, body, response) {
|
|
121
121
|
if (!body.targetNode) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PodMigrationHttpHandler.js","sourceRoot":"","sources":["../../../src/http/cluster/PodMigrationHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n BadRequestHttpError,\n NotFoundHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { PodMigrationService } from '../../service/PodMigrationService';\n\ninterface PodMigrationHttpHandlerOptions {\n identityDbUrl: string;\n currentNodeId: string;\n basePath?: string;\n enabled?: boolean | string;\n}\n\ninterface MigrateRequest {\n targetNode: string;\n}\n\n/**\n * HTTP Handler for Pod migration operations.\n * \n * Migration is now instant - it only updates the nodeId in the database.\n * Binary files are accessed via presigned URL redirect (302) from object storage.\n * \n * Endpoints:\n * - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)\n * - GET /.cluster/pods/{podId} - Get pod info\n * - GET /.cluster/pods - List all pods with node info\n */\nexport class PodMigrationHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n \n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly migrationService: PodMigrationService;\n private readonly basePath: string;\n private readonly basePathWithSlash: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodMigrationHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = new PodLookupRepository(db);\n this.edgeNodeRepository = new EdgeNodeRepository(db);\n this.migrationService = new PodMigrationService({\n identityDbUrl: options.identityDbUrl,\n currentNodeId: options.currentNodeId,\n });\n this.basePath = this.normalizeBasePath(options.basePath ?? '/.cluster/pods');\n this.basePathWithSlash = `${this.basePath}/`;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodMigrationHttpHandler initialized: basePath=${this.basePath}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod migration API disabled.');\n }\n\n const url = this.parseUrl(request);\n if (!this.matchesPath(url.pathname)) {\n throw new NotImplementedHttpError(`Path ${url.pathname} does not match ${this.basePath}`);\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const method = (request.method ?? 'GET').toUpperCase();\n const relativePath = this.getRelativePath(url.pathname);\n\n try {\n // GET /.cluster/pods - List pods\n if (relativePath === '' && method === 'GET') {\n await this.handleListPods(response);\n return;\n }\n\n // Parse pod ID and action\n const match = relativePath.match(/^([^/]+)(?:\\/(.+))?$/);\n if (!match) {\n throw new BadRequestHttpError('Invalid path format. Expected: /pods/{podId} or /pods/{podId}/migrate');\n }\n\n const podId = decodeURIComponent(match[1]);\n const action = match[2];\n\n // GET /.cluster/pods/{podId} - Get pod info\n if (!action && method === 'GET') {\n await this.handleGetPod(podId, response);\n return;\n }\n\n // POST /.cluster/pods/{podId}/migrate - Migrate pod (instant)\n if (action === 'migrate' && method === 'POST') {\n const body = await this.parseJsonBody(request);\n await this.handleMigrate(podId, body, response);\n return;\n }\n\n throw new NotImplementedHttpError(`${method} ${url.pathname} not implemented`);\n } catch (error: unknown) {\n if (error instanceof BadRequestHttpError || \n error instanceof NotFoundHttpError ||\n error instanceof NotImplementedHttpError) {\n throw error;\n }\n this.logger.error(`Error handling ${method} ${url.pathname}: ${(error as Error).message}`);\n throw error;\n }\n }\n\n /**\n * List all pods with their node assignments.\n */\n private async handleListPods(response: HttpResponse): Promise<void> {\n const pods = await this.podLookupRepository.listAllPods();\n \n this.sendJson(response, 200, {\n pods: pods.map(pod => ({\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n })),\n });\n }\n\n /**\n * Get single pod info.\n */\n private async handleGetPod(podId: string, response: HttpResponse): Promise<void> {\n const pod = await this.podLookupRepository.findById(podId);\n if (!pod) {\n throw new NotFoundHttpError(`Pod ${podId} not found`);\n }\n\n this.sendJson(response, 200, {\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n });\n }\n\n /**\n * Migrate pod to target node.\n * This is instant - only updates nodeId. Binary files use cross-region fallback.\n */\n private async handleMigrate(\n podId: string,\n body: MigrateRequest,\n response: HttpResponse,\n ): Promise<void> {\n if (!body.targetNode) {\n throw new BadRequestHttpError('Missing required field: targetNode');\n }\n\n try {\n const result = await this.migrationService.migratePod(podId, body.targetNode);\n\n this.logger.info(`Pod migrated: pod=${podId}, from=${result.sourceNodeId}, to=${result.targetNodeId}`);\n\n this.sendJson(response, 200, {\n message: 'Migration completed',\n podId: result.podId,\n sourceNode: result.sourceNodeId,\n targetNode: result.targetNodeId,\n migratedAt: result.migratedAt.toISOString(),\n });\n } catch (error) {\n const message = (error as Error).message;\n \n if (message.includes('not found')) {\n throw new NotFoundHttpError(message);\n }\n if (message.includes('already on node')) {\n throw new BadRequestHttpError(message);\n }\n \n throw error;\n }\n }\n\n // ============ Utility methods ============\n\n private matchesPath(pathname: string): boolean {\n return pathname === this.basePath || pathname.startsWith(this.basePathWithSlash);\n }\n\n private getRelativePath(pathname: string): string {\n if (pathname === this.basePath) {\n return '';\n }\n if (pathname.startsWith(this.basePathWithSlash)) {\n return pathname.slice(this.basePathWithSlash.length);\n }\n return '';\n }\n\n private normalizeBasePath(basePath: string): string {\n let normalized = basePath;\n if (!normalized.startsWith('/')) {\n normalized = `/${normalized}`;\n }\n if (normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? 'localhost';\n return new URL(request.url ?? '/', `http://${hostHeader}`);\n }\n\n private async parseJsonBody(request: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8');\n resolve(body ? JSON.parse(body) : {});\n } catch (error) {\n reject(new BadRequestHttpError('Invalid JSON body'));\n }\n });\n request.on('error', reject);\n });\n }\n\n private sendJson(response: HttpResponse, status: number, data: object): void {\n const body = JSON.stringify(data);\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('Content-Length', Buffer.byteLength(body));\n response.end(body);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return true; // Enabled by default\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PodMigrationHttpHandler.js","sourceRoot":"","sources":["../../../src/http/cluster/PodMigrationHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n BadRequestHttpError,\n NotFoundHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { PodMigrationService } from '../../service/PodMigrationService';\n\ninterface PodMigrationHttpHandlerOptions {\n identityDbUrl: string;\n currentNodeId: string;\n basePath?: string;\n enabled?: boolean | string;\n}\n\ninterface MigrateRequest {\n targetNode: string;\n}\n\n/**\n * HTTP Handler for Pod migration operations.\n * \n * Migration is now instant - it only updates the Pod routing assignment.\n * Binary files are accessed via presigned URL redirect (302) from object storage.\n * \n * Endpoints:\n * - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)\n * - GET /.cluster/pods/{podId} - Get pod info\n * - GET /.cluster/pods - List all pods with node info\n */\nexport class PodMigrationHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n \n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly migrationService: PodMigrationService;\n private readonly basePath: string;\n private readonly basePathWithSlash: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodMigrationHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = new PodLookupRepository(db);\n this.edgeNodeRepository = new EdgeNodeRepository(db);\n this.migrationService = new PodMigrationService({\n identityDbUrl: options.identityDbUrl,\n currentNodeId: options.currentNodeId,\n });\n this.basePath = this.normalizeBasePath(options.basePath ?? '/.cluster/pods');\n this.basePathWithSlash = `${this.basePath}/`;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodMigrationHttpHandler initialized: basePath=${this.basePath}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod migration API disabled.');\n }\n\n const url = this.parseUrl(request);\n if (!this.matchesPath(url.pathname)) {\n throw new NotImplementedHttpError(`Path ${url.pathname} does not match ${this.basePath}`);\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const method = (request.method ?? 'GET').toUpperCase();\n const relativePath = this.getRelativePath(url.pathname);\n\n try {\n // GET /.cluster/pods - List pods\n if (relativePath === '' && method === 'GET') {\n await this.handleListPods(response);\n return;\n }\n\n // Parse pod ID and action\n const match = relativePath.match(/^([^/]+)(?:\\/(.+))?$/);\n if (!match) {\n throw new BadRequestHttpError('Invalid path format. Expected: /pods/{podId} or /pods/{podId}/migrate');\n }\n\n const podId = decodeURIComponent(match[1]);\n const action = match[2];\n\n // GET /.cluster/pods/{podId} - Get pod info\n if (!action && method === 'GET') {\n await this.handleGetPod(podId, response);\n return;\n }\n\n // POST /.cluster/pods/{podId}/migrate - Migrate pod (instant)\n if (action === 'migrate' && method === 'POST') {\n const body = await this.parseJsonBody(request);\n await this.handleMigrate(podId, body, response);\n return;\n }\n\n throw new NotImplementedHttpError(`${method} ${url.pathname} not implemented`);\n } catch (error: unknown) {\n if (error instanceof BadRequestHttpError || \n error instanceof NotFoundHttpError ||\n error instanceof NotImplementedHttpError) {\n throw error;\n }\n this.logger.error(`Error handling ${method} ${url.pathname}: ${(error as Error).message}`);\n throw error;\n }\n }\n\n /**\n * List all pods with their node assignments.\n */\n private async handleListPods(response: HttpResponse): Promise<void> {\n const pods = await this.podLookupRepository.listAllPods();\n \n this.sendJson(response, 200, {\n pods: pods.map(pod => ({\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n })),\n });\n }\n\n /**\n * Get single pod info.\n */\n private async handleGetPod(podId: string, response: HttpResponse): Promise<void> {\n const pod = await this.podLookupRepository.findById(podId);\n if (!pod) {\n throw new NotFoundHttpError(`Pod ${podId} not found`);\n }\n\n this.sendJson(response, 200, {\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n });\n }\n\n /**\n * Migrate pod to target node.\n * This is instant - only updates routing ownership. Binary files use cross-region fallback.\n */\n private async handleMigrate(\n podId: string,\n body: MigrateRequest,\n response: HttpResponse,\n ): Promise<void> {\n if (!body.targetNode) {\n throw new BadRequestHttpError('Missing required field: targetNode');\n }\n\n try {\n const result = await this.migrationService.migratePod(podId, body.targetNode);\n\n this.logger.info(`Pod migrated: pod=${podId}, from=${result.sourceNodeId}, to=${result.targetNodeId}`);\n\n this.sendJson(response, 200, {\n message: 'Migration completed',\n podId: result.podId,\n sourceNode: result.sourceNodeId,\n targetNode: result.targetNodeId,\n migratedAt: result.migratedAt.toISOString(),\n });\n } catch (error) {\n const message = (error as Error).message;\n \n if (message.includes('not found')) {\n throw new NotFoundHttpError(message);\n }\n if (message.includes('already on node')) {\n throw new BadRequestHttpError(message);\n }\n \n throw error;\n }\n }\n\n // ============ Utility methods ============\n\n private matchesPath(pathname: string): boolean {\n return pathname === this.basePath || pathname.startsWith(this.basePathWithSlash);\n }\n\n private getRelativePath(pathname: string): string {\n if (pathname === this.basePath) {\n return '';\n }\n if (pathname.startsWith(this.basePathWithSlash)) {\n return pathname.slice(this.basePathWithSlash.length);\n }\n return '';\n }\n\n private normalizeBasePath(basePath: string): string {\n let normalized = basePath;\n if (!normalized.startsWith('/')) {\n normalized = `/${normalized}`;\n }\n if (normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? 'localhost';\n return new URL(request.url ?? '/', `http://${hostHeader}`);\n }\n\n private async parseJsonBody(request: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8');\n resolve(body ? JSON.parse(body) : {});\n } catch (error) {\n reject(new BadRequestHttpError('Invalid JSON body'));\n }\n });\n request.on('error', reject);\n });\n }\n\n private sendJson(response: HttpResponse, status: number, data: object): void {\n const body = JSON.stringify(data);\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('Content-Length', Buffer.byteLength(body));\n response.end(body);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return true; // Enabled by default\n }\n}\n"]}
|
|
@@ -50,11 +50,11 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
|
|
|
50
50
|
}
|
|
51
51
|
async handleGet(target, response) {
|
|
52
52
|
if (target.type === 'account') {
|
|
53
|
-
const quota = await this.quotaService.
|
|
53
|
+
const quota = await this.quotaService.getAccountQuota(target.id);
|
|
54
54
|
this.writeJson(response, 200, {
|
|
55
55
|
type: 'account',
|
|
56
56
|
accountId: target.id,
|
|
57
|
-
|
|
57
|
+
quota,
|
|
58
58
|
});
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
@@ -62,13 +62,13 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
|
|
|
62
62
|
if (!podInfo) {
|
|
63
63
|
throw new community_server_2.BadRequestHttpError('Unknown pod identifier.');
|
|
64
64
|
}
|
|
65
|
-
const quota = await this.quotaService.
|
|
65
|
+
const quota = await this.quotaService.getPodQuota(target.id);
|
|
66
66
|
this.writeJson(response, 200, {
|
|
67
67
|
type: 'pod',
|
|
68
68
|
podId: target.id,
|
|
69
69
|
accountId: podInfo.accountId,
|
|
70
70
|
baseUrl: podInfo.baseUrl ?? null,
|
|
71
|
-
|
|
71
|
+
quota,
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
async handlePut(target, request, response) {
|
|
@@ -85,23 +85,23 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
if (target.type === 'account') {
|
|
88
|
-
await this.quotaService.
|
|
89
|
-
const latest = await this.quotaService.
|
|
88
|
+
await this.quotaService.setAccountQuota(target.id, quota);
|
|
89
|
+
const latest = await this.quotaService.getAccountQuota(target.id);
|
|
90
90
|
this.writeJson(response, 200, {
|
|
91
91
|
status: 'updated',
|
|
92
92
|
targetType: target.type,
|
|
93
93
|
targetId: target.id,
|
|
94
|
-
|
|
94
|
+
quota: latest,
|
|
95
95
|
});
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
|
-
await this.quotaService.
|
|
99
|
-
const latest = await this.quotaService.
|
|
98
|
+
await this.quotaService.setPodQuota(target.id, quota);
|
|
99
|
+
const latest = await this.quotaService.getPodQuota(target.id);
|
|
100
100
|
this.writeJson(response, 200, {
|
|
101
101
|
status: 'updated',
|
|
102
102
|
targetType: target.type,
|
|
103
103
|
targetId: target.id,
|
|
104
|
-
|
|
104
|
+
quota: latest,
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
async handleDelete(target, response) {
|
|
@@ -112,10 +112,10 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
if (target.type === 'account') {
|
|
115
|
-
await this.quotaService.
|
|
115
|
+
await this.quotaService.clearAccountQuota(target.id);
|
|
116
116
|
}
|
|
117
117
|
else {
|
|
118
|
-
await this.quotaService.
|
|
118
|
+
await this.quotaService.clearPodQuota(target.id);
|
|
119
119
|
}
|
|
120
120
|
this.writeJson(response, 200, {
|
|
121
121
|
status: 'cleared',
|
|
@@ -175,17 +175,23 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
extractQuota(body) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
const quota = {};
|
|
179
|
+
let hasQuotaField = false;
|
|
180
|
+
for (const field of ['storageLimitBytes', 'bandwidthLimitBps', 'computeLimitSeconds', 'tokenLimitMonthly']) {
|
|
181
|
+
if (!Object.prototype.hasOwnProperty.call(body, field)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const value = body[field];
|
|
185
|
+
if (value !== null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {
|
|
186
|
+
throw new community_server_2.BadRequestHttpError(`${field} must be a non-negative number or null.`);
|
|
187
|
+
}
|
|
188
|
+
quota[field] = value;
|
|
189
|
+
hasQuotaField = true;
|
|
184
190
|
}
|
|
185
|
-
if (
|
|
186
|
-
throw new community_server_2.BadRequestHttpError('
|
|
191
|
+
if (!hasQuotaField) {
|
|
192
|
+
throw new community_server_2.BadRequestHttpError('Body must include at least one quota field.');
|
|
187
193
|
}
|
|
188
|
-
return
|
|
194
|
+
return quota;
|
|
189
195
|
}
|
|
190
196
|
writeOptions(response) {
|
|
191
197
|
response.statusCode = 204;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaAdminHttpHandler.js","sourceRoot":"","sources":["../../../src/http/quota/QuotaAdminHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { createSolidTokenVerifier } from '@solid/access-token-verifier';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpRequest, HttpResponse } from '@solid/community-server';\nimport {\n \n BadRequestHttpError,\n ForbiddenHttpError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n UnauthorizedHttpError,\n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { AccountRepository } from '../../identity/drizzle/AccountRepository';\nimport { AccountRoleRepository } from '../../identity/drizzle/AccountRoleRepository';\nimport type { QuotaService } from '../../quota/QuotaService';\n\ninterface QuotaAdminHttpHandlerOptions {\n identityDbUrl: string;\n basePath?: string;\n roleRepository?: AccountRoleRepository;\n quotaService: QuotaService;\n}\n\ntype QuotaTarget =\n | { type: 'account'; id: string }\n | { type: 'pod'; id: string };\n\nexport class QuotaAdminHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly verify = createSolidTokenVerifier();\n private readonly accountRepo: AccountRepository;\n private readonly roleRepo: AccountRoleRepository;\n private readonly quotaService: QuotaService;\n private readonly basePath: string;\n\n public constructor(options: QuotaAdminHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.accountRepo = new AccountRepository(db);\n this.roleRepo = options.roleRepository ?? new AccountRoleRepository(db);\n this.basePath = options.basePath ?? '/api/quota/';\n this.quotaService = options.quotaService;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n const path = this.getUrl(request).pathname;\n if (!path.startsWith(this.basePath)) {\n throw new NotImplementedHttpError('Not a quota admin request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const method = (request.method ?? 'GET').toUpperCase();\n if (method === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n\n if (![ 'GET', 'PUT', 'DELETE' ].includes(method)) {\n throw new MethodNotAllowedHttpError([ 'GET', 'PUT', 'DELETE', 'OPTIONS' ]);\n }\n\n await this.authenticateAdmin(request);\n\n const target = this.parseTarget(this.getUrl(request).pathname);\n\n switch (method) {\n case 'GET':\n await this.handleGet(target, response);\n break;\n case 'PUT':\n await this.handlePut(target, request, response);\n break;\n case 'DELETE':\n await this.handleDelete(target, response);\n break;\n }\n }\n\n private async handleGet(target: QuotaTarget, response: HttpResponse): Promise<void> {\n if (target.type === 'account') {\n const quota = await this.quotaService.getAccountLimit(target.id);\n this.writeJson(response, 200, {\n type: 'account',\n accountId: target.id,\n quotaLimit: quota ?? null,\n });\n return;\n }\n\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n const quota = await this.quotaService.getPodLimit(target.id);\n this.writeJson(response, 200, {\n type: 'pod',\n podId: target.id,\n accountId: podInfo.accountId,\n baseUrl: podInfo.baseUrl ?? null,\n quotaLimit: quota ?? null,\n });\n }\n\n private async handlePut(target: QuotaTarget, request: HttpRequest, response: HttpResponse): Promise<void> {\n const body = await this.readJson(request);\n if (body == null || typeof body !== 'object') {\n throw new BadRequestHttpError('Request body must be an object.');\n }\n const payload = body as Record<string, unknown>;\n const quota = this.extractQuota(payload);\n\n if (target.type === 'pod') {\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n }\n\n if (target.type === 'account') {\n await this.quotaService.setAccountLimit(target.id, quota);\n const latest = await this.quotaService.getAccountLimit(target.id);\n this.writeJson(response, 200, {\n status: 'updated',\n targetType: target.type,\n targetId: target.id,\n quotaLimit: latest ?? null,\n });\n return;\n }\n\n await this.quotaService.setPodLimit(target.id, quota);\n const latest = await this.quotaService.getPodLimit(target.id);\n\n this.writeJson(response, 200, {\n status: 'updated',\n targetType: target.type,\n targetId: target.id,\n quotaLimit: latest ?? null,\n });\n }\n\n private async handleDelete(target: QuotaTarget, response: HttpResponse): Promise<void> {\n if (target.type === 'pod') {\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n }\n\n if (target.type === 'account') {\n await this.quotaService.setAccountLimit(target.id, null);\n } else {\n await this.quotaService.setPodLimit(target.id, null);\n }\n this.writeJson(response, 200, {\n status: 'cleared',\n targetType: target.type,\n targetId: target.id,\n });\n }\n\n private async authenticateAdmin(request: HttpRequest): Promise<void> {\n const authorization = request.headers.authorization;\n if (!authorization || !authorization.startsWith('Bearer ')) {\n throw new UnauthorizedHttpError('Quota管理接口需要携带 Bearer Token。');\n }\n let payload: Record<string, unknown>;\n try {\n payload = await this.verify(authorization) as unknown as Record<string, unknown>;\n } catch (error: unknown) {\n throw new UnauthorizedHttpError('无法验证访问令牌。', { cause: error });\n }\n const webId = this.extractWebId(payload);\n if (!webId) {\n throw new UnauthorizedHttpError('访问令牌缺少 webid。');\n }\n const context = await this.roleRepo.findByWebId(webId);\n if (!context || !context.roles.includes('admin')) {\n throw new ForbiddenHttpError('仅限管理员修改配额。');\n }\n }\n\n private parseTarget(pathname: string): QuotaTarget {\n const relative = pathname.slice(this.basePath.length);\n const segments = relative.split('/').filter(Boolean);\n if (segments.length !== 2) {\n throw new BadRequestHttpError('Quota path must match /accounts/{id} or /pods/{id}.');\n }\n const [ scope, identifier ] = segments;\n if (!identifier) {\n throw new BadRequestHttpError('Missing identifier.');\n }\n if (scope === 'accounts') {\n return { type: 'account', id: decodeURIComponent(identifier) };\n }\n if (scope === 'pods') {\n return { type: 'pod', id: decodeURIComponent(identifier) };\n }\n throw new BadRequestHttpError('Unknown quota scope.');\n }\n\n private async readJson(request: HttpRequest): Promise<unknown> {\n const body = await this.readBody(request);\n if (!body) {\n return undefined;\n }\n try {\n return JSON.parse(body);\n } catch (error: unknown) {\n throw new BadRequestHttpError('Body must contain valid JSON.', { cause: error });\n }\n }\n\n private extractQuota(body: Record<string, unknown>): number | null {\n if (!Object.prototype.hasOwnProperty.call(body, 'quotaLimit')) {\n throw new BadRequestHttpError('Body must include quotaLimit.');\n }\n const quotaValue = body.quotaLimit as unknown;\n if (quotaValue === null) {\n return null;\n }\n if (typeof quotaValue !== 'number' || !Number.isFinite(quotaValue) || quotaValue < 0) {\n throw new BadRequestHttpError('quotaLimit must be a non-negative number or null.');\n }\n return quotaValue;\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,PUT,DELETE,OPTIONS');\n response.end();\n }\n\n private writeJson(response: HttpResponse, status: number, payload: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.setHeader('Cache-Control', 'no-store');\n response.end(JSON.stringify(payload));\n }\n\n private async readBody(request: HttpRequest): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => resolve(data));\n request.on('error', reject);\n });\n }\n\n private getUrl(request: HttpRequest): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private extractWebId(payload: Record<string, unknown>): string | undefined {\n const getString = (key: string): string | undefined => {\n const value = payload[key];\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n };\n const direct = getString('webid') ?? getString('webId') ?? getString('sub');\n if (direct) {\n return direct;\n }\n const clientId = getString('client_id');\n if (clientId && clientId.startsWith('https://')) {\n return clientId;\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"QuotaAdminHttpHandler.js","sourceRoot":"","sources":["../../../src/http/quota/QuotaAdminHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { createSolidTokenVerifier } from '@solid/access-token-verifier';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpRequest, HttpResponse } from '@solid/community-server';\nimport {\n BadRequestHttpError,\n ForbiddenHttpError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n UnauthorizedHttpError,\n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { AccountRepository } from '../../identity/drizzle/AccountRepository';\nimport { AccountRoleRepository } from '../../identity/drizzle/AccountRoleRepository';\nimport type { AccountQuota, QuotaService } from '../../quota/QuotaService';\n\ninterface QuotaAdminHttpHandlerOptions {\n identityDbUrl: string;\n basePath?: string;\n roleRepository?: AccountRoleRepository;\n quotaService: QuotaService;\n}\n\ntype QuotaTarget =\n | { type: 'account'; id: string }\n | { type: 'pod'; id: string };\n\nexport class QuotaAdminHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly verify = createSolidTokenVerifier();\n private readonly accountRepo: AccountRepository;\n private readonly roleRepo: AccountRoleRepository;\n private readonly quotaService: QuotaService;\n private readonly basePath: string;\n\n public constructor(options: QuotaAdminHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.accountRepo = new AccountRepository(db);\n this.roleRepo = options.roleRepository ?? new AccountRoleRepository(db);\n this.basePath = options.basePath ?? '/api/quota/';\n this.quotaService = options.quotaService;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n const path = this.getUrl(request).pathname;\n if (!path.startsWith(this.basePath)) {\n throw new NotImplementedHttpError('Not a quota admin request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const method = (request.method ?? 'GET').toUpperCase();\n if (method === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n\n if (![ 'GET', 'PUT', 'DELETE' ].includes(method)) {\n throw new MethodNotAllowedHttpError([ 'GET', 'PUT', 'DELETE', 'OPTIONS' ]);\n }\n\n await this.authenticateAdmin(request);\n\n const target = this.parseTarget(this.getUrl(request).pathname);\n\n switch (method) {\n case 'GET':\n await this.handleGet(target, response);\n break;\n case 'PUT':\n await this.handlePut(target, request, response);\n break;\n case 'DELETE':\n await this.handleDelete(target, response);\n break;\n }\n }\n\n private async handleGet(target: QuotaTarget, response: HttpResponse): Promise<void> {\n if (target.type === 'account') {\n const quota = await this.quotaService.getAccountQuota(target.id);\n this.writeJson(response, 200, {\n type: 'account',\n accountId: target.id,\n quota,\n });\n return;\n }\n\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n const quota = await this.quotaService.getPodQuota(target.id);\n this.writeJson(response, 200, {\n type: 'pod',\n podId: target.id,\n accountId: podInfo.accountId,\n baseUrl: podInfo.baseUrl ?? null,\n quota,\n });\n }\n\n private async handlePut(target: QuotaTarget, request: HttpRequest, response: HttpResponse): Promise<void> {\n const body = await this.readJson(request);\n if (body == null || typeof body !== 'object') {\n throw new BadRequestHttpError('Request body must be an object.');\n }\n const payload = body as Record<string, unknown>;\n const quota = this.extractQuota(payload);\n\n if (target.type === 'pod') {\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n }\n\n if (target.type === 'account') {\n await this.quotaService.setAccountQuota(target.id, quota);\n const latest = await this.quotaService.getAccountQuota(target.id);\n this.writeJson(response, 200, {\n status: 'updated',\n targetType: target.type,\n targetId: target.id,\n quota: latest,\n });\n return;\n }\n\n await this.quotaService.setPodQuota(target.id, quota);\n const latest = await this.quotaService.getPodQuota(target.id);\n\n this.writeJson(response, 200, {\n status: 'updated',\n targetType: target.type,\n targetId: target.id,\n quota: latest,\n });\n }\n\n private async handleDelete(target: QuotaTarget, response: HttpResponse): Promise<void> {\n if (target.type === 'pod') {\n const podInfo = await this.accountRepo.getPodInfo(target.id);\n if (!podInfo) {\n throw new BadRequestHttpError('Unknown pod identifier.');\n }\n }\n\n if (target.type === 'account') {\n await this.quotaService.clearAccountQuota(target.id);\n } else {\n await this.quotaService.clearPodQuota(target.id);\n }\n this.writeJson(response, 200, {\n status: 'cleared',\n targetType: target.type,\n targetId: target.id,\n });\n }\n\n private async authenticateAdmin(request: HttpRequest): Promise<void> {\n const authorization = request.headers.authorization;\n if (!authorization || !authorization.startsWith('Bearer ')) {\n throw new UnauthorizedHttpError('Quota管理接口需要携带 Bearer Token。');\n }\n let payload: Record<string, unknown>;\n try {\n payload = await this.verify(authorization) as unknown as Record<string, unknown>;\n } catch (error: unknown) {\n throw new UnauthorizedHttpError('无法验证访问令牌。', { cause: error });\n }\n const webId = this.extractWebId(payload);\n if (!webId) {\n throw new UnauthorizedHttpError('访问令牌缺少 webid。');\n }\n const context = await this.roleRepo.findByWebId(webId);\n if (!context || !context.roles.includes('admin')) {\n throw new ForbiddenHttpError('仅限管理员修改配额。');\n }\n }\n\n private parseTarget(pathname: string): QuotaTarget {\n const relative = pathname.slice(this.basePath.length);\n const segments = relative.split('/').filter(Boolean);\n if (segments.length !== 2) {\n throw new BadRequestHttpError('Quota path must match /accounts/{id} or /pods/{id}.');\n }\n const [ scope, identifier ] = segments;\n if (!identifier) {\n throw new BadRequestHttpError('Missing identifier.');\n }\n if (scope === 'accounts') {\n return { type: 'account', id: decodeURIComponent(identifier) };\n }\n if (scope === 'pods') {\n return { type: 'pod', id: decodeURIComponent(identifier) };\n }\n throw new BadRequestHttpError('Unknown quota scope.');\n }\n\n private async readJson(request: HttpRequest): Promise<unknown> {\n const body = await this.readBody(request);\n if (!body) {\n return undefined;\n }\n try {\n return JSON.parse(body);\n } catch (error: unknown) {\n throw new BadRequestHttpError('Body must contain valid JSON.', { cause: error });\n }\n }\n\n private extractQuota(body: Record<string, unknown>): Partial<AccountQuota> {\n const quota: Partial<AccountQuota> = {};\n let hasQuotaField = false;\n for (const field of [ 'storageLimitBytes', 'bandwidthLimitBps', 'computeLimitSeconds', 'tokenLimitMonthly' ] as const) {\n if (!Object.prototype.hasOwnProperty.call(body, field)) {\n continue;\n }\n const value = body[field];\n if (value !== null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {\n throw new BadRequestHttpError(`${field} must be a non-negative number or null.`);\n }\n quota[field] = value;\n hasQuotaField = true;\n }\n if (!hasQuotaField) {\n throw new BadRequestHttpError('Body must include at least one quota field.');\n }\n return quota;\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,PUT,DELETE,OPTIONS');\n response.end();\n }\n\n private writeJson(response: HttpResponse, status: number, payload: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.setHeader('Cache-Control', 'no-store');\n response.end(JSON.stringify(payload));\n }\n\n private async readBody(request: HttpRequest): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => resolve(data));\n request.on('error', reject);\n });\n }\n\n private getUrl(request: HttpRequest): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private extractWebId(payload: Record<string, unknown>): string | undefined {\n const getString = (key: string): string | undefined => {\n const value = payload[key];\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n };\n const direct = getString('webid') ?? getString('webId') ?? getString('sub');\n if (direct) {\n return direct;\n }\n const clientId = getString('client_id');\n if (clientId && clientId.startsWith('https://')) {\n return clientId;\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -1,31 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface PodQuotaContext {
|
|
3
|
-
accountId: string;
|
|
4
|
-
podId: string;
|
|
5
|
-
baseUrl?: string;
|
|
6
|
-
podQuota?: number;
|
|
7
|
-
accountQuota?: number;
|
|
8
|
-
}
|
|
1
|
+
import type { IdentityDatabase } from './db';
|
|
9
2
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Reads account/pod data from CSS's internal_kv table.
|
|
13
|
-
* Quota limits are stored in identity_account_usage / identity_pod_usage tables.
|
|
3
|
+
* Reads account/pod ownership data from the CSS identity facts.
|
|
4
|
+
* Quota values live in identity_usage and are managed by UsageRepository.
|
|
14
5
|
*/
|
|
15
6
|
export declare class AccountRepository {
|
|
16
|
-
private readonly
|
|
17
|
-
private readonly kvTableName;
|
|
18
|
-
private readonly accountUsageTable;
|
|
19
|
-
private readonly podUsageTable;
|
|
7
|
+
private readonly podLookupRepo;
|
|
20
8
|
constructor(db: IdentityDatabase, kvTableName?: string);
|
|
21
|
-
getAccountQuota(accountId: string): Promise<number | undefined>;
|
|
22
9
|
getPodInfo(podId: string): Promise<{
|
|
23
10
|
accountId: string;
|
|
24
|
-
podQuota?: number;
|
|
25
11
|
baseUrl?: string;
|
|
26
12
|
} | undefined>;
|
|
27
|
-
private getPodQuota;
|
|
28
|
-
getQuotaContext(accountId: string, podId: string): Promise<PodQuotaContext | undefined>;
|
|
29
|
-
setAccountQuota(accountId: string, quota?: number): Promise<void>;
|
|
30
|
-
setPodQuota(podId: string, quota?: number): Promise<void>;
|
|
31
13
|
}
|