134 lines
4.9 KiB
TypeScript
134 lines
4.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@hivegrid/db/pretenant-security.server", () => ({
|
|
appendIpAddressLogCapability: vi.fn(),
|
|
findIpAddressCapability: vi.fn(),
|
|
upsertIpAddressCapability: vi.fn(),
|
|
}));
|
|
vi.mock("~/utils/cache.server", () => ({
|
|
cachified: vi.fn(({ getFreshValue }) => getFreshValue()),
|
|
clearCacheKey: vi.fn(),
|
|
}));
|
|
vi.mock("~/utils/server/IpUtils", () => ({ getTrustedClientIpOrNull: vi.fn(() => "203.0.113.10") }));
|
|
vi.mock("~/utils/db/blacklist.db.server", () => ({
|
|
addBlacklistAttempt: vi.fn(),
|
|
addToBlacklist: vi.fn(),
|
|
findInBlacklist: vi.fn(() => null),
|
|
}));
|
|
// AD-22: Keep GeoIP and hashing hermetic while preserving deterministic hashes.
|
|
vi.mock("./geoip.server", () => ({
|
|
hashIp: (ip: string) => `hash-${ip.length}`,
|
|
lookupCountry: vi.fn(async () => null),
|
|
}));
|
|
|
|
import { appendIpAddressLogCapability, findIpAddressCapability, upsertIpAddressCapability } from "@hivegrid/db/pretenant-security.server";
|
|
import { addBlacklistAttempt, findInBlacklist } from "~/utils/db/blacklist.db.server";
|
|
import IpAddressServiceServer from "./IpAddressService.server";
|
|
|
|
const RAW_IP = "203.0.113.10";
|
|
const HASHED_IP = `hash-${RAW_IP.length}`;
|
|
const ipRecord = {
|
|
id: "ip_1",
|
|
ip: HASHED_IP,
|
|
provider: "",
|
|
type: "",
|
|
countryCode: "",
|
|
countryName: "",
|
|
regionCode: "",
|
|
regionName: "",
|
|
city: "",
|
|
zipCode: "",
|
|
latitude: null,
|
|
longitude: null,
|
|
metadata: "null",
|
|
createdAt: new Date("2026-01-01T00:00:00.000Z"),
|
|
updatedAt: new Date("2026-01-01T00:00:00.000Z"),
|
|
} as Awaited<ReturnType<typeof upsertIpAddressCapability>>;
|
|
|
|
const appendLogMock = vi.mocked(appendIpAddressLogCapability);
|
|
const findIpAddressMock = vi.mocked(findIpAddressCapability);
|
|
const upsertIpAddressMock = vi.mocked(upsertIpAddressCapability);
|
|
const findInBlacklistMock = vi.mocked(findInBlacklist);
|
|
const addBlacklistAttemptMock = vi.mocked(addBlacklistAttempt);
|
|
|
|
describe("IpAddressServiceServer", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
findInBlacklistMock.mockResolvedValue(null);
|
|
findIpAddressMock.mockResolvedValue(null);
|
|
upsertIpAddressMock.mockResolvedValue(ipRecord);
|
|
appendLogMock.mockResolvedValue({ id: "log_1" } as Awaited<ReturnType<typeof appendIpAddressLogCapability>>);
|
|
});
|
|
|
|
it("persists only the IP hash through pre-tenant capabilities (AD-22)", async () => {
|
|
const request = new Request("https://admin.hivegrid.de/login");
|
|
|
|
await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).resolves.toBeUndefined();
|
|
|
|
expect(findIpAddressMock).toHaveBeenCalledWith(HASHED_IP);
|
|
expect(upsertIpAddressMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
ip: HASHED_IP,
|
|
metadata: "null",
|
|
})
|
|
);
|
|
expect(appendLogMock).toHaveBeenCalledWith({
|
|
ipAddressId: "ip_1",
|
|
ip: HASHED_IP,
|
|
url: "/login",
|
|
action: "login",
|
|
description: "admin@example.com",
|
|
metadata: null,
|
|
error: null,
|
|
success: true,
|
|
});
|
|
expect(JSON.stringify(upsertIpAddressMock.mock.calls)).not.toContain(RAW_IP);
|
|
expect(JSON.stringify(appendLogMock.mock.calls)).not.toContain(RAW_IP);
|
|
});
|
|
|
|
it("does not replace the request IP with a fixed development address", async () => {
|
|
const request = new Request("https://admin.hivegrid.de/login");
|
|
|
|
await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).resolves.toBeUndefined();
|
|
|
|
expect(upsertIpAddressMock).toHaveBeenCalledWith(expect.objectContaining({ ip: HASHED_IP }));
|
|
expect(upsertIpAddressMock).not.toHaveBeenCalledWith(expect.objectContaining({ ip: "hash-14" }));
|
|
});
|
|
|
|
it("persists stable blacklisted error codes with a hashed IP", async () => {
|
|
findInBlacklistMock.mockResolvedValue({ id: "blacklist_1" } as never);
|
|
const request = new Request("https://admin.hivegrid.de/login");
|
|
|
|
await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).rejects.toThrow("blacklisted");
|
|
|
|
expect(addBlacklistAttemptMock).toHaveBeenCalledWith({ id: "blacklist_1" });
|
|
expect(appendLogMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
ip: HASHED_IP,
|
|
error: "blacklisted",
|
|
success: false,
|
|
})
|
|
);
|
|
});
|
|
|
|
it("persists stable unauthorized codes for provider-rejected IPs", async () => {
|
|
findIpAddressMock.mockResolvedValue({
|
|
...ipRecord,
|
|
provider: "ipapi.is",
|
|
metadata: JSON.stringify({ is_crawler: true }),
|
|
});
|
|
const request = new Request("https://admin.hivegrid.de/login");
|
|
|
|
await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).rejects.toThrow("unauthorized");
|
|
|
|
expect(upsertIpAddressMock).not.toHaveBeenCalled();
|
|
expect(appendLogMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
ip: HASHED_IP,
|
|
error: "unauthorized",
|
|
success: false,
|
|
})
|
|
);
|
|
});
|
|
});
|