diff --git a/LICENSE b/LICENSE index c7ebe92..ed12564 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ MIT License -Copyright (c) 2026 hightrusted +Copyright (c) 2026 hightrusted GmbH -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2534ce3..85a136b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # hightrusted CAPTURE — Examples -> **Status:** v0.1 Preview — Beispiele wachsen mit den Use-Cases unserer Kunden +> **Status:** v0.1 — drei lauffähige Beispiele, weitere folgen -Längere, in sich geschlossene Beispiel-Anwendungen für die -[hightrusted CAPTURE API](https://capture.hightrusted.net). +Vollständige Beispiel-Anwendungen für die [hightrusted CAPTURE API](https://capture.hightrusted.net). Während die SDK-Repos (`python`, `node`, `php`) die Bibliothek selbst enthalten, zeigt dieses Repo **vollständige Anwendungen** — Code, den du forken, anpassen @@ -11,25 +10,29 @@ und produktiv einsetzen kannst. **Made in Germany.** Server in Deutschland. DSGVO-nativ. -## Beispiele (geplant) +## Aktuelle Beispiele -| Verzeichnis | Sprache | Use-Case | -|-----------------------------|----------|----------| -| `markenrecht-monitoring/` | Python | Tägliche Capture markenrechtlich relevanter Domains, automatische E-Mail bei Änderung | -| `agb-archivierung/` | Node.js | AGB & Datenschutzerklärungen großer Anbieter regelmäßig sichern und versionieren | -| `wordpress-plugin/` | PHP | WordPress-Plugin: Beweissicherung von Kommentaren, Forenbeiträgen, Bewertungen | -| `compliance-batch/` | Python | Batch-Capture: CSV mit URLs einlesen, alle capturen, Audit-Log als ZIP | -| `webhook-receiver/` | Node.js | Express-Webhook-Server, der `capture.ready` Events validiert und PDFs ablegt | -| `nextcloud-integration/` | PHP | Nextcloud-App: Rechtsklick auf Link → Capture erstellen → in Nextcloud speichern | +| Verzeichnis | Sprache | Use-Case | +|--------------------------|----------|----------| +| [`markenrecht-monitoring/`](./markenrecht-monitoring) | Python | Tägliche Capture markenrechtlich relevanter URLs als Cronjob, mit YAML-Config und Audit-Index | +| [`webhook-receiver/`](./webhook-receiver) | Node.js | Express-Server, der `capture.ready` Events validiert und PDFs archiviert | +| [`wordpress-plugin/`](./wordpress-plugin) | PHP | WordPress-Plugin: Captures direkt aus dem Backend, Meta-Box im Editor | -> Diese Beispiele werden Schritt für Schritt befüllt. Jedes Verzeichnis enthält -> einen eigenen `README.md` mit Setup-Anleitung, Voraussetzungen und Lizenz. +Jedes Verzeichnis hat einen eigenen `README.md` mit Setup-Anleitung, +Voraussetzungen und Use-Case-Beschreibung. + +## Geplant + +- `agb-archivierung/` (Node.js) — AGB & Datenschutzerklärungen großer Anbieter regelmäßig versionieren +- `compliance-batch/` (Python) — CSV mit URLs einlesen, alle capturen, Audit-Log als ZIP +- `nextcloud-integration/` (PHP) — Nextcloud-App: Rechtsklick auf Link → Capture +- `salesforce-integration/` (Apex) — Capture aus dem CRM heraus auslösen ## Anwendungsfälle in der Praxis **Kanzleien & Sachverständige.** Beweissicherung von Web-Inhalten, die Beklagte -nach Klage-Eingang löschen würden — ein Capture mit qualifiziertem Zeitstempel -ist gerichtsverwertbar. +nach Klage-Eingang löschen würden. Capture mit qualifiziertem Zeitstempel ist +gerichtsverwertbar. **Aufsichtsbehörden & Compliance-Abteilungen.** Regelmäßige Snapshots von Anbieter-AGB, Preislisten und Datenschutzerklärungen für die Aufbewahrung. @@ -37,6 +40,9 @@ Anbieter-AGB, Preislisten und Datenschutzerklärungen für die Aufbewahrung. **KMU mit kritischen Web-Inhalten.** Eigene Webseite, Online-Bewertungen, Bewerber-Portale archivieren. +**Markeninhaber.** Trittbrettfahrer-Domains und Fake-Shops überwachen, +Beweise sichern bevor der Beklagte löscht. + ## Voraussetzungen Ein hightrusted-Account mit API-Key — kostenlos im Developer-Plan @@ -52,30 +58,26 @@ Siehe [CONTRIBUTING.md](./CONTRIBUTING.md). **Im selben Produkt** ([`hightrusted-capture`](https://git.hightrusted.net/hightrusted-capture)): -- [`openapi`](https://git.hightrusted.net/hightrusted-capture/openapi) — OpenAPI 3.1 Spec (Single Source of Truth) +- [`openapi`](https://git.hightrusted.net/hightrusted-capture/openapi) — OpenAPI 3.1 Spec - [`postman`](https://git.hightrusted.net/hightrusted-capture/postman) — Postman Collection -- [`python`](https://git.hightrusted.net/hightrusted-capture/python) — Python-SDK -- [`node`](https://git.hightrusted.net/hightrusted-capture/node) — Node.js-SDK -- [`php`](https://git.hightrusted.net/hightrusted-capture/php) — PHP-SDK +- [`python`](https://git.hightrusted.net/hightrusted-capture/python) / [`node`](https://git.hightrusted.net/hightrusted-capture/node) / [`php`](https://git.hightrusted.net/hightrusted-capture/php) — SDKs **Plattform-übergreifend** ([`hightrusted`](https://git.hightrusted.net/hightrusted)): -- [`platform`](https://git.hightrusted.net/hightrusted/platform) — Plattform-Übersicht, Architektur, Produkt-Liste -- [`developer-portal`](https://git.hightrusted.net/hightrusted/developer-portal) — gemeinsame Konventionen, Auth, Errors, Rate-Limits -- [`compliance`](https://git.hightrusted.net/hightrusted/compliance) — DSGVO, AGB-Templates, Whitepaper +- [`platform`](https://git.hightrusted.net/hightrusted/platform) +- [`developer-portal`](https://git.hightrusted.net/hightrusted/developer-portal) +- [`compliance`](https://git.hightrusted.net/hightrusted/compliance) ## Support - **Doku:** https://capture.hightrusted.net/api/docs -- **Status-Page:** https://status.hightrusted.net - **Developer Support:** developers@hightrusted.net -- **Sicherheitslücken:** siehe [SECURITY.md](./SECURITY.md) ## Lizenz -MIT — siehe [LICENSE](./LICENSE). +MIT — siehe [LICENSE](./LICENSE). Beispiele dürfen frei kopiert, angepasst und +in kommerziellen Projekten eingesetzt werden. --- **hightrusted GmbH** — *The European Trust Infrastructure.* -Made in Germany. DSGVO-nativ. eIDAS-konform. diff --git a/SECURITY.md b/SECURITY.md index 9ed887a..63ed5b2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -25,9 +25,3 @@ Während der `v0.x`-Phase werden nur die jeweils aktuellste Minor-Version und deren letzte zwei Patch-Versionen aktiv mit Sicherheits-Updates versorgt. Ab `v1.0` gilt: aktuelle Major + vorherige Major (12 Monate Übergangsfrist). - -## Out of Scope - -Diese Policy gilt für die SDKs in diesem Repository und die zugehörige -hightrusted CAPTURE API. Für Schwachstellen in anderen hightrusted-Produkten -(SIGN, ID, MEET, PAY, …) gilt jeweils die dortige `SECURITY.md`. diff --git a/markenrecht-monitoring/README.md b/markenrecht-monitoring/README.md new file mode 100644 index 0000000..8e79eea --- /dev/null +++ b/markenrecht-monitoring/README.md @@ -0,0 +1,60 @@ +# Markenrecht-Monitoring + +Tägliches Capture markenrechtlich relevanter URLs mit qualifiziertem Zeitstempel. +Optional als Cronjob — sodass du immer ein gerichtsverwertbares Snapshot des +Status hast, **bevor** der Beklagte die Beweise löschen kann. + +## Use-Case + +Du beobachtest, ob Wettbewerber oder Trittbrettfahrer deine Marke, dein +Produktdesign oder deine geschützten Inhalte missbrauchen. Klassisches Problem +bei Klage: *"Eure Ehren, der Beklagte hat die strittige Inhalte direkt nach +Erhalt der Abmahnung gelöscht."* + +Mit täglichen Captures hast du den Beweis vor der Löschung. + +## Setup + +```bash +pip install hightrusted-capture pyyaml +export HIGHTRUSTED_API_KEY=ht_live_... +``` + +URL-Liste anlegen — `urls.yaml`: + +```yaml +urls: + - url: https://example.com/markenrechtsverletzung + case: case-2026-001 + - url: https://shop.example.com/fake-product + case: case-2026-002 +``` + +Lauf: + +```bash +python monitor.py urls.yaml ./archiv +``` + +Ergebnis: Pro Tag ein Unterordner unter `./archiv/2026-04-25/` mit allen PDFs +und einer `index.yaml`, die den Audit-Trail dokumentiert. + +## Cron-Beispiel + +Tägliches Capture um 03:00: + +```cron +0 3 * * * cd /opt/markenrecht && python monitor.py urls.yaml ./archiv >> monitor.log 2>&1 +``` + +## Was die Captures enthalten + +Jedes PDF ist: +- PDF/A-3 (langzeitarchivierungsfähig) +- mit eingebettetem RFC 3161-Zeitstempel von open-tsa.eu +- mit unveränderbarem Audit-Log +- Jahre später noch verifizierbar — auch nach Löschung der Original-URL + +## Lizenz + +MIT — frei nutzbar und anpassbar. diff --git a/markenrecht-monitoring/monitor.py b/markenrecht-monitoring/monitor.py new file mode 100644 index 0000000..15b8059 --- /dev/null +++ b/markenrecht-monitoring/monitor.py @@ -0,0 +1,108 @@ +""" +Markenrecht-Monitoring mit hightrusted CAPTURE. + +Liest eine Liste von URLs aus einer YAML-Datei, erstellt für jede URL ein +Capture mit qualifiziertem Zeitstempel und legt die PDFs in einem strukturierten +Archiv-Ordner ab. Optional: Versand einer Zusammenfassung per E-Mail. + +Use-Case: + Du betreibst eine Marke und beobachtest, ob Wettbewerber/Trittbrettfahrer + deine Marke missbrauchen. Sobald sie es bemerken und löschen, hast du + bereits ein gerichtsverwertbares Capture. + +Voraussetzung: + pip install hightrusted-capture pyyaml + +Lauf: + export HIGHTRUSTED_API_KEY=ht_live_... + python monitor.py urls.yaml ./archiv +""" + +from __future__ import annotations + +import argparse +import logging +import sys +from datetime import datetime, timezone +from pathlib import Path + +import yaml +from hightrusted_capture import ( + Client, + HightrustedError, + QuotaExceededError, + UnreachableUrlError, +) + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)-7s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +log = logging.getLogger("markenrecht-monitor") + + +def main(urls_file: Path, archive_root: Path) -> int: + config = yaml.safe_load(urls_file.read_text()) + urls: list[dict] = config["urls"] + + today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + target_dir = archive_root / today + target_dir.mkdir(parents=True, exist_ok=True) + + client = Client() # API-Key aus ENV + summary = {"ok": [], "failed": []} + + for entry in urls: + url = entry["url"] + case = entry.get("case", "default") + log.info("→ %s [%s]", url, case) + + try: + capture = client.capture( + url=url, + reference=f"monitor:{case}:{today}", + viewport={"width": 1920, "height": 1080}, + ) + pdf_path = target_dir / f"{case}_{capture['id'][:8]}.pdf" + client.download_pdf(capture["id"], pdf_path) + + summary["ok"].append({ + "url": url, + "case": case, + "capture_id": capture["id"], + "verify_url": capture["verify_url"], + "pdf": str(pdf_path), + "timestamp": capture["timestamp"]["issued_at"], + }) + log.info(" ✓ %s", pdf_path.name) + + except UnreachableUrlError as e: + log.warning(" ✗ Quelle nicht erreichbar: %s", e.message) + summary["failed"].append({"url": url, "case": case, "reason": "unreachable"}) + + except QuotaExceededError: + log.error("Monats-Quota erschöpft. Abbruch.") + return 2 + + except HightrustedError as e: + log.error(" ✗ %s (request_id=%s)", e.message, e.request_id) + summary["failed"].append({"url": url, "case": case, "reason": e.code}) + + # Audit-Index schreiben + index_path = target_dir / "index.yaml" + index_path.write_text(yaml.safe_dump(summary, allow_unicode=True, sort_keys=False)) + log.info("Index gespeichert: %s", index_path) + + log.info("─" * 60) + log.info("✓ %d erfolgreich, ✗ %d fehlgeschlagen", len(summary["ok"]), len(summary["failed"])) + + return 0 if not summary["failed"] else 1 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__.split("\n")[1]) + parser.add_argument("urls_file", type=Path, help="YAML mit zu überwachenden URLs") + parser.add_argument("archive_root", type=Path, help="Wohin die PDFs gespeichert werden") + args = parser.parse_args() + sys.exit(main(args.urls_file, args.archive_root)) diff --git a/markenrecht-monitoring/urls.example.yaml b/markenrecht-monitoring/urls.example.yaml new file mode 100644 index 0000000..4311b50 --- /dev/null +++ b/markenrecht-monitoring/urls.example.yaml @@ -0,0 +1,13 @@ +# URLs für Markenrecht-Monitoring +# Für jede Capture wird eine "case"-ID vergeben — sie taucht im Dateinamen +# und in der Capture-Reference auf, sodass du später nach Fall filtern kannst. + +urls: + - url: https://example.com/markenrechtsverletzung + case: case-2026-001-mueller-vs-schmidt + + - url: https://shop.example.com/fake-product + case: case-2026-002-fake-shop + + - url: https://forum.example.com/thread/12345 + case: case-2026-003-rufschaedigung diff --git a/webhook-receiver/README.md b/webhook-receiver/README.md new file mode 100644 index 0000000..f3f84ec --- /dev/null +++ b/webhook-receiver/README.md @@ -0,0 +1,61 @@ +# Webhook-Receiver für hightrusted CAPTURE + +Minimaler Express-Server, der `capture.ready` / `capture.failed` Events +entgegennimmt, die HMAC-Signatur verifiziert und das fertige PDF im Archiv +ablegt. + +## Use-Case + +Wenn du Captures mit `mode=webhook` anlegst, liefert hightrusted das fertige +Ergebnis per HTTP-POST aus — du musst nicht pollen. Dieser Server nimmt das +entgegen, prüft, archiviert. + +## Setup + +```bash +npm install +export HIGHTRUSTED_API_KEY=ht_live_... +export HIGHTRUSTED_WEBHOOK_SECRET=wh_secret_... +export ARCHIVE_DIR=./archiv + +npm start +``` + +Der Server lauscht auf Port 8000 (per `PORT` änderbar). Der Endpoint heißt +`/webhooks/capture`. + +## Public Reachability + +Lokal läuft der Server auf `localhost`. Damit hightrusted's Server ihn erreichen, +brauchst du eine öffentlich erreichbare URL. Drei Optionen: + +1. **Produktion:** hinter nginx auf einem öffentlichen Server, mit TLS +2. **Entwicklung:** [ngrok](https://ngrok.com) — `ngrok http 8000` +3. **Eigene Tunnel:** Cloudflare Tunnel, Tailscale Funnel, etc. + +## Capture mit Webhook anlegen + +```bash +curl -X POST https://capture.hightrusted.net/api/v1/captures \ + -H "Authorization: Bearer ht_live_..." \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "mode": "webhook", + "webhook_url": "https://your-domain.tld/webhooks/capture", + "reference": "case-001" + }' +``` + +## Sicherheit + +- **Signatur immer prüfen** — sonst akzeptiert der Server gespoofte Callbacks. +- **Schnell mit 200 antworten** — der Server muss in <10 s antworten, sonst + retried hightrusted. Long-Running-Tasks (PDF herunterladen, in Archiv legen) + laufen asynchron, *nachdem* der 200 raus ist. +- **Idempotenz** — derselbe Event kann mehrfach kommen (bei Retry). In Produktion + solltest du eine Tabelle mit gesehenen `event_id`s pflegen. + +## Lizenz + +MIT. diff --git a/webhook-receiver/package.json b/webhook-receiver/package.json new file mode 100644 index 0000000..aea866a --- /dev/null +++ b/webhook-receiver/package.json @@ -0,0 +1,18 @@ +{ + "name": "hightrusted-capture-webhook-receiver", + "version": "0.1.0", + "description": "Express-basierter Webhook-Receiver für hightrusted CAPTURE", + "type": "module", + "main": "server.mjs", + "engines": { + "node": ">=18" + }, + "scripts": { + "start": "node server.mjs" + }, + "dependencies": { + "@hightrusted/capture": "^0.1.0", + "express": "^4.19.0" + }, + "license": "MIT" +} diff --git a/webhook-receiver/server.mjs b/webhook-receiver/server.mjs new file mode 100644 index 0000000..8f55c41 --- /dev/null +++ b/webhook-receiver/server.mjs @@ -0,0 +1,96 @@ +// hightrusted CAPTURE — Webhook Receiver (Express) +// +// Empfängt capture.ready / capture.failed Events, verifiziert die HMAC-Signatur, +// lädt das fertige PDF herunter und legt es im Archiv ab. +// +// Use-Case: +// Du sendest Capture-Anfragen mit mode=webhook. Statt zu pollen, bekommst du +// den fertigen Capture per HTTP-POST geliefert. Dieser Server nimmt das +// entgegen, validiert und archiviert. +// +// Setup: +// npm install express @hightrusted/capture +// export HIGHTRUSTED_API_KEY=ht_live_... +// export HIGHTRUSTED_WEBHOOK_SECRET=wh_secret_... +// node server.mjs + +import express from 'express'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { Client, verifyWebhookSignature } from '@hightrusted/capture'; + +const PORT = process.env.PORT ?? 8000; +const WEBHOOK_SECRET = process.env.HIGHTRUSTED_WEBHOOK_SECRET; +const ARCHIVE_DIR = process.env.ARCHIVE_DIR ?? './archiv'; + +if (!WEBHOOK_SECRET) { + console.error('HIGHTRUSTED_WEBHOOK_SECRET fehlt — siehe Dashboard.'); + process.exit(1); +} + +const client = new Client(); // API-Key aus ENV +const app = express(); + +// WICHTIG: raw body brauchen wir für die HMAC-Verifikation. +// Express muss den Body als Buffer durchreichen, nicht parsen. +app.post( + '/webhooks/capture', + express.raw({ type: 'application/json', limit: '1mb' }), + async (req, res) => { + const signature = req.header('X-Hightrusted-Signature') ?? ''; + if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) { + console.warn('[%s] Signature mismatch', new Date().toISOString()); + return res.status(401).json({ error: 'invalid_signature' }); + } + + let payload; + try { + payload = JSON.parse(req.body.toString('utf-8')); + } catch { + return res.status(400).json({ error: 'invalid_json' }); + } + + const { event, capture } = payload; + console.log('[%s] %s — %s', new Date().toISOString(), event, capture?.id); + + // Sofort 200 — der Rest läuft asynchron, sonst rennt der Webhook in Timeout + res.status(200).json({ ok: true }); + + if (event === 'capture.ready') { + try { + await archiveCapture(capture); + } catch (err) { + console.error('Archive-Fehler:', err); + } + } else if (event === 'capture.failed') { + console.error('Capture failed: ref=%s reason=%s', capture.reference, payload.error?.code); + // → hier: Mandant benachrichtigen, ggf. retry + } + } +); + +app.get('/health', (_req, res) => res.json({ ok: true })); + +app.listen(PORT, () => { + console.log(`Webhook-Receiver lauscht auf :${PORT}/webhooks/capture`); +}); + +// ───────────────────────────────────────────────────────────────── +async function archiveCapture(capture) { + const date = new Date(capture.timestamp.issued_at).toISOString().slice(0, 10); + const dir = join(ARCHIVE_DIR, date); + await mkdir(dir, { recursive: true }); + + const ref = capture.reference ?? capture.id.slice(0, 8); + const safe = ref.replace(/[^a-zA-Z0-9_-]/g, '_'); + const pdfPath = join(dir, `${safe}_${capture.id.slice(0, 8)}.pdf`); + + await client.downloadPdf(capture.id, pdfPath); + await writeFile( + `${pdfPath}.json`, + JSON.stringify(capture, null, 2), + 'utf-8' + ); + + console.log(' → archiviert: %s', pdfPath); +} diff --git a/wordpress-plugin/README.md b/wordpress-plugin/README.md new file mode 100644 index 0000000..ce1afcb --- /dev/null +++ b/wordpress-plugin/README.md @@ -0,0 +1,70 @@ +# WordPress-Plugin für hightrusted CAPTURE + +Forensische Captures direkt aus dem WordPress-Backend. Beim Speichern eines +Beitrags optional eine Capture mit qualifiziertem Zeitstempel erstellen lassen +— nützlich für Foren, Bewertungsportale, Kommentarsysteme und alles, wo +beweisrelevante Inhalte vor Moderation/Löschung gesichert werden müssen. + +## Use-Case + +- **Bewertungsportale** — Negative Bewertung kommt rein → Capture erstellen → später ist die Bewertung weg, aber das gerichtsverwertbare PDF ist da. +- **Foren mit Moderation** — Beleidigender Beitrag wird gemeldet → Moderator macht Capture **bevor** er löscht. +- **News-Seiten mit Updates** — Snapshot der jeweiligen Version eines Artikels für die Versionierung. +- **Affiliate-Inhalte** — Stand der eigenen Inhalte zum Veröffentlichungs-Zeitpunkt sichern (Werbe-Compliance). + +## Installation + +```bash +cd wp-content/plugins/ +git clone https://git.hightrusted.net/hightrusted-capture/examples.git capture-temp +mv capture-temp/wordpress-plugin hightrusted-capture +rm -rf capture-temp +cd hightrusted-capture +composer install --no-dev +``` + +Dann im WordPress-Admin: **Plugins → hightrusted CAPTURE aktivieren**. + +## Konfiguration + +Im Admin: **Einstellungen → hightrusted CAPTURE** → API-Key eintragen. +API-Key holen unter https://capture.hightrusted.net/dashboard/api-keys. + +## Nutzung + +Im Beitrags-/Seiten-Editor erscheint rechts eine Meta-Box "hightrusted CAPTURE". + +- **Neuer Beitrag:** Checkbox *"Jetzt forensische Capture erstellen"* anhaken → speichern. +- **Bestehender Beitrag mit Capture:** Verifikations-Link wird angezeigt. Optional *"Erneut capturen"* anhaken. + +Die Capture-Metadaten werden in den Post-Metas gespeichert: +- `_hightrusted_capture_id` +- `_hightrusted_verify_url` +- `_hightrusted_issued_at` + +## Frontend-Integration (optional) + +Im Theme zeigen, dass der Beitrag forensisch gesichert ist: + +```php +ID, '_hightrusted_verify_url', true); +if ($verifyUrl): ?> +

+ 🔒 + Forensische Capture mit qualifiziertem Zeitstempel + +

+ +``` + +## Roadmap + +- [ ] Auto-Capture bei jedem `post_publish`-Event (kostet Quota — optional) +- [ ] Bulk-Capture für ausgewählte Beiträge +- [ ] WP-Cron für regelmäßige Re-Captures +- [ ] Gutenberg-Block "Capture-Status anzeigen" + +## Lizenz + +MIT. diff --git a/wordpress-plugin/composer.json b/wordpress-plugin/composer.json new file mode 100644 index 0000000..4dedb32 --- /dev/null +++ b/wordpress-plugin/composer.json @@ -0,0 +1,10 @@ +{ + "name": "hightrusted/capture-wordpress-plugin", + "description": "WordPress-Plugin für hightrusted CAPTURE — forensische Captures aus dem Backend", + "type": "wordpress-plugin", + "license": "MIT", + "require": { + "php": ">=8.1", + "hightrusted/capture": "^0.1.0" + } +} diff --git a/wordpress-plugin/hightrusted-capture.php b/wordpress-plugin/hightrusted-capture.php new file mode 100644 index 0000000..5d105ef --- /dev/null +++ b/wordpress-plugin/hightrusted-capture.php @@ -0,0 +1,205 @@ + +
+

hightrusted CAPTURE

+
+ + + + + + +
+ +

+ API-Key holen unter + + capture.hightrusted.net + +

+
+ +
+
+ ID, '_hightrusted_capture_id', true); + $verifyUrl = get_post_meta($post->ID, '_hightrusted_verify_url', true); + $issuedAt = get_post_meta($post->ID, '_hightrusted_issued_at', true); + + if ($captureId) { + ?> +

✓ Capture vorhanden

+

+

Verifikations-Link

+

Zeitstempel:

+

+ +

+ +

Noch keine Capture für diesen Beitrag.

+

+ +

+ $apiKey]); + $capture = $client->capture([ + 'url' => get_permalink($postId), + 'reference' => "wp-post:{$postId}", + 'viewport' => ['width' => 1920, 'height' => 1080], + ]); + + update_post_meta($postId, '_hightrusted_capture_id', $capture['id']); + update_post_meta($postId, '_hightrusted_verify_url', $capture['verify_url']); + update_post_meta($postId, '_hightrusted_issued_at', $capture['timestamp']['issued_at']); + + self::adminNotice('success', sprintf( + 'hightrusted CAPTURE erstellt: Verifikations-Link', + esc_url($capture['verify_url']), + )); + } catch (HightrustedException $e) { + self::adminNotice('error', "hightrusted CAPTURE: {$e->getMessage()} (Code: {$e->errorCode})"); + } + } + + private static function adminNotice(string $type, string $message): void + { + $key = 'hightrusted_capture_notice'; + set_transient($key, ['type' => $type, 'message' => $message], 30); + add_action('admin_notices', function () use ($key) { + $notice = get_transient($key); + if (!$notice) return; + delete_transient($key); + printf( + '

%s

', + esc_attr($notice['type']), + wp_kses_post($notice['message']), + ); + }); + } +} + +HightrustedCapturePlugin::init();