- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift) - LogLift evidence pipeline (migration 078): upload webhook, B2 storage client, receiver/matcher, EventLogCollector PowerShell script - IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket xrefs, applications/configurations browse pages + apply/revert/audit endpoints - Link-aware analyzer bundles (migration 073) + provider toggle (migration 074): link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion panels, analyze-bundle endpoint - Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts admin page, reconciler service, resolve endpoints - Dashboard overhaul: integration-health service + alerts, overview/health endpoints - Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
3 KiB
TypeScript
105 lines
3 KiB
TypeScript
/**
|
|
* GET /api/analyzer/share/recipients
|
|
*
|
|
* Returns suggestions for the share modal:
|
|
* - recent: the last few unique recipient emails the calling user has
|
|
* shared with (from analyzer_shares).
|
|
* - directory: the AD/Microsoft Graph user directory, filtered to
|
|
* ALLOWED_SHARE_DOMAINS and active accounts only.
|
|
*
|
|
* The share endpoint itself still validates the domain server-side, so
|
|
* the directory list is a UX nicety, not a security boundary.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAuth } from '@/lib/auth-utils';
|
|
import postgresClient from '@/lib/services/postgres-client';
|
|
|
|
const RECENT_LIMIT = 5;
|
|
|
|
function getAllowedDomains(): string[] {
|
|
const raw = process.env.ALLOWED_SHARE_DOMAINS ?? '';
|
|
return raw
|
|
.split(',')
|
|
.map((d) => d.trim().toLowerCase())
|
|
.filter((d) => d.length > 0);
|
|
}
|
|
|
|
interface RecentRow {
|
|
shared_with_email: string;
|
|
last_shared_at: Date | string;
|
|
}
|
|
|
|
interface DirectoryRow {
|
|
email: string;
|
|
display_name: string | null;
|
|
job_title: string | null;
|
|
department: string | null;
|
|
}
|
|
|
|
function toIso(d: Date | string): string {
|
|
return d instanceof Date ? d.toISOString() : new Date(d).toISOString();
|
|
}
|
|
|
|
function emailDomain(email: string): string {
|
|
return email.split('@')[1]?.toLowerCase() ?? '';
|
|
}
|
|
|
|
export async function GET(_request: NextRequest) {
|
|
const { session, error } = await requireAuth();
|
|
if (error) return error;
|
|
|
|
const userId = (session?.user as { id: string } | undefined)?.id ?? null;
|
|
const allowedDomains = getAllowedDomains();
|
|
|
|
if (allowedDomains.length === 0) {
|
|
return NextResponse.json({
|
|
recent: [],
|
|
directory: [],
|
|
allowedDomains: [],
|
|
});
|
|
}
|
|
|
|
const recentRows = userId
|
|
? (
|
|
await postgresClient.query<RecentRow>(
|
|
`SELECT shared_with_email,
|
|
MAX(shared_at) AS last_shared_at
|
|
FROM analyzer_shares
|
|
WHERE shared_by_user_id = $1
|
|
GROUP BY shared_with_email
|
|
ORDER BY MAX(shared_at) DESC
|
|
LIMIT $2`,
|
|
[userId, RECENT_LIMIT]
|
|
)
|
|
).rows
|
|
: [];
|
|
|
|
const directoryRes = await postgresClient.query<DirectoryRow>(
|
|
`SELECT email, display_name, job_title, department
|
|
FROM graph_users
|
|
WHERE account_enabled = true
|
|
AND email IS NOT NULL
|
|
AND lower(split_part(email, '@', 2)) = ANY($1::text[])
|
|
ORDER BY COALESCE(display_name, email)`,
|
|
[allowedDomains]
|
|
);
|
|
|
|
// Drop recents whose domain is no longer allowed (defensive — share endpoint
|
|
// would reject them anyway, but the picker shouldn't dangle).
|
|
const recent = recentRows
|
|
.filter((r) => allowedDomains.includes(emailDomain(r.shared_with_email)))
|
|
.map((r) => ({
|
|
email: r.shared_with_email,
|
|
lastSharedAt: toIso(r.last_shared_at),
|
|
}));
|
|
|
|
const directory = directoryRes.rows.map((d) => ({
|
|
email: d.email,
|
|
displayName: d.display_name,
|
|
jobTitle: d.job_title,
|
|
department: d.department,
|
|
}));
|
|
|
|
return NextResponse.json({ recent, directory, allowedDomains });
|
|
}
|