- Database: kanzlai.audit_log table with RLS, append-only policies
(no UPDATE/DELETE), indexes for entity, user, and time queries
- Backend: AuditService.Log() with context-based tenant/user/IP/UA
extraction, wired into all 7 services (case, deadline, appointment,
document, note, party, tenant)
- API: GET /api/audit-log with entity_type, entity_id, user_id,
from/to date, and pagination filters
- Frontend: Protokoll tab on case detail page with chronological
audit entries, diff preview, and pagination
Required by § 50 BRAO and DSGVO Art. 5(2).
1. Tenant isolation bypass (CRITICAL): TenantResolver now verifies user
has access to X-Tenant-ID via user_tenants lookup before setting context.
Added VerifyAccess method to TenantLookup interface and TenantService.
2. Consolidated tenant resolution: Removed duplicate resolveTenant() from
helpers.go and tenant resolution from auth middleware. TenantResolver is
now the single source of truth. Deadlines and AI handlers use
auth.TenantFromContext() instead of direct DB queries.
3. CalDAV credential masking: tenant settings responses now mask CalDAV
passwords with "********" via maskSettingsPassword helper. Applied to
GetTenant, ListTenants, and UpdateSettings responses.
4. CORS + security headers: New middleware/security.go with CORS
(restricted to FRONTEND_ORIGIN) and security headers (X-Frame-Options,
X-Content-Type-Options, HSTS, Referrer-Policy, X-XSS-Protection).
5. Internal error leaking: All writeError(w, 500, err.Error()) replaced
with internalError() that logs via slog and returns generic "internal
error" to client. Same for jsonError in tenant handler.
6. Input validation: Max length on title (500), description (10000),
case_number (100), search (200). Pagination clamped to max 100.
Content-Disposition filename sanitized against header injection.
Regression test added for tenant access denial (403 on unauthorized
X-Tenant-ID). All existing tests pass, go vet clean.
Backend: PUT /api/tenants/{id}/settings endpoint for updating tenant
settings (JSONB merge). Frontend: /einstellungen page with CalDAV
config (URL, credentials, calendar path, sync toggle, interval),
manual sync button, live sync status display. /einstellungen/team
page with member list, invite-by-email, role management.
Tenant management:
- POST /api/tenants — create tenant (creator becomes owner)
- GET /api/tenants — list tenants for authenticated user
- GET /api/tenants/:id — tenant details with access check
- POST /api/tenants/:id/invite — invite user by email (owner/admin)
- DELETE /api/tenants/:id/members/:uid — remove member
- GET /api/tenants/:id/members — list members
New packages:
- internal/services/tenant_service.go — CRUD on tenants + user_tenants
- internal/handlers/tenant_handler.go — HTTP handlers with auth checks
- internal/auth/tenant_resolver.go — X-Tenant-ID header middleware,
defaults to user's first tenant for scoped routes
Authorization: owners/admins can invite and remove members. Cannot
remove the last owner. Users can remove themselves. TenantResolver
applies to resource routes (cases, deadlines, etc.) but not tenant
management routes.