From d1647e7b5889cc385e808c709db51c698c163b26 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 12:44:03 +0000 Subject: [PATCH] Fix: Eintrag anlegen scheiterte nach SSO-Anmeldung Das Speichern eines neuen Eintrags im Popup las den Token direkt aus chrome.storage.local und verlangte den manuellen apiToken. Nach einer SSO-Anmeldung wird dieser Schluessel jedoch geloescht (nur Refresh-Token plus kurzlebiger Access-Token in storage.session), sodass das Anlegen immer mit "Nicht konfiguriert." abbrach. Das Anlegen laeuft jetzt wie alle uebrigen Aufrufe ueber den Background-Service-Worker (apiFetch): SSO-Access-Token mit automatischer Erneuerung oder, falls gesetzt, manueller Token. Eine serverseitige Sperre (423) fuehrt zur PIN-Abfrage statt zu einer Fehlermeldung, und der Eintrags-Cache wird nach dem Anlegen verworfen. Ausserdem entfaellt in den Optionen ein redundanter Statusabruf, der ebenfalls nur den manuellen Token beruecksichtigte; loadConnStatus() deckt beide Anmeldewege bereits ab. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01G12DRMpe4UjYDwuRU1sjy1 --- CHANGELOG.md | 9 +++++++++ extension/background.js | 32 +++++++++++++++++++++++++++++ extension/options.js | 19 ----------------- extension/popup.js | 45 +++++++++++++++++------------------------ 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de2519..fb53562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/). Die Erweiterungsversion (`manifest.json`) ist unabhängig von der OpenNIT-Serverversion. +## [Unreleased] + +### Behoben +- **Neuen Eintrag anlegen funktioniert wieder nach SSO-Anmeldung:** Das Speichern eines im Popup + erzeugten Passworts brach bisher mit „Nicht konfiguriert." ab, wenn die Erweiterung über + „Mit OpenNIT anmelden" verbunden war – es wurde ausschließlich der manuelle Token akzeptiert. + Das Anlegen nutzt nun denselben Zugang wie alle übrigen Aufrufe (inkl. automatischer + Token-Erneuerung). Ist der Tresor gesperrt, erscheint die PIN-Abfrage statt einer Fehlermeldung. + ## [2.4.0] - 2026-07-01 ### Hinzugefügt diff --git a/extension/background.js b/extension/background.js index 01b988c..4291339 100644 --- a/extension/background.js +++ b/extension/background.js @@ -147,6 +147,37 @@ async function fetchEntries(force = false) { return { entries: null, locked: false }; } +/** + * Legt einen persönlichen Eintrag im Tresor an. + * + * Läuft bewusst über `apiFetch`, damit derselbe Zugang wie für alle übrigen + * Aufrufe gilt: SSO-Access-Token (inkl. automatischer Erneuerung) oder – falls + * gesetzt – der manuelle Token. + * + * @param {{title?:string,username?:string,password?:string,url?:string,notes?:string}} fields + * @return {Promise<{ok:boolean,id?:number,locked?:boolean,error?:string}>} + */ +async function createEntry(fields) { + if (!(await isUnlocked())) return { ok: false, locked: true, error: 'Tresor gesperrt.' }; + const body = new URLSearchParams(); + ['title', 'username', 'password', 'url', 'notes'].forEach(k => body.append(k, fields?.[k] ?? '')); + try { + const res = await apiFetch('/api/vault/extension/entries', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); + if (!res) return { ok: false, error: 'Nicht konfiguriert.' }; + if (res.status === 423) { await onServerLocked(); return { ok: false, locked: true, error: 'Tresor gesperrt.' }; } + const data = await res.json().catch(() => ({})); + if (data.ok) { + cachedEntries = null; cacheTime = 0; + return { ok: true, id: data.id }; + } + return { ok: false, error: data.error || 'Fehler beim Speichern.' }; + } catch (e) { return { ok: false, error: 'Verbindungsfehler.' }; } +} + async function fetchPassword(entryId) { if (!(await isUnlocked())) return null; try { @@ -252,6 +283,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { }); return true; } + if (msg.type === 'CREATE_ENTRY') { createEntry(msg.entry || {}).then(sendResponse); return true; } if (msg.type === 'GET_PASSWORD') { fetchPassword(msg.id).then(password => sendResponse({ password })); return true; } if (msg.type === 'GET_TOTP') { fetchTotp(msg.id).then(result => sendResponse(result)); return true; } if (msg.type === 'GET_FAVICON') { fetchFavicon(msg.id).then(dataUrl => sendResponse({ dataUrl })); return true; } diff --git a/extension/options.js b/extension/options.js index 25181f6..c0ebc76 100644 --- a/extension/options.js +++ b/extension/options.js @@ -25,25 +25,6 @@ $('btnSaveSec').addEventListener('click', () => { }); }); -// App-Name und Verbindungsstatus laden (falls Token bereits gesetzt) -chrome.storage.local.get(['serverUrl', 'apiToken'], async cfg => { - if (!cfg.serverUrl || !cfg.apiToken) return; - try { - const res = await fetch(`${cfg.serverUrl}/api/vault/extension/status`, { - headers: { 'Authorization': `Bearer ${cfg.apiToken}` } - }); - const data = await res.json(); - if (data.ok) { - // Name der Erweiterung bleibt fest „OpenNIT Vault"; die Instanz wird - // beim angemeldeten Nutzer zur Orientierung angezeigt. - if (data.user) { - $('headerUser').textContent = data.app_name ? (data.user + ' · ' + data.app_name) : data.user; - $('headerStatus').style.display = ''; - } - } - } catch { /* ignore */ } -}); - // HTTPS erzwingen (außer localhost) – sonst gingen Token und Passwörter im // Klartext über die Leitung. function isSecureServerUrl(url) { diff --git a/extension/popup.js b/extension/popup.js index 545e113..871d1ff 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -261,45 +261,36 @@ function generatePassword() { $('nePassword').type = 'text'; } -async function saveNewEntry() { +// Speichern läuft – wie alle anderen Aufrufe – über den Background-Service-Worker, +// der den gültigen Zugang (SSO oder manueller Token) beisteuert. +function saveNewEntry() { const title = $('neTitle').value.trim(); if (!title) { $('newEntryMsg').textContent = 'Titel ist erforderlich.'; return; } - const cfg = await new Promise(r => chrome.storage.local.get(['serverUrl', 'apiToken'], r)); - if (!cfg.serverUrl || !cfg.apiToken) { $('newEntryMsg').textContent = 'Nicht konfiguriert.'; return; } - $('btnSaveNew').disabled = true; $('btnSaveNew').textContent = '...'; $('newEntryMsg').textContent = ''; - const fd = new FormData(); - fd.append('title', title); - fd.append('username', $('neUsername').value.trim()); - fd.append('password', $('nePassword').value); - fd.append('url', $('neUrl').value.trim()); - fd.append('notes', $('neNotes').value.trim()); + const entry = { + title: title, + username: $('neUsername').value.trim(), + password: $('nePassword').value, + url: $('neUrl').value.trim(), + notes: $('neNotes').value.trim(), + }; - try { - const res = await fetch(cfg.serverUrl + '/api/vault/extension/entries', { - method: 'POST', - headers: { 'Authorization': 'Bearer ' + cfg.apiToken }, - body: fd, - }); - const data = await res.json(); - if (data.ok) { - chrome.runtime.sendMessage({ type: 'CLEAR_CACHE' }); + chrome.runtime.sendMessage({ type: 'CREATE_ENTRY', entry }, resp => { + $('btnSaveNew').disabled = false; + $('btnSaveNew').textContent = 'Speichern'; + if (resp?.ok) { closeNewPanel(); reload(true); showToast('Eintrag gespeichert'); - } else { - $('newEntryMsg').textContent = data.error || 'Fehler beim Speichern.'; + return; } - } catch (e) { - $('newEntryMsg').textContent = 'Verbindungsfehler: ' + e.message; - } - - $('btnSaveNew').disabled = false; - $('btnSaveNew').textContent = 'Speichern'; + if (resp?.locked) { showLockScreen(); return; } + $('newEntryMsg').textContent = resp?.error || 'Fehler beim Speichern.'; + }); } // ── Liste (Klick öffnet Detailansicht) ─────────────────────────────────────