Merge pull request #160 from VAR-Virtual-Air-Rescue/staging

Fixed Wrong IP being loged
This commit was merged in pull request #160.
This commit is contained in:
PxlLoewe
2026-02-01 11:50:14 +01:00
committed by GitHub
7 changed files with 135 additions and 13 deletions

View File

@@ -0,0 +1,19 @@
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
const AdminAccountLogLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = session.user;
if (!user?.permissions.includes("ADMIN_USER_ADVANCED"))
return <Error title="Keine Berechtigung" statusCode={403} />;
return <>{children}</>;
};
AdminAccountLogLayout.displayName = "AdminAccountLogLayout";
export default AdminAccountLogLayout;

View File

@@ -0,0 +1,91 @@
"use client";
import { LogsIcon } from "lucide-react";
import { PaginatedTable } from "../../../_components/PaginatedTable";
import Link from "next/link";
import { ColumnDef } from "@tanstack/react-table";
import { Log, Prisma, User } from "@repo/db";
export default () => {
return (
<>
<PaginatedTable
stickyHeaders
initialOrderBy={[{ id: "timestamp", desc: true }]}
prismaModel="log"
showSearch
include={{
User: true,
}}
getFilter={(searchTerm) =>
({
OR: [
{
User: {
firstname: { contains: searchTerm, mode: "insensitive" },
lastname: { contains: searchTerm, mode: "insensitive" },
publicId: { contains: searchTerm, mode: "insensitive" },
},
},
{ deviceId: { contains: searchTerm, mode: "insensitive" } },
{ ip: { contains: searchTerm, mode: "insensitive" } },
],
}) as Prisma.LogWhereInput
}
columns={
[
{
header: "ID",
accessorKey: "id",
},
{
header: "Aktion",
accessorKey: "action",
cell: ({ row }) => {
const action = row.original.type;
if (action !== "PROFILE_CHANGE") {
return <span className="text-blue-500">{action}</span>;
} else {
return (
<span className="text-yellow-500">{`${row.original.field} von "${row.original.oldValue}" zu "${row.original.newValue}"`}</span>
);
}
},
},
{
header: "IP",
accessorKey: "ip",
},
{
header: "Browser-ID",
accessorKey: "deviceId",
},
{
header: "Zeitstempel",
accessorKey: "timestamp",
cell: (info) => new Date(info.getValue<string>()).toLocaleString("de-DE"),
},
{
header: "Benutzer",
accessorKey: "userId",
cell: ({ row }) => {
return (
<Link href={`/admin/user/${row.original.userId}`} className={"link"}>
{row.original.User
? `${row.original.User.firstname} ${row.original.User.lastname} - ${row.original.User.publicId}`
: "Unbekannt"}
</Link>
);
},
},
] as ColumnDef<Log & { User: User }>[]
}
leftOfSearch={
<span className="flex items-center gap-2">
<LogsIcon className="h-5 w-5" /> Account Log
</span>
}
/>
</>
);
};

View File

