feat: Phase 3+4 — security hardening + polish + offline + Electron push
Phase 3 (security): - P3.1: Feature gate server-side enforcement (403 on disabled features) - P3.2: Unified auth error interceptor (401→logout) - P3.3: Store-level state isolation via StoreSnapshot contract (added message-list-tabs + task stores to snapshot/restore cycle) - P3.4: Push event bus extraction — email-store no longer imports calendar/contact/filter/file stores directly - P1.3: Auth localStorage AES-GCM encryption via custom Zustand adapter Phase 4 (polish): - P4.1: Offline write queue — pending operations in localStorage, auto-retry on reconnect, offline-queue-indicator banner - P4.2: Identity spoofing — fromOverrideEmail domain validation - P4.3: WebSocket push for Electron via main-process IPC bridge (ws package with Authorization headers)
This commit is contained in:
@@ -6,6 +6,7 @@ import { normalizeCalendarEventLike } from '@/lib/calendar-event-normalization';
|
||||
import { expandRecurringEvents } from '@/lib/recurrence-expansion';
|
||||
import { parseISO } from 'date-fns';
|
||||
import type { CalendarEvent } from '@/lib/jmap/types';
|
||||
import { isFeatureEnabledServer } from '@/lib/admin/feature-gate';
|
||||
|
||||
/**
|
||||
* POST /api/calendar-agenda
|
||||
@@ -100,6 +101,10 @@ function firstCalendarId(event: Partial<CalendarEvent>): string | null {
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
if (!isFeatureEnabledServer('calendarEnabled')) {
|
||||
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const creds = await getStalwartCredentials(request);
|
||||
if (!creds) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '@/lib/mail-index/reindex';
|
||||
import { CONTENT_TYPES, isContentType, type ContentType } from '@/lib/mail-index/store';
|
||||
import { JmapIndexError } from '@/lib/mail-index/jmap';
|
||||
import { isFeatureEnabledServer } from '@/lib/admin/feature-gate';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
@@ -40,6 +41,10 @@ function parseIdMap(raw: unknown): Partial<Record<ContentType, string[]>> | unde
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
if (!isFeatureEnabledServer('aiAssistantEnabled')) {
|
||||
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 });
|
||||
}
|
||||
|
||||
if (!getStoreDir()) {
|
||||
return new NextResponse(null, { status: 404 });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getPluginRegistry, getThemeRegistry } from '@/lib/admin/plugin-registry
|
||||
import { listDevPlugins } from '@/lib/admin/plugin-dev';
|
||||
import { configManager } from '@/lib/admin/config-manager';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { isFeatureEnabledServer } from '@/lib/admin/feature-gate';
|
||||
|
||||
/**
|
||||
* GET /api/plugins - Public endpoint for clients to discover server-managed plugins & themes
|
||||
@@ -11,6 +12,10 @@ import { logger } from '@/lib/logger';
|
||||
* No admin auth required - this is how regular users receive plugins/themes.
|
||||
*/
|
||||
export async function GET() {
|
||||
if (!isFeatureEnabledServer('pluginsEnabled')) {
|
||||
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
await configManager.ensureLoaded();
|
||||
const policy = configManager.getPolicy();
|
||||
|
||||
@@ -15,12 +15,17 @@ import { NextResponse } from 'next/server';
|
||||
import { readStalwartAuthContext } from '@/lib/stalwart/auth-context';
|
||||
import { fetchJmapSession, postJmap, rebaseApiUrl } from '@/lib/stalwart/jmap-api';
|
||||
import { CaError, getCaProvider } from '@/lib/smime-ca';
|
||||
import { isFeatureEnabledServer } from '@/lib/admin/feature-gate';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
const MAX_CSR_BYTES = 8 * 1024;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (!isFeatureEnabledServer('smimeEnabled')) {
|
||||
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 });
|
||||
}
|
||||
|
||||
const provider = getCaProvider();
|
||||
if (!provider) {
|
||||
return NextResponse.json(
|
||||
|
||||
Reference in New Issue
Block a user