feat: initial examples content (v0.1.0)
- Markenrecht-Monitoring (Python) — täglich URLs capturen - Webhook-Receiver (Node.js Express) — capture.ready Events archivieren - WordPress-Plugin (PHP) — Captures aus dem WP-Backend
This commit is contained in:
parent
b250afb3e4
commit
64e9f1144b
12 changed files with 686 additions and 37 deletions
20
LICENSE
20
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.
|
||||
|
|
|
|||
56
README.md
56
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.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
60
markenrecht-monitoring/README.md
Normal file
60
markenrecht-monitoring/README.md
Normal file
|
|
@ -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.
|
||||
108
markenrecht-monitoring/monitor.py
Normal file
108
markenrecht-monitoring/monitor.py
Normal file
|
|
@ -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))
|
||||
13
markenrecht-monitoring/urls.example.yaml
Normal file
13
markenrecht-monitoring/urls.example.yaml
Normal file
|
|
@ -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
|
||||
61
webhook-receiver/README.md
Normal file
61
webhook-receiver/README.md
Normal file
|
|
@ -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.
|
||||
18
webhook-receiver/package.json
Normal file
18
webhook-receiver/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
96
webhook-receiver/server.mjs
Normal file
96
webhook-receiver/server.mjs
Normal file
|
|
@ -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);
|
||||
}
|
||||
70
wordpress-plugin/README.md
Normal file
70
wordpress-plugin/README.md
Normal file
|
|
@ -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
|
||||
<?php
|
||||
$verifyUrl = get_post_meta($post->ID, '_hightrusted_verify_url', true);
|
||||
if ($verifyUrl): ?>
|
||||
<p class="forensic-snapshot">
|
||||
🔒 <a href="<?= esc_url($verifyUrl) ?>" target="_blank">
|
||||
Forensische Capture mit qualifiziertem Zeitstempel
|
||||
</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## 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.
|
||||
10
wordpress-plugin/composer.json
Normal file
10
wordpress-plugin/composer.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
205
wordpress-plugin/hightrusted-capture.php
Normal file
205
wordpress-plugin/hightrusted-capture.php
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: hightrusted CAPTURE
|
||||
* Plugin URI: https://capture.hightrusted.net
|
||||
* Description: Forensische Web-Captures direkt aus dem WordPress-Backend — mit qualifiziertem Zeitstempel nach RFC 3161 / eIDAS Art. 41.
|
||||
* Version: 0.1.0
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.1
|
||||
* Author: hightrusted GmbH
|
||||
* Author URI: https://hightrusted.net
|
||||
* License: MIT
|
||||
* License URI: https://opensource.org/licenses/MIT
|
||||
* Text Domain: hightrusted-capture
|
||||
*
|
||||
* Use-Case:
|
||||
* Du betreibst ein Forum/Bewertungsportal/Kommentarsystem auf WordPress.
|
||||
* Bei beleidigenden, rechtswidrigen oder einfach beweisrelevanten Inhalten
|
||||
* willst du *vor* der Moderation eine forensische Capture haben.
|
||||
*
|
||||
* Das Plugin fügt einen Button "Capture erstellen" zur Beitrags-/Kommentar-
|
||||
* Ansicht hinzu. Klick → Capture entsteht via API → URL + Verify-Link in den
|
||||
* Beitrags-Metadaten gespeichert.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit; // Direktaufruf verbieten
|
||||
}
|
||||
|
||||
require_once __DIR__.'/vendor/autoload.php';
|
||||
|
||||
use Hightrusted\Capture\Client;
|
||||
use Hightrusted\Capture\HightrustedException;
|
||||
|
||||
class HightrustedCapturePlugin
|
||||
{
|
||||
private const OPTION_API_KEY = 'hightrusted_capture_api_key';
|
||||
|
||||
public static function init(): void
|
||||
{
|
||||
add_action('admin_menu', [self::class, 'addAdminMenu']);
|
||||
add_action('admin_init', [self::class, 'registerSettings']);
|
||||
add_action('add_meta_boxes', [self::class, 'addMetaBox']);
|
||||
add_action('save_post', [self::class, 'handleManualCapture'], 10, 2);
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Settings-Page
|
||||
// ────────────────────────────────────────────────────────────
|
||||
public static function addAdminMenu(): void
|
||||
{
|
||||
add_options_page(
|
||||
'hightrusted CAPTURE',
|
||||
'hightrusted CAPTURE',
|
||||
'manage_options',
|
||||
'hightrusted-capture',
|
||||
[self::class, 'renderSettingsPage'],
|
||||
);
|
||||
}
|
||||
|
||||
public static function registerSettings(): void
|
||||
{
|
||||
register_setting('hightrusted_capture', self::OPTION_API_KEY);
|
||||
}
|
||||
|
||||
public static function renderSettingsPage(): void
|
||||
{
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>hightrusted CAPTURE</h1>
|
||||
<form method="post" action="options.php">
|
||||
<?php settings_fields('hightrusted_capture'); ?>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="api_key">API-Key</label></th>
|
||||
<td>
|
||||
<input type="password" name="<?= esc_attr(self::OPTION_API_KEY) ?>"
|
||||
id="api_key" class="regular-text"
|
||||
value="<?= esc_attr(get_option(self::OPTION_API_KEY, '')) ?>" />
|
||||
<p class="description">
|
||||
API-Key holen unter
|
||||
<a href="https://capture.hightrusted.net/dashboard/api-keys" target="_blank">
|
||||
capture.hightrusted.net
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Meta-Box im Beitrags-Editor
|
||||
// ────────────────────────────────────────────────────────────
|
||||
public static function addMetaBox(): void
|
||||
{
|
||||
add_meta_box(
|
||||
'hightrusted_capture_box',
|
||||
'hightrusted CAPTURE',
|
||||
[self::class, 'renderMetaBox'],
|
||||
['post', 'page'],
|
||||
'side',
|
||||
'high',
|
||||
);
|
||||
}
|
||||
|
||||
public static function renderMetaBox(\WP_Post $post): void
|
||||
{
|
||||
wp_nonce_field('hightrusted_capture', 'hightrusted_capture_nonce');
|
||||
|
||||
$captureId = get_post_meta($post->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) {
|
||||
?>
|
||||
<p><strong>✓ Capture vorhanden</strong></p>
|
||||
<p><code><?= esc_html($captureId) ?></code></p>
|
||||
<p><a href="<?= esc_url($verifyUrl) ?>" target="_blank">Verifikations-Link</a></p>
|
||||
<p><em>Zeitstempel: <?= esc_html($issuedAt) ?></em></p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="hightrusted_recapture" value="1">
|
||||
Erneut capturen (überschreibt obige)
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<p>Noch keine Capture für diesen Beitrag.</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="hightrusted_capture_now" value="1">
|
||||
Jetzt forensische Capture erstellen
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Speichern: Capture erstellen wenn angefordert
|
||||
// ────────────────────────────────────────────────────────────
|
||||
public static function handleManualCapture(int $postId, \WP_Post $post): void
|
||||
{
|
||||
if (!isset($_POST['hightrusted_capture_nonce'])
|
||||
|| !wp_verify_nonce($_POST['hightrusted_capture_nonce'], 'hightrusted_capture')) {
|
||||
return;
|
||||
}
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
|
||||
if (!current_user_can('edit_post', $postId)) return;
|
||||
|
||||
$shouldCapture = !empty($_POST['hightrusted_capture_now'])
|
||||
|| !empty($_POST['hightrusted_recapture']);
|
||||
if (!$shouldCapture) return;
|
||||
|
||||
$apiKey = get_option(self::OPTION_API_KEY);
|
||||
if (!$apiKey) {
|
||||
self::adminNotice('error', 'hightrusted CAPTURE: API-Key fehlt. Siehe Einstellungen.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = new Client(['api_key' => $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: <a href="%s" target="_blank">Verifikations-Link</a>',
|
||||
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(
|
||||
'<div class="notice notice-%s is-dismissible"><p>%s</p></div>',
|
||||
esc_attr($notice['type']),
|
||||
wp_kses_post($notice['message']),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HightrustedCapturePlugin::init();
|
||||
Loading…
Add table
Reference in a new issue