@@ -102,8 +102,13 @@ export const ProfileForm = ({
userId: user.id, userId: user.id,
}); });
} }
const ip = await fetch("https://api.ipify.org/?format=json")
.then((res) => res.json())
.then((data) => data.ip);
if (user.firstname !== values.firstname) { if (user.firstname !== values.firstname) {
await logAction("PROFILE_CHANGE", { await logAction("PROFILE_CHANGE", {
ip,
field: "firstname", field: "firstname",
oldValue: user.firstname, oldValue: user.firstname,
newValue: values.firstname, newValue: values.firstname,
@@ -111,6 +116,7 @@ export const ProfileForm = ({
} }
if (user.lastname !== values.lastname) { if (user.lastname !== values.lastname) {
await logAction("PROFILE_CHANGE", { await logAction("PROFILE_CHANGE", {
ip,
field: "lastname", field: "lastname",
oldValue: user.lastname, oldValue: user.lastname,
newValue: values.lastname, newValue: values.lastname,
@@ -118,6 +124,7 @@ export const ProfileForm = ({
} }
if (user.email !== values.email) { if (user.email !== values.email) {
await logAction("PROFILE_CHANGE", { await logAction("PROFILE_CHANGE", {
ip,
field: "email", field: "email",
oldValue: user.email, oldValue: user.email,
newValue: values.email, newValue: values.email,

View File

@@ -48,9 +48,11 @@ export const Login = () => {
return; return;
} }
console.log("data", data); const ip = await fetch("https://api.ipify.org/?format=json")
.then((res) => res.json())
.then((data) => data.ip);
await logAction("LOGIN"); await logAction("LOGIN", { ip });
redirect(searchParams.get("redirect") || "/"); redirect(searchParams.get("redirect") || "/");
} catch (error) { } catch (error) {
showBoundary(error); showBoundary(error);

View File

@@ -26,6 +26,7 @@ export async function getOrSetDeviceId() {
export const logAction = async ( export const logAction = async (
type: LOG_TYPE, type: LOG_TYPE,
otherValues?: { otherValues?: {
ip: string;
field?: string; field?: string;
oldValue?: string; oldValue?: string;
newValue?: string; newValue?: string;
@@ -35,13 +36,6 @@ export const logAction = async (
const headersList = await headers(); const headersList = await headers();
const user = await getServerSession(); const user = await getServerSession();
console.log(Array.from(headersList.entries()));
const ip =
headersList.get("X-Forwarded-For") ||
headersList.get("Forwarded") ||
headersList.get("X-Real-IP");
const deviceId = await getOrSetDeviceId(); const deviceId = await getOrSetDeviceId();
if (type == "LOGIN" || type == "REGISTER") { if (type == "LOGIN" || type == "REGISTER") {
const existingLogs = await prisma.log.findMany({ const existingLogs = await prisma.log.findMany({
@@ -52,7 +46,7 @@ export const logAction = async (
}, },
OR: [ OR: [
{ {
ip: ip, ip: otherValues?.ip,
}, },
{ {
deviceId: deviceId, deviceId: deviceId,
@@ -82,7 +76,7 @@ export const logAction = async (
browser: headersList.get("user-agent") || "unknown", browser: headersList.get("user-agent") || "unknown",
userId: user?.user.id || otherValues?.userId, userId: user?.user.id || otherValues?.userId,
deviceId: deviceId, deviceId: deviceId,
ip, ip: otherValues?.ip,
...otherValues, ...otherValues,
}, },
}); });

View File

@@ -94,7 +94,12 @@ export const Register = () => {
return; return;
} }
await sendVerificationLink(user.id); await sendVerificationLink(user.id);
const ip = await fetch("https://api.ipify.org/?format=json")
.then((res) => res.json())
.then((data) => data.ip);
await logAction("REGISTER", { await logAction("REGISTER", {
ip: ip,
userId: user.id, userId: user.id,
}); });
await signIn("credentials", { await signIn("credentials", {

View File

@@ -6,7 +6,6 @@ import {
RocketIcon, RocketIcon,
ReaderIcon, ReaderIcon,
DownloadIcon, DownloadIcon,
UpdateIcon,
ActivityLogIcon, ActivityLogIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import Link from "next/link"; import Link from "next/link";
@@ -14,7 +13,7 @@ import { WarningAlert } from "./ui/PageAlert";
import { getServerSession } from "api/auth/[...nextauth]/auth"; import { getServerSession } from "api/auth/[...nextauth]/auth";
import { Error } from "./Error"; import { Error } from "./Error";
import Image from "next/image"; import Image from "next/image";
import { Loader, Plane, Radar, Workflow } from "lucide-react"; import { Plane, Radar, Workflow } from "lucide-react";
import { BookingButton } from "./BookingButton"; import { BookingButton } from "./BookingButton";
export const VerticalNav = async () => { export const VerticalNav = async () => {
@@ -103,6 +102,11 @@ export const VerticalNav = async () => {
<Link href="/admin/penalty">Audit-Log</Link> <Link href="/admin/penalty">Audit-Log</Link>
</li> </li>
)} )}
{session.user.permissions.includes("ADMIN_USER_ADVANCED") && (
<li>
<Link href="/admin/account-log">Account Log</Link>
</li>
)}
{session.user.permissions.includes("ADMIN_CHANGELOG") && ( {session.user.permissions.includes("ADMIN_CHANGELOG") && (
<li> <li>
<Link href="/admin/changelog">Changelog</Link> <Link href="/admin/changelog">Changelog</Link>