feat(portal): refine account identity controls
CI / validate (push) Successful in 4m51s
Release / release (push) Successful in 6m36s

This commit is contained in:
dmg
2026-08-01 18:06:30 -04:00
parent 86c87153b4
commit 19a5d04178
17 changed files with 357 additions and 43 deletions
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { groupAccessAddresses } from "./access-address-groups";
describe("groupAccessAddresses", () => {
it("collapses repeated observations from the same network into one recent summary", () => {
const groups = groupAccessAddresses([
{ id: "old", ipAddress: "198.51.100.21", source: "web", classification: "clear", observedAt: new Date("2026-08-01T10:00:00Z"), intelligence: null },
{ id: "new", ipAddress: "198.51.100.240", source: "game", classification: "clear", observedAt: new Date("2026-08-01T12:00:00Z"), intelligence: { provider: "proxycheck" } },
{ id: "other", ipAddress: "203.0.113.9", source: "web", classification: "vpn", observedAt: new Date("2026-08-01T11:00:00Z"), intelligence: null },
]);
expect(groups).toHaveLength(2);
expect(groups[0]).toMatchObject({
network: "198.51.100.0/24",
latestAddress: "198.51.100.240",
sources: ["game", "web"],
count: 2,
classification: "clear",
intelligence: { provider: "proxycheck" },
});
expect(groups[0]?.latestObservedAt.toISOString()).toBe("2026-08-01T12:00:00.000Z");
});
});
+60
View File
@@ -0,0 +1,60 @@
import { addressGroup } from "@minecraft-account-manager/network";
type AccessObservation = {
id: string;
ipAddress: string;
source: string;
classification: string;
observedAt: Date;
intelligence: Record<string, unknown> | null;
};
export type AccessAddressGroup = {
network: string;
latestAddress: string;
sources: string[];
count: number;
firstObservedAt: Date;
latestObservedAt: Date;
classification: string;
intelligence: Record<string, unknown> | null;
};
export function groupAccessAddresses(observations: AccessObservation[]) {
const groups = new Map<string, AccessAddressGroup & { sourceSet: Set<string> }>();
for (const observation of observations) {
const network = addressGroup(observation.ipAddress);
const existing = groups.get(network);
if (!existing) {
groups.set(network, {
network,
latestAddress: observation.ipAddress,
sources: [],
sourceSet: new Set([observation.source]),
count: 1,
firstObservedAt: observation.observedAt,
latestObservedAt: observation.observedAt,
classification: observation.classification,
intelligence: observation.intelligence,
});
continue;
}
existing.count += 1;
existing.sourceSet.add(observation.source);
if (observation.observedAt < existing.firstObservedAt) {
existing.firstObservedAt = observation.observedAt;
}
if (observation.observedAt > existing.latestObservedAt) {
existing.latestAddress = observation.ipAddress;
existing.latestObservedAt = observation.observedAt;
existing.classification = observation.classification;
existing.intelligence = observation.intelligence;
}
}
return [...groups.values()]
.map(({ sourceSet, ...group }) => ({ ...group, sources: [...sourceSet].sort() }))
.sort((left, right) => right.latestObservedAt.getTime() - left.latestObservedAt.getTime());
}
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";
import { hasDiscordNicknameConfirmation } from "./dashboard-change-confirmation";
describe("dashboard identity change confirmation", () => {
it("accepts only the explicit Discord nickname confirmation value", () => {
expect(hasDiscordNicknameConfirmation(new FormData())).toBe(false);
const declined = new FormData();
declined.set("confirmDiscordNickname", "no");
expect(hasDiscordNicknameConfirmation(declined)).toBe(false);
const confirmed = new FormData();
confirmed.set("confirmDiscordNickname", "yes");
expect(hasDiscordNicknameConfirmation(confirmed)).toBe(true);
});
});
@@ -0,0 +1,3 @@
export function hasDiscordNicknameConfirmation(formData: FormData) {
return formData.get("confirmDiscordNickname") === "yes";
}