fix: localize Secrets operation errors
This commit is contained in:
parent
bee365ad22
commit
1f03226378
|
|
@ -167,6 +167,38 @@
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userFacingError(action, err) {
|
||||||
|
var raw = (err && err.message) ? err.message : String(err || '');
|
||||||
|
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
||||||
|
console.error('[verstak.secrets] ' + action + ' failed', err);
|
||||||
|
}
|
||||||
|
if (action === 'unlock') {
|
||||||
|
var minimumLength = raw.match(/master password must be at least (\d+) characters/i);
|
||||||
|
if (minimumLength) {
|
||||||
|
return tr('ui.masterPasswordMinLength', { count: minimumLength[1] }, 'Master password must be at least ' + minimumLength[1] + ' characters.');
|
||||||
|
}
|
||||||
|
if (/master password is empty/i.test(raw)) {
|
||||||
|
return tr('ui.masterPasswordRequired', null, 'Enter a master password.');
|
||||||
|
}
|
||||||
|
if (/invalid master password/i.test(raw)) {
|
||||||
|
return tr('ui.masterPasswordInvalid', null, 'The master password is incorrect.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tr('ui.' + action + 'Error', null, {
|
||||||
|
status: 'Could not check the secret storage. Please try again.',
|
||||||
|
load: 'Could not load secrets. Please try again.',
|
||||||
|
read: 'Could not open this secret. Please try again.',
|
||||||
|
save: 'Could not save the secret. Please try again.',
|
||||||
|
delete: 'Could not delete the secret. Please try again.',
|
||||||
|
copyLink: 'Could not copy the secret link. Please try again.',
|
||||||
|
unlock: 'Could not unlock secrets. Check the master password and try again.'
|
||||||
|
}[action] || 'Could not complete this action. Please try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserFacingError(action, err) {
|
||||||
|
setStatus(userFacingError(action, err), true);
|
||||||
|
}
|
||||||
|
|
||||||
function recordWorkspaceRoot(record) {
|
function recordWorkspaceRoot(record) {
|
||||||
var scope = record && record.scope || {};
|
var scope = record && record.scope || {};
|
||||||
return scope.kind === ScopeWorkspace ? cleanWorkspace(scope.workspaceRootPath) : '';
|
return scope.kind === ScopeWorkspace ? cleanWorkspace(scope.workspaceRootPath) : '';
|
||||||
|
|
@ -249,7 +281,7 @@
|
||||||
return loadRecords();
|
return loadRecords();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
unlockBtn.disabled = false;
|
unlockBtn.disabled = false;
|
||||||
setStatus((err && err.message) ? err.message : String(err), true);
|
setUserFacingError('unlock', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [tr('ui.unlock', null, 'Unlock')]);
|
}, [tr('ui.unlock', null, 'Unlock')]);
|
||||||
|
|
@ -458,7 +490,7 @@
|
||||||
selectedValue = '';
|
selectedValue = '';
|
||||||
return loadRecords();
|
return loadRecords();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus((err && err.message) ? err.message : String(err), true);
|
setUserFacingError('save', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [tr('ui.save', null, 'Save')]),
|
}, [tr('ui.save', null, 'Save')]),
|
||||||
|
|
@ -505,6 +537,8 @@
|
||||||
selectedValue = '';
|
selectedValue = '';
|
||||||
mode = 'selected';
|
mode = 'selected';
|
||||||
render();
|
render();
|
||||||
|
}).catch(function (err) {
|
||||||
|
setUserFacingError('load', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -522,7 +556,7 @@
|
||||||
render();
|
render();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
setStatus((err && err.message) ? err.message : String(err), true);
|
setUserFacingError('read', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -549,7 +583,7 @@
|
||||||
selectedValue = '';
|
selectedValue = '';
|
||||||
return loadRecords();
|
return loadRecords();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus((err && err.message) ? err.message : String(err), true);
|
setUserFacingError('delete', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,7 +593,7 @@
|
||||||
setStatus(tr('ui.linkCopied', null, 'Secret link copied'), false);
|
setStatus(tr('ui.linkCopied', null, 'Secret link copied'), false);
|
||||||
});
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus((err && err.message) ? err.message : String(err), true);
|
setUserFacingError('copyLink', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -569,7 +603,7 @@
|
||||||
if (unlocked) return loadWorkspaceOptions().then(loadRecords);
|
if (unlocked) return loadWorkspaceOptions().then(loadRecords);
|
||||||
render();
|
render();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = (err && err.message) ? err.message : String(err);
|
statusText = userFacingError('status', err);
|
||||||
statusError = true;
|
statusError = true;
|
||||||
renderLocked();
|
renderLocked();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
"ui.masterPassword": "Master password",
|
"ui.masterPassword": "Master password",
|
||||||
"ui.repeatPassword": "Repeat master password",
|
"ui.repeatPassword": "Repeat master password",
|
||||||
"ui.passwordMismatch": "Master passwords do not match",
|
"ui.passwordMismatch": "Master passwords do not match",
|
||||||
|
"ui.masterPasswordMinLength": "Master password must be at least {count} characters.",
|
||||||
|
"ui.masterPasswordRequired": "Enter a master password.",
|
||||||
|
"ui.masterPasswordInvalid": "The master password is incorrect.",
|
||||||
"ui.unlock": "Unlock",
|
"ui.unlock": "Unlock",
|
||||||
"ui.createMaster": "Create master password",
|
"ui.createMaster": "Create master password",
|
||||||
"ui.password": "Password",
|
"ui.password": "Password",
|
||||||
|
|
@ -38,6 +41,13 @@
|
||||||
"ui.search": "Search secrets",
|
"ui.search": "Search secrets",
|
||||||
"ui.chooseWorkspace": "Choose a Deal",
|
"ui.chooseWorkspace": "Choose a Deal",
|
||||||
"ui.workspaceRequired": "Choose a Deal for this secret.",
|
"ui.workspaceRequired": "Choose a Deal for this secret.",
|
||||||
|
"ui.statusError": "Could not check the secret storage. Please try again.",
|
||||||
|
"ui.loadError": "Could not load secrets. Please try again.",
|
||||||
|
"ui.readError": "Could not open this secret. Please try again.",
|
||||||
|
"ui.saveError": "Could not save the secret. Please try again.",
|
||||||
|
"ui.deleteError": "Could not delete the secret. Please try again.",
|
||||||
|
"ui.copyLinkError": "Could not copy the secret link. Please try again.",
|
||||||
|
"ui.unlockError": "Could not unlock secrets. Check the master password and try again.",
|
||||||
"ui.value": "Value",
|
"ui.value": "Value",
|
||||||
"ui.save": "Save",
|
"ui.save": "Save",
|
||||||
"ui.cancel": "Cancel",
|
"ui.cancel": "Cancel",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
"ui.masterPassword": "Мастер-пароль",
|
"ui.masterPassword": "Мастер-пароль",
|
||||||
"ui.repeatPassword": "Повторите мастер-пароль",
|
"ui.repeatPassword": "Повторите мастер-пароль",
|
||||||
"ui.passwordMismatch": "Мастер-пароли не совпадают",
|
"ui.passwordMismatch": "Мастер-пароли не совпадают",
|
||||||
|
"ui.masterPasswordMinLength": "Мастер-пароль должен содержать не менее {count} символов.",
|
||||||
|
"ui.masterPasswordRequired": "Введите мастер-пароль.",
|
||||||
|
"ui.masterPasswordInvalid": "Мастер-пароль указан неверно.",
|
||||||
"ui.unlock": "Разблокировать",
|
"ui.unlock": "Разблокировать",
|
||||||
"ui.createMaster": "Создать мастер-пароль",
|
"ui.createMaster": "Создать мастер-пароль",
|
||||||
"ui.password": "Пароль",
|
"ui.password": "Пароль",
|
||||||
|
|
@ -38,6 +41,13 @@
|
||||||
"ui.search": "Поиск секретов",
|
"ui.search": "Поиск секретов",
|
||||||
"ui.chooseWorkspace": "Выберите дело",
|
"ui.chooseWorkspace": "Выберите дело",
|
||||||
"ui.workspaceRequired": "Выберите дело для этого секрета.",
|
"ui.workspaceRequired": "Выберите дело для этого секрета.",
|
||||||
|
"ui.statusError": "Не удалось проверить хранилище секретов. Повторите попытку.",
|
||||||
|
"ui.loadError": "Не удалось загрузить секреты. Повторите попытку.",
|
||||||
|
"ui.readError": "Не удалось открыть секрет. Повторите попытку.",
|
||||||
|
"ui.saveError": "Не удалось сохранить секрет. Повторите попытку.",
|
||||||
|
"ui.deleteError": "Не удалось удалить секрет. Повторите попытку.",
|
||||||
|
"ui.copyLinkError": "Не удалось скопировать ссылку на секрет. Повторите попытку.",
|
||||||
|
"ui.unlockError": "Не удалось разблокировать секреты. Проверьте мастер-пароль и повторите попытку.",
|
||||||
"ui.value": "Значение",
|
"ui.value": "Значение",
|
||||||
"ui.save": "Сохранить",
|
"ui.save": "Сохранить",
|
||||||
"ui.cancel": "Отмена",
|
"ui.cancel": "Отмена",
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,15 @@ function makeDocument() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadComponent(document) {
|
function loadComponent(document, errorLog) {
|
||||||
const registry = {};
|
const registry = {};
|
||||||
vm.runInNewContext(source, {
|
vm.runInNewContext(source, {
|
||||||
console,
|
console: {
|
||||||
|
...console,
|
||||||
|
error(...args) {
|
||||||
|
errorLog.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
|
},
|
||||||
document,
|
document,
|
||||||
window: {
|
window: {
|
||||||
VerstakPluginRegister(pluginId, bundle) {
|
VerstakPluginRegister(pluginId, bundle) {
|
||||||
|
|
@ -138,13 +143,15 @@ async function flush() {
|
||||||
if (!(manifest.contributes.settingsPanels || []).some((item) => item.component === 'SecretsView')) throw new Error('secrets settings panel missing');
|
if (!(manifest.contributes.settingsPanels || []).some((item) => item.component === 'SecretsView')) throw new Error('secrets settings panel missing');
|
||||||
|
|
||||||
const document = makeDocument();
|
const document = makeDocument();
|
||||||
const component = loadComponent(document);
|
const errorLog = [];
|
||||||
|
const component = loadComponent(document, errorLog);
|
||||||
const records = [
|
const records = [
|
||||||
{ id: 'global.server', title: 'Global Server', username: 'root', scope: { kind: 'global' }, updatedAt: '2026-06-29T00:00:00Z' },
|
{ id: 'global.server', title: 'Global Server', username: 'root', scope: { kind: 'global' }, updatedAt: '2026-06-29T00:00:00Z' },
|
||||||
{ id: 'client-a.db', title: 'Client A DB', username: 'app', scope: { kind: 'workspace', workspaceRootPath: 'ClientA' }, updatedAt: '2026-06-29T00:00:00Z' },
|
{ id: 'client-a.db', title: 'Client A DB', username: 'app', scope: { kind: 'workspace', workspaceRootPath: 'ClientA' }, updatedAt: '2026-06-29T00:00:00Z' },
|
||||||
];
|
];
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
let unlocked = false;
|
let unlocked = false;
|
||||||
|
let unlockError = '';
|
||||||
const readCalls = [];
|
const readCalls = [];
|
||||||
const copied = [];
|
const copied = [];
|
||||||
const deleted = [];
|
const deleted = [];
|
||||||
|
|
@ -152,6 +159,7 @@ async function flush() {
|
||||||
secrets: {
|
secrets: {
|
||||||
status: async () => ({ initialized, unlocked }),
|
status: async () => ({ initialized, unlocked }),
|
||||||
unlock: async (password) => {
|
unlock: async (password) => {
|
||||||
|
if (unlockError) throw new Error(unlockError);
|
||||||
if (password !== 'master-password') throw new Error('bad password');
|
if (password !== 'master-password') throw new Error('bad password');
|
||||||
initialized = true;
|
initialized = true;
|
||||||
unlocked = true;
|
unlocked = true;
|
||||||
|
|
@ -197,6 +205,21 @@ async function flush() {
|
||||||
const confirmInput = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-master-password-confirm') === '');
|
const confirmInput = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-master-password-confirm') === '');
|
||||||
const unlockButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-unlock') === '');
|
const unlockButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-unlock') === '');
|
||||||
if (!passwordInput || !confirmInput || !unlockButton) throw new Error('setup controls missing');
|
if (!passwordInput || !confirmInput || !unlockButton) throw new Error('setup controls missing');
|
||||||
|
unlockError = '[plugin:verstak.secrets] secrets.unlock failed: master password must be at least 8 characters';
|
||||||
|
passwordInput.value = 'short';
|
||||||
|
confirmInput.value = 'short';
|
||||||
|
unlockButton.click();
|
||||||
|
await flush();
|
||||||
|
if (!container.textContent.includes('Master password must be at least 8 characters')) {
|
||||||
|
throw new Error('weak master password error was not explained clearly');
|
||||||
|
}
|
||||||
|
if (container.textContent.includes('[plugin:') || container.textContent.includes('secrets.unlock')) {
|
||||||
|
throw new Error('technical unlock details leaked into the Secrets UI');
|
||||||
|
}
|
||||||
|
if (!errorLog.some((entry) => entry.includes('[plugin:verstak.secrets] secrets.unlock failed'))) {
|
||||||
|
throw new Error('technical unlock details were not retained in the console log');
|
||||||
|
}
|
||||||
|
unlockError = '';
|
||||||
passwordInput.value = 'master-password';
|
passwordInput.value = 'master-password';
|
||||||
confirmInput.value = 'master-password';
|
confirmInput.value = 'master-password';
|
||||||
unlockButton.click();
|
unlockButton.click();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue