@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
|
@@ -60,7 +60,7 @@ export interface SubdomainServiceOptions {
|
|
|
60
60
|
* 4. DNS 记录管理
|
|
61
61
|
* 5. 隧道创建
|
|
62
62
|
*
|
|
63
|
-
* 注册信息持久化到 EdgeNodeRepository(
|
|
63
|
+
* 注册信息持久化到 EdgeNodeRepository(cluster_node.subdomain 字段)
|
|
64
64
|
*/
|
|
65
65
|
export declare class SubdomainService {
|
|
66
66
|
private readonly baseDomain;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubdomainService.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TunnelProvider, TunnelConfig } from '../tunnel/TunnelProvider';\nimport type { DnsProvider } from '../dns/DnsProvider';\nimport type { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\n/**\n * 子域名注册信息\n */\nexport interface SubdomainRegistration {\n /** 子域名 (如 mynode) */\n subdomain: string;\n\n /** 完整域名 (如 mynode.pods.undefieds.co) */\n fullDomain: string;\n\n /** 访问模式 */\n mode: 'direct' | 'tunnel';\n\n /** 公网 IP (直连模式) */\n ipv4?: string;\n\n /** 隧道配置 (隧道模式) */\n tunnelConfig?: TunnelConfig;\n\n /** 注册时间 */\n registeredAt: Date;\n\n /** 所有者 ID */\n ownerId?: string;\n\n /** 绑定的节点 ID */\n nodeId?: string;\n}\n\n/**\n * 连通性检测结果\n */\nexport interface ConnectivityResult {\n /** 是否可达 */\n reachable: boolean;\n\n /** 公网 IP */\n ipv4?: string;\n\n /** 延迟 (ms) */\n latency?: number;\n\n /** 错误信息 */\n error?: string;\n}\n\n/**\n * SubdomainService 配置\n */\nexport interface SubdomainServiceOptions {\n /** 基础域名 (如 pods.undefieds.co) */\n baseDomain: string;\n\n /** DNS Provider */\n dnsProvider: DnsProvider;\n\n /** Tunnel Provider */\n tunnelProvider: TunnelProvider;\n\n /** Edge Node Repository (持久化) */\n edgeNodeRepo: EdgeNodeRepository;\n\n /** 保留的子域名列表 */\n reservedSubdomains?: string[];\n}\n\n/**\n * 子域名管理服务\n *\n * 负责:\n * 1. 子域名可用性检查\n * 2. 连通性检测\n * 3. 直连/隧道模式选择\n * 4. DNS 记录管理\n * 5. 隧道创建\n *\n * 注册信息持久化到 EdgeNodeRepository(identity_edge_node.subdomain 字段)\n */\nexport class SubdomainService {\n private readonly baseDomain: string;\n private readonly dnsProvider: DnsProvider;\n private readonly tunnelProvider: TunnelProvider;\n private readonly edgeNodeRepo: EdgeNodeRepository;\n private readonly reservedSubdomains: Set<string>;\n\n constructor(options: SubdomainServiceOptions) {\n this.baseDomain = options.baseDomain;\n this.dnsProvider = options.dnsProvider;\n this.tunnelProvider = options.tunnelProvider;\n this.edgeNodeRepo = options.edgeNodeRepo;\n this.reservedSubdomains = new Set(options.reservedSubdomains ?? [\n 'www', 'api', 'app', 'admin', 'mail', 'ftp', 'ssh',\n 'pods', 'center', 'edge', 'node', 'test', 'dev', 'staging',\n ]);\n }\n\n /**\n * 检查子域名是否可用\n */\n async checkAvailability(subdomain: string): Promise<{\n available: boolean;\n reason?: string;\n }> {\n if (!this.isValidSubdomain(subdomain)) {\n return {\n available: false,\n reason: 'Invalid subdomain format. Use 3-63 lowercase letters, numbers, or hyphens.',\n };\n }\n\n if (this.reservedSubdomains.has(subdomain.toLowerCase())) {\n return {\n available: false,\n reason: 'This subdomain is reserved.',\n };\n }\n\n const existing = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (existing) {\n return {\n available: false,\n reason: 'This subdomain is already registered.',\n };\n }\n\n return { available: true };\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n nodeId: string;\n localPort: number;\n ipv4?: string;\n ownerId?: string;\n }): Promise<SubdomainRegistration> {\n const { subdomain, nodeId, localPort, ipv4, ownerId } = options;\n const normalizedSubdomain = subdomain.toLowerCase();\n\n const availability = await this.checkAvailability(normalizedSubdomain);\n if (!availability.available) {\n throw new Error(availability.reason);\n }\n\n const fullDomain = `${normalizedSubdomain}.${this.baseDomain}`;\n\n // 连通性检测\n let mode: 'direct' | 'tunnel' = 'tunnel';\n let verifiedIp: string | undefined;\n\n if (ipv4) {\n const connectivity = await this.checkConnectivity(ipv4, localPort);\n if (connectivity.reachable) {\n mode = 'direct';\n verifiedIp = ipv4;\n }\n }\n\n // DNS / 隧道\n let tunnelConfig: TunnelConfig | undefined;\n\n if (mode === 'direct') {\n const type = this.isIpv6(verifiedIp!) ? 'AAAA' : 'A';\n await this.dnsProvider.upsertRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type,\n value: verifiedIp!,\n ttl: 60,\n });\n } else {\n tunnelConfig = await this.tunnelProvider.setup({\n subdomain: normalizedSubdomain,\n localPort,\n });\n }\n\n // 持久化到 EdgeNodeRepository\n await this.edgeNodeRepo.updateNodeMode(nodeId, {\n accessMode: mode === 'direct' ? 'direct' : 'proxy',\n ipv4: verifiedIp,\n publicPort: localPort,\n subdomain: normalizedSubdomain,\n connectivityStatus: mode === 'direct' ? 'reachable' : 'unknown',\n });\n\n return {\n subdomain: normalizedSubdomain,\n fullDomain,\n mode,\n ipv4: verifiedIp,\n tunnelConfig,\n registeredAt: new Date(),\n ownerId,\n nodeId,\n };\n }\n\n /**\n * 释放子域名\n */\n async release(subdomain: string): Promise<void> {\n const normalizedSubdomain = subdomain.toLowerCase();\n const node = await this.edgeNodeRepo.findNodeBySubdomain(normalizedSubdomain);\n\n if (!node) {\n throw new Error('Subdomain not found');\n }\n\n // 获取完整连通性信息以判断模式\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(node.nodeId);\n const accessMode = info?.accessMode;\n\n // 删除 DNS 记录\n try {\n await this.dnsProvider.deleteRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type: accessMode === 'direct' ? 'A' : 'CNAME',\n });\n } catch {\n // DNS 删除失败不阻塞释放\n }\n\n // 清除 DB 中的 subdomain\n await this.edgeNodeRepo.updateNodeMode(node.nodeId, {\n accessMode: 'proxy',\n subdomain: undefined,\n connectivityStatus: 'unknown',\n });\n }\n\n /**\n * 获取注册信息\n */\n async getRegistration(subdomain: string): Promise<SubdomainRegistration | undefined> {\n const node = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (!node) {\n return undefined;\n }\n return this.nodeToRegistration(node);\n }\n\n /**\n * 获取所有注册\n */\n async getAllRegistrations(): Promise<SubdomainRegistration[]> {\n const nodes = await this.edgeNodeRepo.listNodes();\n const results: SubdomainRegistration[] = [];\n for (const n of nodes) {\n if (!n.metadata || !(n.metadata as Record<string, unknown>).subdomain) {\n // 需要查 connectivity info 获取 subdomain\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(n.nodeId);\n if (info?.subdomain) {\n results.push({\n subdomain: info.subdomain,\n fullDomain: `${info.subdomain}.${this.baseDomain}`,\n mode: info.accessMode === 'direct' ? 'direct' : 'tunnel',\n ipv4: info.ipv4,\n registeredAt: new Date(),\n nodeId: n.nodeId,\n });\n }\n }\n }\n return results;\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(subdomain: string): Promise<void> {\n const reg = await this.getRegistration(subdomain);\n if (!reg) {\n throw new Error('Subdomain not found');\n }\n if (reg.mode !== 'tunnel' || !reg.tunnelConfig) {\n throw new Error('Subdomain is not in tunnel mode');\n }\n await this.tunnelProvider.start(reg.tunnelConfig);\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(): Promise<void> {\n await this.tunnelProvider.stop();\n }\n\n // ============ 私有方法 ============\n\n private nodeToRegistration(node: {\n nodeId: string;\n accessMode?: string;\n metadata?: Record<string, unknown> | null;\n subdomain?: string;\n }): SubdomainRegistration {\n const sub = node.subdomain!;\n return {\n subdomain: sub,\n fullDomain: `${sub}.${this.baseDomain}`,\n mode: node.accessMode === 'direct' ? 'direct' : 'tunnel',\n registeredAt: new Date(),\n nodeId: node.nodeId,\n ownerId: (node.metadata as Record<string, unknown> | null)?.ownerId as string | undefined,\n };\n }\n\n private isValidSubdomain(subdomain: string): boolean {\n const regex = /^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/;\n return regex.test(subdomain) && subdomain.length >= 3 && subdomain.length <= 63;\n }\n\n private async checkConnectivity(\n ip: string,\n port: number,\n ): Promise<ConnectivityResult> {\n try {\n const start = Date.now();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const host = this.isIpv6(ip) ? `[${ip}]` : ip;\n const response = await fetch(`http://${host}:${port}/.well-known/solid`, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n return {\n reachable: response.ok || response.status === 401,\n ipv4: ip,\n latency: Date.now() - start,\n };\n } catch (error) {\n return {\n reachable: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n private isIpv6(ip: string): boolean {\n return ip.includes(':');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SubdomainService.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TunnelProvider, TunnelConfig } from '../tunnel/TunnelProvider';\nimport type { DnsProvider } from '../dns/DnsProvider';\nimport type { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\n/**\n * 子域名注册信息\n */\nexport interface SubdomainRegistration {\n /** 子域名 (如 mynode) */\n subdomain: string;\n\n /** 完整域名 (如 mynode.pods.undefieds.co) */\n fullDomain: string;\n\n /** 访问模式 */\n mode: 'direct' | 'tunnel';\n\n /** 公网 IP (直连模式) */\n ipv4?: string;\n\n /** 隧道配置 (隧道模式) */\n tunnelConfig?: TunnelConfig;\n\n /** 注册时间 */\n registeredAt: Date;\n\n /** 所有者 ID */\n ownerId?: string;\n\n /** 绑定的节点 ID */\n nodeId?: string;\n}\n\n/**\n * 连通性检测结果\n */\nexport interface ConnectivityResult {\n /** 是否可达 */\n reachable: boolean;\n\n /** 公网 IP */\n ipv4?: string;\n\n /** 延迟 (ms) */\n latency?: number;\n\n /** 错误信息 */\n error?: string;\n}\n\n/**\n * SubdomainService 配置\n */\nexport interface SubdomainServiceOptions {\n /** 基础域名 (如 pods.undefieds.co) */\n baseDomain: string;\n\n /** DNS Provider */\n dnsProvider: DnsProvider;\n\n /** Tunnel Provider */\n tunnelProvider: TunnelProvider;\n\n /** Edge Node Repository (持久化) */\n edgeNodeRepo: EdgeNodeRepository;\n\n /** 保留的子域名列表 */\n reservedSubdomains?: string[];\n}\n\n/**\n * 子域名管理服务\n *\n * 负责:\n * 1. 子域名可用性检查\n * 2. 连通性检测\n * 3. 直连/隧道模式选择\n * 4. DNS 记录管理\n * 5. 隧道创建\n *\n * 注册信息持久化到 EdgeNodeRepository(cluster_node.subdomain 字段)\n */\nexport class SubdomainService {\n private readonly baseDomain: string;\n private readonly dnsProvider: DnsProvider;\n private readonly tunnelProvider: TunnelProvider;\n private readonly edgeNodeRepo: EdgeNodeRepository;\n private readonly reservedSubdomains: Set<string>;\n\n constructor(options: SubdomainServiceOptions) {\n this.baseDomain = options.baseDomain;\n this.dnsProvider = options.dnsProvider;\n this.tunnelProvider = options.tunnelProvider;\n this.edgeNodeRepo = options.edgeNodeRepo;\n this.reservedSubdomains = new Set(options.reservedSubdomains ?? [\n 'www', 'api', 'app', 'admin', 'mail', 'ftp', 'ssh',\n 'pods', 'center', 'edge', 'node', 'test', 'dev', 'staging',\n ]);\n }\n\n /**\n * 检查子域名是否可用\n */\n async checkAvailability(subdomain: string): Promise<{\n available: boolean;\n reason?: string;\n }> {\n if (!this.isValidSubdomain(subdomain)) {\n return {\n available: false,\n reason: 'Invalid subdomain format. Use 3-63 lowercase letters, numbers, or hyphens.',\n };\n }\n\n if (this.reservedSubdomains.has(subdomain.toLowerCase())) {\n return {\n available: false,\n reason: 'This subdomain is reserved.',\n };\n }\n\n const existing = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (existing) {\n return {\n available: false,\n reason: 'This subdomain is already registered.',\n };\n }\n\n return { available: true };\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n nodeId: string;\n localPort: number;\n ipv4?: string;\n ownerId?: string;\n }): Promise<SubdomainRegistration> {\n const { subdomain, nodeId, localPort, ipv4, ownerId } = options;\n const normalizedSubdomain = subdomain.toLowerCase();\n\n const availability = await this.checkAvailability(normalizedSubdomain);\n if (!availability.available) {\n throw new Error(availability.reason);\n }\n\n const fullDomain = `${normalizedSubdomain}.${this.baseDomain}`;\n\n // 连通性检测\n let mode: 'direct' | 'tunnel' = 'tunnel';\n let verifiedIp: string | undefined;\n\n if (ipv4) {\n const connectivity = await this.checkConnectivity(ipv4, localPort);\n if (connectivity.reachable) {\n mode = 'direct';\n verifiedIp = ipv4;\n }\n }\n\n // DNS / 隧道\n let tunnelConfig: TunnelConfig | undefined;\n\n if (mode === 'direct') {\n const type = this.isIpv6(verifiedIp!) ? 'AAAA' : 'A';\n await this.dnsProvider.upsertRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type,\n value: verifiedIp!,\n ttl: 60,\n });\n } else {\n tunnelConfig = await this.tunnelProvider.setup({\n subdomain: normalizedSubdomain,\n localPort,\n });\n }\n\n // 持久化到 EdgeNodeRepository\n await this.edgeNodeRepo.updateNodeMode(nodeId, {\n accessMode: mode === 'direct' ? 'direct' : 'proxy',\n ipv4: verifiedIp,\n publicPort: localPort,\n subdomain: normalizedSubdomain,\n connectivityStatus: mode === 'direct' ? 'reachable' : 'unknown',\n });\n\n return {\n subdomain: normalizedSubdomain,\n fullDomain,\n mode,\n ipv4: verifiedIp,\n tunnelConfig,\n registeredAt: new Date(),\n ownerId,\n nodeId,\n };\n }\n\n /**\n * 释放子域名\n */\n async release(subdomain: string): Promise<void> {\n const normalizedSubdomain = subdomain.toLowerCase();\n const node = await this.edgeNodeRepo.findNodeBySubdomain(normalizedSubdomain);\n\n if (!node) {\n throw new Error('Subdomain not found');\n }\n\n // 获取完整连通性信息以判断模式\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(node.nodeId);\n const accessMode = info?.accessMode;\n\n // 删除 DNS 记录\n try {\n await this.dnsProvider.deleteRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type: accessMode === 'direct' ? 'A' : 'CNAME',\n });\n } catch {\n // DNS 删除失败不阻塞释放\n }\n\n // 清除 DB 中的 subdomain\n await this.edgeNodeRepo.updateNodeMode(node.nodeId, {\n accessMode: 'proxy',\n subdomain: undefined,\n connectivityStatus: 'unknown',\n });\n }\n\n /**\n * 获取注册信息\n */\n async getRegistration(subdomain: string): Promise<SubdomainRegistration | undefined> {\n const node = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (!node) {\n return undefined;\n }\n return this.nodeToRegistration(node);\n }\n\n /**\n * 获取所有注册\n */\n async getAllRegistrations(): Promise<SubdomainRegistration[]> {\n const nodes = await this.edgeNodeRepo.listNodes();\n const results: SubdomainRegistration[] = [];\n for (const n of nodes) {\n if (!n.metadata || !(n.metadata as Record<string, unknown>).subdomain) {\n // 需要查 connectivity info 获取 subdomain\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(n.nodeId);\n if (info?.subdomain) {\n results.push({\n subdomain: info.subdomain,\n fullDomain: `${info.subdomain}.${this.baseDomain}`,\n mode: info.accessMode === 'direct' ? 'direct' : 'tunnel',\n ipv4: info.ipv4,\n registeredAt: new Date(),\n nodeId: n.nodeId,\n });\n }\n }\n }\n return results;\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(subdomain: string): Promise<void> {\n const reg = await this.getRegistration(subdomain);\n if (!reg) {\n throw new Error('Subdomain not found');\n }\n if (reg.mode !== 'tunnel' || !reg.tunnelConfig) {\n throw new Error('Subdomain is not in tunnel mode');\n }\n await this.tunnelProvider.start(reg.tunnelConfig);\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(): Promise<void> {\n await this.tunnelProvider.stop();\n }\n\n // ============ 私有方法 ============\n\n private nodeToRegistration(node: {\n nodeId: string;\n accessMode?: string;\n metadata?: Record<string, unknown> | null;\n subdomain?: string;\n }): SubdomainRegistration {\n const sub = node.subdomain!;\n return {\n subdomain: sub,\n fullDomain: `${sub}.${this.baseDomain}`,\n mode: node.accessMode === 'direct' ? 'direct' : 'tunnel',\n registeredAt: new Date(),\n nodeId: node.nodeId,\n ownerId: (node.metadata as Record<string, unknown> | null)?.ownerId as string | undefined,\n };\n }\n\n private isValidSubdomain(subdomain: string): boolean {\n const regex = /^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/;\n return regex.test(subdomain) && subdomain.length >= 3 && subdomain.length <= 63;\n }\n\n private async checkConnectivity(\n ip: string,\n port: number,\n ): Promise<ConnectivityResult> {\n try {\n const start = Date.now();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const host = this.isIpv6(ip) ? `[${ip}]` : ip;\n const response = await fetch(`http://${host}:${port}/.well-known/solid`, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n return {\n reachable: response.ok || response.status === 401,\n ipv4: ip,\n latency: Date.now() - start,\n };\n } catch (error) {\n return {\n reachable: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n private isIpv6(ip: string): boolean {\n return ip.includes(':');\n }\n}\n"]}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService",
|
|
107
107
|
"@type": "Class",
|
|
108
108
|
"requireElement": "SubdomainService",
|
|
109
|
-
"comment": "子域名管理服务 负责: 1. 子域名可用性检查 2. 连通性检测 3. 直连/隧道模式选择 4. DNS 记录管理 5. 隧道创建 注册信息持久化到 EdgeNodeRepository(
|
|
109
|
+
"comment": "子域名管理服务 负责: 1. 子域名可用性检查 2. 连通性检测 3. 直连/隧道模式选择 4. DNS 记录管理 5. 隧道创建 注册信息持久化到 EdgeNodeRepository(cluster_node.subdomain 字段)",
|
|
110
110
|
"parameters": [
|
|
111
111
|
{
|
|
112
112
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_baseDomain",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undefineds.co/xpod",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
4
|
"description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
|
|
5
5
|
"repository": "https://github.com/undefinedsco/xpod",
|
|
6
6
|
"author": "developer@undefineds.co",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ApiServer } from '../ApiServer';
|
|
2
|
-
import type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';
|
|
3
|
-
export interface ApiKeyHandlerOptions {
|
|
4
|
-
store: DrizzleClientCredentialsStore;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Handler for API Key management
|
|
8
|
-
*
|
|
9
|
-
* GET /v1/keys - List user's API keys
|
|
10
|
-
* POST /v1/keys - Store a new API key (after creating in CSS)
|
|
11
|
-
* DELETE /v1/keys/:clientId - Delete an API key
|
|
12
|
-
*
|
|
13
|
-
* All endpoints require Solid Token (only frontend can manage keys)
|
|
14
|
-
*/
|
|
15
|
-
export declare function registerApiKeyRoutes(server: ApiServer, options: ApiKeyHandlerOptions): void;
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerApiKeyRoutes = registerApiKeyRoutes;
|
|
4
|
-
const global_logger_factory_1 = require("global-logger-factory");
|
|
5
|
-
const AuthContext_1 = require("../auth/AuthContext");
|
|
6
|
-
/**
|
|
7
|
-
* Handler for API Key management
|
|
8
|
-
*
|
|
9
|
-
* GET /v1/keys - List user's API keys
|
|
10
|
-
* POST /v1/keys - Store a new API key (after creating in CSS)
|
|
11
|
-
* DELETE /v1/keys/:clientId - Delete an API key
|
|
12
|
-
*
|
|
13
|
-
* All endpoints require Solid Token (only frontend can manage keys)
|
|
14
|
-
*/
|
|
15
|
-
function registerApiKeyRoutes(server, options) {
|
|
16
|
-
const logger = (0, global_logger_factory_1.getLoggerFor)('ApiKeyHandler');
|
|
17
|
-
const store = options.store;
|
|
18
|
-
const rejectApiKey = (request, response) => {
|
|
19
|
-
const auth = request.auth;
|
|
20
|
-
if (auth && (0, AuthContext_1.isSolidAuth)(auth) && auth.viaApiKey) {
|
|
21
|
-
sendJson(response, 403, { error: 'API key is not allowed for this endpoint' });
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
};
|
|
26
|
-
// GET /v1/keys - List user's API keys
|
|
27
|
-
server.get('/v1/keys', async (request, response, _params) => {
|
|
28
|
-
if (rejectApiKey(request, response)) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const auth = request.auth;
|
|
32
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
33
|
-
if (!webId) {
|
|
34
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
try {
|
|
38
|
-
// Use webId as account identifier
|
|
39
|
-
const keys = await store.listByAccount(webId);
|
|
40
|
-
sendJson(response, 200, {
|
|
41
|
-
keys: keys.map((k) => ({
|
|
42
|
-
clientId: k.clientId,
|
|
43
|
-
webId: k.webId,
|
|
44
|
-
displayName: k.displayName,
|
|
45
|
-
createdAt: k.createdAt.toISOString(),
|
|
46
|
-
})),
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
logger.error(`Failed to list API keys: ${error}`);
|
|
51
|
-
sendJson(response, 500, { error: 'Failed to list keys' });
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
// POST /v1/keys - Store API key (frontend calls this after creating credentials in CSS)
|
|
55
|
-
server.post('/v1/keys', async (request, response, _params) => {
|
|
56
|
-
if (rejectApiKey(request, response)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const auth = request.auth;
|
|
60
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
61
|
-
if (!webId) {
|
|
62
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const body = await readJsonBody(request);
|
|
66
|
-
if (!body || typeof body !== 'object') {
|
|
67
|
-
sendJson(response, 400, { error: 'Request body must be a JSON object' });
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const payload = body;
|
|
71
|
-
// These come from CSS client credentials creation
|
|
72
|
-
const clientId = payload.clientId;
|
|
73
|
-
const displayName = typeof payload.displayName === 'string' ? payload.displayName : undefined;
|
|
74
|
-
if (typeof clientId !== 'string' || !clientId.trim()) {
|
|
75
|
-
sendJson(response, 400, { error: 'clientId is required' });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
// Use webId as account identifier
|
|
80
|
-
await store.store({
|
|
81
|
-
clientId,
|
|
82
|
-
webId,
|
|
83
|
-
accountId: webId,
|
|
84
|
-
displayName,
|
|
85
|
-
});
|
|
86
|
-
logger.info(`Stored API key ${clientId} for user ${webId}`);
|
|
87
|
-
sendJson(response, 201, {
|
|
88
|
-
clientId,
|
|
89
|
-
displayName,
|
|
90
|
-
message: 'API key stored successfully.',
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
logger.error(`Failed to store API key: ${error}`);
|
|
95
|
-
sendJson(response, 500, { error: 'Failed to store key' });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
// DELETE /v1/keys/:clientId - Delete an API key
|
|
99
|
-
server.delete('/v1/keys/:clientId', async (request, response, params) => {
|
|
100
|
-
if (rejectApiKey(request, response)) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const auth = request.auth;
|
|
104
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
105
|
-
const clientId = decodeURIComponent(params.clientId);
|
|
106
|
-
if (!webId) {
|
|
107
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
// Delete with webId check to ensure ownership
|
|
112
|
-
const deleted = await store.delete(clientId, webId);
|
|
113
|
-
if (!deleted) {
|
|
114
|
-
sendJson(response, 404, { error: 'Key not found or access denied' });
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
logger.info(`Deleted API key ${clientId}`);
|
|
118
|
-
sendJson(response, 200, { status: 'deleted', clientId });
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
logger.error(`Failed to delete API key: ${error}`);
|
|
122
|
-
sendJson(response, 500, { error: 'Failed to delete key' });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function readJsonBody(request) {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
let data = '';
|
|
129
|
-
request.setEncoding('utf8');
|
|
130
|
-
request.on('data', (chunk) => {
|
|
131
|
-
data += chunk;
|
|
132
|
-
});
|
|
133
|
-
request.on('end', () => {
|
|
134
|
-
if (!data) {
|
|
135
|
-
resolve(undefined);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
resolve(JSON.parse(data));
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
resolve(undefined);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
request.on('error', reject);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
function sendJson(response, status, data) {
|
|
149
|
-
response.statusCode = status;
|
|
150
|
-
response.setHeader('Content-Type', 'application/json');
|
|
151
|
-
response.end(JSON.stringify(data));
|
|
152
|
-
}
|
|
153
|
-
//# sourceMappingURL=ApiKeyHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeyHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ApiKeyHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';\nimport { getWebId, isSolidAuth } from '../auth/AuthContext';\n\nexport interface ApiKeyHandlerOptions {\n store: DrizzleClientCredentialsStore;\n}\n\n/**\n * Handler for API Key management\n * \n * GET /v1/keys - List user's API keys\n * POST /v1/keys - Store a new API key (after creating in CSS)\n * DELETE /v1/keys/:clientId - Delete an API key\n * \n * All endpoints require Solid Token (only frontend can manage keys)\n */\nexport function registerApiKeyRoutes(server: ApiServer, options: ApiKeyHandlerOptions): void {\n const logger = getLoggerFor('ApiKeyHandler');\n const store = options.store;\n\n const rejectApiKey = (request: AuthenticatedRequest, response: ServerResponse): boolean => {\n const auth = request.auth;\n if (auth && isSolidAuth(auth) && auth.viaApiKey) {\n sendJson(response, 403, { error: 'API key is not allowed for this endpoint' });\n return true;\n }\n return false;\n };\n\n // GET /v1/keys - List user's API keys\n server.get('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Use webId as account identifier\n const keys = await store.listByAccount(webId);\n sendJson(response, 200, {\n keys: keys.map((k) => ({\n clientId: k.clientId,\n webId: k.webId,\n displayName: k.displayName,\n createdAt: k.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n logger.error(`Failed to list API keys: ${error}`);\n sendJson(response, 500, { error: 'Failed to list keys' });\n }\n });\n\n // POST /v1/keys - Store API key (frontend calls this after creating credentials in CSS)\n server.post('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n\n // These come from CSS client credentials creation\n const clientId = payload.clientId;\n const displayName = typeof payload.displayName === 'string' ? payload.displayName : undefined;\n\n if (typeof clientId !== 'string' || !clientId.trim()) {\n sendJson(response, 400, { error: 'clientId is required' });\n return;\n }\n\n try {\n // Use webId as account identifier\n await store.store({\n clientId,\n webId,\n accountId: webId,\n displayName,\n });\n\n logger.info(`Stored API key ${clientId} for user ${webId}`);\n\n sendJson(response, 201, {\n clientId,\n displayName,\n message: 'API key stored successfully.',\n });\n } catch (error) {\n logger.error(`Failed to store API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to store key' });\n }\n });\n\n // DELETE /v1/keys/:clientId - Delete an API key\n server.delete('/v1/keys/:clientId', async (request, response, params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n const clientId = decodeURIComponent(params.clientId);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Delete with webId check to ensure ownership\n const deleted = await store.delete(clientId, webId);\n if (!deleted) {\n sendJson(response, 404, { error: 'Key not found or access denied' });\n return;\n }\n\n logger.info(`Deleted API key ${clientId}`);\n sendJson(response, 200, { status: 'deleted', clientId });\n } catch (error) {\n logger.error(`Failed to delete API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to delete key' });\n }\n });\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\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}"]}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { IdentityDatabase } from '../../identity/drizzle/db';
|
|
2
|
-
import type { ClientCredentialsRecord, ClientCredentialsStore } from '../auth/ClientCredentialsAuthenticator';
|
|
3
|
-
export interface DrizzleClientCredentialsStoreOptions {
|
|
4
|
-
db: IdentityDatabase;
|
|
5
|
-
/**
|
|
6
|
-
* Whether using SQLite (default: false for PostgreSQL)
|
|
7
|
-
*/
|
|
8
|
-
isSqlite?: boolean;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Storage for API Keys (client credentials) using Drizzle ORM
|
|
12
|
-
*
|
|
13
|
-
* Only stores clientId → webId/accountId mapping.
|
|
14
|
-
* The actual clientSecret lives in the sk-xxx token and is never persisted.
|
|
15
|
-
*/
|
|
16
|
-
export declare class DrizzleClientCredentialsStore implements ClientCredentialsStore {
|
|
17
|
-
private readonly logger;
|
|
18
|
-
private readonly db;
|
|
19
|
-
private readonly apiClientCredentials;
|
|
20
|
-
constructor(options: DrizzleClientCredentialsStoreOptions);
|
|
21
|
-
/**
|
|
22
|
-
* Store API Key registration (called when user creates API Key via frontend)
|
|
23
|
-
*/
|
|
24
|
-
store(options: {
|
|
25
|
-
clientId: string;
|
|
26
|
-
webId: string;
|
|
27
|
-
accountId: string;
|
|
28
|
-
displayName?: string;
|
|
29
|
-
}): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* Find by client_id (the "API Key")
|
|
32
|
-
*/
|
|
33
|
-
findByClientId(clientId: string): Promise<ClientCredentialsRecord | undefined>;
|
|
34
|
-
/**
|
|
35
|
-
* List API Keys for an account
|
|
36
|
-
*/
|
|
37
|
-
listByAccount(accountId: string): Promise<Array<{
|
|
38
|
-
clientId: string;
|
|
39
|
-
webId: string;
|
|
40
|
-
displayName?: string;
|
|
41
|
-
createdAt: Date;
|
|
42
|
-
}>>;
|
|
43
|
-
/**
|
|
44
|
-
* Find the most recently created API Key for an account.
|
|
45
|
-
*/
|
|
46
|
-
findByAccountId(accountId: string): Promise<ClientCredentialsRecord | undefined>;
|
|
47
|
-
/**
|
|
48
|
-
* Delete an API Key
|
|
49
|
-
*/
|
|
50
|
-
delete(clientId: string, accountId?: string): Promise<boolean>;
|
|
51
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DrizzleClientCredentialsStore = void 0;
|
|
4
|
-
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
-
const global_logger_factory_1 = require("global-logger-factory");
|
|
6
|
-
const schema_pg_1 = require("../../identity/drizzle/schema.pg");
|
|
7
|
-
const schema_sqlite_1 = require("../../identity/drizzle/schema.sqlite");
|
|
8
|
-
/**
|
|
9
|
-
* Storage for API Keys (client credentials) using Drizzle ORM
|
|
10
|
-
*
|
|
11
|
-
* Only stores clientId → webId/accountId mapping.
|
|
12
|
-
* The actual clientSecret lives in the sk-xxx token and is never persisted.
|
|
13
|
-
*/
|
|
14
|
-
class DrizzleClientCredentialsStore {
|
|
15
|
-
constructor(options) {
|
|
16
|
-
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
17
|
-
this.db = options.db;
|
|
18
|
-
this.apiClientCredentials = options.isSqlite ? schema_sqlite_1.apiClientCredentials : schema_pg_1.apiClientCredentials;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Store API Key registration (called when user creates API Key via frontend)
|
|
22
|
-
*/
|
|
23
|
-
async store(options) {
|
|
24
|
-
await this.db
|
|
25
|
-
.insert(this.apiClientCredentials)
|
|
26
|
-
.values({
|
|
27
|
-
clientId: options.clientId,
|
|
28
|
-
webId: options.webId,
|
|
29
|
-
accountId: options.accountId,
|
|
30
|
-
displayName: options.displayName ?? null,
|
|
31
|
-
})
|
|
32
|
-
.onConflictDoUpdate({
|
|
33
|
-
target: this.apiClientCredentials.clientId,
|
|
34
|
-
set: {
|
|
35
|
-
displayName: options.displayName ?? null,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
this.logger.info(`Stored API Key: ${options.clientId}`);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Find by client_id (the "API Key")
|
|
42
|
-
*/
|
|
43
|
-
async findByClientId(clientId) {
|
|
44
|
-
const rows = await this.db
|
|
45
|
-
.select()
|
|
46
|
-
.from(this.apiClientCredentials)
|
|
47
|
-
.where((0, drizzle_orm_1.eq)(this.apiClientCredentials.clientId, clientId))
|
|
48
|
-
.limit(1);
|
|
49
|
-
if (rows.length === 0) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
const row = rows[0];
|
|
53
|
-
return {
|
|
54
|
-
clientId: row.clientId,
|
|
55
|
-
webId: row.webId,
|
|
56
|
-
accountId: row.accountId,
|
|
57
|
-
displayName: row.displayName ?? undefined,
|
|
58
|
-
createdAt: row.createdAt,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* List API Keys for an account
|
|
63
|
-
*/
|
|
64
|
-
async listByAccount(accountId) {
|
|
65
|
-
const rows = await this.db
|
|
66
|
-
.select({
|
|
67
|
-
clientId: this.apiClientCredentials.clientId,
|
|
68
|
-
webId: this.apiClientCredentials.webId,
|
|
69
|
-
displayName: this.apiClientCredentials.displayName,
|
|
70
|
-
createdAt: this.apiClientCredentials.createdAt,
|
|
71
|
-
})
|
|
72
|
-
.from(this.apiClientCredentials)
|
|
73
|
-
.where((0, drizzle_orm_1.eq)(this.apiClientCredentials.accountId, accountId))
|
|
74
|
-
.orderBy((0, drizzle_orm_1.sql) `${this.apiClientCredentials.createdAt} DESC`);
|
|
75
|
-
return rows.map((row) => ({
|
|
76
|
-
clientId: row.clientId,
|
|
77
|
-
webId: row.webId,
|
|
78
|
-
displayName: row.displayName ?? undefined,
|
|
79
|
-
createdAt: row.createdAt,
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Find the most recently created API Key for an account.
|
|
84
|
-
*/
|
|
85
|
-
async findByAccountId(accountId) {
|
|
86
|
-
const rows = await this.db
|
|
87
|
-
.select()
|
|
88
|
-
.from(this.apiClientCredentials)
|
|
89
|
-
.where((0, drizzle_orm_1.eq)(this.apiClientCredentials.accountId, accountId))
|
|
90
|
-
.orderBy((0, drizzle_orm_1.sql) `${this.apiClientCredentials.createdAt} DESC`)
|
|
91
|
-
.limit(1);
|
|
92
|
-
if (rows.length === 0) {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
const row = rows[0];
|
|
96
|
-
return {
|
|
97
|
-
clientId: row.clientId,
|
|
98
|
-
webId: row.webId,
|
|
99
|
-
accountId: row.accountId,
|
|
100
|
-
displayName: row.displayName ?? undefined,
|
|
101
|
-
createdAt: row.createdAt,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Delete an API Key
|
|
106
|
-
*/
|
|
107
|
-
async delete(clientId, accountId) {
|
|
108
|
-
await this.db
|
|
109
|
-
.delete(this.apiClientCredentials)
|
|
110
|
-
.where((0, drizzle_orm_1.eq)(this.apiClientCredentials.clientId, clientId));
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
exports.DrizzleClientCredentialsStore = DrizzleClientCredentialsStore;
|
|
115
|
-
//# sourceMappingURL=DrizzleClientCredentialsStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DrizzleClientCredentialsStore.js","sourceRoot":"","sources":["../../../src/api/store/DrizzleClientCredentialsStore.ts"],"names":[],"mappings":"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","sourcesContent":["import { eq, sql } from 'drizzle-orm';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { IdentityDatabase } from '../../identity/drizzle/db';\nimport { apiClientCredentials as pgApiClientCredentials } from '../../identity/drizzle/schema.pg';\nimport { apiClientCredentials as sqliteApiClientCredentials } from '../../identity/drizzle/schema.sqlite';\nimport type { ClientCredentialsRecord, ClientCredentialsStore } from '../auth/ClientCredentialsAuthenticator';\n\nexport interface DrizzleClientCredentialsStoreOptions {\n db: IdentityDatabase;\n /**\n * Whether using SQLite (default: false for PostgreSQL)\n */\n isSqlite?: boolean;\n}\n\n/**\n * Storage for API Keys (client credentials) using Drizzle ORM\n *\n * Only stores clientId → webId/accountId mapping.\n * The actual clientSecret lives in the sk-xxx token and is never persisted.\n */\nexport class DrizzleClientCredentialsStore implements ClientCredentialsStore {\n private readonly logger = getLoggerFor(this);\n private readonly db: IdentityDatabase;\n private readonly apiClientCredentials: typeof pgApiClientCredentials | typeof sqliteApiClientCredentials;\n\n public constructor(options: DrizzleClientCredentialsStoreOptions) {\n this.db = options.db;\n this.apiClientCredentials = options.isSqlite ? sqliteApiClientCredentials : pgApiClientCredentials;\n }\n\n /**\n * Store API Key registration (called when user creates API Key via frontend)\n */\n public async store(options: {\n clientId: string;\n webId: string;\n accountId: string;\n displayName?: string;\n }): Promise<void> {\n await this.db\n .insert(this.apiClientCredentials)\n .values({\n clientId: options.clientId,\n webId: options.webId,\n accountId: options.accountId,\n displayName: options.displayName ?? null,\n })\n .onConflictDoUpdate({\n target: this.apiClientCredentials.clientId,\n set: {\n displayName: options.displayName ?? null,\n },\n });\n\n this.logger.info(`Stored API Key: ${options.clientId}`);\n }\n\n /**\n * Find by client_id (the \"API Key\")\n */\n public async findByClientId(clientId: string): Promise<ClientCredentialsRecord | undefined> {\n const rows = await this.db\n .select()\n .from(this.apiClientCredentials)\n .where(eq(this.apiClientCredentials.clientId, clientId))\n .limit(1);\n\n if (rows.length === 0) {\n return undefined;\n }\n\n const row = rows[0];\n return {\n clientId: row.clientId,\n webId: row.webId,\n accountId: row.accountId,\n displayName: row.displayName ?? undefined,\n createdAt: row.createdAt,\n };\n }\n\n /**\n * List API Keys for an account\n */\n public async listByAccount(accountId: string): Promise<Array<{\n clientId: string;\n webId: string;\n displayName?: string;\n createdAt: Date;\n }>> {\n const rows = await this.db\n .select({\n clientId: this.apiClientCredentials.clientId,\n webId: this.apiClientCredentials.webId,\n displayName: this.apiClientCredentials.displayName,\n createdAt: this.apiClientCredentials.createdAt,\n })\n .from(this.apiClientCredentials)\n .where(eq(this.apiClientCredentials.accountId, accountId))\n .orderBy(sql`${this.apiClientCredentials.createdAt} DESC`);\n\n return rows.map((row: typeof rows[number]) => ({\n clientId: row.clientId,\n webId: row.webId,\n displayName: row.displayName ?? undefined,\n createdAt: row.createdAt,\n }));\n }\n\n /**\n * Find the most recently created API Key for an account.\n */\n public async findByAccountId(accountId: string): Promise<ClientCredentialsRecord | undefined> {\n const rows = await this.db\n .select()\n .from(this.apiClientCredentials)\n .where(eq(this.apiClientCredentials.accountId, accountId))\n .orderBy(sql`${this.apiClientCredentials.createdAt} DESC`)\n .limit(1);\n\n if (rows.length === 0) {\n return undefined;\n }\n\n const row = rows[0];\n return {\n clientId: row.clientId,\n webId: row.webId,\n accountId: row.accountId,\n displayName: row.displayName ?? undefined,\n createdAt: row.createdAt,\n };\n }\n\n /**\n * Delete an API Key\n */\n public async delete(clientId: string, accountId?: string): Promise<boolean> {\n await this.db\n .delete(this.apiClientCredentials)\n .where(eq(this.apiClientCredentials.clientId, clientId));\n return true;\n }\n}\n"]}
|