import { resolveEffectiveGroup } from "@minecraft-account-manager/auth"; import { formatManagedDiscordNickname } from "@minecraft-account-manager/minecraft"; import { groups, ipIntelligence, ipObservations, minecraftAccounts, userGroupMemberships } from "@minecraft-account-manager/database"; import { and, desc, eq, isNull, or } from "drizzle-orm"; import { logout } from "@/app/auth/actions"; import { db } from "@/lib/database"; import { requireCurrentUser } from "@/lib/auth/user-session"; import { groupAccessAddresses } from "@/lib/access-address-groups"; import { discordIdentity } from "@/lib/discord-identity"; import { intelligenceSummary } from "@/lib/event-ip-summary"; import { NicknameNotice } from "@/components/nickname-notice"; import { addMinecraftAccount, removeMinecraftAccount, setPrimaryAccount, updateFirstName, } from "./actions"; function queryValue(value: string | string[] | undefined) { return Array.isArray(value) ? value[0] : value; } const errorMessages: Record = { "invalid-name": "Enter a valid name between 1 and 50 characters.", "invalid-username": "Java usernames use 3–16 letters, numbers, or underscores.", "already-registered": "That Minecraft account is already registered.", "unknown-account": "That account is no longer available.", "nickname-not-configured": "Discord nickname updates are not configured.", "nickname-update-failed": "Discord rejected the nickname update. An admin may need to adjust bot permissions.", "vpn-blocked": "Turn off your VPN, proxy, or Tor connection before adding a Minecraft account.", "ip-check-unavailable": "We could not verify your network, so account addition is temporarily blocked.", }; export const dynamic = "force-dynamic"; export default async function AccountPage({ searchParams, }: { searchParams: Promise>; }) { const user = await requireCurrentUser("/account"); const query = await searchParams; const error = queryValue(query.error); const unverified = queryValue(query.unverified); const [accounts, observations, discord, availableGroups] = await Promise.all([ db .select() .from(minecraftAccounts) .where(and(eq(minecraftAccounts.userId, user.id), isNull(minecraftAccounts.deletedAt))) .orderBy(desc(minecraftAccounts.isPrimary), minecraftAccounts.username), db .select({ id: ipObservations.id, ipAddress: ipObservations.ipAddress, source: ipObservations.source, classification: ipObservations.classification, observedAt: ipObservations.observedAt, intelligence: ipIntelligence.rawResponse, }) .from(ipObservations) .leftJoin(ipIntelligence, eq(ipIntelligence.ipAddress, ipObservations.ipAddress)) .where(eq(ipObservations.userId, user.id)) .orderBy(desc(ipObservations.observedAt)) .limit(100), discordIdentity(user), db.select({ id: groups.id, name: groups.name, accessEnabled: groups.accessEnabled, anonymizedNetworksAllowed: groups.anonymizedNetworksAllowed, isDefault: groups.isDefault }) .from(groups) .leftJoin(userGroupMemberships, eq(userGroupMemberships.groupId, groups.id)) .where(or(eq(groups.isDefault, true), eq(userGroupMemberships.userId, user.id))) .orderBy(desc(groups.isDefault), groups.name), ]); const addressGroups = groupAccessAddresses(observations); const primary = accounts.find((account) => account.isPrimary); const desiredNickname = user.firstName ? formatManagedDiscordNickname(user.firstName, primary?.username ?? null) : null; const explicitGroup = availableGroups.find((group) => !group.isDefault) ?? null; const defaultGroup = availableGroups.find((group) => group.isDefault) ?? null; const effectiveGroup = resolveEffectiveGroup(explicitGroup, defaultGroup); const nicknameUpdated = queryValue(query.nicknameUpdated); const nicknameExpected = queryValue(query.nicknameExpected); const nicknameError = error === "nickname-update-failed" || error === "nickname-not-configured" ? error : null; return (

Account registry

{user.firstName ?? user.discordUsername}

{error && !nicknameError && (

{errorMessages[error] ?? "The requested change could not be completed."}

)}

Whitelist identities

Minecraft accounts

{accounts.length} active
{accounts.map((account) => (

{account.username}

{account.isPrimary && Primary} {account.validationStatus === "verified" ? "UUID verified" : "User confirmed"}

{account.minecraftUuid ?? "UUID will be learned at game login"}

{!account.isPrimary &&
}
Remove

Remove {account.username}? Your Discord nickname will update automatically.

))} {accounts.length === 0 &&

No active Minecraft accounts. Add one before joining the server.

}
{unverified ? (

Mojang couldn’t verify “{unverified}”

Continue only if you are certain the spelling is correct.

Cancel
) : (
)}

Recent security activity

Access addresses

{addressGroups.length ? (

Similar IPv4 /24 and IPv6 /64 networks are grouped. Counts cover your 100 most recent observations.

{addressGroups.map((group) => { const summary = intelligenceSummary(group.intelligence); return (

{group.network}

{group.count} {group.count === 1 ? "observation" : "observations"}

Latest address {group.latestAddress}

{summary.location ?? "Location unavailable"} · {summary.classification ?? group.classification}

{group.sources.join(" + ")}
Last seen {group.latestObservedAt.toISOString()}
); })}
) :

No web or game access addresses have been recorded yet.

}
); }