feat(dashboard): refine activity telemetry and maps
CI / validate (push) Successful in 5m24s
Release / release (push) Successful in 11m6s

This commit is contained in:
dmg
2026-08-01 20:28:32 -04:00
parent 9116107917
commit ebc7c7df17
32 changed files with 695 additions and 72 deletions
@@ -0,0 +1,134 @@
import { hashToken } from "@minecraft-account-manager/auth";
import { beforeEach, describe, expect, it, vi } from "vitest";
const databaseState = vi.hoisted(() => ({
account: { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", userId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" } as { id: string; userId: string } | null,
inserts: [] as Record<string, unknown>[],
credentialHash: "" as string | null,
replay: false,
}));
vi.mock("@/lib/database", () => ({
db: {
select: () => ({
from: () => ({
where: () => ({
limit: async () => databaseState.credentialHash ? [{ secretHash: databaseState.credentialHash }] : [],
}),
}),
}),
transaction: async (callback: (tx: unknown) => Promise<unknown>) => callback({
delete: () => ({ where: async () => undefined }),
insert: () => ({
values: async (value: Record<string, unknown>) => {
if (databaseState.replay && "requestId" in value) {
throw { code: "23505", constraint_name: "plugin_requests_pkey" };
}
databaseState.inserts.push(value);
},
}),
select: () => ({
from: () => ({
where: () => ({
limit: async () => databaseState.account ? [databaseState.account] : [],
}),
}),
}),
}),
},
}));
import { GET, POST } from "./route";
function validRequest(overrides: Record<string, unknown> = {}) {
return new Request("http://localhost/api/velocity/connection", {
method: "POST",
headers: { authorization: "Bearer valid-token", "content-type": "application/json" },
body: JSON.stringify({
requestId: "8dd9dbdc-020a-4077-983c-77747522de8f",
serverId: "velocity-main",
minecraftUuid: "069a79f444e94726a5befca90e38aaf5",
username: "Notch",
occurredAt: new Date().toISOString(),
...overrides,
}),
});
}
describe("Velocity connection reporting endpoint", () => {
beforeEach(() => {
databaseState.account = { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", userId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" };
databaseState.inserts = [];
databaseState.credentialHash = hashToken("valid-token");
databaseState.replay = false;
});
it("rejects methods other than POST with Problem Details", async () => {
const response = GET(new Request("http://localhost/api/velocity/connection"));
expect(response.status).toBe(405);
expect(response.headers.get("content-type")).toContain("application/problem+json");
expect(response.headers.get("allow")).toBe("POST");
});
it("requires a server credential", async () => {
const response = await POST(new Request("http://localhost/api/velocity/connection", {
method: "POST",
headers: { "content-type": "application/json" },
body: "{}",
}));
expect(response.status).toBe(401);
});
it("validates the report before database access", async () => {
const response = await POST(new Request("http://localhost/api/velocity/connection", {
method: "POST",
headers: { authorization: "Bearer test", "content-type": "application/json" },
body: JSON.stringify({ username: "bad name" }),
}));
expect(response.status).toBe(400);
await expect(response.json()).resolves.toMatchObject({ type: "urn:error:invalid-velocity-connection-request", status: 400 });
});
it("rejects invalid or revoked server credentials", async () => {
databaseState.credentialHash = null;
const response = await POST(validRequest());
expect(response.status).toBe(401);
expect(databaseState.inserts).toHaveLength(0);
});
it("rejects stale reports before recording them", async () => {
const response = await POST(validRequest({ occurredAt: "2026-01-01T00:00:00.000Z" }));
expect(response.status).toBe(401);
expect(databaseState.inserts).toHaveLength(0);
});
it("authenticates and atomically records a confirmed account connection", async () => {
const response = await POST(validRequest());
expect(response.status).toBe(204);
expect(databaseState.inserts).toEqual(expect.arrayContaining([
expect.objectContaining({ requestId: "8dd9dbdc-020a-4077-983c-77747522de8f", serverId: "velocity-main" }),
expect.objectContaining({
type: "games.minecraft.account-manager.game.player.connected",
subject: "minecraft-account/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
actorUserId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
}),
]));
});
it("rejects replayed request IDs", async () => {
databaseState.replay = true;
const response = await POST(validRequest());
expect(response.status).toBe(409);
await expect(response.json()).resolves.toMatchObject({
type: "urn:error:replayed-velocity-connection-request",
status: 409,
});
});
it("does not record an event for an unknown account", async () => {
databaseState.account = null;
const response = await POST(validRequest());
expect(response.status).toBe(404);
expect(databaseState.inserts).toHaveLength(1);
});
});
@@ -0,0 +1,142 @@
import { randomUUID } from "node:crypto";
import { isRequestTimestampFresh, verifyHashedToken } from "@minecraft-account-manager/auth";
import { problemDetails, velocityConnectionRequestSchema } from "@minecraft-account-manager/contracts";
import { events, minecraftAccounts, pluginCredentials, pluginRequests } from "@minecraft-account-manager/database";
import { and, eq, isNull, lt } from "drizzle-orm";
import { NextResponse } from "next/server";
import { db } from "@/lib/database";
import { isUniqueConstraintViolation } from "@/lib/database-errors";
import { logger } from "@/lib/logger";
import { problemInstance, problemResponse } from "@/lib/problem-response";
const MAX_CLOCK_SKEW_MS = 45_000;
function methodNotAllowed(request: Request) {
const response = problemResponse(problemDetails(
"urn:error:method-not-allowed",
"Method not allowed",
405,
"This endpoint only accepts POST requests.",
problemInstance(request),
));
response.headers.set("allow", "POST");
return response;
}
export const GET = methodNotAllowed;
export const PUT = methodNotAllowed;
export const PATCH = methodNotAllowed;
export const DELETE = methodNotAllowed;
export async function POST(request: Request) {
const instance = problemInstance(request);
const authorization = request.headers.get("authorization") ?? "";
const token = authorization.startsWith("Bearer ") ? authorization.slice(7).trim() : "";
if (!token) return problemResponse(problemDetails(
"urn:error:unauthorized",
"Unauthorized",
401,
"A valid Velocity server credential is required.",
instance,
));
const mediaType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
if (mediaType !== "application/json") return problemResponse(problemDetails(
"urn:error:unsupported-media-type",
"Unsupported media type",
415,
"Velocity connection reports must use application/json.",
instance,
));
const parsed = velocityConnectionRequestSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return problemResponse(problemDetails(
"urn:error:invalid-velocity-connection-request",
"Invalid Velocity connection report",
400,
"The request body does not match the required Velocity connection contract.",
instance,
{ issues: parsed.error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message, code: issue.code })) },
));
const input = parsed.data;
const occurredAt = new Date(input.occurredAt);
if (!isRequestTimestampFresh(occurredAt, new Date(), MAX_CLOCK_SKEW_MS)) return problemResponse(problemDetails(
"urn:error:expired-velocity-connection-request",
"Expired Velocity connection report",
401,
"The request timestamp is outside the allowed clock-skew window.",
instance,
));
const [credential] = await db
.select({ secretHash: pluginCredentials.secretHash })
.from(pluginCredentials)
.where(and(eq(pluginCredentials.serverId, input.serverId), isNull(pluginCredentials.revokedAt)))
.limit(1);
if (!credential || !verifyHashedToken(token, credential.secretHash)) return problemResponse(problemDetails(
"urn:error:unauthorized",
"Unauthorized",
401,
"The Velocity server credential is invalid or revoked.",
instance,
));
try {
const recorded = await db.transaction(async (tx) => {
await tx.delete(pluginRequests).where(lt(pluginRequests.expiresAt, new Date()));
await tx.insert(pluginRequests).values({
requestId: input.requestId,
serverId: input.serverId,
receivedAt: new Date(),
expiresAt: new Date(Date.now() + 5 * 60_000),
});
const [account] = await tx
.select({ id: minecraftAccounts.id, userId: minecraftAccounts.userId })
.from(minecraftAccounts)
.where(and(eq(minecraftAccounts.minecraftUuid, input.minecraftUuid), isNull(minecraftAccounts.deletedAt)))
.limit(1);
if (!account) return false;
await tx.insert(events).values({
id: randomUUID(),
source: `/velocity/${input.serverId}`,
type: "games.minecraft.account-manager.game.player.connected",
subject: `minecraft-account/${account.id}`,
time: occurredAt,
actorUserId: account.userId,
correlationId: input.requestId,
data: {
username: input.username,
minecraftUuid: input.minecraftUuid,
serverId: input.serverId,
},
});
return true;
});
if (!recorded) return problemResponse(problemDetails(
"urn:error:unknown-minecraft-account",
"Unknown Minecraft account",
404,
"The connected Minecraft account is no longer registered.",
instance,
));
return new NextResponse(null, { status: 204 });
} catch (error) {
if (isUniqueConstraintViolation(error, "plugin_requests_pkey")) return problemResponse(problemDetails(
"urn:error:replayed-velocity-connection-request",
"Velocity request replayed",
409,
"This Velocity request ID has already been processed.",
instance,
));
logger.error({ err: error, event: "velocity.connection_report_failed" }, "Failed to record a confirmed Velocity connection");
return problemResponse(problemDetails(
"urn:error:service-unavailable",
"Service unavailable",
503,
"The connection report could not be recorded.",
instance,
));
}
}