Files
legion_kk/tests/unit/tribes-admin-api.test.ts
self 0e97e9ef33 feat: manage Tribes admins from settings
Replace the old profile-era settings flow with online human-admin CRUD backed by the Tribes admin API.

Derive the first-admin tutorial step from transient live admin state and remove ACME email plumbing from Legion deployment inputs.
2026-06-26 20:47:43 +02:00

586 lines
16 KiB
TypeScript

import assert from "node:assert/strict"
import test from "node:test"
import {
createBootstrapAdmin,
decryptBootstrapAdminPrivateKey
} from "../../src/main/bootstrap-crypto"
import { buildManagementUrl, TribesAdminApiClient } from "../../src/main/tribes-admin-api"
import type { TribesSystemTarget } from "../../src/shared/tribes-deployment"
const slowTest = process.env["LEGION_RUN_SLOW_TESTS"] === "1" ? test : test.skip
test("buildManagementUrl omits default ports", () => {
assert.equal(
buildManagementUrl({
host: "node-a.example.test",
port: 443,
scheme: "https"
}),
"https://node-a.example.test/api/admin/management"
)
assert.equal(
buildManagementUrl({
host: "node-a.example.test",
port: 4000,
scheme: "http"
}),
"http://node-a.example.test:4000/api/admin/management"
)
assert.equal(
buildManagementUrl({
host: "2001:db8::10",
port: 443,
scheme: "https"
}),
"https://[2001:db8::10]/api/admin/management"
)
})
slowTest("TribesAdminApiClient sends NIP-98 authorization header", async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
let requestUrl = ""
let requestHeaders: Headers | undefined
let requestBody = ""
globalThis.fetch = (async (input, init) => {
requestUrl = String(input)
requestHeaders = new Headers(init?.headers)
requestBody = String(init?.body ?? "")
return new Response(
JSON.stringify({
ok: true,
result: {
ok: true,
node: {
pubkey: "a".repeat(64),
transport_address: "wss://node-a.example.test:4413/relay",
scope: "all",
status: "active",
activated_at: "2026-04-18T00:00:00Z",
deactivated_at: null
}
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
)
}) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
const result = await client.clusterNodeUpsert({
pubkey: "a".repeat(64),
transportAddress: "wss://node-a.example.test:4413/relay"
})
assert.equal(requestUrl, "http://node-a.example.test:4000/api/admin/management")
assert.equal(requestHeaders?.get("content-type"), "application/json")
assert.match(requestHeaders?.get("authorization") ?? "", /^Nostr /)
assert.match(requestBody, /"cluster_nodes\.upsert"/)
assert.equal(result.node.pubkey, "a".repeat(64))
} finally {
globalThis.fetch = originalFetch
}
})
slowTest("TribesAdminApiClient retries explicit HTTP 503 management responses", async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
let attempts = 0
globalThis.fetch = (async () => {
attempts += 1
if (attempts === 1) {
return new Response(
JSON.stringify({
ok: false,
error: "service starting; database not ready"
}),
{
status: 503,
headers: {
"content-type": "application/json"
}
}
)
}
return new Response(
JSON.stringify({
ok: true,
result: {
ok: true,
schema_version: "1",
generated_at: "2026-04-20T00:00:00Z",
node_count: 0,
nodes: []
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
)
}) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
await client.clusterNodesList()
assert.equal(attempts, 2)
} finally {
globalThis.fetch = originalFetch
}
})
slowTest("TribesAdminApiClient reports non-JSON management responses clearly", async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
globalThis.fetch = (async () =>
new Response("Not Found", {
status: 404,
headers: {
"content-type": "text/plain"
}
})) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
await assert.rejects(
() =>
client.clusterNodeUpsert({
pubkey: "a".repeat(64),
transportAddress: "wss://node-a.example.test:4413/relay"
}),
/non-JSON response with HTTP 404: Not Found/
)
} finally {
globalThis.fetch = originalFetch
}
})
slowTest("TribesAdminApiClient exports, previews, and stores system targets", async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
const methods: string[] = []
const target: TribesSystemTarget = {
channels: [],
plugins: [],
rolloutPolicy: {
prepare_timeout_seconds: 1800,
commit_timeout_seconds: 300,
require_all_nodes_ready: true
}
}
globalThis.fetch = (async (_input, init) => {
const body = JSON.parse(String(init?.body ?? "{}")) as { method?: string }
methods.push(String(body.method ?? ""))
return new Response(
JSON.stringify({
ok: true,
result:
body.method === "cluster_preview_rollout"
? {
ok: true,
schema_version: "1",
mode: "auto_switch",
target,
plan: {
plan_schema_version: "1",
plan_hash: "plan-test",
resolved_channels: [],
resolved_plugins: [],
resolved_extra_packages: [],
core_migration_target: false,
core_destructive_rollback_migrations: [],
closure_estimate_bytes: false
},
preview: {}
}
: body.method === "cluster_update_sources.export"
? {
ok: true,
schema_version: "1",
generated_at: "2026-06-18T00:00:00.000Z",
update_defaults: {
trusted_signers: [],
channels: [],
substitute_servers: [],
substitute_keys: []
}
}
: {
ok: true,
schema_version: "1",
target
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
)
}) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
const exported = await client.clusterExportSystemTarget()
const preview = await client.clusterPreviewRollout({ mode: "auto_switch" })
const updateSources = await client.clusterUpdateSourcesExport()
await client.clusterSetSystemTarget(target)
assert.deepEqual(methods, [
"cluster_export_system_target",
"cluster_preview_rollout",
"cluster_update_sources.export",
"cluster_set_system_target"
])
assert.equal(exported.target.rolloutPolicy.prepare_timeout_seconds, 1800)
assert.equal(preview.plan.plan_hash, "plan-test")
assert.deepEqual(updateSources.update_defaults.channels, [])
} finally {
globalThis.fetch = originalFetch
}
})
slowTest("TribesAdminApiClient calls plugin management methods through plugin.call", async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
const requests: Array<{ method?: string; params?: Record<string, unknown> }> = []
globalThis.fetch = (async (_input, init) => {
const body = JSON.parse(String(init?.body ?? "{}")) as {
method?: string
params?: Record<string, unknown>
}
requests.push(body)
return new Response(
JSON.stringify({
ok: true,
result:
body.method === "plugin.management_methods"
? {
ok: true,
schema_version: "1",
plugin: "tribe-one-sender",
method_count: 1,
methods: [
{
name: "capabilities",
version: "1",
auth: "admin",
sensitive_response: false
}
]
}
: {
ok: true,
stream: {
id: "stream-1",
slug: "default"
}
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
)
}) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
const methods = await client.pluginManagementMethods("tribe-one-sender")
const result = await client.pluginCall<{ ok: true; stream: { id: string } }>(
"tribe-one-sender",
"stream.ensure_default",
{ title: "Main stream" }
)
assert.equal(methods.plugin, "tribe-one-sender")
assert.equal(result.stream.id, "stream-1")
assert.deepEqual(
requests.map((request) => request.method),
["plugin.management_methods", "plugin.call"]
)
assert.deepEqual(requests[1]?.params, {
plugin: "tribe-one-sender",
method: "stream.ensure_default",
version: "1",
params: {
title: "Main stream"
}
})
} finally {
globalThis.fetch = originalFetch
}
})
test("TribesAdminApiClient creates hosted human admins and refreshes the list", async () => {
const privateKeyHex = "1".repeat(64)
const originalFetch = globalThis.fetch
const requests: Array<{ method: string; params: Record<string, unknown> }> = []
globalThis.fetch = (async (_input, init) => {
const body = JSON.parse(String(init?.body ?? "{}")) as {
method: string
params: Record<string, unknown>
}
requests.push(body)
const account = {
id: "user-1",
username: "alice",
display_name: "Alice",
account_type: "human",
role: "admin",
status: "active",
public_key_hex: "a".repeat(64),
npub: "npub1alice",
key_custody: "password_encrypted_private_key",
inserted_at: "2026-06-26T12:00:00Z",
updated_at: "2026-06-26T12:00:00Z"
}
const result =
body.method === "admin_accounts.list"
? {
ok: true,
schema_version: "1",
accounts: [account],
account_count: 1,
next_cursor: null
}
: {
ok: true,
schema_version: "1",
account
}
return new Response(JSON.stringify({ ok: true, result }), {
status: 200,
headers: {
"content-type": "application/json"
}
})
}) as typeof fetch
try {
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 4000,
scheme: "http"
},
{
privateKeyHex
}
)
const result = await client.adminAccountCreate({
username: "alice",
displayName: "Alice",
password: "alice-password-123",
privateKeyHex: "b".repeat(64)
})
assert.deepEqual(
requests.map((request) => request.method),
["admin_accounts.create", "admin_accounts.list"]
)
assert.deepEqual(requests[0]?.params, {
username: "alice",
display_name: "Alice",
account_type: "human",
role: "admin",
status: "active",
key_custody: {
mode: "password_encrypted_private_key",
private_key_hex: "b".repeat(64),
password: "alice-password-123"
}
})
assert.deepEqual(requests[1]?.params, {
account_type: "human",
role: "admin"
})
assert.equal(result.accounts[0]?.username, "alice")
assert.equal(result.accounts[0]?.keyCustody, "password_encrypted_private_key")
assert.equal(result.accounts[0]?.publicKeyHex, "a".repeat(64))
} finally {
globalThis.fetch = originalFetch
}
})
slowTest(
"TribesAdminApiClient enables test-only TLS bypass for self-signed endpoints",
async () => {
const admin = createBootstrapAdmin("bootstrap-password-123")
const privateKeyHex = decryptBootstrapAdminPrivateKey({
password: "bootstrap-password-123",
encryptedNostrPrivateKey: admin.encryptedNostrPrivateKey,
nostrPrivateKeyNonce: admin.nostrPrivateKeyNonce,
nostrPrivateKeySalt: admin.nostrPrivateKeySalt
})
const originalFetch = globalThis.fetch
const previousTlsFlag = process.env["NODE_TLS_REJECT_UNAUTHORIZED"]
let tlsFlagDuringFetch: string | undefined
globalThis.fetch = (async () => {
tlsFlagDuringFetch = process.env["NODE_TLS_REJECT_UNAUTHORIZED"]
return new Response(
JSON.stringify({
ok: true,
result: {
ok: true,
schema_version: "1",
generated_at: "2026-04-20T00:00:00Z",
node_count: 0,
nodes: []
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
)
}) as typeof fetch
try {
delete process.env["NODE_TLS_REJECT_UNAUTHORIZED"]
const client = new TribesAdminApiClient(
{
host: "node-a.example.test",
port: 443,
scheme: "https"
},
{
privateKeyHex
},
{
allowSelfSignedTls: true
}
)
await client.clusterNodesList()
assert.equal(tlsFlagDuringFetch, "0")
assert.equal(process.env["NODE_TLS_REJECT_UNAUTHORIZED"], undefined)
} finally {
globalThis.fetch = originalFetch
if (typeof previousTlsFlag === "string") {
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = previousTlsFlag
} else {
delete process.env["NODE_TLS_REJECT_UNAUTHORIZED"]
}
}
}
)