mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-11-12 07:59:17 +00:00
Compare commits
2 commits
f0cf08f2a4
...
c835d5284e
| Author | SHA1 | Date | |
|---|---|---|---|
| c835d5284e | |||
| b2e1ab2d2f |
6 changed files with 64 additions and 46 deletions
7
backend/src/app/login/route.ts
Normal file
7
backend/src/app/login/route.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -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()) : [];
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
37
frontend/src/pages/account-error.tsx
Normal file
37
frontend/src/pages/account-error.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue