fix: auth errors

- Fixes Discord not logging in
- Also shows the login page but with an error message for future errors
This commit is contained in:
trafficlunar 2026-10-24 00:30:01 +01:00
parent f0cf08f2a4
commit b2e1ab2d2f
6 changed files with 64 additions and 46 deletions

View file

@ -0,0 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
export function GET(req: NextRequest) {
const target = new URL("/login", process.env.FRONTEND_URL ?? "http://localhost:5173");
target.search = req.nextUrl.search; // keeps ?error=
return NextResponse.redirect(target);
}

View file

@ -8,7 +8,7 @@ import { prisma } from "@/lib/prisma";
export const { handlers, signIn, signOut, auth } = NextAuth({ export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
providers: [Discord, Github({ issuer: "https://github.com/login/oauth" }), Google], providers: [Discord({ issuer: "https://discord.com" }), Github({ issuer: "https://github.com/login/oauth" }), Google],
trustHost: true, trustHost: true,
cookies: { cookies: {
sessionToken: { sessionToken: {
@ -26,6 +26,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
strategy: "database", strategy: "database",
maxAge: 30 * 24 * 60 * 60, maxAge: 30 * 24 * 60 * 60,
}, },
pages: { signIn: "/login", error: "/login" },
callbacks: { callbacks: {
async signIn({ user }) { async signIn({ user }) {
const blacklist = process.env.BLACKLISTED_EMAILS ? process.env.BLACKLISTED_EMAILS.split(",").map((item) => item.trim().toLowerCase()) : []; const blacklist = process.env.BLACKLISTED_EMAILS ? process.env.BLACKLISTED_EMAILS.split(",").map((item) => item.trim().toLowerCase()) : [];

View file

@ -23,8 +23,8 @@ import ReportMiiPage from "./pages/report/mii.tsx";
import ReportUserPage from "./pages/report/user.tsx"; import ReportUserPage from "./pages/report/user.tsx";
import EditMiiPage from "./pages/edit.tsx"; import EditMiiPage from "./pages/edit.tsx";
import PunishedPage from "./pages/punished.tsx"; import PunishedPage from "./pages/punished.tsx";
import BreachNoticePage from "./pages/breach-notice.tsx";
import AccountDeletionPage from "./pages/account-deletion.tsx"; import AccountDeletionPage from "./pages/account-deletion.tsx";
import AccountErrorPage from "./pages/account-error.tsx";
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
@ -50,8 +50,8 @@ createRoot(document.getElementById("root")!).render(
<Route path="/privacy" element={<PrivacyPage />} /> <Route path="/privacy" element={<PrivacyPage />} />
<Route path="/terms-of-service" element={<TermsOfServicePage />} /> <Route path="/terms-of-service" element={<TermsOfServicePage />} />
<Route path="/punished" element={<PunishedPage />} /> <Route path="/punished" element={<PunishedPage />} />
<Route path="/breach-notice" element={<BreachNoticePage />} />
<Route path="/account-deletion" element={<AccountDeletionPage />} /> <Route path="/account-deletion" element={<AccountDeletionPage />} />
<Route path="/account-error" element={<AccountErrorPage />} />
<Route path="*" element={<NotFoundPage />} /> <Route path="*" element={<NotFoundPage />} />
</Routes> </Routes>
</Layout> </Layout>

View file

@ -0,0 +1,37 @@
const ERRORS: Record<string, { title: string; messages: string[] }> = {
OAuthAccountNotLinked: {
title: "Account already exists",
messages: [
"An account with this email already exists, but it was created using a different login method.",
"Please sign in with the provider you originally used (Google, GitHub, or Discord).",
],
},
Configuration: {
title: "Server error",
messages: ["There is a problem with the server configuration. Please e-mail hello@trafficlunar.net or message @trafficlunar on Discord"],
},
};
const FALLBACK = {
title: "Sign in failed",
messages: ["Something went wrong while signing in. Please try again."],
};
export default function AccountErrorPage() {
const code = new URLSearchParams(window.location.search).get("error") ?? "";
const { title, messages } = ERRORS[code] ?? FALLBACK;
return (
<div className="grow flex items-center justify-center">
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-8 max-w-lg w-full text-center flex flex-col gap-2">
<h2 className="text-3xl font-black">{title}</h2>
{messages.map((message) => (
<p key={message}>{message}</p>
))}
<a href="/" className="mt-4 mx-auto px-4 py-2 rounded-lg bg-amber-500 text-white font-bold hover:bg-amber-600 transition-colors">
Back to home
</a>
</div>
</div>
);
}

View file

@ -1,41 +0,0 @@
export default function BreachNoticePage() {
return (
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl p-6">
<h1 className="text-2xl font-bold">Breach Notice</h1>
<h2 className="font-light">
<strong className="font-medium">Date:</strong> 13 May 2026
</h2>
<hr className="border-black/20 mt-1 mb-4" />
<p>
I have been made aware today (13 May 2026) that there has been a security breach on 6 May 2026 of the website. The following information have been
collected:
</p>
<ul className="list-disc list-inside indent-4 my-4">
<li>User IDs</li>
<li>Email addresses</li>
<li>Email verfication statuses</li>
<li>Avatar images</li>
<li>About Me descriptions</li>
<li>Profile creation dates</li>
<li>Profile update dates</li>
<li>Profile picture update dates</li>
<li>Amount of Miis you have liked</li>
</ul>
<p className="mb-2">
The only data that wouldn't normally be viewable is your email address (the same thing you would give to people to receive emails), everything else was
publicly viewable (such as on the profile pages). The code that caused this to happen has already been fixed.
</p>
<p className="mb-2">
As far as I'm aware, no other information have been collected. The breach has been notified to the Information Commissioner's Office. Please take care
when opening any suspicious emails. <strong>TomodachiShare does not send any emails.</strong> Please do not click on any suspicious links.
</p>
<p>
<strong>Your Discord/Google/GitHub accounts are completely unaffected by this.</strong>
</p>
</div>
);
}

View file

@ -1,20 +1,34 @@
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { useStore } from "@nanostores/react"; import { useStore } from "@nanostores/react";
import { Link, Navigate } from "react-router"; import { Link, Navigate, useSearchParams } from "react-router";
import { session } from "../session"; import { session } from "../session";
const API_URL = import.meta.env.VITE_API_URL;
const ERRORS: Record<string, string> = {
OAuthAccountNotLinked:
"An account with this email already exists, but it was created with a different login method. Please sign in with the provider you originally used.",
AccessDenied: "You don't have permission to sign in with this account.",
Configuration: "There is a problem with the server configuration. Please e-mail hello@trafficlunar.net or message @trafficlunar on Discord",
};
export default function LoginPage() { export default function LoginPage() {
const $session = useStore(session); const $session = useStore(session);
const [searchParams] = useSearchParams();
if ($session === undefined) return <div className="p-6 text-center">Loading...</div>; if ($session === undefined) return <div className="p-6 text-center">Loading...</div>;
if ($session) return <Navigate to="/" replace />; if ($session) return <Navigate to="/" replace />;
const API_URL = import.meta.env.VITE_API_URL; const code = searchParams.get("error");
const errorMessage = code ? (ERRORS[code] ?? "Something went wrong while signing in. Please try again. Message trafficlunar if this continues") : null;
return ( return (
<div className="grow flex items-center justify-center"> <div className="grow flex items-center justify-center">
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg px-10 py-12 max-w-md text-center"> <div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg px-10 py-12 max-w-md text-center">
<h1 className="text-3xl font-bold mb-4">Welcome to TomodachiShare!</h1> <h1 className="text-3xl font-bold mb-4">Welcome to TomodachiShare!</h1>
{errorMessage && <p className="mb-6 rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm font-medium text-red-700">Error: {errorMessage}</p>}
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mb-8"> <div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mb-8">
<hr className="grow border-zinc-300" /> <hr className="grow border-zinc-300" />
<span>Choose your login method</span> <span>Choose your login method</span>