First Upload

This commit is contained in:
friloo 2026-07-01 18:26:57 +02:00 committed by GitHub
parent efc6fcbafa
commit c61677d47f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 3921 additions and 0 deletions

78
docs/ARCHITECTURE.md Normal file
View file

@ -0,0 +1,78 @@
# Architektur
## Überblick
Die Erweiterung ist ein **Manifest-V3-Client** ohne eigenen Server. Sie besteht aus vier Kontexten, die
über `chrome.runtime`-Nachrichten kommunizieren:
```
┌───────────────┐ Messages ┌──────────────────────┐ HTTPS/Bearer ┌──────────────────┐
│ popup.html │ ───────────▶ │ background.js │ ───────────────▶ │ OpenNIT-Server │
│ popup.js │ ◀─────────── │ (Service Worker) │ ◀─────────────── │ /api/vault/... │
└───────────────┘ │ - Cache (5 Min) │ └──────────────────┘
┌───────────────┐ Messages │ - Lock-Gate │
│ content.js │ ───────────▶ │ - Favicon-Cache │
│ (jede Seite) │ ◀─────────── │ - Clipboard-Timer │
└───────────────┘ └──────────┬───────────┘
│ CLIP_WRITE
┌────────▼─────────┐
│ offscreen.html │ (Zwischenablage leeren)
└──────────────────┘
```
## Komponenten
| Datei | Rolle |
|-------|-------|
| `background.js` | Zentrale Logik: API-Aufrufe, 5-Minuten-Cache, **Lock-Gate**, Favicon-Cache (Data-URLs), Zwischenablage-Timer. Alle Secrets fließen hier durch. |
| `content.js` | Wird auf jeder Seite ausgeführt. Erkennt Benutzer-/Passwort-/OTP-Felder (inkl. Shadow-DOM, mehrstufige Logins, segmentierte OTP-Felder), zeigt das Vorschlags-Dropdown und füllt Felder framework-kompatibel (React/Vue/Angular). |
| `popup.html` / `popup.js` | Toolbar-Popup: Liste, Suche, Detailansicht, Anlegen + Generator, PIN-Schirm. |
| `options.html` / `options.js` | Einstellungen: Server-URL, Token, PIN-Sperrdauer, Zwischenablage. |
| `offscreen.html` / `offscreen.js` | Minimaldokument, das ausschließlich die Zwischenablage leert (MV3-konform). |
## Nachrichten (Auszug)
| Typ | Von → Nach | Zweck |
|-----|-----------|-------|
| `CHECK_STATUS` | popup/content → bg | Token prüfen, App-Name/User, `pin_enabled` |
| `GET_LOCK` / `DO_UNLOCK` / `LOCK_NOW` | popup → bg | PIN-Sperre abfragen/entsperren/sperren |
| `GET_ENTRIES` / `GET_MATCHING_ENTRIES` | popup/content → bg | Einträge (alle / passend zur URL) |
| `GET_PASSWORD` / `GET_TOTP` | popup/content → bg | Secret **on demand** |
| `GET_FAVICON` | popup/content → bg | Favicon als Data-URL (serverseitig gecacht) |
| `VAULT_FILL` | popup → content | Aktives Tab-Formular ausfüllen |
| `SCHEDULE_CLIP_CLEAR` | popup/content → bg | Zwischenablage-Leerung planen |
## Server-API (in OpenNIT)
Alle Endpunkte unter `/api/vault/extension/` mit `Authorization: Bearer <token>`:
- `GET /entries` Liste (Titel, Benutzer, URL, Notizen, `has_totp`, `favicon_domain`, `has_favicon`)
- `GET /entries/{id}/password` Passwort (protokolliert im Audit-Log)
- `GET /entries/{id}/totp` aktueller TOTP-Code + Restsekunden
- `GET /entries/{id}/favicon?fetch=1` gecachtes Favicon (bei Bedarf serverseitig geholt)
- `POST /entries` neuen Eintrag anlegen
- `GET /status` Token gültig? + `pin_enabled` / `pin_lock_secs`
- `POST /unlock` Tresor-PIN verifizieren + serverseitiges Entsperr-Fenster für den Token setzen
- `POST /lock` Token sofort wieder sperren (Entsperr-Fenster zurücksetzen)
- `GET /oauth/authorize` · `POST /oauth/authorize` SSO-Anmeldung/Zustimmung (Session, PKCE)
- `POST /oauth/token` Authorization-Code- bzw. Refresh-Grant (öffentlich, PKCE) → Access/Refresh
- `POST /oauth/revoke` Refresh-Kette widerrufen (Logout)
Details zum SSO-Flow: [`SSO-PLAN.md`](SSO-PLAN.md). Die Erweiterung nutzt SSO über `chrome.identity`;
Access-Tokens werden im Hintergrund still per Refresh (mit Rotation) erneuert.
## Lock-Gate (serverseitig erzwungen)
Ist für den Nutzer ein **Tresor-PIN** aktiv, liefern die Server-Endpunkte für Einträge/Passwort/TOTP
erst nach frischer PIN-Entsperrung Daten (`unlocked_until` pro Token) und antworten sonst mit **HTTP 423**.
Der Client spiegelt den Zustand nur (PIN-Schirm) die eigentliche Durchsetzung liegt im Server, damit ein
**gestohlener Token allein wertlos** ist. Der Client hält seinen Entsperr-Status zusätzlich in
`chrome.storage.session` (verfällt beim Schließen des Browsers). Die Einstellung „PIN-Sperre" legt nur die
Fensterdauer fest; „Bis der Browser geschlossen wird" nutzt ein langes Serverfenster + Client-Sitzungsende.
## Sicherheitsprinzipien
- **Kein Remote-Code** alle Skripte im Paket (MV3-CSP-konform, keine Inline-Skripte).
- **Secrets on demand** Passwörter/TOTP erst bei Nutzung, nie in der Liste.
- **Kein persistentes Secret** nur URL, Token, Einstellungen in `chrome.storage`.
- **Server-seitige Krypto** Ver-/Entschlüsselung im OpenNIT-Server, nicht im Browser.

76
docs/INSTALL.md Normal file
View file

@ -0,0 +1,76 @@
# Installation & Einrichtung
## Voraussetzungen
- Eine erreichbare **OpenNIT-Instanz** mit aktiviertem Modul **Passwort-Tresor**.
- Ein Chromium-basierter Browser (Chrome, Edge, Brave, Vivaldi …).
## 1. Erweiterung laden
### A) Entpackt aus dem Quellcode (Entwicklung / self-hosted)
1. `chrome://extensions` öffnen.
2. Oben rechts **Entwicklermodus** einschalten.
3. **„Entpackte Erweiterung laden"** klicken und den Ordner **`extension/`** auswählen.
4. Die Erweiterung erscheint in der Liste; per Puzzle-Symbol an die Toolbar anheften.
### B) Aus dem Chrome Web Store
Sobald veröffentlicht: im Store nach **„OpenNIT Vault"** suchen und **„Hinzufügen"** klicken.
### C) Fertiges ZIP aus dem OpenNIT-Backend
Im OpenNIT-Web-Tresor gibt es unter **„Extension"** einen ZIP-Download mit bereits vorausgefüllter
Server-URL. Diesen entpacken und wie unter **A)** laden.
## 2. Anmelden
**Empfohlen SSO:** In den Erweiterungs-Einstellungen die **Server-URL** eintragen und auf
**„Mit OpenNIT anmelden"** klicken. Es öffnet sich die gewohnte OpenNIT-Anmeldung (lokal + 2FA /
Microsoft 365 / Keycloak). Nach erfolgreicher Anmeldung und Zustimmung ist die Erweiterung verbunden
der Zugang wird automatisch erneuert. Über **„Abmelden"** wird die Sitzung serverseitig widerrufen.
> Voraussetzung: Der Betreiber muss ggf. die Redirect-URI der Erweiterung hinterlegen
> (Backend → Administration → **Vault-Erweiterung**). `*.chromiumapp.org` ist standardmäßig erlaubt.
**Alternative manueller Token** (Kiosk/Headless ohne interaktiven Login): siehe Abschnitt „Erweitert"
in den Einstellungen und die folgenden Schritte.
## 2b. Manuellen Token erzeugen
1. In OpenNIT den **Passwort-Tresor** öffnen.
2. Auf **„Extension"** klicken und einen **API-Token** generieren.
3. Der Token wird **nur einmal** angezeigt kopieren.
> Der Token verschlüsselt serverseitig deinen Vault-Schlüssel. Behandle ihn wie ein Passwort.
## 3. Erweiterung konfigurieren
1. Auf das OpenNIT-Vault-Symbol klicken → Zahnrad **Einstellungen** (oder `chrome://extensions`
Details → Erweiterungsoptionen).
2. **Server-URL** eintragen (z. B. `https://vault.firma.de`, ohne `/` am Ende).
3. **API-Token** einfügen → **Speichern**.
4. **Verbindung testen** es sollte „Verbunden als …" erscheinen.
## 4. Optional: PIN-Sperre
1. In OpenNIT unter **Tresor-PIN** einen PIN festlegen (falls noch nicht geschehen).
2. In den Erweiterungs-Einstellungen unter **Sicherheit** eine **Sperrdauer** wählen
(5 Min / 15 Min / 1 Std / bis der Browser geschlossen wird).
3. Ab jetzt verlangt die Erweiterung nach Ablauf den **Tresor-PIN**, bevor Zugangsdaten sichtbar werden.
## 5. Nutzung
- **Autofill:** Login-Feld auf einer Website anklicken → Vorschläge erscheinen → Eintrag wählen.
- **Popup:** Symbol anklicken → suchen → Eintrag anklicken für Details (Anzeigen/Kopieren, 2FA,
„Auf dieser Seite ausfüllen").
- **Neu anlegen:** im Popup auf **+** → Felder ausfüllen, Passwort per Generator erzeugen → Speichern.
## Fehlerbehebung
| Problem | Ursache / Lösung |
|--------|------------------|
| „Nicht verbunden" | Server-URL/Token prüfen; endet die URL ohne `/`? Ist die Instanz erreichbar (HTTPS/Zertifikat)? |
| Keine Vorschläge auf einer Seite | Ist der Tresor per PIN gesperrt? Passt eine hinterlegte URL zur Domain? Seite neu laden. |
| „Seite nicht bereit" beim Ausfüllen | Seite einmal neu laden, damit das Content-Script aktiv ist. |
| Favicons fehlen | Werden serverseitig per Cron nachgeladen; erscheinen nach dem ersten Durchlauf. |

59
docs/PERMISSIONS.md Normal file
View file

@ -0,0 +1,59 @@
# Berechtigungen Begründung
Diese Übersicht erklärt jede angeforderte Berechtigung (auch für die Chrome-Web-Store-Prüfung).
## `host_permissions: ["<all_urls>"]`
**Warum:** Ein Passwort-Manager muss Login-Felder auf **beliebigen** Websites erkennen und auf Wunsch
ausfüllen können. Deshalb ist Zugriff auf alle URLs erforderlich.
**Was NICHT passiert:** Es werden keine Seiteninhalte gelesen, gespeichert oder übertragen ausschließlich
Formularfelder (Benutzer/Passwort/OTP) werden erkannt und beim Ausfüllen beschrieben. Diese verlassen den
Browser nicht. Es findet kein Tracking und keine Analyse statt.
## `content_scripts` (matches `<all_urls>`, `run_at: document_idle`)
**Warum:** Das In-Seite-Dropdown mit Vorschlägen und die Felderkennung laufen als Content-Script.
Notwendig für Autofill und die 2FA-Erkennung (inkl. Shadow-DOM und mehrstufiger Logins).
## `scripting`
**Warum:** Werte werden über den nativen Value-Setter gesetzt und Events ausgelöst, damit auch
React/Vue/Angular-Formulare die Eingaben übernehmen.
## `activeTab`
**Warum:** Zugriff auf den aktiven Tab beim Ausfüllen aus dem Popup („Auf dieser Seite ausfüllen").
## `storage`
**Warum:** Lokale Speicherung von Server-URL, API-Token und Einstellungen; Entsperr-Status in
`storage.session`.
## `alarms`
**Warum:** Zeitgesteuertes Leeren der Zwischenablage (~30 s) sowie periodisches Verwerfen des
Einträge-Caches (5 Min).
## `offscreen`
**Warum:** In Manifest V3 hat der Service Worker keinen DOM-Zugriff. Zum programmatischen Leeren der
Zwischenablage wird ein kurzlebiges Offscreen-Dokument (Reason `CLIPBOARD`) genutzt.
## `identity`
**Warum:** Für die **SSO-Anmeldung** (`chrome.identity.launchWebAuthFlow`, OAuth 2.0 + PKCE). Öffnet die
OpenNIT-Login-Seite und empfängt die Weiterleitung an `https://<extension-id>.chromiumapp.org/`. Es wird
**kein** Zugriff auf Google-Konten o. Ä. genommen nur der Web-Auth-Flow zur konfigurierten OpenNIT-Instanz.
## Bewusst NICHT angefordert
- **`tabs`** entfällt: Die aktive Tab-Adresse ist bereits über `host_permissions` verfügbar. Dadurch
erscheint **keine** „Browserverlauf lesen"-Warnung.
- Keine `cookies`, `history`, `webRequest`, `downloads`, `notifications` o. Ä.
## Single-Purpose-Erklärung (für den Store)
> OpenNIT Vault dient einem einzigen Zweck: dem Verwalten und automatischen Ausfüllen von Zugangsdaten
> und 2FA-Codes aus einer selbst gehosteten OpenNIT-Instanz. Alle Berechtigungen dienen ausschließlich
> diesem Zweck.

276
docs/SSO-PLAN.md Normal file
View file

@ -0,0 +1,276 @@
# Umsetzungsplan: SSO-Anmeldung für die OpenNIT-Vault-Erweiterung
_Status: Konzept (noch nicht implementiert) · Stand 2026-07-01_
Ziel: Der Nutzer installiert die Erweiterung, gibt **nur die Server-URL** ein und **meldet sich an wie an
OpenNIT** (lokaler Login + 2. Faktor / Microsoft 365 / Keycloak). Die Erweiterung erhält daraufhin ihre
Zugangstokens **automatisch** kein manuelles Kopieren mehr. Kurz vor Ablauf fordert die Erweiterung eine
erneute Anmeldung (Re-Auth).
Dieses Dokument ist der **gründliche Umsetzungsplan** kein Code.
---
## 1. Leitidee
**OpenNIT wird zum OAuth-2.0-Autorisierungsserver für seine eigene Erweiterung.**
Die Erweiterung ist ein **öffentlicher OAuth-Client** (kein Client-Secret) und spricht **ausschließlich mit
OpenNIT** nicht direkt mit Microsoft/Keycloak. Wie sich der Nutzer bei OpenNIT anmeldet (lokal + TOTP/
WebAuthn, Azure AD, Keycloak), ist für die Erweiterung **transparent**: Sie nutzt einfach die bestehende
OpenNIT-Login-Seite. Das löst automatisch **alle** Anmeldemethoden mit einem einzigen Extension-Flow.
Verwendeter Standard: **OAuth 2.0 Authorization Code Flow mit PKCE** (RFC 7636), umgesetzt über die
Browser-API **`chrome.identity.launchWebAuthFlow`**.
> Bereits vorhandene Bausteine in OpenNIT, auf denen aufgesetzt wird:
> `src/Auth/OAuthClient.php` (OIDC/PKCE zu Azure/Keycloak), `src/Auth/TokenManager.php`,
> `src/Auth/SessionManager.php`, `src/Controllers/AuthController.php` (lokaler + Azure + Keycloak Login),
> `src/Vault/VaultManager.php` (`unlockOrSetup`, `unlockOrSetupSso`, `storeVmkInSession`,
> `getVmkFromSession`, PIN-Entsperrung), sowie das bestehende Token-Modell in
> `src/Controllers/VaultApiController.php` (`tokenGenerate`, VMK-Wrapping via `deriveKeyFromToken`).
---
## 2. Zwei Token-Typen (statt einem 180-Tage-Token)
| Token | Lebensdauer (Vorschlag) | Speicherort | Zweck |
|-------|-------------------------|-------------|-------|
| **Access-Token** | kurz (1560 Min) | `chrome.storage.session` (flüchtig) | Bearer für alle `/api/vault/extension/*`-Aufrufe |
| **Refresh-Token** | lang (1430 Tage, rotierend) | `chrome.storage.local` | Holt still neue Access-Tokens; wird bei jeder Nutzung **rotiert** |
Vorteile gegenüber dem heutigen manuellen 180-Tage-Token:
- Ein gestohlener **Access-Token** verfällt in Minuten.
- Der **Refresh-Token rotiert** bei jeder Nutzung → **Diebstahl-Erkennung** (Reuse Detection).
- **Kein Copy-&-Paste** eines langlebigen Geheimnisses.
- **Zentraler Widerruf** über die Web-Oberfläche (Sitzungen beenden).
- Kombinierbar mit dem bereits umgesetzten **serverseitigen PIN-Gate** (Defense in Depth).
---
## 3. Der VMK-Kern (wichtigster Design-Punkt)
OpenNIT ver-/entschlüsselt Tresor-Einträge serverseitig mit dem **Vault Master Key (VMK)**. Jeder
Extension-Token muss den VMK also (server-seitig) verfügbar machen. Heute wrappt `tokenGenerate` den VMK
unter einem aus dem Token abgeleiteten Schlüssel.
**Herausforderung:** Beim **stillen Refresh** (ohne Nutzerinteraktion, ohne Passwort) muss der Server den
VMK weiterhin bereitstellen können.
**Lösung VMK „wandert" mit dem Refresh-Token:**
1. **Bei der Erstanmeldung** (Authorization-Code-Grant) ist der Web-Login gerade erfolgt → der VMK liegt in
der Session (lokaler Login entsperrt per Passwort; SSO-Nutzer per `unlockOrSetupSso`). Der Server:
- wrappt den VMK unter einem aus dem **Refresh-Token** abgeleiteten Schlüssel → speichert ihn in der
Refresh-Zeile,
- wrappt den VMK unter einem aus dem **Access-Token** abgeleiteten Schlüssel → Access-Zeile (kurzlebig).
2. **Beim Refresh:** Server entpackt den VMK mit dem Refresh-Schlüssel, **rotiert** (neuer Refresh-Token,
VMK neu gewrappt, alter Token als „rotiert" markiert), gibt einen neuen Access-Token (VMK gewrappt) aus.
Damit ist der **Refresh-Token** faktisch das langlebige VMK-tragende Geheimnis (wie heute der manuelle
Token) aber **kürzerlebig, rotierend, per SSO bezogen und widerrufbar**. Der **Access-Token** ist das
kurzlebige Arbeitspferd.
> Hinweis PIN-Gate: Das serverseitige PIN-Gate (bereits umgesetzt) bleibt orthogonal bestehen auch mit
> gültigem Access-Token liefern die Secret-Endpunkte bei aktivem Tresor-PIN erst nach PIN-Entsperrung.
> Ob das PIN-Gate bei SSO zusätzlich gefordert wird, ist eine Betreiber-Entscheidung (siehe §9).
---
## 4. Ablauf (Sequenz)
```
Erweiterung OpenNIT (AS) IdP (M365/Keycloak/lokal)
│ │ │
│ 1. launchWebAuthFlow(authorize? │ │
│ client_id, redirect_uri, │ │
│ code_challenge, state, scope) │ │
│──────────────────────────────────▶│ │
│ │ 2. Keine Session? → /auth/login │
│ │────────── Login (lokal+2FA/SSO) ───────▶│
│ │◀───────────── authentifiziert ─────────│
│ │ 3. Vault entsperrt? sonst PIN-Prompt │
│ │ 4. Zustimmung („Vault-Zugriff erlauben")│
│◀── 5. Redirect: redirect_uri?code=…&state ─┤ │
│ 6. state prüfen, code extrahieren │ │
│ 7. POST /oauth/token │ │
│ (code, code_verifier) │ │
│──────────────────────────────────▶│ 8. PKCE prüfen, VMK wrappen │
│◀── {access_token, refresh_token, expires_in, refresh_expires_in} ──────────┤
│ 9. Tokens speichern, loslegen │ │
│ … │ │
│ 10. Access-Token abgelaufen → POST /oauth/token (grant=refresh_token) │
│──────────────────────────────────▶│ 11. rotieren, neuen Access ausgeben │
│◀───────────────────────────────────┤ │
```
Silent-Refresh (Schritt 10/11) läuft unsichtbar. Erst wenn der **Refresh-Token abläuft** oder der Refresh
scheitert, startet die Erweiterung erneut `launchWebAuthFlow` bei noch lebender OpenNIT/IdP-Sitzung ist
das ein **stiller Redirect ohne Eingabe**, sonst ein voller Login.
---
## 5. Neue Server-Endpunkte
Alle unter `/api/vault/extension/oauth/`:
| Methode & Pfad | Auth | Zweck |
|----------------|------|-------|
| `GET /authorize` | Web-Session (Login-Redirect) | Zeigt Zustimmung, ggf. PIN-Entsperrung; erzeugt Auth-Code |
| `POST /token` | öffentlich (PKCE) | `grant_type=authorization_code` **oder** `refresh_token` → Access/Refresh |
| `POST /revoke` | Bearer/Refresh | Widerruft einen Refresh-Token (Logout in der Erweiterung) |
| `GET /sessions` (Web) | Web-Session | Liste aktiver Erweiterungs-Sitzungen im Web-Vault |
| `POST /sessions/{id}/revoke` (Web) | Web-Session | Einzelne Sitzung serverseitig beenden |
Bestehende Endpunkte (`/entries`, `/entries/{id}/password`, `/totp`, `/favicon`, `/status`, `/unlock`,
`/lock`) bleiben unverändert sie akzeptieren künftig **Access-Tokens** genauso wie die bisherigen
manuellen Tokens (siehe §8 Kompatibilität).
---
## 6. Datenmodell (neue Migrationen)
**`vault_oauth_auth_codes`** (kurzlebige Autorisierungscodes)
```
code_hash CHAR(64) PK -- sha256(code)
user_id BIGINT
code_challenge VARCHAR(128) -- PKCE (S256)
redirect_uri VARCHAR(255)
scope VARCHAR(255)
expires_at DATETIME -- ~60 Sekunden
used_at DATETIME NULL -- Einmalverwendung
created_at DATETIME
```
**`vault_oauth_refresh_tokens`** (langlebig, VMK-tragend, rotierend)
```
id BIGINT PK
user_id BIGINT
token_hash CHAR(64) -- sha256(refresh_token)
vmk_enc/nonce/tag -- VMK gewrappt unter Refresh-Schlüssel (HKDF)
device_label VARCHAR(120) -- z. B. "Chrome auf Laptop"
rotated_from BIGINT NULL -- Vorgänger (Reuse-Detection-Kette)
revoked TINYINT DEFAULT 0
refresh_expires_at DATETIME -- absolutes Ablaufdatum (1430 Tage)
created_at, last_used_at DATETIME
```
**Access-Tokens:** die vorhandene Tabelle `vault_extension_tokens` weiternutzen (kurzes `expires_at`,
`unlocked_until` für das PIN-Gate). Optional Spalte `refresh_id` (Herkunft) für Bulk-Revoke.
**Client-Registrierung:** ein fester `client_id` für die Erweiterung + erlaubte Redirect-URIs, konfiguriert
in `system_settings` bzw. einer kleinen `vault_oauth_clients`-Tabelle (Admin-GUI, siehe §7).
---
## 7. Admin-Konfiguration (Pflicht laut OpenNIT-Konventionen)
Neue Admin-Seite `/admin/vault/extension` (Layout `layouts/admin`, Capability `manage_vault_*`), nur bei
aktivem Vault-Modul:
- **Extension-Client-ID** (Vorgabe fix) und **erlaubte Redirect-URIs**. Empfehlung: die konkrete
`https://<extension-id>.chromiumapp.org/` **pinnen** (Store-ID bzw. Unpacked-ID), statt Wildcard.
- **Token-Lebensdauern**: Access (1560 Min), Refresh (1430 Tage), Re-Auth-Vorwarnung (Tage).
- **SSO-Login in der Erweiterung**: an/aus; welche Methoden angeboten werden (erbt aus dem Web-Login).
- **PIN-Gate bei SSO**: zusätzlich fordern (Defense in Depth) oder bei erfolgreichem SSO überspringen.
- Übersicht/Widerruf aktiver Sitzungen.
Alle Werte in der DB (`system_settings`), nicht in Config-Dateien (OpenNIT-Regel). Secrets nie ins Audit-Log.
---
## 8. Erweiterungs-Seite (Client)
- Manifest: Berechtigung **`identity`** ergänzen; Redirect-URI ist `chrome.identity.getRedirectURL()`
(`https://<id>.chromiumapp.org/`).
- **Options/Popup:** Button **„Mit OpenNIT anmelden"** (nach Eingabe der Server-URL). Startet
`launchWebAuthFlow({interactive:true})`.
- **Token-Haltung:** Refresh-Token in `chrome.storage.local`, Access-Token + Ablauf in
`chrome.storage.session`.
- **Auto-Refresh:** Der Background-Service-Worker hält den Access-Token frisch; vor jedem API-Call bei
Ablauf still refreshen. Bei `401`/abgelaufenem Refresh → interaktiver Re-Login.
- **Re-Auth-Vorwarnung:** X Tage vor `refresh_expires_at` ein dezenter Hinweis „Bitte neu anmelden".
- **PKCE/State** clientseitig erzeugen (Web Crypto). `state` gegen CSRF prüfen.
- **Logout:** `POST /oauth/revoke` + lokale Tokens löschen.
---
## 9. Sicherheitsdesign & Bedrohungsmodell
**Was SSO verbessert (ggü. manuellem Token):**
- Kein langlebiges Klartext-Geheimnis zum Kopieren.
- Access-Tokens kurzlebig; Refresh-Tokens **rotieren** → Reuse-Detection: Wird ein bereits rotierter
Refresh-Token erneut vorgelegt, wird die **gesamte Kette widerrufen** und Re-Login erzwungen (+ Audit-Alarm).
- Wiederverwendung der **vorhandenen Anmeldung inkl. 2. Faktor** (M365/Keycloak/lokal+TOTP/WebAuthn).
- **Zentraler Widerruf** je Gerät/Sitzung.
**Pflicht-Härtungen im Flow:**
- **PKCE (S256)** öffentlicher Client, kein Secret.
- **Redirect-URI-Allowlist** (Extension-ID pinnen).
- **`state`** gegen CSRF; **Auth-Code** einmalig, ~60 s gültig, an PKCE-Challenge + redirect_uri + user gebunden.
- **Rate-Limiting** auf `/authorize`, `/token` (bestehender RateLimiter greift; zusätzlich pro Nutzer/Client).
- **Audit-Log** für Ausgabe/Refresh/Rotation/Reuse/Revoke.
- **Nur HTTPS** (bereits in der Erweiterung erzwungen).
**Was bestehen bleibt (bewusst):**
- Der **Refresh-Token ist VMK-tragend „at rest"** in `chrome.storage.local` wie heute der Token.
Restrisiko gemindert durch: Rotation, kürzere Lebensdauer, **PIN-Gate**, optionales **Geräte-Binding**
(client-generierte Device-ID als zusätzlicher Faktor beim Refresh), und schnellen Widerruf.
- `chrome.storage` ist nicht hardware-gebunden ein vollständig kompromittiertes Endgerät bleibt ein
vollständig kompromittiertes Endgerät (gilt für jeden Passwort-Manager).
- Der Server kann prinzipbedingt den VMK entpacken (nötig für serverseitige Krypto). DB-Zugriff = Vollzugriff
(unverändert; separat durch DB-/Server-Härtung zu adressieren).
---
## 10. Kompatibilität & Migration
- **Manuelle Tokens bleiben gültig** (Tabelle `vault_extension_tokens`) kein Bruch bestehender
Installationen. Der SSO-Flow ist **additiv**.
- Die Secret-Endpunkte akzeptieren Access-Tokens **und** Alt-Tokens (dieselbe `authenticateByToken`-Logik,
ergänzt um Access-Token-Lookup).
- Options-Seite: **„Mit OpenNIT anmelden"** wird der Standardweg; **manueller Token** wandert unter
„Erweitert" (für Umgebungen ohne interaktiven Login, z. B. Kiosk/Headless).
- Empfehlung: nach Einführung die Standard-Laufzeit manueller Tokens verkürzen.
---
## 11. Phasenplan & Aufwand (grob)
| Phase | Inhalt | Aufwand |
|-------|--------|---------|
| **0. Feinkonzept** | Endpunkt-/DB-Spezifikation, Admin-Settings festzurren, Lebensdauern | 0,51 Tag |
| **1. Server-AS** | `authorize`/`token`/`revoke`, Migrationen, VMK-Wrapping+Rotation, Reuse-Detection, Audit | 35 Tage |
| **2. Admin-GUI** | Client-/Redirect-Config, Lebensdauern, Sitzungsübersicht/Widerruf | 12 Tage |
| **3. Erweiterung** | `identity`-Flow, Token-Haltung, Auto-Refresh, Re-Auth-UX, Logout | 23 Tage |
| **4. Härtung & Test** | Rate-Limits, Edge-Cases (PIN-gesperrter Vault beim authorize, SSO-only-Nutzer), End-to-End-Tests | 12 Tage |
| **5. Doku & Rollout** | Handbuch, Store-Update, Deprecation-Hinweis manueller Token | 0,51 Tag |
**Gesamt: ~1,52,5 Wochen** für eine solide erste Version. Da OAuth/PKCE-Infrastruktur (`OAuthClient`,
`TokenManager`) und der Vault-Unlock (`unlockOrSetupSso`) bereits existieren, ist ein Teil der Grundlage da.
---
## 12. Edge-Cases, die das Feinkonzept klären muss
- **PIN-gesperrter Vault beim `authorize`**: Ist der Web-Vault gerade PIN-gesperrt, liegt der VMK nicht in
der Session → im Consent-Schritt PIN-Entsperrung verlangen (vorhandener PIN-Flow).
- **SSO-only-Nutzer** (kein lokales Passwort): VMK-Bereitstellung über `unlockOrSetupSso` am `authorize`
bereits gegeben, da der Login gerade lief.
- **Team-Schlüssel**: `provisionPendingTeamKeys` beim Token-Issuing berücksichtigen (wie heute).
- **Mehrere Geräte**: pro Gerät eine Refresh-Kette (`device_label`), unabhängig widerrufbar.
- **Passwortänderung / VMK-Rotation** serverseitig: bestehende Refresh-Tokens ggf. invalidieren → Re-Login.
- **Uhrzeit/Ablauf**: absolute Ablaufzeiten serverseitig führend.
---
## 13. Offene Entscheidungen (für dich)
1. **Lebensdauern**: Access-Token (15/30/60 Min?) und Refresh-Token (14/30 Tage?) + Re-Auth-Vorwarnung (Tage?).
2. **Manuellen Token behalten** (als „Erweitert"-Fallback) oder mittelfristig entfernen?
3. **PIN-Gate bei SSO**: zusätzlich fordern (max. Sicherheit) oder nach erfolgreichem SSO überspringen (Komfort)?
4. **Redirect-URI**: Extension-ID pinnen (empfohlen) vs. `*.chromiumapp.org` erlauben?
5. **Geräte-Binding** des Refresh-Tokens umsetzen (empfohlen) ja/nein?
6. **Angebotene Login-Methoden** in der Erweiterung: alle aus dem Web-Login (lokal+2FA / Azure / Keycloak)?
> Sobald diese sechs Punkte entschieden sind, kann Phase 0 (Feinkonzept mit exakten Endpunkt- und
> DB-Spezifikationen) beginnen.