Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:43:26 +02:00
commit 5aa3e8d685
700 files changed
+132801

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { exportJWK, generateKeyPair, SignJWT } from "jose";
const mocks = vi.hoisted(() => ({
oidcLoginAttempt: { deleteMany: vi.fn(), create: vi.fn(), findUnique: vi.fn(), delete: vi.fn() },
externalIdentity: { findUnique: vi.fn(), update: vi.fn(), upsert: vi.fn(), deleteMany: vi.fn() },
user: { update: vi.fn(), findUniqueOrThrow: vi.fn() },
transaction: vi.fn(async (input: unknown) => Array.isArray(input) ? Promise.all(input) : undefined),
}));
vi.mock("../src/db/prisma.js", () => ({ prisma: { oidcLoginAttempt: mocks.oidcLoginAttempt, externalIdentity: mocks.externalIdentity, user: mocks.user, $transaction: mocks.transaction } }));
const { oidcLoginAttempt, externalIdentity, user } = mocks;
import { loadEnv } from "../src/config/env.js";
import { consumeEntraAttempt, createEntraAuthorization, exchangeEntraCode, getEntraLinkStatus, isEntraConfigured, linkEntraIdentity, resolveEntraUser, unlinkEntraIdentity } from "../src/security/entraOidc.js";
const tenantId = "11111111-1111-4111-8111-111111111111";
const clientId = "22222222-2222-4222-8222-222222222222";
const baseInput = {
NODE_ENV: "test", DATABASE_URL: "postgresql://example.invalid/projectbrain", PROJECTBRAIN_DATA_SOURCE: "database",
PROJECTBRAIN_OWNER_USERNAME: "owner", PROJECTBRAIN_OWNER_PASSWORD: "correct-horse-battery-staple",
PROJECTBRAIN_AUTH_SECRET: "test-secret-with-at-least-thirty-two-characters", PROJECTBRAIN_PUBLIC_URL: "https://projectbrain.example.com",
PROJECTBRAIN_ENTRA_TENANT_ID: tenantId, PROJECTBRAIN_ENTRA_CLIENT_ID: clientId, PROJECTBRAIN_ENTRA_CLIENT_SECRET: "unit-test-client-secret",
};
const env = () => loadEnv(baseInput);
const profile = { tenantId, subject: "33333333-3333-4333-8333-333333333333", displayName: "Entra Owner", preferredUsername: "owner@example.com" };
const storedUser = { id: "owner-id", username: "owner", role: "owner" as const, credentialVersion: 1, disabledAt: null };
beforeEach(() => {
vi.clearAllMocks();
oidcLoginAttempt.deleteMany.mockResolvedValue({ count: 0 });
oidcLoginAttempt.create.mockResolvedValue({ id: "attempt-id" });
oidcLoginAttempt.delete.mockResolvedValue({ id: "attempt-id" });
externalIdentity.update.mockResolvedValue({ id: "identity-id" });
externalIdentity.upsert.mockResolvedValue({ id: "identity-id" });
externalIdentity.deleteMany.mockResolvedValue({ count: 1 });
user.update.mockResolvedValue(storedUser);
user.findUniqueOrThrow.mockResolvedValue(storedUser);
});
describe("Microsoft Entra OIDC service", () => {
it("creates a tenant-bound authorization URL with PKCE and stores only hashed state", async () => {
expect(isEntraConfigured(env())).toBe(true);
expect(isEntraConfigured(loadEnv({ ...baseInput, PROJECTBRAIN_ENTRA_TENANT_ID: undefined, PROJECTBRAIN_ENTRA_CLIENT_ID: undefined, PROJECTBRAIN_ENTRA_CLIENT_SECRET: undefined }))).toBe(false);
const authorization = new URL(await createEntraAuthorization(env(), { purpose: "login", returnTo: "//evil.example/path" }));
expect(authorization.hostname).toBe("login.microsoftonline.com");
expect(authorization.pathname).toContain(tenantId);
expect(authorization.searchParams.get("code_challenge_method")).toBe("S256");
const rawState = authorization.searchParams.get("state")!;
const stored = oidcLoginAttempt.create.mock.calls[0][0].data;
expect(stored.stateHash).not.toBe(rawState);
expect(stored.stateHash).toMatch(/^[a-f0-9]{64}$/);
expect(stored.returnTo).toBe("/");
await expect(createEntraAuthorization(env(), { purpose: "link" })).rejects.toThrow("ENTRA_LINK_USER_REQUIRED");
});
it("consumes valid state exactly once and rejects missing or expired attempts", async () => {
oidcLoginAttempt.findUnique.mockResolvedValueOnce(null);
expect(await consumeEntraAttempt(env(), "missing-state")).toBeUndefined();
oidcLoginAttempt.findUnique.mockResolvedValueOnce({ id: "expired", provider: "microsoft-entra", purpose: "login", userId: null, codeVerifier: "verifier", nonce: "nonce", returnTo: "/settings", expiresAt: new Date(Date.now() - 1_000) });
expect(await consumeEntraAttempt(env(), "expired-state")).toBeUndefined();
oidcLoginAttempt.findUnique.mockResolvedValueOnce({ id: "valid", provider: "microsoft-entra", purpose: "link", userId: "owner-id", codeVerifier: "verifier", nonce: "nonce", returnTo: "/settings", expiresAt: new Date(Date.now() + 60_000) });
await expect(consumeEntraAttempt(env(), "valid-state")).resolves.toEqual({ purpose: "link", userId: "owner-id", codeVerifier: "verifier", nonce: "nonce", returnTo: "/settings" });
expect(oidcLoginAttempt.delete).toHaveBeenCalledTimes(2);
});
it("validates Microsoft signature, issuer, audience, tenant, nonce and oid", async () => {
const { publicKey, privateKey } = await generateKeyPair("RS256");
const publicJwk = { ...await exportJWK(publicKey), kid: "unit-key", alg: "RS256", use: "sig" };
const sign = (nonce: string, oid = profile.subject) => new SignJWT({ tid: tenantId, oid, nonce, name: profile.displayName, preferred_username: profile.preferredUsername })
.setProtectedHeader({ alg: "RS256", kid: "unit-key" }).setIssuer(`https://login.microsoftonline.com/${tenantId}/v2.0`).setAudience(clientId).setIssuedAt().setExpirationTime("5m").sign(privateKey);
const idToken = await sign("expected-nonce");
vi.stubGlobal("fetch", vi.fn(async (input: string | URL | Request) => {
const url = String(input instanceof Request ? input.url : input);
return url.endsWith("/token")
? new Response(JSON.stringify({ id_token: idToken }), { status: 200, headers: { "content-type": "application/json" } })
: new Response(JSON.stringify({ keys: [publicJwk] }), { status: 200, headers: { "content-type": "application/json" } });
}));
try {
await expect(exchangeEntraCode(env(), "code", { purpose: "login", codeVerifier: "verifier", nonce: "expected-nonce", returnTo: "/" })).resolves.toEqual(profile);
await expect(exchangeEntraCode(env(), "code", { purpose: "login", codeVerifier: "verifier", nonce: "wrong-nonce", returnTo: "/" })).rejects.toThrow("ENTRA_NONCE_INVALID");
} finally {
vi.unstubAllGlobals();
}
}, 15_000);
it("resolves, links, reports and unlinks only durable mapped identities", async () => {
externalIdentity.findUnique.mockResolvedValueOnce(null);
expect(await resolveEntraUser(profile)).toBeUndefined();
externalIdentity.findUnique.mockResolvedValueOnce({ id: "identity-id", userId: storedUser.id, user: storedUser });
await expect(resolveEntraUser(profile)).resolves.toEqual({ id: storedUser.id, username: "owner", role: "owner", credentialVersion: 1 });
externalIdentity.findUnique.mockResolvedValueOnce({ id: "foreign", userId: "other-user" });
await expect(linkEntraIdentity(storedUser.id, profile)).rejects.toThrow("ENTRA_IDENTITY_ALREADY_LINKED");
externalIdentity.findUnique.mockResolvedValueOnce(null);
await expect(linkEntraIdentity(storedUser.id, profile)).resolves.toMatchObject({ id: storedUser.id });
externalIdentity.findUnique.mockResolvedValueOnce({ displayName: profile.displayName, preferredUsername: profile.preferredUsername });
await expect(getEntraLinkStatus(storedUser.id)).resolves.toEqual({ linked: true, displayName: profile.displayName, preferredUsername: profile.preferredUsername });
await unlinkEntraIdentity(storedUser.id);
expect(externalIdentity.deleteMany).toHaveBeenCalledWith({ where: { provider: "microsoft-entra", userId: storedUser.id } });
});
});