Database: time_entries, billing_rates, invoices tables with RLS.
Backend: CRUD services+handlers for time entries, billing rates, invoices.
- Time entries: list/create/update/delete, summary by case/user/month
- Billing rates: upsert with auto-close previous, current rate lookup
- Invoices: create with auto-number (RE-YYYY-NNN), status transitions
(draft->sent->paid, cancellation), link time entries on invoice create
API: 11 new endpoints under /api/time-entries, /api/billing-rates, /api/invoices
Frontend: Zeiterfassung tab on case detail, /abrechnung overview with filters,
/abrechnung/rechnungen list+detail with status actions, billing rates settings
Also: resolved merge conflicts between audit-trail and role-based branches,
added missing types (Notification, AuditLogResponse, NotificationPreferences)
- 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.
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.
- CaseService: list (paginated, filterable), get detail (with parties,
events, deadline count), create, update, soft-delete (archive)
- PartyService: list by case, create, update, delete
- Auto-create case_events on case creation, status change, party add,
and case archive
- Auth middleware now resolves tenant_id from user_tenants table
- All operations scoped to tenant_id from auth context