wulf-pulse/app/api/mimecast/mailbox-remediate/route.ts
lorentz bc3904de4e feat: mailbox remediation via Graph Mail.ReadWrite — search + move to Deleted Items from analysis dialog
- Add searchMailboxMessages, deleteMailboxMessage, moveToDeletedItems to MsGraphClient
- POST /api/mimecast/mailbox-remediate: search, move, delete actions with permission error handling
- DeliveredAnalysisDialog: Remove from mailbox panel with search → confirm → delete flow
  - Shows matching messages in mailbox with checkboxes, received time, read/unread status
  - Moves selected to Deleted Items (recoverable) via Graph API
  - Surfaces clear permission guidance if Mail.ReadWrite not yet granted
2026-04-01 10:09:38 -04:00

77 lines
2.9 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { getMsgraphClient, isMsgraphConfigured } from '@/lib/services/msgraph-factory';
export const dynamic = 'force-dynamic';
/**
* POST /api/mimecast/mailbox-remediate
* Body: { action: 'search' | 'delete' | 'move', userEmail, fromAddress?, subject?, messageIds? }
*
* Requires Mail.ReadWrite application permission on the Graph app registration.
*/
export async function POST(req: NextRequest) {
try {
if (!isMsgraphConfigured()) {
return NextResponse.json({ error: 'Microsoft Graph not configured' }, { status: 503 });
}
const { action, userEmail, fromAddress, subject, messageIds, hardDelete } = await req.json();
if (!userEmail) {
return NextResponse.json({ error: 'userEmail is required' }, { status: 400 });
}
const client = getMsgraphClient();
if (action === 'search') {
if (!fromAddress && !subject) {
return NextResponse.json({ error: 'fromAddress or subject required for search' }, { status: 400 });
}
const messages = await client.searchMailboxMessages({
userEmail,
fromAddress: fromAddress || undefined,
subject: subject || undefined,
maxResults: 100,
});
return NextResponse.json({ messages, total: messages.length });
}
if (action === 'delete' || action === 'move') {
if (!Array.isArray(messageIds) || messageIds.length === 0) {
return NextResponse.json({ error: 'messageIds array required' }, { status: 400 });
}
const results: { id: string; ok: boolean; error?: string }[] = [];
for (const id of messageIds) {
try {
if (action === 'delete' && hardDelete) {
await client.deleteMailboxMessage(userEmail, id);
} else {
await client.moveToDeletedItems(userEmail, id);
}
results.push({ id, ok: true });
} catch (err: any) {
results.push({ id, ok: false, error: err.message });
}
}
const succeeded = results.filter(r => r.ok).length;
const failed = results.filter(r => !r.ok).length;
return NextResponse.json({ results, succeeded, failed });
}
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
} catch (error: any) {
const isPermission = error.message?.includes('403') || error.message?.includes('AccessDenied') || error.message?.includes('Access is denied');
if (isPermission) {
return NextResponse.json({
error: 'Mail.ReadWrite permission not granted',
detail: 'Grant Mail.ReadWrite application permission to the Graph app in Azure Portal → App registrations → API permissions, then click Grant admin consent.',
permissionRequired: true,
}, { status: 403 });
}
console.error('[mailbox-remediate] error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}