Compare commits

..

No commits in common. "c835d5284e6b02efbe8d1e439c797a9549d1f7b2" and "f0cf08f2a43283603abe141e66b509c1f17f5d6f" have entirely different histories.

6 changed files with 46 additions and 64 deletions

View file

@ -1,7 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
export function GET(req: NextRequest) {
const target = new URL("/login", process.env.NEXT_PUBLIC_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({
adapter: PrismaAdapter(prisma),
providers: [Discord({ issuer: "https://discord.com" }), Github({ issuer: "https://github.com/login/oauth" }), Google],
providers: [Discord, Github({ issuer: "https://github.com/login/oauth" }), Google],
trustHost: true,
cookies: {
sessionToken: {
@ -26,7 +26,6 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
strategy: "database",
maxAge: 30 * 24 * 60 * 60,
},
pages: { signIn: "/login", error: "/login" },
callbacks: {
async signIn({ user }) {
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 EditMiiPage from "./pages/edit.tsx";
import PunishedPage from "./pages/punished.tsx";
import BreachNoticePage from "./pages/breach-notice.tsx";
import AccountDeletionPage from "./pages/account-deletion.tsx";
import AccountErrorPage from "./pages/account-error.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
@ -50,8 +50,8 @@ createRoot(document.getElementById("root")!).render(
<Route path="/privacy" element={<PrivacyPage />} />
<Route path="/terms-of-service" element={<TermsOfServicePage />} />
<Route path="/punished" element={<PunishedPage />} />
<Route path="/breach-notice" element={<BreachNoticePage />} />
<Route path="/account-deletion" element={<AccountDeletionPage />} />
<Route path="/account-error" element={<AccountErrorPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Layout>

View file

@ -1,37 +0,0 @@
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

@ -0,0 +1,41 @@
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,34 +1,20 @@
import { Icon } from "@iconify/react";
import { useStore } from "@nanostores/react";
import { Link, Navigate, useSearchParams } from "react-router";
import { Link, Navigate } from "react-router";
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() {
const $session = useStore(session);
const [searchParams] = useSearchParams();
if ($session === undefined) return <div className="p-6 text-center">Loading...</div>;
if ($session) return <Navigate to="/" replace />;
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;
const API_URL = import.meta.env.VITE_API_URL;
return (
<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">
<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">
<hr className="grow border-zinc-300" />
<span>Choose your login method</span>