163 lines
6.1 KiB
TypeScript
163 lines
6.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { certificateFixture } from "../certificate-fixture";
|
|
import { parse } from "yaml";
|
|
|
|
test("AnyTLS provisioning, per-user credentials, cumulative accounting and revocation", async () => {
|
|
const dir = mkdtempSync(path.join(tmpdir(), "hy2-anytls-"));
|
|
process.env.DATABASE_PATH = path.join(dir, "db.sqlite");
|
|
process.env.APP_URL = "https://panel.example.com";
|
|
const { ensureDb } = await import("../../lib/db");
|
|
const { encryptText, decryptText } = await import("../../lib/crypto");
|
|
const { saveCertificate } = await import("../../lib/certificates");
|
|
const { createInstallation, installationForDownload, provisionNode } =
|
|
await import("../../lib/provisioning");
|
|
const { syncAnyTLSNode } = await import("../../lib/node-agent");
|
|
const { anytlsPassword } = await import("../../lib/anytls");
|
|
const { buildInstallScript } = await import("../../lib/install-script");
|
|
const {
|
|
getNodeBySlug,
|
|
listUsers,
|
|
getSubscriptionSettings,
|
|
listEnabledNodesForPolling,
|
|
} = await import("../../lib/store");
|
|
const { buildSubscriptionConfig } = await import("../../lib/subscription");
|
|
const db = await ensureDb();
|
|
try {
|
|
const fixture = certificateFixture();
|
|
const certificateId = Number(
|
|
await saveCertificate(
|
|
"Wildcard",
|
|
fixture.certificate,
|
|
fixture.privateKey,
|
|
),
|
|
);
|
|
const id = await createInstallation({
|
|
slug: "anytls-test",
|
|
name: "AnyTLS",
|
|
certificateId,
|
|
protocol: "anytls",
|
|
});
|
|
const job = await installationForDownload(id);
|
|
assert.equal(job.protocol, "anytls");
|
|
assert.match(buildInstallScript(job), /--port/);
|
|
const installToken = decryptText(job.token_encrypted)!;
|
|
const ports = {
|
|
domain: "jp2.p.xn--876a.net",
|
|
port: 51046,
|
|
listenPort: 51046,
|
|
statsPort: 19090,
|
|
controlPort: 19091,
|
|
};
|
|
await assert.rejects(
|
|
() =>
|
|
provisionNode(installToken, "prepare", {
|
|
...ports,
|
|
controlPort: 19090,
|
|
}),
|
|
/端口/,
|
|
);
|
|
const bundle = await provisionNode(installToken, "prepare", ports);
|
|
assert.ok(bundle.agent && bundle.config && bundle.agentSource);
|
|
assert.equal(bundle.agent.slug, "anytls-test");
|
|
assert.match(bundle.agentSource, /reset=False/);
|
|
const initial = JSON.parse(bundle.config);
|
|
assert.equal(initial.inbounds[0].type, "anytls");
|
|
assert.equal(initial.inbounds[0].listen_port, 51046);
|
|
assert.equal(initial.experimental.v2ray_api.listen, "127.0.0.1:19090");
|
|
const user = (await listUsers())[0];
|
|
const password = anytlsPassword(user);
|
|
assert.equal(initial.inbounds[0].users[0].password, password);
|
|
assert.notEqual(password, decryptText(user.auth_secret_encrypted));
|
|
await provisionNode(installToken, "complete");
|
|
const node = (await getNodeBySlug(job.slug))!;
|
|
assert.equal(node.node_type, "anytls");
|
|
assert.equal((await listEnabledNodesForPolling()).length, 0);
|
|
const subscription = parse(
|
|
buildSubscriptionConfig({
|
|
user,
|
|
nodes: [node],
|
|
settings: await getSubscriptionSettings(),
|
|
}),
|
|
);
|
|
assert.equal(subscription.proxies[0].type, "anytls");
|
|
assert.equal(subscription.proxies[0].password, password);
|
|
assert.equal(subscription.proxies[0]["skip-cert-verify"], false);
|
|
const report = {
|
|
epoch: "boot:123:456",
|
|
traffic: { [user.auth_id]: { tx: 1000, rx: 500 } },
|
|
streamCount: 2,
|
|
};
|
|
await assert.rejects(
|
|
() => syncAnyTLSNode(job.slug, "0".repeat(64), report),
|
|
/凭据/,
|
|
);
|
|
await assert.rejects(
|
|
() =>
|
|
syncAnyTLSNode(job.slug, bundle.agent.token, {
|
|
...report,
|
|
traffic: { [user.auth_id]: { tx: -1, rx: 2 } },
|
|
}),
|
|
/无效/,
|
|
);
|
|
await syncAnyTLSNode(job.slug, bundle.agent.token, report);
|
|
await syncAnyTLSNode(job.slug, bundle.agent.token, report);
|
|
let refreshed = (await listUsers())[0];
|
|
assert.equal(refreshed.used_tx_bytes, 1000);
|
|
assert.equal(refreshed.used_rx_bytes, 500);
|
|
await syncAnyTLSNode(job.slug, bundle.agent.token, {
|
|
...report,
|
|
traffic: { [user.auth_id]: { tx: 900, rx: 400 } },
|
|
});
|
|
refreshed = (await listUsers())[0];
|
|
assert.equal(refreshed.used_tx_bytes, 1000);
|
|
await syncAnyTLSNode(job.slug, bundle.agent.token, {
|
|
...report,
|
|
epoch: "boot:124:500",
|
|
traffic: { [user.auth_id]: { tx: 100, rx: 50 } },
|
|
});
|
|
refreshed = (await listUsers())[0];
|
|
assert.equal(refreshed.used_tx_bytes, 1100);
|
|
// Make this account an ordinary user to exercise quota/expiry/disable rules.
|
|
db.prepare(
|
|
"UPDATE hy2_users SET is_admin=0,traffic_limit_bytes=1000 WHERE id=?",
|
|
).run(user.id);
|
|
const capped = await syncAnyTLSNode(job.slug, bundle.agent.token, report);
|
|
assert.equal(capped.config.inbounds[0].users.length, 0);
|
|
db.prepare(
|
|
"UPDATE hy2_users SET traffic_limit_bytes=NULL,expires_at='2000-01-01' WHERE id=?",
|
|
).run(user.id);
|
|
assert.equal(
|
|
(await syncAnyTLSNode(job.slug, bundle.agent.token, report)).config
|
|
.inbounds[0].users.length,
|
|
0,
|
|
);
|
|
db.prepare("UPDATE hy2_users SET expires_at=NULL,enabled=0 WHERE id=?").run(
|
|
user.id,
|
|
);
|
|
assert.equal(
|
|
(await syncAnyTLSNode(job.slug, bundle.agent.token, report)).config
|
|
.inbounds[0].users.length,
|
|
0,
|
|
);
|
|
db.prepare(
|
|
"UPDATE hy2_users SET enabled=1,auth_secret_encrypted=? WHERE id=?",
|
|
).run(encryptText("new-password"), user.id);
|
|
const updated = await syncAnyTLSNode(job.slug, bundle.agent.token, report);
|
|
assert.notEqual(updated.config.inbounds[0].users[0].password, password);
|
|
db.prepare("UPDATE hy2_nodes SET enabled=0 WHERE id=?").run(node.id);
|
|
assert.equal(
|
|
(await syncAnyTLSNode(job.slug, bundle.agent.token, report)).config
|
|
.inbounds[0].users.length,
|
|
0,
|
|
);
|
|
assert.deepEqual(db.pragma("foreign_key_check"), []);
|
|
} finally {
|
|
db.close();
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|