248 lines
7.2 KiB
JavaScript
248 lines
7.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { randomUUID, randomBytes } from "node:crypto";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
process.loadEnvFile(".env.local");
|
|
const origin = process.argv[2] || "https://minegeo.vercel.app";
|
|
const db = createClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
process.env.SUPABASE_SECRET_KEY,
|
|
{ auth: { persistSession: false, autoRefreshToken: false } },
|
|
);
|
|
const accounts = [],
|
|
maps = [];
|
|
const checked = (r) => {
|
|
if (r.error) throw new Error(r.error.message);
|
|
return r.data;
|
|
};
|
|
async function request(
|
|
account,
|
|
path,
|
|
body,
|
|
method = body === undefined ? "GET" : "POST",
|
|
status = 200,
|
|
) {
|
|
const response = await fetch(`${origin}/api/v1/${path}`, {
|
|
method,
|
|
headers: {
|
|
origin,
|
|
"content-type": "application/json",
|
|
...(account?.cookie ? { cookie: account.cookie } : {}),
|
|
},
|
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
signal: AbortSignal.timeout(60000),
|
|
});
|
|
const data = await response.json();
|
|
assert.equal(response.status, status, `${path}: ${JSON.stringify(data)}`);
|
|
return data;
|
|
}
|
|
async function account(role) {
|
|
const email = `admin-permissions-${randomUUID()}@example.com`,
|
|
password = randomBytes(24).toString("base64url");
|
|
const { user } = checked(
|
|
await db.auth.admin.createUser({
|
|
email,
|
|
password,
|
|
email_confirm: true,
|
|
user_metadata: { display_name: `Permissions ${role} fixture` },
|
|
}),
|
|
);
|
|
const fixture = { id: user.id, cookie: "" };
|
|
accounts.push(fixture);
|
|
checked(
|
|
await db.from("profiles").insert({
|
|
id: user.id,
|
|
role,
|
|
display_name: `Permissions ${role} fixture`,
|
|
}),
|
|
);
|
|
const response = await fetch(`${origin}/api/v1/auth`, {
|
|
method: "POST",
|
|
headers: { origin, "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
action: "login",
|
|
email,
|
|
password,
|
|
next: "/moderation",
|
|
}),
|
|
});
|
|
assert.equal(response.status, 200, "Fixture login failed");
|
|
fixture.cookie = response.headers
|
|
.getSetCookie()
|
|
.map((value) => value.split(";")[0])
|
|
.join("; ");
|
|
assert.ok(fixture.cookie, "Login did not set a session cookie");
|
|
return fixture;
|
|
}
|
|
try {
|
|
const player = await account("contributor"),
|
|
moderator = await account("moderator"),
|
|
admin = await account("admin"),
|
|
missing = randomUUID();
|
|
const bounds = { minX: 0, minZ: 0, maxX: 64, maxZ: 64 };
|
|
for (const state of ["review", "unpublished"]) {
|
|
const map = checked(
|
|
await db
|
|
.from("datasets")
|
|
.insert({
|
|
owner_id: player.id,
|
|
name: `Permissions ${state} fixture`,
|
|
source: "plugin",
|
|
bounds,
|
|
state,
|
|
panorama_count: 5,
|
|
})
|
|
.select("id")
|
|
.single(),
|
|
);
|
|
maps.push(map.id);
|
|
checked(
|
|
await db.from("dataset_inputs").insert({
|
|
dataset_id: map.id,
|
|
validated: true,
|
|
manifest: {
|
|
format: "minegeo-dataset/v1",
|
|
minecraftVersion: "1.21.11",
|
|
dimension: "minecraft:overworld",
|
|
source: "plugin",
|
|
name: "Permissions map fixture",
|
|
description: "Disposable permission verification",
|
|
serverName: "Fixture server",
|
|
north: "-z",
|
|
bounds,
|
|
viewpoints: Array.from({ length: 5 }, (_, i) => ({
|
|
id: `p${i}`,
|
|
x: i * 4,
|
|
y: 64,
|
|
z: i * 4,
|
|
})),
|
|
assets: [
|
|
{ path: "terrain.json.gz", sha256: "a".repeat(64), bytes: 1 },
|
|
],
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
await request(player, "moderation", undefined, "GET", 403);
|
|
const queue = await request(moderator, "moderation");
|
|
assert.equal(queue.role, "moderator");
|
|
assert.deepEqual(queue.reports, []);
|
|
assert.deepEqual(queue.appeals, []);
|
|
assert.ok(
|
|
queue.maps.every(
|
|
(m) =>
|
|
m.state === "review" ||
|
|
(m.state === "published" && m.update_state === "review"),
|
|
),
|
|
);
|
|
assert.ok(queue.maps.some((m) => m.id === maps[0]));
|
|
assert.ok(!queue.maps.some((m) => m.id === maps[1]));
|
|
assert.ok(
|
|
(await request(moderator, `moderation/${maps[0]}/panoramas`)).locations
|
|
.length === 5,
|
|
);
|
|
await request(
|
|
moderator,
|
|
`moderation/${maps[1]}/panoramas`,
|
|
undefined,
|
|
"GET",
|
|
403,
|
|
);
|
|
for (const actor of [player, moderator]) {
|
|
for (const [path, body, method] of [
|
|
["moderation/users", undefined, "GET"],
|
|
[
|
|
`moderation/users/${player.id}`,
|
|
{ prefix: "Spoof", icon: "star" },
|
|
"PATCH",
|
|
],
|
|
["moderation/settings", { betaBadgeEnabled: false }, "PATCH"],
|
|
[
|
|
`moderation/appeals/${missing}`,
|
|
{ decision: "approve", reason: "A valid reason here." },
|
|
"POST",
|
|
],
|
|
[`moderation/reports/${missing}/panoramas`, undefined, "GET"],
|
|
[
|
|
`reports/${missing}`,
|
|
{ decision: "confirm", reason: "A valid reason here." },
|
|
"PATCH",
|
|
],
|
|
["moderation", { datasetId: maps[0], decision: "unpublish" }, "POST"],
|
|
])
|
|
await request(actor, path, body, method, 403);
|
|
}
|
|
await request(moderator, "moderation", {
|
|
datasetId: maps[0],
|
|
decision: "reject",
|
|
reason: "Returned by the permission verification fixture.",
|
|
});
|
|
assert.equal(
|
|
checked(
|
|
await db.from("datasets").select("state").eq("id", maps[0]).single(),
|
|
).state,
|
|
"rejected",
|
|
);
|
|
const full = await request(admin, "moderation");
|
|
assert.equal(full.role, "admin");
|
|
assert.ok(full.maps.some((m) => m.id === maps[1]));
|
|
assert.equal(
|
|
(await request(admin, `moderation/users?q=${player.id}`)).users[0].id,
|
|
player.id,
|
|
);
|
|
await request(
|
|
admin,
|
|
`moderation/users/${player.id}`,
|
|
{ prefix: "Builder", icon: "gem" },
|
|
"PATCH",
|
|
);
|
|
assert.equal(
|
|
(await request(player, "profile")).profile.custom_prefix,
|
|
"Builder",
|
|
);
|
|
await request(
|
|
admin,
|
|
`moderation/users/${player.id}`,
|
|
{ prefix: null, icon: "star" },
|
|
"PATCH",
|
|
);
|
|
await request(
|
|
admin,
|
|
"moderation/settings",
|
|
{ betaBadgeEnabled: "invalid" },
|
|
"PATCH",
|
|
400,
|
|
);
|
|
await request(
|
|
admin,
|
|
`reports/${missing}`,
|
|
{ decision: "dismiss", reason: "A valid reason here." },
|
|
"PATCH",
|
|
400,
|
|
);
|
|
await request(
|
|
admin,
|
|
`moderation/appeals/${missing}`,
|
|
{ decision: "approve", reason: "A valid reason here." },
|
|
"POST",
|
|
400,
|
|
);
|
|
for (const actor of [player, moderator, admin])
|
|
await request(actor, "profile", { role: "admin" }, "PATCH", 400);
|
|
assert.equal((await request(admin, "profile")).profile.role, "admin");
|
|
console.log(
|
|
"Production permissions passed: signed-in players denied, moderator queue restricted, pending panoramas and feedback work, admin controls available, role spoofing denied.",
|
|
);
|
|
} finally {
|
|
if (maps.length)
|
|
checked(await db.from("review_decisions").delete().in("dataset_id", maps));
|
|
for (const id of maps)
|
|
checked(await db.from("datasets").delete().eq("id", id));
|
|
for (const fixture of accounts) {
|
|
if (fixture.cookie)
|
|
await request(fixture, "auth/logout", {}).catch(() => {});
|
|
checked(await db.from("profiles").delete().eq("id", fixture.id));
|
|
checked(await db.auth.admin.deleteUser(fixture.id));
|
|
}
|
|
console.log("Disposable permission fixtures removed.");
|
|
}
|