feat: add Stalwart account security management

- Add Stalwart API client library (lib/stalwart/client.ts)
- Add server-side proxy routes for auth, crypto, password, principal, probe
- Add account security Zustand store with full state management
- Add Security settings tab with password change, display name, TOTP 2FA,
  app passwords, and encryption-at-rest controls
- Add stalwartFeaturesEnabled config flag (opt-out via STALWART_FEATURES=false)
- Add i18n translations for all 8 locales (en, de, es, fr, it, ja, nl, pt)
- Add tests for Stalwart client (24 tests) and security store (29 tests)
This commit is contained in:
Linus Rath
2026-03-12 02:20:56 +01:00
parent 68d4a9a641
commit ab72fc06ff
26 changed files with 3143 additions and 310 deletions
+7 -6
View File
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface ResizeHandleProps {
onResizeStart?: () => void;
onResize: (delta: number) => void;
onResizeEnd?: () => void;
onDoubleClick?: () => void;
@@ -12,17 +13,18 @@ interface ResizeHandleProps {
const KEYBOARD_STEP = 10;
export function ResizeHandle({ onResize, onResizeEnd, onDoubleClick, className }: ResizeHandleProps) {
export function ResizeHandle({ onResizeStart, onResize, onResizeEnd, onDoubleClick, className }: ResizeHandleProps) {
const isDragging = useRef(false);
const lastX = useRef(0);
const startX = useRef(0);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
isDragging.current = true;
lastX.current = e.clientX;
startX.current = e.clientX;
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
}, []);
onResizeStart?.();
}, [onResizeStart]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
let delta = 0;
@@ -37,8 +39,7 @@ export function ResizeHandle({ onResize, onResizeEnd, onDoubleClick, className }
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging.current) return;
const delta = e.clientX - lastX.current;
lastX.current = e.clientX;
const delta = e.clientX - startX.current;
onResize(delta);
};