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:
parent
e1ce87f387
commit
ea5cd8d623
4 changed files with 59 additions and 46 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) ─────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue