feat(legal): imprint & privacy notices, "Über MindSift"

- LEGAL_IMPRINT_/LEGAL_PRIVACY_ URL and Markdown (inline or *_FILE);
  only http(s) URLs accepted
- /auth/status exposes legal info; public GET /legal/{imprint|privacy}
- Links on login/setup screen and in the settings footer: URL opens a new
  tab, Markdown a reading modal; hidden when nothing is configured
- "Über MindSift" modal (version, license, repository) in settings and help
- Docs and compose/env examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 23:10:27 +01:00
1 parent a62b204e14
commit d6c70d7a8e
17 files changed
+318 -2

No files matched your search

+39
View File
@@ -1366,3 +1366,42 @@ func TestFirstBootSetupWithSampleData(t *testing.T) {
t.Fatalf("expected sample notes and task lists, got %d notes, %d task lists", len(docs), len(lists))
}
}
// Impressum and Datenschutz must be reachable without login; unset texts stay hidden.
func TestLegalTextsArePublic(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "legal.db")
store, err := storage.NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
cfg := &config.Config{Port: "8080", DatabasePath: dbPath, JWTSecret: "test-jwt-secret-key",
Legal: config.LegalTexts{ImprintContent: "# Impressum\nMax Muster", PrivacyURL: "https://example.org/datenschutz"}}
router := NewServer(cfg, store, ws.NewHub(), nil).Router()
rec := httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/auth/status", nil))
var st struct {
Legal struct {
ImprintURL string `json:"imprintUrl"`
HasImprintContent bool `json:"hasImprintContent"`
PrivacyURL string `json:"privacyUrl"`
HasPrivacyContent bool `json:"hasPrivacyContent"`
} `json:"legal"`
}
_ = json.Unmarshal(rec.Body.Bytes(), &st)
if !st.Legal.HasImprintContent || st.Legal.HasPrivacyContent || st.Legal.PrivacyURL != "https://example.org/datenschutz" {
t.Fatalf("unexpected legal status: %+v", st.Legal)
}
rec = httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/legal/imprint", nil))
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "Max Muster") {
t.Fatalf("imprint without login: %d %s", rec.Code, rec.Body.String())
}
rec = httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/legal/privacy", nil))
if rec.Code != http.StatusNotFound {
t.Fatalf("privacy without text should be 404, got %d", rec.Code)
}
}