import { mkdtempSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path"; import { afterAll, expect, test } from "@effect/vitest"; import { mintInviteCode } from "../testing/mint-invite"; // The self-host admin users plane (`/api/admin/users*`), over the REAL booted// app: Better Auth, the libSQL store, and the mounted route layer.//// Self-host has no organization-owned api key (Better Auth keys always belong// to the user who made one), so the credential here is the operator's own// owner/admin session — the same gate the invite-code admin API uses. What this// pins is that the mount is live, that the owner/admin gate actually refuses a// plain member, and that a member who has used the instance shows up in the// owner's view without their credential coming with them. process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-admin-users-"));process.env.BETTER_AUTH_SECRET = "admin-users-test-secret-0123456789-abcdefghij";process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@admin-users.test";process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456"; const { makeSelfHostApiHandler } = await import("../app");const { handler, dispose } = await makeSelfHostApiHandler();afterAll(() => dispose()); const BASE = "http://localhost:4788"; const signIn = async (email: string, password: string): Promise<string> => { const response = await handler( new Request(`${BASE}/api/auth/sign-in/email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password }), }), ); return response.headers.get("set-auth-token") ?? "";}; const adminUsers = (token?: string, path = "/api/admin/users") => handler( new Request(`${BASE}${path}`, { headers: token ? { authorization: `Bearer ${token}` } : {}, }), ); test("the owner sees who uses the instance; a plain member cannot look", async () => { const adminToken = await signIn( process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL!, process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD!, ); expect(adminToken).not.toBe(""); // A second person joins through the real invite flow and uses the instance. const inviteCode = await mintInviteCode(handler); const signUp = await handler( new Request(`${BASE}/api/auth/sign-up/email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "member@admin-users.test", password: "password-12345678", name: "Member", inviteCode, }), }), ); expect(signUp.status).toBe(200); const memberToken = signUp.headers.get("set-auth-token") ?? ""; expect(memberToken).not.toBe(""); // A user earns their row the first time they touch the EXECUTOR plane, so // have both make one product read before looking at the admin view. for (const token of [adminToken, memberToken]) { const integrations = await handler( new Request(`${BASE}/api/integrations`, { headers: { authorization: `Bearer ${token}` } }), ); expect(integrations.status).toBe(200); } // The owner's view reports both people. const response = await adminUsers(adminToken); expect(response.status).toBe(200); const body = (await response.json()) as { users: ReadonlyArray<{ externalId: string; lastSeenAt: number | null }>; }; expect(body.users.length, "both the owner and the invited member are reported").toBe(2); expect(typeof body.users[0]?.lastSeenAt, "last-seen crosses the wire as a number").toBe("number"); // The joined view is mounted too. const joined = await adminUsers(adminToken, "/api/admin/users/with-connections"); expect(joined.status).toBe(200); const joinedBody = (await joined.json()) as { users: ReadonlyArray<{ externalId: string; connections: readonly unknown[] }>; }; expect(joinedBody.users.length).toBe(2); expect( joinedBody.users.every((user) => Array.isArray(user.connections)), "every user carries a connections array", ).toBe(true); // The gate: a plain member may not read the instance-wide view. const asMember = await adminUsers(memberToken); expect(asMember.status, "a member is refused").toBe(403); // And an anonymous caller has no session at all. const anonymous = await adminUsers(); expect(anonymous.status, "no session → unauthorized").toBe(401);});
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, expect, test } from "@effect/vitest";
import { mintInviteCode } from "../testing/mint-invite";
// The self-host admin users plane (`/api/admin/users*`), over the REAL booted
// app: Better Auth, the libSQL store, and the mounted route layer.
//
// Self-host has no organization-owned api key (Better Auth keys always belong
// to the user who made one), so the credential here is the operator's own
// owner/admin session — the same gate the invite-code admin API uses. What this
// pins is that the mount is live, that the owner/admin gate actually refuses a
// plain member, and that a member who has used the instance shows up in the
// owner's view without their credential coming with them.
process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-admin-users-"));
process.env.BETTER_AUTH_SECRET = "admin-users-test-secret-0123456789-abcdefghij";
process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@admin-users.test";
process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456";
const { makeSelfHostApiHandler } = await import("../app");
const { handler, dispose } = await makeSelfHostApiHandler();
afterAll(() => dispose());
const BASE = "http://localhost:4788";
const signIn = async (email: string, password: string): Promise<string> => {
const response = await handler(
new Request(`${BASE}/api/auth/sign-in/email`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email, password }),
}),
);
return response.headers.get("set-auth-token") ?? "";
};
const adminUsers = (token?: string, path = "/api/admin/users") =>
handler(
new Request(`${BASE}${path}`, {
headers: token ? { authorization: `Bearer ${token}` } : {},
}),
);
test("the owner sees who uses the instance; a plain member cannot look", async () => {
const adminToken = await signIn(
process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL!,
process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD!,
);
expect(adminToken).not.toBe("");
// A second person joins through the real invite flow and uses the instance.
const inviteCode = await mintInviteCode(handler);
const signUp = await handler(
new Request(`${BASE}/api/auth/sign-up/email`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: "member@admin-users.test",
password: "password-12345678",
name: "Member",
inviteCode,
}),
}),
);
expect(signUp.status).toBe(200);
const memberToken = signUp.headers.get("set-auth-token") ?? "";
expect(memberToken).not.toBe("");
// A user earns their row the first time they touch the EXECUTOR plane, so
// have both make one product read before looking at the admin view.
for (const token of [adminToken, memberToken]) {
const integrations = await handler(
new Request(`${BASE}/api/integrations`, { headers: { authorization: `Bearer ${token}` } }),
);
expect(integrations.status).toBe(200);
}
// The owner's view reports both people.
const response = await adminUsers(adminToken);
expect(response.status).toBe(200);
const body = (await response.json()) as {
users: ReadonlyArray<{ externalId: string; lastSeenAt: number | null }>;
};
expect(body.users.length, "both the owner and the invited member are reported").toBe(2);
expect(typeof body.users[0]?.lastSeenAt, "last-seen crosses the wire as a number").toBe("number");
// The joined view is mounted too.
const joined = await adminUsers(adminToken, "/api/admin/users/with-connections");
expect(joined.status).toBe(200);
const joinedBody = (await joined.json()) as {
users: ReadonlyArray<{ externalId: string; connections: readonly unknown[] }>;
};
expect(joinedBody.users.length).toBe(2);
expect(
joinedBody.users.every((user) => Array.isArray(user.connections)),
"every user carries a connections array",
).toBe(true);
// The gate: a plain member may not read the instance-wide view.
const asMember = await adminUsers(memberToken);
expect(asMember.status, "a member is refused").toBe(403);
// And an anonymous caller has no session at all.
const anonymous = await adminUsers();
expect(anonymous.status, "no session → unauthorized").toBe(401);
});