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
This commit is contained in:
parent
c2ebbe586b
commit
bc3904de4e
3 changed files with 316 additions and 1 deletions
77
app/api/mimecast/mailbox-remediate/route.ts
Normal file
77
app/api/mimecast/mailbox-remediate/route.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
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 });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue