feat(admission): add group VPN exceptions
CI / validate (push) Successful in 5m45s
Release / release (push) Successful in 7m21s

This commit is contained in:
dmg
2026-08-02 10:16:16 -04:00
parent 24808b0f8c
commit 71856bb869
34 changed files with 1820 additions and 138 deletions
@@ -1,7 +1,8 @@
import { appSettings } from "@minecraft-account-manager/database";
import { eq } from "drizzle-orm";
import { DEFAULT_ADMISSION_MESSAGES } from "@/lib/admission-settings";
import { db } from "@/lib/database";
import { saveDiscordSettings } from "../actions";
import { saveAdmissionSettings } from "../actions";
export const dynamic = "force-dynamic";
@@ -12,7 +13,11 @@ export default async function SettingsPage({
}) {
const query = await searchParams;
const [settings] = await db.select().from(appSettings).where(eq(appSettings.id, "default")).limit(1);
const message = settings?.registrationMessage ?? "Please register your Minecraft account before joining.";
const messages = {
registrationMessage: settings?.registrationMessage ?? DEFAULT_ADMISSION_MESSAGES.registrationMessage,
groupAccessDeniedMessage: settings?.groupAccessDeniedMessage ?? DEFAULT_ADMISSION_MESSAGES.groupAccessDeniedMessage,
vpnDeniedMessage: settings?.vpnDeniedMessage ?? DEFAULT_ADMISSION_MESSAGES.vpnDeniedMessage,
};
const guildId = process.env.DISCORD_GUILD_ID?.trim();
const inviteUrl = process.env.DISCORD_INVITE_URL?.trim();
@@ -29,23 +34,50 @@ export default async function SettingsPage({
</dl>
</section>
<form action={saveDiscordSettings} className="border border-line bg-panel p-7 shadow-[8px_8px_0_var(--color-shadow)] sm:p-9">
<form action={saveAdmissionSettings} className="border border-line bg-panel p-7 shadow-[8px_8px_0_var(--color-shadow)] sm:p-9">
{query.saved && <p className="mb-6 border-l-2 border-signal pl-4 font-mono text-xs font-bold uppercase tracking-wider" role="status">Settings saved</p>}
{query.error && <p className="mb-6 border-l-2 border-accent pl-4 text-sm" role="alert">Check the configuration value and try again.</p>}
<label className="block font-mono text-xs font-bold uppercase tracking-wider" htmlFor="registrationMessage">Denied-player message</label>
<textarea
className="mt-3 min-h-32 w-full resize-y border border-line bg-canvas px-4 py-3 text-sm leading-6 outline-none focus:border-accent"
defaultValue={message}
id="registrationMessage"
maxLength={500}
minLength={10}
name="registrationMessage"
required
/>
<fieldset className="space-y-7">
<legend className="font-display text-2xl font-black uppercase">Minecraft denial messages</legend>
<p className="text-sm leading-6 text-muted">Each plain-text message is returned for one admission outcome. Messages must be between 10 and 500 characters.</p>
<AdmissionMessageField description="Shown when the Minecraft identity is not registered." label="Registration required" name="registrationMessage" value={messages.registrationMessage} />
<AdmissionMessageField description="Shown when the effective group has Minecraft access disabled." label="Group access disabled" name="groupAccessDeniedMessage" value={messages.groupAccessDeniedMessage} />
<AdmissionMessageField description="Shown for VPN, proxy, or Tor connections when the effective group has no exception." label="VPN, proxy, or Tor denied" name="vpnDeniedMessage" value={messages.vpnDeniedMessage} />
</fieldset>
<button className="mt-8 border border-ink bg-ink px-6 py-3 font-mono text-xs font-bold uppercase tracking-[0.16em] text-canvas hover:bg-accent" type="submit">Save configuration</button>
</form>
</div>
</main>
);
}
function AdmissionMessageField({
description,
label,
name,
value,
}: {
description: string;
label: string;
name: "registrationMessage" | "groupAccessDeniedMessage" | "vpnDeniedMessage";
value: string;
}) {
const descriptionId = `${name}-description`;
return (
<div>
<label className="block font-mono text-xs font-bold uppercase tracking-wider" htmlFor={name}>{label}</label>
<p className="mt-2 text-xs leading-5 text-muted" id={descriptionId}>{description}</p>
<textarea
aria-describedby={descriptionId}
className="mt-3 min-h-28 w-full resize-y border border-line bg-canvas px-4 py-3 text-sm leading-6 outline-none focus:border-accent"
defaultValue={value}
id={name}
maxLength={500}
minLength={10}
name={name}
required
/>
</div>
);
}