feat(companysetting): add writeonly column and mask values for GUI
This commit is contained in:
@@ -12,13 +12,18 @@ export default function get(pool) {
|
||||
* @param {string} key - The setting key.
|
||||
* @param {object} [options] - Optional options.
|
||||
* @param {boolean} [options.frontendOnly] - Only return the setting if it is frontend-visible.
|
||||
* @returns {Promise<{key: string, value: string, frontend: boolean}|undefined>} The setting, or `undefined` if not found.
|
||||
* @param {boolean} [options.maskWriteonly] - Mask writeonly settings as '***'. Defaults to `frontendOnly` (usergui gets '***', backend gets full value).
|
||||
* @returns {Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}|undefined>} The setting, or `undefined` if not found.
|
||||
*
|
||||
* @example
|
||||
* const setting = await store.companysetting.get(42, 'theme');
|
||||
* // => { key, value, frontend }
|
||||
* const setting = await store.companysetting.get(42, 'ui.theme');
|
||||
* // => { key, value, frontend, writeonly }
|
||||
*/
|
||||
return async (googleAuthId, key, { frontendOnly = false } = {}) => {
|
||||
return async (
|
||||
googleAuthId,
|
||||
key,
|
||||
{ frontendOnly = false, maskWriteonly } = {},
|
||||
) => {
|
||||
if (!key?.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -39,6 +44,7 @@ export default function get(pool) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return shapeSetting(row);
|
||||
const shouldMask = maskWriteonly ?? frontendOnly;
|
||||
return shapeSetting(row, { exposeValue: !shouldMask });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,16 +11,23 @@ export default function list(pool) {
|
||||
/**
|
||||
* Lists the settings of the user's selected company, optionally only frontend-visible ones.
|
||||
*
|
||||
* Write-only settings (such as `keys.tavily`, `keys.openrouter`) have their value masked
|
||||
* with `'***'` by default for usergui presentation.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [options] - Optional options.
|
||||
* @param {boolean} [options.frontendOnly] - Only return frontend-visible settings.
|
||||
* @returns {Promise<Array<{key: string, value: string, frontend: boolean}>>} The settings.
|
||||
* @param {boolean} [options.maskWriteonly] - Whether to mask writeonly settings with '***'. Defaults to `true`.
|
||||
* @returns {Promise<Array<{key: string, value: string, frontend: boolean, writeonly: boolean}>>} The settings.
|
||||
*
|
||||
* @example
|
||||
* const settings = await store.companysetting.list(42, { frontendOnly: true });
|
||||
* // => [{ key, value, frontend }]
|
||||
* // => [{ key, value, frontend, writeonly }]
|
||||
*/
|
||||
return async (googleAuthId, { frontendOnly = false } = {}) => {
|
||||
return async (
|
||||
googleAuthId,
|
||||
{ frontendOnly = false, maskWriteonly = true } = {},
|
||||
) => {
|
||||
const { companyId } = await access(googleAuthId);
|
||||
|
||||
const result = await pool
|
||||
@@ -28,6 +35,8 @@ export default function list(pool) {
|
||||
.input('companyId', companyId)
|
||||
.query(frontendOnly ? LIST_BY_COMPANY_FRONTEND : LIST_BY_COMPANY);
|
||||
|
||||
return result.recordset.map(shapeSetting);
|
||||
return result.recordset.map((row) =>
|
||||
shapeSetting(row, { exposeValue: !maskWriteonly }),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ const ALLOWED_KEYS = new Set([
|
||||
'labelprinter.type',
|
||||
]);
|
||||
|
||||
// Keys that are write-only by default: the GUI receives a scrambled "***"
|
||||
// placeholder when reading, but the backend always gets the real value.
|
||||
const WRITEONLY_KEYS = new Set([
|
||||
'keys.tavily',
|
||||
'keys.openrouter',
|
||||
]);
|
||||
|
||||
export default function set(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
|
||||
@@ -19,17 +26,22 @@ export default function set(pool) {
|
||||
* Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,
|
||||
* `keys.openrouter`, `labelprinter.addr`, `labelprinter.type`.
|
||||
*
|
||||
* Keys in the write-only set (`keys.tavily`, `keys.openrouter`) are stored
|
||||
* with `writeonly = 1`; when retrieved by the GUI the value is returned as
|
||||
* `"***"`. The backend always receives the real value.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input - The setting input.
|
||||
* @param {string} input.key - The setting key (must be in the allow-list).
|
||||
* @param {string} input.value - The setting value.
|
||||
* @param {boolean} [input.frontend] - Whether the setting is frontend-visible.
|
||||
* @returns {Promise<{key: string, value: string, frontend: boolean}>} The upserted setting.
|
||||
* @param {boolean} [input.writeonly] - Whether the setting is write-only. Defaults to true for `keys.tavily` and `keys.openrouter`, false otherwise.
|
||||
* @returns {Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>} The upserted setting.
|
||||
* @throws {Error} If the input is invalid.
|
||||
*
|
||||
* @example
|
||||
* const setting = await store.companysetting.set(42, { key: 'ui.theme', value: 'dark' });
|
||||
* // => { key, value, frontend }
|
||||
* // => { key, value, frontend, writeonly }
|
||||
*/
|
||||
return async (googleAuthId, input = {}) => {
|
||||
const key = input.key?.trim();
|
||||
@@ -41,6 +53,10 @@ export default function set(pool) {
|
||||
}
|
||||
|
||||
const frontend = Boolean(input.frontend);
|
||||
const writeonly =
|
||||
input.writeonly !== undefined
|
||||
? Boolean(input.writeonly)
|
||||
: WRITEONLY_KEYS.has(key);
|
||||
const { companyId } = await access(googleAuthId);
|
||||
|
||||
await pool
|
||||
@@ -49,6 +65,7 @@ export default function set(pool) {
|
||||
.input('key', key)
|
||||
.input('value', input.value)
|
||||
.input('frontend', frontend)
|
||||
.input('writeonly', writeonly)
|
||||
.query(UPSERT);
|
||||
|
||||
const result = await pool
|
||||
@@ -57,6 +74,7 @@ export default function set(pool) {
|
||||
.input('key', key)
|
||||
.query(GET_BY_COMPANY_AND_KEY);
|
||||
|
||||
// Return the masked value to the GUI; the DB holds the real value.
|
||||
return shapeSetting(result.recordset[0]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
function shapeSetting(row) {
|
||||
function shapeSetting(row, { exposeValue = false } = {}) {
|
||||
const writeonly = Boolean(row.writeonly);
|
||||
return {
|
||||
key: row.key,
|
||||
value: row.value,
|
||||
value: writeonly && !exposeValue ? '***' : row.value,
|
||||
frontend: Boolean(row.frontend),
|
||||
writeonly,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user