env-creds-mask
All checks were successful
Deploy on push / deploy (push) Successful in 6s

This commit is contained in:
Untone 2025-05-26 13:31:25 +03:00
parent c06a187fd6
commit 627be9a4f1
3 changed files with 98 additions and 23 deletions

View File

@ -861,6 +861,44 @@ const AdminPage: Component<AdminPageProps> = (props) => {
} }
} }
/**
* Кнопка копирования значения переменной окружения
* @param value - значение для копирования
*/
function CopyButton({ value }: { value: string }) {
/**
* Копирует значение в буфер обмена
* @param e - событие клика
*/
const handleCopy = async (e: MouseEvent) => {
e.preventDefault()
try {
await navigator.clipboard.writeText(value)
// Можно добавить всплывающее уведомление
} catch (err) {
alert('Ошибка копирования: ' + (err as Error).message)
}
}
return (
<button class="copy-btn" title="Скопировать" onClick={handleCopy} style="margin-left: 6px">
📋
</button>
)
}
/**
* Кнопка показать/скрыть значение переменной
* @param shown - показывать ли значение
* @param onToggle - обработчик переключения
*/
function ShowHideButton({ shown, onToggle }: { shown: boolean, onToggle: () => void }) {
return (
<button class="show-btn" title={shown ? 'Скрыть' : 'Показать'} onClick={onToggle} style="margin-left: 6px">
{shown ? '🙈' : '👁️'}
</button>
)
}
/** /**
* Компонент модального окна для редактирования переменной окружения * Компонент модального окна для редактирования переменной окружения
*/ */
@ -909,6 +947,17 @@ const AdminPage: Component<AdminPageProps> = (props) => {
* Компонент для отображения переменных окружения * Компонент для отображения переменных окружения
*/ */
const EnvVariablesTab: Component = () => { const EnvVariablesTab: Component = () => {
// Сигналы для показа/скрытия значений по ключу
const [shownVars, setShownVars] = createSignal<{ [key: string]: boolean }>({})
/**
* Переключает показ значения переменной
* @param key - ключ переменной
*/
const toggleShow = (key: string) => {
setShownVars((prev) => ({ ...prev, [key]: !prev[key] }))
}
return ( return (
<div class="env-variables-container"> <div class="env-variables-container">
<Show when={envLoading()}> <Show when={envLoading()}>
@ -928,7 +977,6 @@ const AdminPage: Component<AdminPageProps> = (props) => {
<Show when={section.description}> <Show when={section.description}>
<p class="section-description">{section.description}</p> <p class="section-description">{section.description}</p>
</Show> </Show>
<div class="variables-list"> <div class="variables-list">
<table> <table>
<thead> <thead>
@ -941,13 +989,19 @@ const AdminPage: Component<AdminPageProps> = (props) => {
</thead> </thead>
<tbody> <tbody>
<For each={section.variables}> <For each={section.variables}>
{(variable) => ( {(variable) => {
const shown = shownVars()[variable.key] || false
return (
<tr> <tr>
<td>{variable.key}</td> <td>{variable.key}</td>
<td> <td>
{variable.isSecret {variable.isSecret && !shown
? '••••••••' ? '••••••••'
: (variable.value || <span class="empty-value">не задано</span>)} : (variable.value || <span class="empty-value">не задано</span>)}
<CopyButton value={variable.value || ''} />
{variable.isSecret && (
<ShowHideButton shown={shown} onToggle={() => toggleShow(variable.key)} />
)}
</td> </td>
<td>{variable.description || '-'}</td> <td>{variable.description || '-'}</td>
<td class="actions"> <td class="actions">
@ -959,7 +1013,8 @@ const AdminPage: Component<AdminPageProps> = (props) => {
</button> </button>
</td> </td>
</tr> </tr>
)} )
}}
</For> </For>
</tbody> </tbody>
</table> </table>

View File

@ -86,7 +86,9 @@ class EnvManager:
# Переменные, которые следует всегда помечать как секретные # Переменные, которые следует всегда помечать как секретные
SECRET_VARS_PATTERNS = [ SECRET_VARS_PATTERNS = [
r".*TOKEN.*", r".*SECRET.*", r".*PASSWORD.*", r".*KEY.*", r".*TOKEN.*", r".*SECRET.*", r".*PASSWORD.*", r".*KEY.*",
r".*PWD.*", r".*PASS.*", r".*CRED.*" r".*PWD.*", r".*PASS.*", r".*CRED.*",
r".*JWT.*", r".*SESSION.*", r".*OAUTH.*",
r".*GITHUB.*", r".*GOOGLE.*", r".*FACEBOOK.*"
] ]
def __init__(self): def __init__(self):
@ -178,9 +180,27 @@ class EnvManager:
def _is_secret_variable(self, key: str) -> bool: def _is_secret_variable(self, key: str) -> bool:
""" """
Проверяет, является ли переменная секретной Проверяет, является ли переменная секретной.
Секретными считаются:
- переменные, подходящие под SECRET_VARS_PATTERNS
- переменные с ключами DATABASE_URL, REDIS_URL, DB_URL (точное совпадение, без учета регистра)
>>> EnvManager()._is_secret_variable('MY_SECRET_TOKEN')
True
>>> EnvManager()._is_secret_variable('database_url')
True
>>> EnvManager()._is_secret_variable('REDIS_URL')
True
>>> EnvManager()._is_secret_variable('DB_URL')
True
>>> EnvManager()._is_secret_variable('SOME_PUBLIC_KEY')
True
>>> EnvManager()._is_secret_variable('SOME_PUBLIC_VAR')
False
""" """
key_upper = key.upper() key_upper = key.upper()
if key_upper in {"DATABASE_URL", "REDIS_URL", "DB_URL"}:
return True
return any(re.match(pattern, key_upper) for pattern in self.SECRET_VARS_PATTERNS) return any(re.match(pattern, key_upper) for pattern in self.SECRET_VARS_PATTERNS)
def _determine_variable_type(self, value: str) -> str: def _determine_variable_type(self, value: str) -> str:

View File

@ -75,7 +75,7 @@ class ViewedStorage:
await redis.connect() await redis.connect()
# Логируем настройки Redis соединения # Логируем настройки Redis соединения
logger.info(f" * Redis connection: {redis._client}") logger.info("* Redis connected")
# Получаем список всех ключей migrated_views_* и находим самый последний # Получаем список всех ключей migrated_views_* и находим самый последний
keys = await redis.execute("KEYS", "migrated_views_*") keys = await redis.execute("KEYS", "migrated_views_*")