feat: Mimecast multi-tenant held mail viewer
- migration 062: mimecast_tenants table (company_id, client_id/secret, account_code) - Seed Wulf (CUSA13A95) + Seubert (CUSA96A181) tenants - MimecastClient.getHeldMessages(): full pagination via meta.pagination.next cursor (API always returns 10/page regardless of pageSize param, totalCount in meta) - getMimecastClientForTenant() factory for per-tenant instantiation - GET /api/mimecast/held?tenantId=&recipient= — fetches all tenants in parallel, merges + sorts by date, returns per-tenant counts + combined messages[] - Held Mail tab on /admin/sync/mimecast (on-demand load, recipient filter, tenant badges, policy filter dropdown, DMARC/impersonation highlighted red)
This commit is contained in:
parent
a98c0daf15
commit
fcdec8e38b
12 changed files with 2208 additions and 27 deletions
|
|
@ -73,6 +73,32 @@ export interface MimecastMessageInfo {
|
|||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface MimecastHeldMessage {
|
||||
id: string;
|
||||
subject: string;
|
||||
from: string;
|
||||
fromDisplay: string;
|
||||
to: string;
|
||||
toDisplay: string;
|
||||
dateReceived: string;
|
||||
reason: string;
|
||||
reasonCode: string;
|
||||
policyInfo: string;
|
||||
route: string;
|
||||
hasAttachments: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface MimecastCloudUser {
|
||||
emailAddress: string;
|
||||
domain: string;
|
||||
lockedOut: boolean;
|
||||
status?: string;
|
||||
name?: string;
|
||||
alias?: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface PaginatedResult<T> {
|
||||
items: T[];
|
||||
nextCursor: string | null;
|
||||
|
|
@ -398,6 +424,87 @@ export class MimecastClient {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Cloud Gateway ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* GET /user/cloud-gateway/v1/users?emailAddress=...&domain=...
|
||||
*/
|
||||
async getCloudUser(emailAddress: string, domain: string): Promise<MimecastCloudUser | null> {
|
||||
const data = await this.request<any>('GET', '/user/cloud-gateway/v1/users', undefined, {
|
||||
emailAddress,
|
||||
domain,
|
||||
});
|
||||
const user = data?.value?.[0] ?? data?.data?.[0] ?? null;
|
||||
if (!user) return null;
|
||||
return {
|
||||
emailAddress: user.emailAddress ?? emailAddress,
|
||||
domain: user.domain ?? domain,
|
||||
lockedOut: user.lockedOut ?? false,
|
||||
status: user.status ?? undefined,
|
||||
name: user.name ?? undefined,
|
||||
alias: user.alias ?? undefined,
|
||||
_raw: user,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Held Messages ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* POST /api/gateway/get-hold-message-list
|
||||
* admin: true = see user-level hold queues as an admin
|
||||
* pageSize is ignored by the API (always returns 10); paginate via meta.pagination.next
|
||||
* Fetches ALL pages up to maxMessages limit.
|
||||
*/
|
||||
async getHeldMessages(options: {
|
||||
recipient?: string;
|
||||
maxMessages?: number;
|
||||
} = {}): Promise<{ messages: MimecastHeldMessage[]; totalCount: number }> {
|
||||
const maxMessages = options.maxMessages ?? 500;
|
||||
const all: MimecastHeldMessage[] = [];
|
||||
let cursor: string | null = null;
|
||||
let totalCount = 0;
|
||||
|
||||
do {
|
||||
const reqBody: any = { admin: true };
|
||||
if (options.recipient) {
|
||||
reqBody.searchBy = { fieldName: 'recipient', value: options.recipient };
|
||||
}
|
||||
|
||||
const body: any = { data: [reqBody] };
|
||||
if (cursor) {
|
||||
body.meta = { pagination: { pageToken: cursor } };
|
||||
}
|
||||
|
||||
const result = await this.request<any>('POST', '/api/gateway/get-hold-message-list', body);
|
||||
const pagination = result?.meta?.pagination ?? {};
|
||||
const msgs: any[] = result?.data ?? [];
|
||||
|
||||
if (totalCount === 0) totalCount = pagination.totalCount ?? msgs.length;
|
||||
|
||||
for (const m of msgs) {
|
||||
all.push({
|
||||
id: m.id,
|
||||
subject: m.subject ?? '',
|
||||
from: m.fromHeader?.emailAddress ?? m.from?.emailAddress ?? '',
|
||||
fromDisplay: m.fromHeader?.displayableName ?? m.from?.displayableName ?? '',
|
||||
to: m.to?.emailAddress ?? '',
|
||||
toDisplay: m.to?.displayableName ?? '',
|
||||
dateReceived: m.dateReceived ?? '',
|
||||
reason: m.reason ?? '',
|
||||
reasonCode: m.reasonCode ?? '',
|
||||
policyInfo: m.policyInfo ?? '',
|
||||
route: m.route ?? '',
|
||||
hasAttachments: m.hasAttachments ?? false,
|
||||
size: m.size ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
cursor = pagination.next ?? null;
|
||||
} while (cursor && all.length < maxMessages);
|
||||
|
||||
return { messages: all, totalCount };
|
||||
}
|
||||
|
||||
// ── Account ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -436,3 +543,17 @@ export function getMimecastClient(): MimecastClient {
|
|||
}
|
||||
return _client;
|
||||
}
|
||||
|
||||
export function getMimecastClientForTenant(tenant: {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
base_url?: string;
|
||||
account_code?: string;
|
||||
}): MimecastClient {
|
||||
return new MimecastClient({
|
||||
clientId: tenant.client_id,
|
||||
clientSecret: tenant.client_secret,
|
||||
baseUrl: tenant.base_url ?? 'https://api.services.mimecast.com',
|
||||
accountCode: tenant.account_code ?? '',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue