fix(260716-n46): clamp blast-radius date window and resolve per-company Mimecast tenant
Bug 1: clamp dateWindow.end to Math.min(createdAt + 24h, Date.now()) so a
freshly-detected campaign (<24h old primary report) never sends Mimecast a
future end-date -- previously rejected as err_track_and_trace_invalid_end_date
and swallowed internally as a false-clean zero-count result.
Bug 2 (D-05): add company_id to the reports SELECT and, when the reporting
company has its own enabled mimecast_tenants row, resolve a tenant-scoped
client via getMimecastClientForTenant() and thread it into getBlastRadius as
{ client, cacheScope: companyId }. Falls back to the global env-configured
client when no company-specific tenant is registered.
This commit is contained in:
parent
12250c1e1d
commit
9951e53832
1 changed files with 54 additions and 10 deletions
|
|
@ -10,6 +10,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { requirePermission } from '@/lib/auth-utils';
|
||||
import postgresClient from '@/lib/services/postgres-client';
|
||||
import { getBlastRadius, type BlastRadiusResult } from '@/lib/services/mimecast-blast-radius';
|
||||
import { getMimecastClientForTenant } from '@/lib/services/mimecast-client';
|
||||
import { mergeTimeline } from '@/lib/services/phishing-timeline';
|
||||
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
|
@ -30,6 +31,7 @@ interface ReportRow {
|
|||
id: string;
|
||||
ticket_id: string;
|
||||
ticket_number: string | null;
|
||||
company_id: string | null;
|
||||
company_name: string | null;
|
||||
title: string | null;
|
||||
created_at: string;
|
||||
|
|
@ -83,6 +85,12 @@ interface AuditEventRow {
|
|||
created_at: string;
|
||||
}
|
||||
|
||||
interface MimecastTenantRow {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
base_url: string | null;
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
|
|
@ -112,8 +120,8 @@ export async function GET(
|
|||
// Bulk-fetch linked reports (+ join contacts for requester email — campaigns
|
||||
// has no recipients column, must be derived via this join).
|
||||
const reportsRes = await postgresClient.query<ReportRow>(
|
||||
`SELECT r.id::text, r.ticket_id::text, r.ticket_number, r.company_name,
|
||||
r.title, r.created_at::text,
|
||||
`SELECT r.id::text, r.ticket_id::text, r.ticket_number, r.company_id::text,
|
||||
r.company_name, r.title, r.created_at::text,
|
||||
c.email_address AS requester_email
|
||||
FROM reports r
|
||||
LEFT JOIN contacts c ON c.id = r.requester_contact_id
|
||||
|
|
@ -173,6 +181,7 @@ export async function GET(
|
|||
id: r.id,
|
||||
ticketId: r.ticket_id,
|
||||
ticketNumber: r.ticket_number,
|
||||
companyId: r.company_id,
|
||||
companyName: r.company_name,
|
||||
title: r.title,
|
||||
createdAt: r.created_at,
|
||||
|
|
@ -255,15 +264,50 @@ export async function GET(
|
|||
| { from?: { email?: string | null } | null }
|
||||
| null;
|
||||
const createdAt = new Date(primaryReport.createdAt);
|
||||
blastRadius = await getBlastRadius({
|
||||
sender: senderIndicator?.value ?? messageHeaders?.from?.email ?? '',
|
||||
recipient: primaryReport.requesterEmail ?? '',
|
||||
subject: primaryMessage?.subject ?? primaryReport.title ?? '',
|
||||
dateWindow: {
|
||||
start: new Date(createdAt.getTime() - 24 * 60 * 60 * 1000),
|
||||
end: new Date(createdAt.getTime() + 24 * 60 * 60 * 1000),
|
||||
|
||||
// Bug 2 (D-05): resolve the reporting company's own registered Mimecast
|
||||
// tenant, if one exists, and query it directly instead of the global
|
||||
// env-configured (Wulf) tenant. Falls back to the global client when
|
||||
// the company has no enabled mimecast_tenants row.
|
||||
let tenantOptions: { client: ReturnType<typeof getMimecastClientForTenant>; cacheScope: string } | undefined;
|
||||
if (primaryReport.companyId) {
|
||||
const tenantRes = await postgresClient.query<MimecastTenantRow>(
|
||||
`SELECT client_id, client_secret, base_url
|
||||
FROM mimecast_tenants
|
||||
WHERE company_id = $1 AND enabled = true
|
||||
ORDER BY id LIMIT 1`,
|
||||
[primaryReport.companyId]
|
||||
);
|
||||
const tenantRow = tenantRes.rows[0];
|
||||
if (tenantRow) {
|
||||
tenantOptions = {
|
||||
client: getMimecastClientForTenant({
|
||||
client_id: tenantRow.client_id,
|
||||
client_secret: tenantRow.client_secret,
|
||||
base_url: tenantRow.base_url ?? undefined,
|
||||
}),
|
||||
cacheScope: primaryReport.companyId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
blastRadius = await getBlastRadius(
|
||||
{
|
||||
sender: senderIndicator?.value ?? messageHeaders?.from?.email ?? '',
|
||||
recipient: primaryReport.requesterEmail ?? '',
|
||||
subject: primaryMessage?.subject ?? primaryReport.title ?? '',
|
||||
dateWindow: {
|
||||
start: new Date(createdAt.getTime() - 24 * 60 * 60 * 1000),
|
||||
// Bug 1: clamp the end of the window to now — a freshly-detected
|
||||
// campaign's primary report is <24h old, which would otherwise
|
||||
// produce a future `end` that Mimecast's real API rejects
|
||||
// (err_track_and_trace_invalid_end_date), swallowed internally by
|
||||
// searchDeliveredMessages() as a false zero-count "clean" result.
|
||||
end: new Date(Math.min(createdAt.getTime() + 24 * 60 * 60 * 1000, Date.now())),
|
||||
},
|
||||
},
|
||||
});
|
||||
tenantOptions
|
||||
);
|
||||
} else {
|
||||
blastRadius = { status: 'unavailable', reason: 'not_configured' };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue