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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G12DRMpe4UjYDwuRU1sjy1
This commit is contained in:
Claude 2026-07-31 12:44:03 +00:00
parent e1ce87f387
commit d1647e7b58
No known key found for this signature in database
4 changed files with 59 additions and 46 deletions

View file

@ -3,6 +3,15 @@
Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/). Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
Die Erweiterungsversion (`manifest.json`) ist unabhängig von der OpenNIT-Serverversion. 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 ## [2.4.0] - 2026-07-01
### Hinzugefügt ### Hinzugefügt

View file

@ -147,6 +147,37 @@ async function fetchEntries(force = false) {
return { entries: null, locked: 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) { async function fetchPassword(entryId) {
if (!(await isUnlocked())) return null; if (!(await isUnlocked())) return null;
try { try {
@ -252,6 +283,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
}); });
return true; 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_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_TOTP') { fetchTotp(msg.id).then(result => sendResponse(result)); return true; }
if (msg.type === 'GET_FAVICON') { fetchFavicon(msg.id).then(dataUrl => sendResponse({ dataUrl })); return true; } if (msg.type === 'GET_FAVICON') { fetchFavicon(msg.id).then(dataUrl => sendResponse({ dataUrl })); return true; }

View file

@ -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 // HTTPS erzwingen (außer localhost) sonst gingen Token und Passwörter im
// Klartext über die Leitung. // Klartext über die Leitung.
function isSecureServerUrl(url) { function isSecureServerUrl(url) {

View file

@ -261,45 +261,36 @@ function generatePassword() {
$('nePassword').type = 'text'; $('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(); const title = $('neTitle').value.trim();
if (!title) { $('newEntryMsg').textContent = 'Titel ist erforderlich.'; return; } 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').disabled = true;
$('btnSaveNew').textContent = '...'; $('btnSaveNew').textContent = '...';
$('newEntryMsg').textContent = ''; $('newEntryMsg').textContent = '';
const fd = new FormData(); const entry = {
fd.append('title', title); title: title,
fd.append('username', $('neUsername').value.trim()); username: $('neUsername').value.trim(),
fd.append('password', $('nePassword').value); password: $('nePassword').value,
fd.append('url', $('neUrl').value.trim()); url: $('neUrl').value.trim(),
fd.append('notes', $('neNotes').value.trim()); notes: $('neNotes').value.trim(),
};
try { chrome.runtime.sendMessage({ type: 'CREATE_ENTRY', entry }, resp => {
const res = await fetch(cfg.serverUrl + '/api/vault/extension/entries', { $('btnSaveNew').disabled = false;
method: 'POST', $('btnSaveNew').textContent = 'Speichern';
headers: { 'Authorization': 'Bearer ' + cfg.apiToken }, if (resp?.ok) {
body: fd,
});
const data = await res.json();
if (data.ok) {
chrome.runtime.sendMessage({ type: 'CLEAR_CACHE' });
closeNewPanel(); closeNewPanel();
reload(true); reload(true);
showToast('Eintrag gespeichert'); showToast('Eintrag gespeichert');
} else { return;
$('newEntryMsg').textContent = data.error || 'Fehler beim Speichern.';
} }
} catch (e) { if (resp?.locked) { showLockScreen(); return; }
$('newEntryMsg').textContent = 'Verbindungsfehler: ' + e.message; $('newEntryMsg').textContent = resp?.error || 'Fehler beim Speichern.';
} });
$('btnSaveNew').disabled = false;
$('btnSaveNew').textContent = 'Speichern';
} }
// ── Liste (Klick öffnet Detailansicht) ───────────────────────────────────── // ── Liste (Klick öffnet Detailansicht) ─────────────────────────────────────