Settings: temporary UAT secret-unmask gate (SLAB_UAT_UNMASK_SECRETS)
Owner-directed, LAN/HTTP dev-test only. When the env flag is set,
GET /api/v1/settings returns decrypted secret values (string) instead of
{configured: bool}; the UI prefills and shows them in clear text. Default
off -> SETTINGS-75 masking and the existing tests stay intact. Revert
before any non-UAT deployment.
This commit is contained in:
+21
-7
@@ -59,18 +59,32 @@ def get_settings() -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
result["woo_base_url"] = os.environ.get("WOO_BASE_URL", "")
|
||||
|
||||
# TEMPORARY UAT override (owner-directed, LAN/HTTP only): when set, secrets are
|
||||
# returned as their decrypted plaintext so they can be inspected on the dev-test
|
||||
# box. Default OFF -> SETTINGS-75 masking intact. Remove before prod.
|
||||
unmask = os.environ.get("SLAB_UAT_UNMASK_SECRETS") == "1"
|
||||
|
||||
for k in sorted(ALL_KEYS):
|
||||
row = rows.get(k, {})
|
||||
if k in SECRET_KEYS:
|
||||
enc = row.get("value_enc")
|
||||
if enc:
|
||||
try:
|
||||
decrypt(enc) # verify roundtrip possible
|
||||
result[k] = {"configured": True}
|
||||
except Exception:
|
||||
result[k] = {"configured": False}
|
||||
if unmask:
|
||||
if enc:
|
||||
try:
|
||||
result[k] = decrypt(enc)
|
||||
except Exception:
|
||||
result[k] = ""
|
||||
else:
|
||||
result[k] = ""
|
||||
else:
|
||||
result[k] = {"configured": False}
|
||||
if enc:
|
||||
try:
|
||||
decrypt(enc) # verify roundtrip possible
|
||||
result[k] = {"configured": True}
|
||||
except Exception:
|
||||
result[k] = {"configured": False}
|
||||
else:
|
||||
result[k] = {"configured": False}
|
||||
else:
|
||||
if k in rows:
|
||||
val = _row_to_value(row)
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface SecretState { configured: boolean }
|
||||
// Masked mode: object {configured}. Unmasked UAT mode (SLAB_UAT_UNMASK_SECRETS): plain string.
|
||||
type Secret = SecretState | string;
|
||||
interface Settings {
|
||||
woo_base_url: string;
|
||||
woo_wp_username: string | null;
|
||||
woo_app_password: SecretState;
|
||||
inference_base_url: SecretState;
|
||||
inference_api_key: SecretState;
|
||||
woo_app_password: Secret;
|
||||
inference_base_url: Secret;
|
||||
inference_api_key: Secret;
|
||||
inference_model: string | null;
|
||||
inference_enabled: boolean;
|
||||
content_llm_enabled: boolean;
|
||||
@@ -24,6 +26,11 @@
|
||||
// local edit buffers (write-only for secrets)
|
||||
let form: any = {};
|
||||
|
||||
// Unmasked UAT mode returns the secret as a plain string; masked mode returns {configured}.
|
||||
const isUnmasked = (s: Secret) => typeof s === 'string';
|
||||
const secretValue = (s: Secret): string => (isUnmasked(s) ? (s as string) : '');
|
||||
const secretConfigured = (s: Secret): boolean => (isUnmasked(s) ? (s as string).length > 0 : s.configured);
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
try {
|
||||
@@ -39,10 +46,10 @@
|
||||
brand_voice: settings.brand_voice || '',
|
||||
geo_context: settings.geo_context || '',
|
||||
woo_create_status: settings.woo_create_status || 'draft',
|
||||
// secrets: only send if user types
|
||||
woo_app_password: '',
|
||||
inference_base_url: '',
|
||||
inference_api_key: '',
|
||||
// secrets: prefill when unmasked (UAT), else blank (masked, write-only)
|
||||
woo_app_password: secretValue(settings.woo_app_password),
|
||||
inference_base_url: secretValue(settings.inference_base_url),
|
||||
inference_api_key: secretValue(settings.inference_api_key),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -77,10 +84,10 @@
|
||||
if (res.ok) {
|
||||
settings = await res.json();
|
||||
message = 'Saved';
|
||||
// clear secret inputs after save
|
||||
form.woo_app_password = '';
|
||||
form.inference_base_url = '';
|
||||
form.inference_api_key = '';
|
||||
// refresh secret buffers from the (possibly unmasked) server state
|
||||
form.woo_app_password = secretValue(settings.woo_app_password);
|
||||
form.inference_base_url = secretValue(settings.inference_base_url);
|
||||
form.inference_api_key = secretValue(settings.inference_api_key);
|
||||
} else {
|
||||
const err = await res.json();
|
||||
message = err.error?.message || 'Save failed';
|
||||
@@ -124,18 +131,18 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Woo App Password {settings.woo_app_password.configured ? '(configured)' : '(not set)'}</label>
|
||||
<input type="password" bind:value={form.woo_app_password} placeholder={settings.woo_app_password.configured ? '••••••••' : ''} />
|
||||
<label>Woo App Password {secretConfigured(settings.woo_app_password) ? '(configured)' : '(not set)'}</label>
|
||||
<input type={isUnmasked(settings.woo_app_password) ? 'text' : 'password'} bind:value={form.woo_app_password} placeholder={isUnmasked(settings.woo_app_password) ? '' : '••••••••'} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Inference Base URL {settings.inference_base_url.configured ? '(configured)' : '(not set)'}</label>
|
||||
<input bind:value={form.inference_base_url} placeholder={settings.inference_base_url.configured ? '••••••••' : ''} />
|
||||
<label>Inference Base URL {secretConfigured(settings.inference_base_url) ? '(configured)' : '(not set)'}</label>
|
||||
<input bind:value={form.inference_base_url} placeholder={isUnmasked(settings.inference_base_url) ? '' : '••••••••'} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Inference API Key {settings.inference_api_key.configured ? '(configured)' : '(not set)'}</label>
|
||||
<input type="password" bind:value={form.inference_api_key} placeholder={settings.inference_api_key.configured ? '••••••••' : ''} />
|
||||
<label>Inference API Key {secretConfigured(settings.inference_api_key) ? '(configured)' : '(not set)'}</label>
|
||||
<input type={isUnmasked(settings.inference_api_key) ? 'text' : 'password'} bind:value={form.inference_api_key} placeholder={isUnmasked(settings.inference_api_key) ? '' : '••••••••'} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -182,17 +189,8 @@
|
||||
|
||||
<style>
|
||||
.settings { max-width: 600px; color: #eee; }
|
||||
.settings label { display: block; margin: 8px 0 4px; }
|
||||
.settings input,
|
||||
.settings select {
|
||||
color: #eee;
|
||||
background: #1c1c1c;
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
}
|
||||
.settings option {
|
||||
color: #eee;
|
||||
background: #1c1c1c;
|
||||
}
|
||||
.settings button { margin-top: 16px; }
|
||||
label { display: block; margin: 8px 0 4px; }
|
||||
input, select, option { color: #eee; background: #1c1c1c; width: 100%; padding: 6px; }
|
||||
option { color: #eee; background: #1c1c1c; }
|
||||
button { margin-top: 16px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user