mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { configManager } from '@/lib/admin/config-manager';
|
|
import { requireAdminAuth, getClientIP } from '@/lib/admin/session';
|
|
import { auditLog } from '@/lib/admin/audit';
|
|
import { logger } from '@/lib/logger';
|
|
import { POLICY_SCOPE_HEADER, type SettingsPolicy } from '@/lib/admin/types';
|
|
import { canSeeInstanceDetails } from '@/lib/auth/instance-details';
|
|
import { configReadOnlyResponse } from '@/lib/admin/config-read-only-response';
|
|
|
|
/**
|
|
* GET /api/admin/policy - Get settings policy (NOT admin-protected - users read this;
|
|
* visitors who are not signed in get the subset the login page needs)
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
await configManager.ensureLoaded();
|
|
const policy = configManager.getPolicy();
|
|
if (!(await canSeeInstanceDetails(request))) {
|
|
// The login page needs the theme policy and gates; the plugin lists,
|
|
// push relays and sidebar app URLs map the deployment and are only
|
|
// used after signing in, when the client fetches the policy again.
|
|
return NextResponse.json(
|
|
{
|
|
...policy,
|
|
forceEnabledPlugins: [],
|
|
approvedPlugins: [],
|
|
pushRelays: [],
|
|
pushRelayUrl: '',
|
|
pushRelayUrlLocked: false,
|
|
defaultSidebarApps: [],
|
|
},
|
|
{ headers: { 'Cache-Control': 'no-store', [POLICY_SCOPE_HEADER]: 'public' } },
|
|
);
|
|
}
|
|
return NextResponse.json(policy, {
|
|
headers: { 'Cache-Control': 'no-store' },
|
|
});
|
|
} catch (error) {
|
|
logger.error('Policy read error', { error: error instanceof Error ? error.message : 'Unknown error' });
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/admin/policy - Update settings policy (admin-protected)
|
|
*/
|
|
export async function PUT(request: NextRequest) {
|
|
try {
|
|
const result = await requireAdminAuth(request);
|
|
if ('error' in result) return result.error;
|
|
|
|
const ip = getClientIP(request);
|
|
const policy = await request.json() as SettingsPolicy;
|
|
|
|
if (!policy || typeof policy !== 'object') {
|
|
return NextResponse.json({ error: 'Invalid policy object' }, { status: 400 });
|
|
}
|
|
|
|
// Basic validation
|
|
if (policy.restrictions && typeof policy.restrictions !== 'object') {
|
|
return NextResponse.json({ error: 'restrictions must be an object' }, { status: 400 });
|
|
}
|
|
if (policy.features && typeof policy.features !== 'object') {
|
|
return NextResponse.json({ error: 'features must be an object' }, { status: 400 });
|
|
}
|
|
if (policy.defaults !== undefined && (typeof policy.defaults !== 'object' || policy.defaults === null || Array.isArray(policy.defaults))) {
|
|
return NextResponse.json({ error: 'defaults must be an object' }, { status: 400 });
|
|
}
|
|
if (policy.themePolicy && typeof policy.themePolicy !== 'object') {
|
|
return NextResponse.json({ error: 'themePolicy must be an object' }, { status: 400 });
|
|
}
|
|
if (policy.defaultSidebarApps !== undefined && !Array.isArray(policy.defaultSidebarApps)) {
|
|
return NextResponse.json({ error: 'defaultSidebarApps must be an array' }, { status: 400 });
|
|
}
|
|
|
|
await configManager.setPolicy(policy);
|
|
await auditLog('policy.update', {
|
|
restrictionCount: Object.keys(policy.restrictions || {}).length,
|
|
// Entries the sanitizer dropped never reach users, so log what stuck.
|
|
defaultSidebarAppCount: configManager.getPolicy().defaultSidebarApps?.length ?? 0,
|
|
}, ip);
|
|
|
|
return NextResponse.json({ ok: true });
|
|
} catch (error) {
|
|
const readOnly = configReadOnlyResponse(error);
|
|
if (readOnly) return readOnly;
|
|
logger.error('Policy update error', { error: error instanceof Error ? error.message : 'Unknown error' });
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|