[grade=B] feat(auth): onboard missing credentials into encrypted vault
This commit is contained in:
@@ -33,6 +33,20 @@
|
||||
return server?.name || dnsId.toUpperCase();
|
||||
}
|
||||
|
||||
async function requireSuccessfulDnsMutation(response, label) {
|
||||
if (!response) throw new Error(`${label} failed: no response`);
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (_) {
|
||||
throw new Error(`${label} failed: invalid server response`);
|
||||
}
|
||||
if (!response.ok || data?.success !== true) {
|
||||
throw new Error(data?.error || `${label} failed (${response.status || 'unknown status'})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Build per-server credential form sections from SITE.dnsServers */
|
||||
function buildCredentialSections() {
|
||||
const container = document.getElementById('dns-cred-sections');
|
||||
@@ -258,14 +272,6 @@
|
||||
document.getElementById('token-save')?.addEventListener('click', async () => {
|
||||
const dnsIds = getDnsIds();
|
||||
|
||||
// Save all to localStorage
|
||||
dnsIds.forEach(dnsId => {
|
||||
setUsername(dnsId, 'readonly', document.getElementById(`${dnsId}-readonly-username`).value.trim());
|
||||
setToken(dnsId, 'readonly', document.getElementById(`${dnsId}-readonly-token`).value.trim());
|
||||
setUsername(dnsId, 'admin', document.getElementById(`${dnsId}-admin-username`).value.trim());
|
||||
setToken(dnsId, 'admin', document.getElementById(`${dnsId}-admin-token`).value.trim());
|
||||
});
|
||||
|
||||
// Build per-server credentials payload for backend sync
|
||||
const servers = {};
|
||||
let hasAnyCreds = false;
|
||||
@@ -304,45 +310,36 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ servers })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
const data = await requireSuccessfulDnsMutation(res, 'DNS credential save');
|
||||
if (data.results) {
|
||||
dnsIds.forEach(dnsId => {
|
||||
const statusEl = document.getElementById(`${dnsId}-token-status`);
|
||||
if (!servers[dnsId]) { statusEl.textContent = ''; return; }
|
||||
const result = data.results[dnsId];
|
||||
if (result?.success) {
|
||||
statusEl.textContent = '\u2713 Verified & saved';
|
||||
statusEl.className = 'token-status success';
|
||||
} else if (result?.partial) {
|
||||
statusEl.textContent = '\u2713 ' + result.partial;
|
||||
statusEl.className = 'token-status success';
|
||||
} else {
|
||||
statusEl.textContent = '\u2717 ' + (result?.error || 'Login failed');
|
||||
statusEl.className = 'token-status error';
|
||||
}
|
||||
});
|
||||
} else if (data.success) {
|
||||
dnsIds.forEach(dnsId => {
|
||||
if (servers[dnsId]) {
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2713 Saved';
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status success';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dnsIds.forEach(dnsId => {
|
||||
if (servers[dnsId]) {
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2717 ' + (data.error || 'Failed');
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status error';
|
||||
}
|
||||
});
|
||||
const failed = Object.keys(servers).filter(dnsId => data.results[dnsId]?.success !== true);
|
||||
if (failed.length) {
|
||||
const details = failed.map(dnsId => data.results[dnsId]?.error || `${dnsId} failed`).join('; ');
|
||||
throw new Error(details);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache locally only after the encrypted server vault confirms success.
|
||||
dnsIds.forEach(dnsId => {
|
||||
setUsername(dnsId, 'readonly', document.getElementById(`${dnsId}-readonly-username`).value.trim());
|
||||
setToken(dnsId, 'readonly', document.getElementById(`${dnsId}-readonly-token`).value.trim());
|
||||
setUsername(dnsId, 'admin', document.getElementById(`${dnsId}-admin-username`).value.trim());
|
||||
setToken(dnsId, 'admin', document.getElementById(`${dnsId}-admin-token`).value.trim());
|
||||
});
|
||||
|
||||
dnsIds.forEach(dnsId => {
|
||||
const statusEl = document.getElementById(`${dnsId}-token-status`);
|
||||
if (!servers[dnsId]) { statusEl.textContent = ''; return; }
|
||||
const result = data.results?.[dnsId];
|
||||
statusEl.textContent = result?.partial ? '\u2713 ' + result.partial : '\u2713 Verified & saved';
|
||||
statusEl.className = 'token-status success';
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to sync DNS credentials to backend:', e);
|
||||
dnsIds.forEach(dnsId => {
|
||||
if (servers[dnsId]) {
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2713 Saved locally (sync failed)';
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status';
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2717 ' + (e.message || 'Save failed');
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status error';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -368,18 +365,24 @@
|
||||
|
||||
document.getElementById('token-clear-all')?.addEventListener('click', async () => {
|
||||
if (confirm('Clear all stored DNS credentials? This cannot be undone.')) {
|
||||
clearAllCredentials();
|
||||
getDnsIds().forEach(dnsId => {
|
||||
document.getElementById(`${dnsId}-readonly-username`).value = '';
|
||||
document.getElementById(`${dnsId}-readonly-token`).value = '';
|
||||
document.getElementById(`${dnsId}-admin-username`).value = '';
|
||||
document.getElementById(`${dnsId}-admin-token`).value = '';
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2713 Cleared';
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status success';
|
||||
});
|
||||
try {
|
||||
await secureFetch('/api/v1/dns/credentials', { method: 'DELETE' });
|
||||
} catch (_) {}
|
||||
const response = await secureFetch('/api/v1/dns/credentials', { method: 'DELETE' });
|
||||
await requireSuccessfulDnsMutation(response, 'DNS credential removal');
|
||||
clearAllCredentials();
|
||||
getDnsIds().forEach(dnsId => {
|
||||
document.getElementById(`${dnsId}-readonly-username`).value = '';
|
||||
document.getElementById(`${dnsId}-readonly-token`).value = '';
|
||||
document.getElementById(`${dnsId}-admin-username`).value = '';
|
||||
document.getElementById(`${dnsId}-admin-token`).value = '';
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2713 Cleared';
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status success';
|
||||
});
|
||||
} catch (e) {
|
||||
getDnsIds().forEach(dnsId => {
|
||||
document.getElementById(`${dnsId}-token-status`).textContent = '\u2717 ' + (e.message || 'Clear failed');
|
||||
document.getElementById(`${dnsId}-token-status`).className = 'token-status error';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
await window.loadServices();
|
||||
await loadTemplateCategories();
|
||||
window.buildGrid();
|
||||
if (typeof window.openRequestedCredentialForm === 'function') {
|
||||
window.openRequestedCredentialForm();
|
||||
}
|
||||
animateTopCards();
|
||||
window.refreshAll();
|
||||
setInterval(() => {
|
||||
|
||||
Reference in New Issue
Block a user