@undefineds.co/xpod 0.2.8 → 0.2.11
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/NodeTokenAuthenticator.js +10 -12
- package/dist/api/auth/NodeTokenAuthenticator.js.map +1 -1
- package/dist/api/container/index.js +3 -2
- package/dist/api/container/index.js.map +1 -1
- package/dist/identity/drizzle/DdnsRepository.d.ts +1 -0
- package/dist/identity/drizzle/DdnsRepository.js +35 -57
- package/dist/identity/drizzle/DdnsRepository.js.map +1 -1
- package/dist/identity/drizzle/db.js +23 -0
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/subdomain/SubdomainClient.js +1 -2
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/package.json +1 -1
- package/static/app/assets/index.css +1 -1
- package/static/app/assets/main.js +16 -16
|
@@ -11,14 +11,13 @@ class NodeTokenAuthenticator {
|
|
|
11
11
|
const auth = request.headers.authorization;
|
|
12
12
|
// 支持两种格式:
|
|
13
13
|
// 1. XpodNode nodeId:token
|
|
14
|
-
// 2. Bearer
|
|
14
|
+
// 2. Bearer <raw-node-token> (带 X-Node-Id 头)
|
|
15
|
+
// 3. Bearer username:secret / base64(username:secret) (兼容旧格式,带 X-Node-Id 头)
|
|
15
16
|
if (auth?.startsWith('XpodNode ')) {
|
|
16
17
|
return true;
|
|
17
18
|
}
|
|
18
19
|
if (auth?.startsWith('Bearer ') && request.headers['x-node-id']) {
|
|
19
|
-
|
|
20
|
-
// Node Token 包含 ':',不是 JWT
|
|
21
|
-
return token.includes(':') || this.isBase64NodeToken(token);
|
|
20
|
+
return true;
|
|
22
21
|
}
|
|
23
22
|
return false;
|
|
24
23
|
}
|
|
@@ -37,16 +36,15 @@ class NodeTokenAuthenticator {
|
|
|
37
36
|
token = credentials.slice(colonIndex + 1);
|
|
38
37
|
}
|
|
39
38
|
else {
|
|
40
|
-
// 格式: Bearer
|
|
39
|
+
// 格式: Bearer <raw-node-token> (带 X-Node-Id 头)
|
|
40
|
+
// 兼容旧的 username:secret / base64(username:secret) 形式。
|
|
41
41
|
nodeId = request.headers['x-node-id'];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!parsed) {
|
|
46
|
-
return { success: false, error: 'Invalid node token format' };
|
|
42
|
+
const bearerToken = auth.slice(7).trim();
|
|
43
|
+
if (!bearerToken) {
|
|
44
|
+
return { success: false, error: 'Empty node token' };
|
|
47
45
|
}
|
|
48
|
-
|
|
49
|
-
token = parsed
|
|
46
|
+
const parsed = this.parseNodeToken(bearerToken);
|
|
47
|
+
token = parsed?.token ?? bearerToken;
|
|
50
48
|
}
|
|
51
49
|
try {
|
|
52
50
|
const secret = await this.repo.getNodeSecret(nodeId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeTokenAuthenticator.js","sourceRoot":"","sources":["../../../src/api/auth/NodeTokenAuthenticator.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AAQrD,MAAa,sBAAsB;IAIjC,YAAmB,OAAsC;QAHxC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAI3C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAEM,eAAe,CAAC,OAAwB;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC3C,UAAU;QACV,2BAA2B;QAC3B,
|
|
1
|
+
{"version":3,"file":"NodeTokenAuthenticator.js","sourceRoot":"","sources":["../../../src/api/auth/NodeTokenAuthenticator.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AAQrD,MAAa,sBAAsB;IAIjC,YAAmB,OAAsC;QAHxC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAI3C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAEM,eAAe,CAAC,OAAwB;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC3C,UAAU;QACV,2BAA2B;QAC3B,6CAA6C;QAC7C,4EAA4E;QAC5E,IAAI,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEM,KAAK,CAAC,YAAY,CAAC,OAAwB;QAChD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,aAAc,CAAC;QAE5C,IAAI,MAAc,CAAC;QACnB,IAAI,KAAa,CAAC;QAElB,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,4BAA4B;YAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;gBACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,4DAA4D,EAAE,CAAC;YACjG,CAAC;YACD,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YAC1C,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,8CAA8C;YAC9C,qDAAqD;YACrD,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAW,CAAC;YAChD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;YACvD,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YAChD,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,WAAW,CAAC;QACvC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,iBAAiB;gBACjB,8BAA8B;gBAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,6BAA6B,CAAC,CAAC;gBAC1E,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP,IAAI,EAAE,MAAM;wBACZ,MAAM;qBACP;iBACF,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC1E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;YACzD,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,MAAM,EAAE,CAAC,CAAC;YAExD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,MAAM;oBACN,SAAS,EAAG,MAAc,CAAC,SAAS;iBACrC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC;YAC1D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;QACpE,CAAC;IACH,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,KAAa;QAClC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,QAAQ,EAAE,GAAG,WAAW,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;gBACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;YACrC,CAAC;QACH,CAAC;QAED,eAAe;QACf,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,CAAC,QAAQ,EAAE,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACtD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACrC,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;oBACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,KAAa;QACrC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF;AA7HD,wDA6HC","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { Authenticator, AuthResult } from './Authenticator';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\n\nexport interface NodeTokenAuthenticatorOptions {\n repository: EdgeNodeRepository;\n}\n\nexport class NodeTokenAuthenticator implements Authenticator {\n private readonly logger = getLoggerFor(this);\n private readonly repo: EdgeNodeRepository;\n\n public constructor(options: NodeTokenAuthenticatorOptions) {\n this.repo = options.repository;\n }\n\n public canAuthenticate(request: IncomingMessage): boolean {\n const auth = request.headers.authorization;\n // 支持两种格式:\n // 1. XpodNode nodeId:token\n // 2. Bearer <raw-node-token> (带 X-Node-Id 头)\n // 3. Bearer username:secret / base64(username:secret) (兼容旧格式,带 X-Node-Id 头)\n if (auth?.startsWith('XpodNode ')) {\n return true;\n }\n if (auth?.startsWith('Bearer ') && request.headers['x-node-id']) {\n return true;\n }\n return false;\n }\n\n public async authenticate(request: IncomingMessage): Promise<AuthResult> {\n const auth = request.headers.authorization!;\n\n let nodeId: string;\n let token: string;\n\n if (auth.startsWith('XpodNode ')) {\n // 格式: XpodNode nodeId:token\n const credentials = auth.slice(9).trim();\n const colonIndex = credentials.indexOf(':');\n if (colonIndex <= 0) {\n return { success: false, error: 'Invalid XpodNode credentials format. Expected nodeId:token' };\n }\n nodeId = credentials.slice(0, colonIndex);\n token = credentials.slice(colonIndex + 1);\n } else {\n // 格式: Bearer <raw-node-token> (带 X-Node-Id 头)\n // 兼容旧的 username:secret / base64(username:secret) 形式。\n nodeId = request.headers['x-node-id'] as string;\n const bearerToken = auth.slice(7).trim();\n if (!bearerToken) {\n return { success: false, error: 'Empty node token' };\n }\n const parsed = this.parseNodeToken(bearerToken);\n token = parsed?.token ?? bearerToken;\n }\n\n try {\n const secret = await this.repo.getNodeSecret(nodeId);\n if (!secret) {\n // 节点不存在,可能是新节点注册\n // 对于 DDNS 分配等操作,允许通过(由业务逻辑处理)\n this.logger.debug(`Node not found: ${nodeId}, allowing for registration`);\n return {\n success: true,\n context: {\n type: 'node',\n nodeId,\n },\n };\n }\n\n if (!secret.tokenHash || !this.repo.matchesToken(secret.tokenHash, token)) {\n return { success: false, error: 'Invalid node token' };\n }\n\n this.logger.debug(`Authenticated edge node: ${nodeId}`);\n\n return {\n success: true,\n context: {\n type: 'node',\n nodeId,\n accountId: (secret as any).accountId,\n },\n };\n } catch (error) {\n this.logger.error(`Node authentication failed: ${error}`);\n return { success: false, error: 'Internal authentication error' };\n }\n }\n\n /**\n * 解析 Node Token (username:secret 或 base64)\n */\n private parseNodeToken(token: string): { username: string; token: string } | undefined {\n if (token.includes(':')) {\n const [username, ...secretParts] = token.split(':');\n const secret = secretParts.join(':');\n if (username && secret) {\n return { username, token: secret };\n }\n }\n\n // 尝试 base64 解码\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf8');\n if (decoded.includes(':')) {\n const [username, ...secretParts] = decoded.split(':');\n const secret = secretParts.join(':');\n if (username && secret) {\n return { username, token: secret };\n }\n }\n } catch {\n // ignore\n }\n\n return undefined;\n }\n\n /**\n * 检查是否是 base64 编码的 Node Token\n */\n private isBase64NodeToken(token: string): boolean {\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf8');\n return decoded.includes(':');\n } catch {\n return false;\n }\n }\n}\n"]}
|
|
@@ -39,6 +39,7 @@ const common_1 = require("./common");
|
|
|
39
39
|
const cloud_1 = require("./cloud");
|
|
40
40
|
const local_1 = require("./local");
|
|
41
41
|
const business_token_1 = require("./business-token");
|
|
42
|
+
const OFFICIAL_CLOUD_IDENTITY_ORIGIN = 'https://id.undefineds.co';
|
|
42
43
|
function ensureTrailingSlash(url) {
|
|
43
44
|
return url.endsWith('/') ? url : `${url}/`;
|
|
44
45
|
}
|
|
@@ -108,8 +109,8 @@ function loadConfigFromEnv() {
|
|
|
108
109
|
nodeToken: process.env.XPOD_NODE_TOKEN,
|
|
109
110
|
// OIDC Issuer (Local 托管式使用 Cloud IdP)
|
|
110
111
|
// 如果配置了 XPOD_NODE_TOKEN,默认使用 Cloud IdP
|
|
111
|
-
oidcIssuer: process.env.XPOD_OIDC_ISSUER ?? process.env.CSS_OIDC_ISSUER ?? (process.env.XPOD_NODE_TOKEN
|
|
112
|
-
?
|
|
112
|
+
oidcIssuer: process.env.XPOD_OIDC_ISSUER ?? process.env.CSS_OIDC_ISSUER ?? process.env.CSS_IDP_URL ?? (process.env.XPOD_NODE_TOKEN
|
|
113
|
+
? OFFICIAL_CLOUD_IDENTITY_ORIGIN
|
|
113
114
|
: undefined),
|
|
114
115
|
// 隧道配置
|
|
115
116
|
cloudflareTunnelToken: process.env.CLOUDFLARE_TUNNEL_TOKEN,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/container/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/container/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Container 入口\n *\n * 使用 Awilix 进行依赖注入,根据 edition 注册不同服务\n */\n\nimport { createContainer, asValue, InjectionMode, type AwilixContainer } from 'awilix';\nimport { randomUUID, createHash } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\nimport { registerCommonServices } from './common';\nimport { registerCloudServices } from './cloud';\nimport { registerLocalServices } from './local';\nimport { registerBusinessToken } from './business-token';\n\nexport type { ApiContainerCradle, ApiContainerConfig } from './types';\n\nconst OFFICIAL_CLOUD_IDENTITY_ORIGIN = 'https://id.undefineds.co';\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\nfunction resolveCssTokenEndpoint(): string {\n if (process.env.CSS_TOKEN_ENDPOINT) {\n return process.env.CSS_TOKEN_ENDPOINT;\n }\n\n if (process.env.CSS_BASE_URL) {\n return `${ensureTrailingSlash(process.env.CSS_BASE_URL)}.oidc/token`;\n }\n\n return 'http://localhost:3000/.oidc/token';\n}\n\n/**\n * 创建 API 容器\n */\nexport function createApiContainer(config: ApiContainerConfig): AwilixContainer<ApiContainerCradle> {\n const container = createContainer<ApiContainerCradle>({\n injectionMode: InjectionMode.PROXY,\n strict: true,\n });\n\n // 注册配置\n container.register({\n config: asValue(config),\n });\n\n // 注册共享服务\n registerCommonServices(container);\n\n // 根据 edition 注册专属服务\n if (config.edition === 'cloud') {\n registerCloudServices(container);\n } else {\n registerLocalServices(container);\n }\n\n // 注册 Business Token (如果配置了 XPOD_BUSINESS_TOKEN)\n registerBusinessToken(container);\n\n return container;\n}\n\n/**\n * 从环境变量读取配置\n */\nexport function loadConfigFromEnv(): ApiContainerConfig {\n const edition = (process.env.XPOD_EDITION ?? 'local') as 'cloud' | 'local';\n\n // Port auto-increment: API_PORT = CSS_PORT + 1 if not explicitly set\n const cssPort = parseInt(process.env.CSS_PORT ?? '3000', 10);\n const apiPort = process.env.API_PORT\n ? parseInt(process.env.API_PORT, 10)\n : cssPort + 1;\n\n return {\n edition,\n port: apiPort,\n host: process.env.API_HOST ?? '0.0.0.0',\n socketPath: process.env.API_SOCKET_PATH,\n databaseUrl: process.env.CSS_IDENTITY_DB_URL ?? process.env.DATABASE_URL ?? '',\n corsOrigins: process.env.CORS_ORIGINS?.split(',').map(s => s.trim()) ?? ['*'],\n cssTokenEndpoint: resolveCssTokenEndpoint(),\n\n // 子域名配置 (cloud 模式)\n subdomain: {\n baseStorageDomain: process.env.CSS_BASE_STORAGE_DOMAIN,\n cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,\n cloudflareApiToken: process.env.CLOUDFLARE_API_TOKEN,\n tencentDnsSecretId: process.env.TENCENT_DNS_SECRET_ID,\n tencentDnsSecretKey: process.env.TENCENT_DNS_SECRET_KEY,\n },\n\n // Local 托管式:连接 Cloud\n cloudApiEndpoint: process.env.XPOD_CLOUD_API_ENDPOINT,\n nodeId: loadOrGenerateDeviceId(process.env.XPOD_NODE_ID),\n nodeToken: process.env.XPOD_NODE_TOKEN,\n\n // OIDC Issuer (Local 托管式使用 Cloud IdP)\n // 如果配置了 XPOD_NODE_TOKEN,默认使用 Cloud IdP\n oidcIssuer: process.env.XPOD_OIDC_ISSUER ?? process.env.CSS_OIDC_ISSUER ?? process.env.CSS_IDP_URL ?? (\n process.env.XPOD_NODE_TOKEN\n ? OFFICIAL_CLOUD_IDENTITY_ORIGIN\n : undefined\n ),\n\n // 隧道配置\n cloudflareTunnelToken: process.env.CLOUDFLARE_TUNNEL_TOKEN,\n // Prefer SAKURA_TUNNEL_TOKEN; keep SAKURA_TOKEN for backward compatibility.\n sakuraTunnelToken: process.env.SAKURA_TUNNEL_TOKEN ?? process.env.SAKURA_TOKEN,\n\n // Edge 节点管理 (cloud 模式)\n edgeNodesEnabled: process.env.XPOD_EDGE_NODES_ENABLED === 'true',\n };\n}\n\n/**\n * 获取设备首个非内部网卡的 MAC 地址。\n * 返回小写冒号分隔格式,如 \"aa:bb:cc:dd:ee:ff\"。\n * 容器/虚拟机中可能拿不到稳定 MAC,此时返回 undefined。\n */\nfunction getFirstMacAddress(): string | undefined {\n const interfaces = os.networkInterfaces();\n for (const name of Object.keys(interfaces)) {\n for (const iface of interfaces[name] ?? []) {\n if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {\n return iface.mac.toLowerCase();\n }\n }\n }\n return undefined;\n}\n\n/**\n * 读取或生成设备 ID(持久化到 data/.device-id)。\n *\n * 优先级:\n * 1. 环境变量 XPOD_NODE_ID\n * 2. 已持久化的 data/.device-id\n * 3. 基于 MAC 地址的 SHA-256 哈希(截取前 32 位 hex)\n * 4. 随机 UUID(容器/虚拟机无稳定 MAC 时兜底)\n *\n * 生成后写入 data/.device-id,后续启动直接读取,保证同一设备 ID 稳定。\n */\nfunction loadOrGenerateDeviceId(envNodeId?: string): string | undefined {\n if (envNodeId) {\n return envNodeId;\n }\n\n const rootDir = process.env.CSS_ROOT_FILE_PATH || './data';\n const deviceIdPath = path.join(rootDir, '.device-id');\n\n // 尝试从文件读取\n try {\n if (fs.existsSync(deviceIdPath)) {\n const content = fs.readFileSync(deviceIdPath, 'utf-8').trim();\n if (content) {\n return content;\n }\n }\n } catch {\n // 读取失败,继续生成\n }\n\n // 优先用 MAC 哈希,拿不到则 UUID 兜底\n const mac = getFirstMacAddress();\n const deviceId = mac\n ? createHash('sha256').update(mac).digest('hex').slice(0, 32)\n : randomUUID();\n\n try {\n if (!fs.existsSync(rootDir)) {\n fs.mkdirSync(rootDir, { recursive: true });\n }\n fs.writeFileSync(deviceIdPath, deviceId, 'utf-8');\n } catch {\n // 写入失败不阻塞启动\n }\n\n return deviceId;\n}\n"]}
|
|
@@ -4,38 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 管理 DDNS 域名池和记录
|
|
6
6
|
*/
|
|
7
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
-
if (k2 === undefined) k2 = k;
|
|
9
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
-
}
|
|
13
|
-
Object.defineProperty(o, k2, desc);
|
|
14
|
-
}) : (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
o[k2] = m[k];
|
|
17
|
-
}));
|
|
18
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
-
}) : function(o, v) {
|
|
21
|
-
o["default"] = v;
|
|
22
|
-
});
|
|
23
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
24
|
-
if (mod && mod.__esModule) return mod;
|
|
25
|
-
var result = {};
|
|
26
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
27
|
-
__setModuleDefault(result, mod);
|
|
28
|
-
return result;
|
|
29
|
-
};
|
|
30
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
8
|
exports.DdnsRepository = void 0;
|
|
32
9
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
33
10
|
const global_logger_factory_1 = require("global-logger-factory");
|
|
34
|
-
const
|
|
11
|
+
const db_1 = require("./db");
|
|
35
12
|
const logger = (0, global_logger_factory_1.getLoggerFor)('DdnsRepository');
|
|
36
13
|
class DdnsRepository {
|
|
37
14
|
constructor(db) {
|
|
38
15
|
this.db = db;
|
|
16
|
+
this.schema = (0, db_1.getSchema)(db);
|
|
39
17
|
}
|
|
40
18
|
// ==================== Domain Pool ====================
|
|
41
19
|
/**
|
|
@@ -43,12 +21,12 @@ class DdnsRepository {
|
|
|
43
21
|
*/
|
|
44
22
|
async addDomain(domain, provider, zoneId) {
|
|
45
23
|
const now = new Date();
|
|
46
|
-
await this.db.insert(
|
|
24
|
+
await this.db.insert(this.schema.ddnsDomains).values({
|
|
47
25
|
domain,
|
|
48
26
|
status: 'active',
|
|
49
27
|
provider,
|
|
50
28
|
zoneId,
|
|
51
|
-
createdAt: now,
|
|
29
|
+
createdAt: (0, db_1.toDbTimestamp)(this.db, now),
|
|
52
30
|
});
|
|
53
31
|
logger.info(`Added domain to pool: ${domain}`);
|
|
54
32
|
return {
|
|
@@ -65,14 +43,14 @@ class DdnsRepository {
|
|
|
65
43
|
async getActiveDomains() {
|
|
66
44
|
const results = await this.db
|
|
67
45
|
.select()
|
|
68
|
-
.from(
|
|
69
|
-
.where((0, drizzle_orm_1.eq)(
|
|
46
|
+
.from(this.schema.ddnsDomains)
|
|
47
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsDomains.status, 'active'));
|
|
70
48
|
return results.map((row) => ({
|
|
71
49
|
domain: row.domain,
|
|
72
50
|
status: row.status,
|
|
73
51
|
provider: row.provider ?? undefined,
|
|
74
52
|
zoneId: row.zoneId ?? undefined,
|
|
75
|
-
createdAt: row.createdAt,
|
|
53
|
+
createdAt: (0, db_1.fromDbTimestamp)(row.createdAt) ?? new Date(0),
|
|
76
54
|
}));
|
|
77
55
|
}
|
|
78
56
|
/**
|
|
@@ -80,9 +58,9 @@ class DdnsRepository {
|
|
|
80
58
|
*/
|
|
81
59
|
async suspendDomain(domain) {
|
|
82
60
|
await this.db
|
|
83
|
-
.update(
|
|
61
|
+
.update(this.schema.ddnsDomains)
|
|
84
62
|
.set({ status: 'suspended' })
|
|
85
|
-
.where((0, drizzle_orm_1.eq)(
|
|
63
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsDomains.domain, domain));
|
|
86
64
|
logger.info(`Suspended domain: ${domain}`);
|
|
87
65
|
}
|
|
88
66
|
// ==================== DDNS Records ====================
|
|
@@ -98,7 +76,7 @@ class DdnsRepository {
|
|
|
98
76
|
}
|
|
99
77
|
const now = new Date();
|
|
100
78
|
const recordType = ipv6Address ? 'AAAA' : 'A';
|
|
101
|
-
await this.db.insert(
|
|
79
|
+
await this.db.insert(this.schema.ddnsRecords).values({
|
|
102
80
|
subdomain,
|
|
103
81
|
domain,
|
|
104
82
|
ipAddress,
|
|
@@ -108,8 +86,8 @@ class DdnsRepository {
|
|
|
108
86
|
username,
|
|
109
87
|
status: 'active',
|
|
110
88
|
ttl: 60,
|
|
111
|
-
createdAt: now,
|
|
112
|
-
updatedAt: now,
|
|
89
|
+
createdAt: (0, db_1.toDbTimestamp)(this.db, now),
|
|
90
|
+
updatedAt: (0, db_1.toDbTimestamp)(this.db, now),
|
|
113
91
|
});
|
|
114
92
|
logger.info(`Allocated subdomain: ${subdomain}.${domain}`);
|
|
115
93
|
return {
|
|
@@ -132,8 +110,8 @@ class DdnsRepository {
|
|
|
132
110
|
async getRecord(subdomain) {
|
|
133
111
|
const results = await this.db
|
|
134
112
|
.select()
|
|
135
|
-
.from(
|
|
136
|
-
.where((0, drizzle_orm_1.eq)(
|
|
113
|
+
.from(this.schema.ddnsRecords)
|
|
114
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.subdomain, subdomain))
|
|
137
115
|
.limit(1);
|
|
138
116
|
if (results.length === 0) {
|
|
139
117
|
return null;
|
|
@@ -150,8 +128,8 @@ class DdnsRepository {
|
|
|
150
128
|
status: row.status ?? 'active',
|
|
151
129
|
bannedReason: row.bannedReason ?? undefined,
|
|
152
130
|
ttl: row.ttl ?? 60,
|
|
153
|
-
createdAt: row.createdAt,
|
|
154
|
-
updatedAt: row.updatedAt,
|
|
131
|
+
createdAt: (0, db_1.fromDbTimestamp)(row.createdAt) ?? new Date(0),
|
|
132
|
+
updatedAt: (0, db_1.fromDbTimestamp)(row.updatedAt) ?? new Date(0),
|
|
155
133
|
};
|
|
156
134
|
}
|
|
157
135
|
/**
|
|
@@ -166,7 +144,7 @@ class DdnsRepository {
|
|
|
166
144
|
throw new Error(`Subdomain ${subdomain} is banned: ${existing.bannedReason}`);
|
|
167
145
|
}
|
|
168
146
|
const now = new Date();
|
|
169
|
-
const updates = { updatedAt: now };
|
|
147
|
+
const updates = { updatedAt: (0, db_1.toDbTimestamp)(this.db, now) };
|
|
170
148
|
if (input.ipAddress !== undefined) {
|
|
171
149
|
updates.ipAddress = input.ipAddress;
|
|
172
150
|
updates.recordType = 'A';
|
|
@@ -178,9 +156,9 @@ class DdnsRepository {
|
|
|
178
156
|
}
|
|
179
157
|
}
|
|
180
158
|
await this.db
|
|
181
|
-
.update(
|
|
159
|
+
.update(this.schema.ddnsRecords)
|
|
182
160
|
.set(updates)
|
|
183
|
-
.where((0, drizzle_orm_1.eq)(
|
|
161
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.subdomain, subdomain));
|
|
184
162
|
logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);
|
|
185
163
|
return {
|
|
186
164
|
...existing,
|
|
@@ -195,13 +173,13 @@ class DdnsRepository {
|
|
|
195
173
|
*/
|
|
196
174
|
async banSubdomain(subdomain, reason) {
|
|
197
175
|
await this.db
|
|
198
|
-
.update(
|
|
176
|
+
.update(this.schema.ddnsRecords)
|
|
199
177
|
.set({
|
|
200
178
|
status: 'banned',
|
|
201
179
|
bannedReason: reason,
|
|
202
|
-
updatedAt: new Date(),
|
|
180
|
+
updatedAt: (0, db_1.toDbTimestamp)(this.db, new Date()),
|
|
203
181
|
})
|
|
204
|
-
.where((0, drizzle_orm_1.eq)(
|
|
182
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.subdomain, subdomain));
|
|
205
183
|
logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);
|
|
206
184
|
}
|
|
207
185
|
/**
|
|
@@ -209,13 +187,13 @@ class DdnsRepository {
|
|
|
209
187
|
*/
|
|
210
188
|
async unbanSubdomain(subdomain) {
|
|
211
189
|
await this.db
|
|
212
|
-
.update(
|
|
190
|
+
.update(this.schema.ddnsRecords)
|
|
213
191
|
.set({
|
|
214
192
|
status: 'active',
|
|
215
193
|
bannedReason: null,
|
|
216
|
-
updatedAt: new Date(),
|
|
194
|
+
updatedAt: (0, db_1.toDbTimestamp)(this.db, new Date()),
|
|
217
195
|
})
|
|
218
|
-
.where((0, drizzle_orm_1.eq)(
|
|
196
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.subdomain, subdomain));
|
|
219
197
|
logger.info(`Unbanned subdomain: ${subdomain}`);
|
|
220
198
|
}
|
|
221
199
|
/**
|
|
@@ -223,8 +201,8 @@ class DdnsRepository {
|
|
|
223
201
|
*/
|
|
224
202
|
async releaseSubdomain(subdomain) {
|
|
225
203
|
await this.db
|
|
226
|
-
.delete(
|
|
227
|
-
.where((0, drizzle_orm_1.eq)(
|
|
204
|
+
.delete(this.schema.ddnsRecords)
|
|
205
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.subdomain, subdomain));
|
|
228
206
|
logger.info(`Released subdomain: ${subdomain}`);
|
|
229
207
|
return true;
|
|
230
208
|
}
|
|
@@ -234,8 +212,8 @@ class DdnsRepository {
|
|
|
234
212
|
async getRecordsByUsername(username) {
|
|
235
213
|
const results = await this.db
|
|
236
214
|
.select()
|
|
237
|
-
.from(
|
|
238
|
-
.where((0, drizzle_orm_1.eq)(
|
|
215
|
+
.from(this.schema.ddnsRecords)
|
|
216
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.username, username));
|
|
239
217
|
return results.map((row) => ({
|
|
240
218
|
subdomain: row.subdomain,
|
|
241
219
|
domain: row.domain,
|
|
@@ -247,8 +225,8 @@ class DdnsRepository {
|
|
|
247
225
|
status: row.status ?? 'active',
|
|
248
226
|
bannedReason: row.bannedReason ?? undefined,
|
|
249
227
|
ttl: row.ttl ?? 60,
|
|
250
|
-
createdAt: row.createdAt,
|
|
251
|
-
updatedAt: row.updatedAt,
|
|
228
|
+
createdAt: (0, db_1.fromDbTimestamp)(row.createdAt) ?? new Date(0),
|
|
229
|
+
updatedAt: (0, db_1.fromDbTimestamp)(row.updatedAt) ?? new Date(0),
|
|
252
230
|
}));
|
|
253
231
|
}
|
|
254
232
|
/**
|
|
@@ -257,8 +235,8 @@ class DdnsRepository {
|
|
|
257
235
|
async getRecordByNodeId(nodeId) {
|
|
258
236
|
const results = await this.db
|
|
259
237
|
.select()
|
|
260
|
-
.from(
|
|
261
|
-
.where((0, drizzle_orm_1.eq)(
|
|
238
|
+
.from(this.schema.ddnsRecords)
|
|
239
|
+
.where((0, drizzle_orm_1.eq)(this.schema.ddnsRecords.nodeId, nodeId))
|
|
262
240
|
.limit(1);
|
|
263
241
|
if (results.length === 0) {
|
|
264
242
|
return null;
|
|
@@ -275,8 +253,8 @@ class DdnsRepository {
|
|
|
275
253
|
status: row.status ?? 'active',
|
|
276
254
|
bannedReason: row.bannedReason ?? undefined,
|
|
277
255
|
ttl: row.ttl ?? 60,
|
|
278
|
-
createdAt: row.createdAt,
|
|
279
|
-
updatedAt: row.updatedAt,
|
|
256
|
+
createdAt: (0, db_1.fromDbTimestamp)(row.createdAt) ?? new Date(0),
|
|
257
|
+
updatedAt: (0, db_1.fromDbTimestamp)(row.updatedAt) ?? new Date(0),
|
|
280
258
|
};
|
|
281
259
|
}
|
|
282
260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DdnsRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DdnsRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Repository\n *\n * 管理 DDNS 域名池和记录\n */\n\nimport { eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\nimport * as pgSchema from './schema.pg';\n\nconst logger = getLoggerFor('DdnsRepository');\n\nexport interface DdnsDomain {\n domain: string;\n status: 'active' | 'suspended';\n provider?: string;\n zoneId?: string;\n createdAt: Date;\n}\n\nexport interface DdnsRecord {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: 'A' | 'AAAA';\n nodeId?: string;\n username?: string;\n status: 'active' | 'banned';\n bannedReason?: string;\n ttl: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateDdnsRecordInput {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n nodeId?: string;\n username?: string;\n}\n\nexport interface UpdateDdnsRecordInput {\n ipAddress?: string;\n ipv6Address?: string;\n}\n\nexport class DdnsRepository {\n constructor(private readonly db: IdentityDatabase) {}\n\n // ==================== Domain Pool ====================\n\n /**\n * 添加域名到池中\n */\n async addDomain(\n domain: string,\n provider?: string,\n zoneId?: string,\n ): Promise<DdnsDomain> {\n const now = new Date();\n\n await this.db.insert(pgSchema.ddnsDomains).values({\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: now,\n });\n\n logger.info(`Added domain to pool: ${domain}`);\n\n return {\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: now,\n };\n }\n\n /**\n * 获取所有活跃的域名\n */\n async getActiveDomains(): Promise<DdnsDomain[]> {\n const results = await this.db\n .select()\n .from(pgSchema.ddnsDomains)\n .where(eq(pgSchema.ddnsDomains.status, 'active'));\n\n return results.map((row: typeof results[0]) => ({\n domain: row.domain,\n status: row.status as 'active' | 'suspended',\n provider: row.provider ?? undefined,\n zoneId: row.zoneId ?? undefined,\n createdAt: row.createdAt,\n }));\n }\n\n /**\n * 暂停域名\n */\n async suspendDomain(domain: string): Promise<void> {\n await this.db\n .update(pgSchema.ddnsDomains)\n .set({ status: 'suspended' })\n .where(eq(pgSchema.ddnsDomains.domain, domain));\n\n logger.info(`Suspended domain: ${domain}`);\n }\n\n // ==================== DDNS Records ====================\n\n /**\n * 分配子域名\n */\n async allocateSubdomain(input: CreateDdnsRecordInput): Promise<DdnsRecord> {\n const { subdomain, domain, ipAddress, ipv6Address, nodeId, username } = input;\n\n // 检查是否已存在\n const existing = await this.getRecord(subdomain);\n if (existing) {\n throw new Error(`Subdomain ${subdomain} already allocated`);\n }\n\n const now = new Date();\n const recordType = ipv6Address ? 'AAAA' : 'A';\n\n await this.db.insert(pgSchema.ddnsRecords).values({\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: now,\n updatedAt: now,\n });\n\n logger.info(`Allocated subdomain: ${subdomain}.${domain}`);\n\n return {\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getRecord(subdomain: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(pgSchema.ddnsRecords)\n .where(eq(pgSchema.ddnsRecords.subdomain, subdomain))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n }\n\n /**\n * 更新 DDNS 记录的 IP 地址\n */\n async updateRecordIp(\n subdomain: string,\n input: UpdateDdnsRecordInput,\n ): Promise<DdnsRecord | null> {\n const existing = await this.getRecord(subdomain);\n if (!existing) {\n return null;\n }\n\n if (existing.status === 'banned') {\n throw new Error(`Subdomain ${subdomain} is banned: ${existing.bannedReason}`);\n }\n\n const now = new Date();\n const updates: Record<string, unknown> = { updatedAt: now };\n\n if (input.ipAddress !== undefined) {\n updates.ipAddress = input.ipAddress;\n updates.recordType = 'A';\n }\n if (input.ipv6Address !== undefined) {\n updates.ipv6Address = input.ipv6Address;\n if (!input.ipAddress) {\n updates.recordType = 'AAAA';\n }\n }\n\n await this.db\n .update(pgSchema.ddnsRecords)\n .set(updates)\n .where(eq(pgSchema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);\n\n return {\n ...existing,\n ipAddress: input.ipAddress ?? existing.ipAddress,\n ipv6Address: input.ipv6Address ?? existing.ipv6Address,\n recordType: (updates.recordType as 'A' | 'AAAA') ?? existing.recordType,\n updatedAt: now,\n };\n }\n\n /**\n * 封禁子域名\n */\n async banSubdomain(subdomain: string, reason: string): Promise<void> {\n await this.db\n .update(pgSchema.ddnsRecords)\n .set({\n status: 'banned',\n bannedReason: reason,\n updatedAt: new Date(),\n })\n .where(eq(pgSchema.ddnsRecords.subdomain, subdomain));\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n }\n\n /**\n * 解封子域名\n */\n async unbanSubdomain(subdomain: string): Promise<void> {\n await this.db\n .update(pgSchema.ddnsRecords)\n .set({\n status: 'active',\n bannedReason: null,\n updatedAt: new Date(),\n })\n .where(eq(pgSchema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Unbanned subdomain: ${subdomain}`);\n }\n\n /**\n * 释放子域名\n */\n async releaseSubdomain(subdomain: string): Promise<boolean> {\n await this.db\n .delete(pgSchema.ddnsRecords)\n .where(eq(pgSchema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Released subdomain: ${subdomain}`);\n return true;\n }\n\n /**\n * 获取用户的所有子域名\n */\n async getRecordsByUsername(username: string): Promise<DdnsRecord[]> {\n const results = await this.db\n .select()\n .from(pgSchema.ddnsRecords)\n .where(eq(pgSchema.ddnsRecords.username, username));\n\n return results.map((row: typeof results[0]) => ({\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n\n /**\n * 获取节点的子域名\n */\n async getRecordByNodeId(nodeId: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(pgSchema.ddnsRecords)\n .where(eq(pgSchema.ddnsRecords.nodeId, nodeId))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DdnsRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DdnsRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Repository\n *\n * 管理 DDNS 域名池和记录\n */\n\nimport { eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { getSchema, toDbTimestamp, fromDbTimestamp } from './db';\n\nconst logger = getLoggerFor('DdnsRepository');\n\nexport interface DdnsDomain {\n domain: string;\n status: 'active' | 'suspended';\n provider?: string;\n zoneId?: string;\n createdAt: Date;\n}\n\nexport interface DdnsRecord {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: 'A' | 'AAAA';\n nodeId?: string;\n username?: string;\n status: 'active' | 'banned';\n bannedReason?: string;\n ttl: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateDdnsRecordInput {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n nodeId?: string;\n username?: string;\n}\n\nexport interface UpdateDdnsRecordInput {\n ipAddress?: string;\n ipv6Address?: string;\n}\n\nexport class DdnsRepository {\n private readonly schema: ReturnType<typeof getSchema>;\n\n constructor(private readonly db: IdentityDatabase) {\n this.schema = getSchema(db);\n }\n\n // ==================== Domain Pool ====================\n\n /**\n * 添加域名到池中\n */\n async addDomain(\n domain: string,\n provider?: string,\n zoneId?: string,\n ): Promise<DdnsDomain> {\n const now = new Date();\n\n await this.db.insert(this.schema.ddnsDomains).values({\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Added domain to pool: ${domain}`);\n\n return {\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: now,\n };\n }\n\n /**\n * 获取所有活跃的域名\n */\n async getActiveDomains(): Promise<DdnsDomain[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsDomains)\n .where(eq(this.schema.ddnsDomains.status, 'active'));\n\n return results.map((row: typeof results[0]) => ({\n domain: row.domain,\n status: row.status as 'active' | 'suspended',\n provider: row.provider ?? undefined,\n zoneId: row.zoneId ?? undefined,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n }));\n }\n\n /**\n * 暂停域名\n */\n async suspendDomain(domain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsDomains)\n .set({ status: 'suspended' })\n .where(eq(this.schema.ddnsDomains.domain, domain));\n\n logger.info(`Suspended domain: ${domain}`);\n }\n\n // ==================== DDNS Records ====================\n\n /**\n * 分配子域名\n */\n async allocateSubdomain(input: CreateDdnsRecordInput): Promise<DdnsRecord> {\n const { subdomain, domain, ipAddress, ipv6Address, nodeId, username } = input;\n\n // 检查是否已存在\n const existing = await this.getRecord(subdomain);\n if (existing) {\n throw new Error(`Subdomain ${subdomain} already allocated`);\n }\n\n const now = new Date();\n const recordType = ipv6Address ? 'AAAA' : 'A';\n\n await this.db.insert(this.schema.ddnsRecords).values({\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: toDbTimestamp(this.db, now),\n updatedAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Allocated subdomain: ${subdomain}.${domain}`);\n\n return {\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getRecord(subdomain: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n\n /**\n * 更新 DDNS 记录的 IP 地址\n */\n async updateRecordIp(\n subdomain: string,\n input: UpdateDdnsRecordInput,\n ): Promise<DdnsRecord | null> {\n const existing = await this.getRecord(subdomain);\n if (!existing) {\n return null;\n }\n\n if (existing.status === 'banned') {\n throw new Error(`Subdomain ${subdomain} is banned: ${existing.bannedReason}`);\n }\n\n const now = new Date();\n const updates: Record<string, unknown> = { updatedAt: toDbTimestamp(this.db, now) };\n\n if (input.ipAddress !== undefined) {\n updates.ipAddress = input.ipAddress;\n updates.recordType = 'A';\n }\n if (input.ipv6Address !== undefined) {\n updates.ipv6Address = input.ipv6Address;\n if (!input.ipAddress) {\n updates.recordType = 'AAAA';\n }\n }\n\n await this.db\n .update(this.schema.ddnsRecords)\n .set(updates)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);\n\n return {\n ...existing,\n ipAddress: input.ipAddress ?? existing.ipAddress,\n ipv6Address: input.ipv6Address ?? existing.ipv6Address,\n recordType: (updates.recordType as 'A' | 'AAAA') ?? existing.recordType,\n updatedAt: now,\n };\n }\n\n /**\n * 封禁子域名\n */\n async banSubdomain(subdomain: string, reason: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'banned',\n bannedReason: reason,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n }\n\n /**\n * 解封子域名\n */\n async unbanSubdomain(subdomain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'active',\n bannedReason: null,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Unbanned subdomain: ${subdomain}`);\n }\n\n /**\n * 释放子域名\n */\n async releaseSubdomain(subdomain: string): Promise<boolean> {\n await this.db\n .delete(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Released subdomain: ${subdomain}`);\n return true;\n }\n\n /**\n * 获取用户的所有子域名\n */\n async getRecordsByUsername(username: string): Promise<DdnsRecord[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.username, username));\n\n return results.map((row: typeof results[0]) => ({\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n }));\n }\n\n /**\n * 获取节点的子域名\n */\n async getRecordByNodeId(nodeId: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.nodeId, nodeId))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n}\n"]}
|
|
@@ -297,6 +297,29 @@ function ensureSqliteTables(sqlite) {
|
|
|
297
297
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
298
298
|
);
|
|
299
299
|
|
|
300
|
+
CREATE TABLE IF NOT EXISTS identity_ddns_domain (
|
|
301
|
+
domain TEXT PRIMARY KEY,
|
|
302
|
+
status TEXT DEFAULT 'active',
|
|
303
|
+
provider TEXT,
|
|
304
|
+
zone_id TEXT,
|
|
305
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
CREATE TABLE IF NOT EXISTS identity_ddns_record (
|
|
309
|
+
subdomain TEXT PRIMARY KEY,
|
|
310
|
+
domain TEXT NOT NULL,
|
|
311
|
+
ip_address TEXT,
|
|
312
|
+
ipv6_address TEXT,
|
|
313
|
+
record_type TEXT DEFAULT 'A',
|
|
314
|
+
node_id TEXT,
|
|
315
|
+
username TEXT,
|
|
316
|
+
status TEXT DEFAULT 'active',
|
|
317
|
+
banned_reason TEXT,
|
|
318
|
+
ttl INTEGER DEFAULT 60,
|
|
319
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
|
320
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
321
|
+
);
|
|
322
|
+
|
|
300
323
|
CREATE TABLE IF NOT EXISTS identity_service_token (
|
|
301
324
|
id TEXT PRIMARY KEY,
|
|
302
325
|
token_hash TEXT NOT NULL UNIQUE,
|