Merge pull request 'mail-search: Akte-Kontext per Bearer' (#416) from feat/mailsearch-bearer into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web (dms-browser) (push) Failing after 48s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web (dms-browser) (push) Failing after 48s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
This commit was merged in pull request #416.
This commit is contained in:
commit
ddda4b93a7
4 files changed
+53
-11
No files matched your search
@@ -13,9 +13,9 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function req(params: Record<string, string>) {
|
||||
function req(params: Record<string, string>, headers?: Record<string, string>) {
|
||||
const search = new URLSearchParams(params).toString();
|
||||
return new NextRequest(`http://localhost/api/case-context?${search}`);
|
||||
return new NextRequest(`http://localhost/api/case-context?${search}`, { headers });
|
||||
}
|
||||
|
||||
describe("GET /api/case-context", () => {
|
||||
@@ -45,6 +45,24 @@ describe("GET /api/case-context", () => {
|
||||
expect(res.status).toBe(502);
|
||||
});
|
||||
|
||||
it("reicht mit Bearer nur das Token weiter und ignoriert user/tenant aus der URL", async () => {
|
||||
global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ Case: null, Notes: [], Reminders: [] }) });
|
||||
|
||||
const res = await GET(req({ module: "mail", type: "message", id: "m1", user: "fremd", tenant: "fremd" }, { authorization: "Bearer tok" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"http://workflow.internal:9700/objects/mail/message/m1/context",
|
||||
{ headers: { Authorization: "Bearer tok" } }
|
||||
);
|
||||
});
|
||||
|
||||
it("akzeptiert mit Bearer fehlende user/tenant", async () => {
|
||||
global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ Case: null, Notes: [], Reminders: [] }) });
|
||||
const res = await GET(req({ module: "mail", type: "message", id: "m1" }, { authorization: "Bearer tok" }));
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("liefert 400 bei fehlenden Pflichtparametern", async () => {
|
||||
const res = await GET(req({ module: "mail" }));
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
@@ -5,11 +5,9 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
// aufruft. Kein eigenes Akte-Datenmodell in Mail (Akzeptanzkriterium 2)
|
||||
// -- diese Route reicht die Antwort nur durch.
|
||||
//
|
||||
// X-User-Id: uebergangsweise Identitaet wie bei caseapi/approvalapi/
|
||||
// backchannelapi selbst etabliert (kein zentrales IAM fuer Workflow-
|
||||
// Zugriffe bisher). Bis zu einer zentralen Session-Anbindung traegt der
|
||||
// Aufrufer die Nutzerkennung mit (gleiche Konvention wie tenantSlug in
|
||||
// app/api/search/route.ts).
|
||||
// Mit Authorization: Bearer (Core-Token, Audience workflow) wird nur das
|
||||
// Token weitergereicht; X-User-Id/X-Tenant-Slug aus der URL gelten nur
|
||||
// ohne Token (Uebergang).
|
||||
|
||||
function backchannelBaseURL(): string {
|
||||
const base = process.env.WORKFLOW_BACKCHANNEL_URL;
|
||||
@@ -25,8 +23,10 @@ export async function GET(request: NextRequest) {
|
||||
const objectId = request.nextUrl.searchParams.get("id");
|
||||
const userId = request.nextUrl.searchParams.get("user");
|
||||
const tenantSlug = request.nextUrl.searchParams.get("tenant");
|
||||
if (!module || !objectType || !objectId || !userId || !tenantSlug) {
|
||||
return NextResponse.json({ error: "'module', 'type', 'id', 'user' und 'tenant' sind erforderlich" }, { status: 400 });
|
||||
const auth = request.headers.get("authorization") ?? "";
|
||||
const bearer = auth.startsWith("Bearer ") && auth.length > 7 ? auth : null;
|
||||
if (!module || !objectType || !objectId || (!bearer && (!userId || !tenantSlug))) {
|
||||
return NextResponse.json({ error: "'module', 'type', 'id' sowie Bearer-Token oder 'user' und 'tenant' sind erforderlich" }, { status: 400 });
|
||||
}
|
||||
|
||||
let base: string;
|
||||
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest) {
|
||||
try {
|
||||
upstream = await fetch(
|
||||
`${base}/objects/${encodeURIComponent(module)}/${encodeURIComponent(objectType)}/${encodeURIComponent(objectId)}/context`,
|
||||
{ headers: { "X-User-Id": userId, "X-Tenant-Slug": tenantSlug } }
|
||||
{ headers: bearer ? { Authorization: bearer } : { "X-User-Id": userId as string, "X-Tenant-Slug": tenantSlug as string } }
|
||||
);
|
||||
} catch (err) {
|
||||
// Akzeptanzkriterium 3: Netzwerkfehler zum Workflow-Board wird als
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// (Akzeptanzkriterium 2) -- gibt die vom Workflow-Board gelieferte
|
||||
// Struktur unveraendert durch.
|
||||
|
||||
import { workflowFetch } from "./http";
|
||||
|
||||
export interface CaseNote {
|
||||
ID: string;
|
||||
Text: string;
|
||||
@@ -42,7 +44,7 @@ export type CaseContextResult =
|
||||
export async function fetchCaseContext(module: string, objectType: string, objectId: string, tenant: string): Promise<CaseContextResult> {
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(`/api/case-context?module=${encodeURIComponent(module)}&type=${encodeURIComponent(objectType)}&id=${encodeURIComponent(objectId)}&tenant=${encodeURIComponent(tenant)}`);
|
||||
res = await workflowFetch(`/api/case-context?module=${encodeURIComponent(module)}&type=${encodeURIComponent(objectType)}&id=${encodeURIComponent(objectId)}&tenant=${encodeURIComponent(tenant)}`);
|
||||
} catch {
|
||||
return { status: "unavailable" };
|
||||
}
|
||||
|
||||
@@ -24,8 +24,30 @@ export function getSession(): Session {
|
||||
return session;
|
||||
}
|
||||
|
||||
let workflowSession: Session | undefined;
|
||||
|
||||
export function workflowBearerEnabled(): boolean {
|
||||
return authMode() === "token" && Boolean(process.env.NEXT_PUBLIC_CORE_API_BASE);
|
||||
}
|
||||
|
||||
export function getWorkflowSession(): Session {
|
||||
if (!workflowSession) {
|
||||
workflowSession = createSession({
|
||||
coreBase: process.env.NEXT_PUBLIC_CORE_API_BASE as string,
|
||||
module: "workflow",
|
||||
onLoginRequired: () => redirectToLogin(process.env.NEXT_PUBLIC_LOGIN_URL),
|
||||
});
|
||||
}
|
||||
return workflowSession;
|
||||
}
|
||||
|
||||
export function workflowFetch(input: string, init?: RequestInit): Promise<Response> {
|
||||
return workflowBearerEnabled() ? getWorkflowSession().fetch(input, init) : fetch(input, init);
|
||||
}
|
||||
|
||||
export function resetSessionForTests(): void {
|
||||
session = undefined;
|
||||
workflowSession = undefined;
|
||||
}
|
||||
|
||||
export function apiFetch(input: string, init?: RequestInit): Promise<Response> {
|
||||
|
||||
Reference in new issue
Block a user