[grade=B] feat(auth): onboard missing credentials into encrypted vault
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

This commit is contained in:
Hermes
2026-08-22 05:41:38 -07:00
parent d313b1e872
commit 84edb035e3
24 changed files with 959 additions and 235 deletions
@@ -0,0 +1,311 @@
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const { JSDOM } = require('jsdom');
const test = require('node:test');
const assert = require('node:assert/strict');
const handoffSource = fs.readFileSync(path.join(__dirname, '..', 'js', 'credential-vault-handoff.js'), 'utf8');
const formSource = fs.readFileSync(path.join(__dirname, '..', 'js', 'service-credentials.js'), 'utf8');
const initSource = fs.readFileSync(path.join(__dirname, '..', 'js', 'core', 'init.js'), 'utf8');
function loadVault() {
const window = { location: { origin: 'https://status.sami' } };
const context = vm.createContext({ window, SITE: { tld: '.sami' }, URL });
vm.runInContext(handoffSource, context);
return window.DCCredentialVault;
}
async function exerciseFailedModalWrite({
service,
fetchJson,
setupInputs,
expectedEndpoint,
writeResponse,
expectedError = /vault write rejected/,
}) {
const dom = new JSDOM('<!doctype html><body></body>', {
url: 'https://status.sami/',
runScripts: 'outside-only',
});
const { window } = dom;
const writeUrls = [];
let resumeCalls = 0;
window.ErrorHandler = class { logError() {} };
window.SITE = { tld: '.sami' };
window.injectModal = (_id, html) => window.document.body.insertAdjacentHTML('beforeend', html);
window.fetch = async (url) => ({ ok: true, json: async () => fetchJson(url) });
window.secureFetch = async (url) => {
writeUrls.push(url);
return writeResponse || {
ok: false,
status: 500,
json: async () => ({ success: false, error: 'vault write rejected' }),
};
};
window.DCCredentialVault = {
isAllowedReturnUrl: () => true,
resume: async () => { resumeCalls++; return true; },
};
window.confirm = () => true;
window.eval(formSource);
await window.openServiceCredsModal(service, { returnUrl: `https://${service.id}.sami/` });
setupInputs(window.document);
window.document.getElementById('svc-creds-save').click();
await new Promise(resolve => setTimeout(resolve, 20));
assert.equal(writeUrls[0], expectedEndpoint);
assert.equal(resumeCalls, 0);
assert.match(window.document.getElementById('svc-creds-error').textContent, expectedError);
}
test('existing dashboard session mints a one-time token and resumes on the target host', async () => {
const vault = loadVault();
const calls = [];
const replacements = [];
const resumed = await vault.resume('https://plex.sami/web/?direct=1#home', 'plex', {
fetch: async (url, options) => {
calls.push({ url, options });
return {
ok: true,
json: async () => ({ success: true, ssoToken: 'one-time-token' }),
};
},
location: { replace: (target) => replacements.push(target) },
});
assert.equal(resumed, true);
assert.equal(calls.length, 1);
assert.equal(calls[0].url, '/api/v1/auth/sso-handoff?serviceId=plex');
assert.equal(calls[0].options.credentials, 'include');
assert.equal(calls[0].options.cache, 'no-store');
assert.equal(
replacements[0],
'https://plex.sami/dashcaddy-sso?token=one-time-token&return=%2Fweb%2F%3Fdirect%3D1%23home',
);
});
test('vault handoff rejects an external return URL before minting a token', async () => {
const vault = loadVault();
let fetchCalled = false;
const resumed = await vault.resume('https://plex.sami.evil.example/phish', 'plex', {
fetch: async () => { fetchCalled = true; },
location: { replace: () => assert.fail('must not navigate') },
});
assert.equal(resumed, false);
assert.equal(fetchCalled, false);
});
test('credential request opens the form and save path calls the tested handoff helper', () => {
assert.match(formSource, /params\.get\('credentials'\)/);
assert.match(formSource, /openServiceCredsModal\(service, \{ returnUrl \}\)/);
assert.match(formSource, /DCCredentialVault\?\.resume\(returnUrl, currentService\.id\)/);
assert.match(initSource, /openRequestedCredentialForm\(\)/);
assert.match(formSource, /Save to vault & open service/);
});
test('actual vault modal save handler stores credentials then resumes the handoff', async () => {
const dom = new JSDOM('<!doctype html><body></body>', {
url: 'https://status.sami/?credentials=plex&return=https%3A%2F%2Fplex.sami%2Fweb%2F',
runScripts: 'outside-only',
});
const { window } = dom;
let stored = false;
const writes = [];
const resumed = [];
window.ErrorHandler = class { logError() {} };
window.SITE = { tld: '.sami' };
window.APPS = [{ id: 'plex', name: 'Plex', appTemplate: 'plex', url: 'https://plex.sami' }];
window.injectModal = (_id, html) => window.document.body.insertAdjacentHTML('beforeend', html);
window.fetch = async () => ({
ok: true,
json: async () => ({
success: true,
hasApiKey: false,
hasBasicAuth: stored,
username: stored ? 'vault-user' : null,
}),
});
window.secureFetch = async (url, options) => {
writes.push({ url, body: JSON.parse(options.body) });
stored = true;
return { ok: true, json: async () => ({ success: true }) };
};
window.DCCredentialVault = {
isAllowedReturnUrl: () => true,
resume: async (returnUrl, serviceId) => { resumed.push({ returnUrl, serviceId }); return true; },
};
window.confirm = () => true;
window.eval(formSource);
await window.openServiceCredsModal(window.APPS[0], { returnUrl: 'https://plex.sami/web/' });
window.document.getElementById('svc-basic-user').value = 'vault-user';
window.document.getElementById('svc-basic-pass').value = 'vault-password';
window.document.getElementById('svc-creds-save').click();
await new Promise(resolve => setTimeout(resolve, 20));
assert.deepEqual(writes, [{
url: '/api/v1/services/plex/credentials',
body: { username: 'vault-user', password: 'vault-password' },
}]);
assert.deepEqual(resumed, [{ returnUrl: 'https://plex.sami/web/', serviceId: 'plex' }]);
});
test('failed credential write does not mint a handoff or navigate', async () => {
const dom = new JSDOM('<!doctype html><body></body>', {
url: 'https://status.sami/',
runScripts: 'outside-only',
});
const { window } = dom;
let resumeCalls = 0;
window.ErrorHandler = class { logError() {} };
window.SITE = { tld: '.sami' };
window.injectModal = (_id, html) => window.document.body.insertAdjacentHTML('beforeend', html);
window.fetch = async () => ({
ok: true,
json: async () => ({ success: true, hasApiKey: false, hasBasicAuth: false, username: null }),
});
window.secureFetch = async () => ({
ok: false,
status: 500,
json: async () => ({ success: false, error: 'vault write rejected' }),
});
window.DCCredentialVault = {
isAllowedReturnUrl: () => true,
resume: async () => { resumeCalls++; return true; },
};
window.confirm = () => true;
window.eval(formSource);
const service = { id: 'plex', name: 'Plex', appTemplate: 'plex', url: 'https://plex.sami' };
await window.openServiceCredsModal(service, { returnUrl: 'https://plex.sami/web/' });
window.document.getElementById('svc-basic-user').value = 'vault-user';
window.document.getElementById('svc-basic-pass').value = 'vault-password';
window.document.getElementById('svc-creds-save').click();
await new Promise(resolve => setTimeout(resolve, 20));
assert.equal(resumeCalls, 0);
assert.match(window.document.getElementById('svc-creds-error').textContent, /vault write rejected/);
});
test('failed ARR credential write does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'radarr', name: 'Radarr', appTemplate: 'radarr', url: 'https://radarr.sami' },
fetchJson: (url) => url.includes('/services/')
? { success: true, hasApiKey: false, hasBasicAuth: false, username: null }
: { success: true, profiles: [] },
setupInputs: (document) => { document.getElementById('svc-apikey-input').value = 'arr-key'; },
expectedEndpoint: '/api/v1/arr/credentials',
});
});
test('failed ARR quality-profile write does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'radarr', name: 'Radarr', appTemplate: 'radarr', url: 'https://radarr.sami' },
fetchJson: (url) => url.includes('/services/')
? { success: true, hasApiKey: true, hasBasicAuth: false, username: null }
: { success: true, profiles: [{ id: 1, name: 'Default' }], storedProfileId: 1 },
setupInputs: () => {},
expectedEndpoint: '/api/v1/arr/quality-profiles',
});
});
test('failed seedhost write does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'torrent', name: 'qBittorrent', isExternal: true, externalUrl: 'https://torrent.sami' },
fetchJson: (url) => url.includes('/seedhost-creds')
? { success: true, hasCredentials: false, username: null }
: { success: true, hasApiKey: false, hasBasicAuth: false, username: null },
setupInputs: (document) => {
document.getElementById('svc-seedhost-user').value = 'seed-user';
document.getElementById('svc-seedhost-pass').value = 'seed-password';
},
expectedEndpoint: '/api/v1/seedhost-creds',
});
});
test('failed generic API-key write does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'custom', name: 'Custom', url: 'https://custom.sami' },
fetchJson: () => ({ success: true, hasApiKey: false, hasBasicAuth: false, username: null }),
setupInputs: (document) => {
document.getElementById('svc-apikey-input').value = 'custom-key';
document.getElementById('svc-basic-user').value = 'user';
document.getElementById('svc-basic-pass').value = 'password';
},
expectedEndpoint: '/api/v1/services/custom/credentials',
});
});
test('HTTP 2xx with malformed JSON does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'plex', name: 'Plex', appTemplate: 'plex', url: 'https://plex.sami' },
fetchJson: () => ({ success: true, hasApiKey: false, hasBasicAuth: false, username: null }),
setupInputs: (document) => {
document.getElementById('svc-basic-user').value = 'user';
document.getElementById('svc-basic-pass').value = 'password';
},
expectedEndpoint: '/api/v1/services/plex/credentials',
writeResponse: { ok: true, status: 200, json: async () => { throw new Error('bad json'); } },
expectedError: /invalid server response/,
});
});
test('HTTP 2xx without success:true does not mint a handoff or navigate', async () => {
await exerciseFailedModalWrite({
service: { id: 'plex', name: 'Plex', appTemplate: 'plex', url: 'https://plex.sami' },
fetchJson: () => ({ success: true, hasApiKey: false, hasBasicAuth: false, username: null }),
setupInputs: (document) => {
document.getElementById('svc-basic-user').value = 'user';
document.getElementById('svc-basic-pass').value = 'password';
},
expectedEndpoint: '/api/v1/services/plex/credentials',
writeResponse: { ok: true, status: 200, json: async () => ({ message: 'ambiguous' }) },
expectedError: /failed \(200\)/,
});
});
test('failed credential clear remains visibly failed and keeps stored-state UI', async () => {
const dom = new JSDOM('<!doctype html><body><button id="creds-btn-plex" class="has-creds"></button></body>', {
url: 'https://status.sami/',
runScripts: 'outside-only',
});
const { window } = dom;
window.ErrorHandler = class { logError() {} };
window.SITE = { tld: '.sami' };
window.injectModal = (_id, html) => window.document.body.insertAdjacentHTML('beforeend', html);
window.fetch = async () => ({
ok: true,
json: async () => ({ success: true, hasApiKey: false, hasBasicAuth: true, username: 'vault-user' }),
});
window.secureFetch = async () => ({
ok: false,
status: 500,
json: async () => ({ success: false, error: 'clear rejected' }),
});
window.DCCredentialVault = { isAllowedReturnUrl: () => false };
window.confirm = () => true;
window.eval(formSource);
const service = { id: 'plex', name: 'Plex', appTemplate: 'plex', url: 'https://plex.sami' };
await window.openServiceCredsModal(service);
window.document.getElementById('svc-creds-clear').click();
await new Promise(resolve => setTimeout(resolve, 20));
assert.match(window.document.getElementById('svc-creds-error').textContent, /clear rejected/);
assert.equal(window.document.getElementById('creds-btn-plex').classList.contains('has-creds'), true);
});
test('handoff rejects a private-TLD host that is not the requested protected service', async () => {
const vault = loadVault();
let fetchCalled = false;
const resumed = await vault.resume('https://dns1.sami/', 'plex', {
fetch: async () => { fetchCalled = true; },
location: { replace: () => assert.fail('must not navigate') },
});
assert.equal(resumed, false);
assert.equal(fetchCalled, false);
});