Apply public-content-visibility: visibility flag + public profile
Implements the public-content-visibility OpenSpec change. Adds the
smallest social surface that lets us demo the product to logged-out
visitors without user signup.
Schema:
- `visibility text NOT NULL DEFAULT 'private'` on routes + activities.
- Shared Visibility type exported from the schema module.
Access:
- New canView(content, viewer, { asDirectLink }) helper in auth.server.ts
centralises the rule: public → anyone; unlisted → anyone on direct
link; private → owner only.
- routes.$id and activities.$id loaders return 404 (not 403) when
canView rejects, so existence of private content isn't leaked.
- Detail pages emit Open Graph + Twitter Card meta on public/unlisted
content only.
Editing:
- Visibility <select> on routes/:id/edit with owner-only access.
- Activity detail page gets a small visibility form + set-visibility
action intent (no separate activity-edit page needed).
- EN + DE i18n under routes.visibility.* and activities.visibility.*.
Listings:
- Listing helpers listPublicRoutesForOwner / listPublicActivitiesForOwner
for cross-user queries. Existing owner-scoped listRoutes/listActivities
stay — owners see their own content regardless of visibility.
Public profile:
- /users/:username is now truly public. Renders the user's public
routes + activities, 404s when no public content exists AND viewer
isn't the owner (prevents account enumeration).
- Owner sees a short "this is your profile" note linking to settings.
- Open Graph meta (og:type=profile) for shareable preview.
Privacy manifest:
- Added a bullet noting public content is world-visible on profile
and indexable by search engines.
- Bumped PRIVACY_LAST_UPDATED to 2026-04-20 + rendered legal-archive
snapshot.
Tests:
- 13 unit tests for canView covering the full matrix.
- 6 E2E tests in e2e/public-content.test.ts covering:
- Private route → 404 for logged-out visitor
- Public route → reachable + OG tags present (og:title, og:type,
og:site_name)
- Owner still sees own private content
- Profile 404 when no public content
- Profile renders when at least one public route exists
- Unlisted route reachable via direct URL but hidden from profile
- Test file runs serially (describe.configure mode=serial) to avoid
WebAuthn virtual-authenticator races under Playwright's default
parallel workers.
- New public-content Playwright project added to config.
Rollout safety: every existing row in prod keeps visibility='private'
by default — nothing becomes visible to outsiders until an owner
explicitly opts in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
74dca52ecc
commit
5905cdadea
17 changed files with 1079 additions and 61 deletions
377
docs/legal-archive/privacy-2026-04-20.md
Normal file
377
docs/legal-archive/privacy-2026-04-20.md
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
# Privacy Policy — trails.cool
|
||||
|
||||
Datenschutzerklärung / Privacy Policy
|
||||
|
||||
Stand / Last updated: 2026-04-20. Die deutsche Fassung ist maßgeblich.
|
||||
The German version is authoritative; English summaries follow each
|
||||
section.
|
||||
|
||||
1. Verantwortlicher
|
||||
|
||||
Verantwortlich für die Datenverarbeitung im Sinne der DSGVO ist:
|
||||
|
||||
Ullrich Schäfer
|
||||
|
||||
Mehringdamm 87
|
||||
|
||||
10965 Berlin
|
||||
|
||||
Germany
|
||||
|
||||
E-Mail:
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
English.
|
||||
Data controller under GDPR is the party named
|
||||
above. Contact for any data-protection matter: legal@trails.cool.
|
||||
|
||||
2. Erhobene Daten und Zwecke
|
||||
|
||||
Wir verarbeiten nur die Daten, die für den Betrieb der jeweiligen
|
||||
Funktion erforderlich sind. trails.cool besteht aus zwei Teilen:
|
||||
dem
|
||||
Journal
|
||||
(trails.cool, mit Konto) und dem
|
||||
|
||||
Planner
|
||||
(planner.trails.cool, anonym).
|
||||
|
||||
Kontodaten (Journal):
|
||||
E-Mail-Adresse,
|
||||
Benutzername, Anzeigename, Passkey-Public-Key. Zweck:
|
||||
Kontoverwaltung und Anmeldung.
|
||||
|
||||
Nutzerinhalte (Journal):
|
||||
Routen (GPX-Daten,
|
||||
Geometrie, Titel, Beschreibung) und Aktivitäten (Titel,
|
||||
Beschreibung, Datum, Verknüpfung zu Routen). Zweck: Speicherung
|
||||
und Anzeige innerhalb des Dienstes.
|
||||
|
||||
Anmeldedaten (Journal):
|
||||
kurzlebige Magic-Link-Token
|
||||
(zur E-Mail-basierten Anmeldung). Zweck: Authentifizierung.
|
||||
|
||||
Sitzungscookie (Journal):
|
||||
eine zufällige
|
||||
Sitzungs-ID nach dem Einloggen. Zweck: Authentifizierung während
|
||||
der Sitzung.
|
||||
|
||||
Planner-Sitzungsdaten:
|
||||
anonyme Sitzungs-ID,
|
||||
kollaborativer Zustand (Wegpunkte, Notizen). Keine Zuordnung zu
|
||||
einer Person. Zweck: gemeinsames Planen von Routen.
|
||||
|
||||
Server-Logfiles:
|
||||
IP-Adresse, Zeitstempel,
|
||||
HTTP-Methode, Pfad, Statuscode, User-Agent. Zweck: Sicherheit,
|
||||
Betrieb, Fehlersuche. Details siehe Abschnitt 4.
|
||||
|
||||
Fehlerdaten (Sentry):
|
||||
Stacktraces,
|
||||
Fehlermeldungen, Browser-/OS-Information aus dem User-Agent,
|
||||
Performance-Metriken. Bei eingeloggten Journal-Nutzer:innen
|
||||
zusätzlich die Nutzer-ID (keine E-Mail, kein Benutzername).
|
||||
IP-Adressen werden nicht aktiv gespeichert, ebenso keine Cookies
|
||||
und keine Formulareingaben.
|
||||
|
||||
English.
|
||||
The Journal stores only what you provide (account
|
||||
details and your own routes/activities) plus short-lived auth
|
||||
artefacts. The Planner is anonymous and holds only ephemeral
|
||||
session state. Server logs and Sentry error data are covered
|
||||
separately below.
|
||||
|
||||
3. Rechtsgrundlagen
|
||||
|
||||
Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung):
|
||||
|
||||
Kontoführung, Anmeldung per Passkey oder Magic Link, Speicherung
|
||||
und Anzeige der von Ihnen erstellten Routen und Aktivitäten,
|
||||
Versand notwendiger Transaktions-E-Mails, Bereitstellung anonymer
|
||||
Planner-Sitzungen. Ein Konto sowie die Bestätigung der
|
||||
Nutzungsbedingungen sind Bestandteil dieses Nutzungsvertrags –
|
||||
sie sind
|
||||
keine
|
||||
Einwilligung im Sinne von Art. 6 Abs. 1
|
||||
lit. a DSGVO.
|
||||
|
||||
Art. 6 Abs. 1 lit. f DSGVO (berechtigte Interessen):
|
||||
|
||||
kurzzeitige Server-Logfiles zur Sicherung des Betriebs und zur
|
||||
Missbrauchsabwehr, Rate-Limiting, Fehlermonitoring über Sentry
|
||||
zur Sicherstellung der Funktionsfähigkeit des Dienstes.
|
||||
|
||||
English.
|
||||
Contract (Art. 6(1)(b)) covers everything
|
||||
account- and content-related; legitimate interests (Art. 6(1)(f))
|
||||
cover short-lived server logs, rate-limiting, and error monitoring.
|
||||
We do
|
||||
not
|
||||
rely on consent for any of this.
|
||||
|
||||
4. Server-Logfiles
|
||||
|
||||
Beim Aufruf der Dienste werden automatisch technische Informationen
|
||||
protokolliert:
|
||||
|
||||
IP-Adresse
|
||||
|
||||
Zeitstempel
|
||||
|
||||
HTTP-Methode, Pfad, Statuscode
|
||||
|
||||
User-Agent (Browser, Betriebssystem)
|
||||
|
||||
Zweck:
|
||||
Sicherheit, Betrieb und Fehlersuche.
|
||||
|
||||
Rechtsgrundlage:
|
||||
Art. 6 Abs. 1 lit. f DSGVO.
|
||||
|
||||
Speicherdauer:
|
||||
maximal 14 Tage, danach automatische
|
||||
Löschung. Eine Zusammenführung dieser Daten mit anderen Datenquellen
|
||||
findet nicht statt.
|
||||
|
||||
English.
|
||||
HTTP requests to our servers are logged (IP,
|
||||
timestamp, method, path, status, user-agent) for up to 14 days for
|
||||
operational and security purposes under Art. 6(1)(f), then deleted.
|
||||
|
||||
5. Speicherdauer
|
||||
|
||||
Konto und zugehörige Inhalte: bis zur Löschung durch Sie
|
||||
|
||||
Planner-Sitzungen: automatische Löschung nach 7 Tagen Inaktivität
|
||||
|
||||
Magic-Link-Token: 15 Minuten
|
||||
|
||||
Server-Logfiles: maximal 14 Tage
|
||||
|
||||
Fehlerdaten (Sentry): 90 Tage
|
||||
|
||||
Hinweis Alpha: Während der Alpha-Phase behält sich der Betreiber
|
||||
ausdrücklich vor, die Datenbank zurückzusetzen oder einzelne
|
||||
Datensätze zu löschen. Dies kann zu Datenverlust führen, bevor Sie
|
||||
eine Löschung veranlassen. Details dazu in den Nutzungsbedingungen.
|
||||
|
||||
English.
|
||||
Account and content kept until you delete them.
|
||||
Ephemeral data (sessions, magic-link tokens, logs, Sentry events)
|
||||
deleted automatically on the schedules above. Alpha caveat: the
|
||||
operator may reset the database or delete individual records
|
||||
during alpha, which can cause data loss before you request
|
||||
deletion. See the Terms of Service.
|
||||
|
||||
6. Empfänger und Drittanbieter
|
||||
|
||||
Wir geben personenbezogene Daten nur an die unten genannten
|
||||
Auftragsverarbeiter und Dritten weiter, und auch dort nur im
|
||||
jeweils notwendigen Umfang.
|
||||
|
||||
Sentry
|
||||
(Functional Software Inc.) – Fehler- und
|
||||
Performance-Monitoring. Was übermittelt wird: Stacktraces,
|
||||
Fehlertext, Browser-/OS-Informationen aus dem User-Agent,
|
||||
Performance-Daten; bei eingeloggten Journal-Nutzer:innen
|
||||
zusätzlich die Nutzer-ID. IP-Adressen werden nicht aktiv
|
||||
gespeichert (
|
||||
sendDefaultPii
|
||||
ist deaktiviert), ebenso
|
||||
keine Cookies und keine vollständigen HTTP-Header. Keine
|
||||
Session-Replays. Sentry agiert als externer Dienstleister
|
||||
(Auftragsverarbeiter) für die Fehlerbehandlung. Da Sentry ein
|
||||
Anbieter mit Sitz in den USA ist, kann im Einzelfall eine
|
||||
Übermittlung personenbezogener Daten in ein Drittland im Sinne
|
||||
der Art. 44 ff. DSGVO stattfinden; wir stützen uns hierbei auf
|
||||
die von Sentry bereitgestellten
|
||||
Standardvertragsklauseln.
|
||||
|
||||
OpenStreetMap
|
||||
– Kartenkacheln werden beim Anzeigen
|
||||
der Karten direkt vom Browser von OSM-Tile-Servern geladen. Dabei
|
||||
werden
|
||||
IP-Adresse und User-Agent
|
||||
an OSM übertragen.
|
||||
Dies ist notwendig, um überhaupt eine Karte darzustellen; es gilt
|
||||
die
|
||||
|
||||
OSM Foundation Privacy Policy
|
||||
|
||||
.
|
||||
|
||||
Overpass API
|
||||
(POI-Daten) – POI-Abfragen laufen
|
||||
serverseitig über unsere eigene Route
|
||||
/api/overpass
|
||||
.
|
||||
Der Upstream-Dienst sieht nur unsere Server-IP, nicht die Ihrer
|
||||
Nutzer:innen. Aktueller Upstream:
|
||||
overpass.private.coffee
|
||||
|
||||
, der ohne Query-Logs arbeitet. Eine selbst gehostete Instanz ist
|
||||
geplant.
|
||||
|
||||
BRouter
|
||||
– Routenberechnung läuft auf einer von uns
|
||||
selbst gehosteten Instanz. Keine Weitergabe an Dritte.
|
||||
|
||||
E-Mail-Versand
|
||||
– Transaktions-E-Mails (Magic
|
||||
Link, Willkommensnachricht) werden über einen konfigurierten
|
||||
externen SMTP-Dienst versendet. Dabei wird die E-Mail-Adresse
|
||||
der Empfänger:in an diesen Dienst übergeben. Selbst-betriebene
|
||||
Instanzen konfigurieren ihren eigenen Mailserver.
|
||||
|
||||
Hosting
|
||||
– Die Dienste werden in Rechenzentren
|
||||
innerhalb der EU betrieben. Ein Auftragsverarbeitungsvertrag mit
|
||||
dem Hoster besteht.
|
||||
|
||||
English.
|
||||
Third parties and what they receive: Sentry (error
|
||||
details, no IPs/cookies); OpenStreetMap tile servers
|
||||
(your IP and user-agent, directly from your browser, to load map
|
||||
tiles); Overpass (via our server-side proxy, so upstream only sees
|
||||
our server); BRouter (self-hosted, no third party involved); SMTP
|
||||
provider (your email address for magic link / welcome mail);
|
||||
hosting provider in the EU under a DPA.
|
||||
|
||||
7. Ihre Rechte
|
||||
|
||||
Als betroffene Person stehen Ihnen die folgenden Rechte zu:
|
||||
|
||||
Auskunft (Art. 15 DSGVO)
|
||||
|
||||
Berichtigung (Art. 16 DSGVO)
|
||||
|
||||
Löschung (Art. 17 DSGVO)
|
||||
|
||||
Einschränkung der Verarbeitung (Art. 18 DSGVO)
|
||||
|
||||
Datenübertragbarkeit (Art. 20 DSGVO)
|
||||
|
||||
Widerspruch gegen Verarbeitung auf Basis berechtigter Interessen (Art. 21 DSGVO)
|
||||
|
||||
Zur Ausübung dieser Rechte genügt eine formlose E-Mail an
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
. Einen vollständigen Export Ihrer Routen und Aktivitäten können Sie
|
||||
jederzeit direkt in den Kontoeinstellungen herunterladen (GPX bzw.
|
||||
JSON). Ihr Konto samt Inhalten können Sie dort ebenfalls selbst
|
||||
löschen.
|
||||
|
||||
English.
|
||||
You have the standard GDPR rights (access,
|
||||
rectification, erasure, restriction, portability, objection). Email
|
||||
us to exercise them. Data exports and account deletion are also
|
||||
available directly in your account settings.
|
||||
|
||||
8. Beschwerderecht
|
||||
|
||||
Sie haben das Recht, sich bei einer Datenschutzaufsichtsbehörde zu
|
||||
beschweren. Für uns zuständig ist:
|
||||
|
||||
Berliner Beauftragte für Datenschutz und Informationsfreiheit
|
||||
|
||||
Friedrichstr. 219
|
||||
|
||||
10969 Berlin
|
||||
|
||||
www.datenschutz-berlin.de
|
||||
|
||||
English.
|
||||
You have the right to lodge a complaint with a
|
||||
supervisory authority; ours is the Berlin data-protection
|
||||
commissioner (address above).
|
||||
|
||||
Privacy Manifest
|
||||
|
||||
A plain-language summary of what we do, for readers who prefer
|
||||
behaviour over paragraphs. Binding text is above.
|
||||
|
||||
Planner (planner.trails.cool)
|
||||
|
||||
Anonymous by design. No account, no identifier, nothing persisted
|
||||
past the session.
|
||||
|
||||
No cookies, no localStorage, no sessionStorage
|
||||
|
||||
No browser-side error tracking (Sentry is not loaded)
|
||||
|
||||
Session data is deleted automatically after 7 days of inactivity
|
||||
|
||||
Journal (trails.cool)
|
||||
|
||||
Holds only what you put in. Exportable any time, deletable any time.
|
||||
|
||||
Account: email, username, display name, passkey public key
|
||||
|
||||
Routes: GPX, geometry, title, description
|
||||
|
||||
Activities: title, description, date, linked route
|
||||
|
||||
GPX / JSON export available per object and overall
|
||||
|
||||
Routes and activities each carry a visibility setting
|
||||
(
|
||||
private
|
||||
/
|
||||
unlisted
|
||||
/
|
||||
public
|
||||
)
|
||||
that defaults to
|
||||
private
|
||||
. Content you explicitly mark
|
||||
|
||||
public
|
||||
is visible to anyone on the internet,
|
||||
including on your public profile at
|
||||
/users/<you>
|
||||
|
||||
and on search engines that index those pages.
|
||||
|
||||
Sentry
|
||||
|
||||
Journal server: always on
|
||||
|
||||
Journal browser: only after login, torn down on logout
|
||||
|
||||
Planner: server only; the browser never loads Sentry
|
||||
|
||||
IPs not actively stored, no cookies, no full headers (
|
||||
sendDefaultPii: false
|
||||
)
|
||||
|
||||
No replays (replay integration not installed, sample rates 0)
|
||||
|
||||
User ID only (no email/username) on Journal-logged-in events
|
||||
|
||||
What we don't do
|
||||
|
||||
Sell data
|
||||
|
||||
Show ads
|
||||
|
||||
Build user profiles
|
||||
|
||||
Run tracking pixels, analytics, or A/B tests
|
||||
|
||||
Security
|
||||
|
||||
Auth via passkey (WebAuthn) or magic link — no passwords stored
|
||||
|
||||
HTTPS + HSTS preload on all origins; session cookies httpOnly + Secure
|
||||
|
||||
CSP, X-Frame-Options, X-Content-Type-Options on every response
|
||||
|
||||
Containers run non-root; host firewall restricts to HTTP/HTTPS/SSH
|
||||
|
||||
Gitleaks + dependency audit on every PR
|
||||
|
||||
Vulnerability reports:
|
||||
|
||||
SECURITY.md
|
||||
Loading…
Add table
Add a link
Reference in a new issue