feat: comprehensive code review fixes — security, performance, quality
Backend security: - Reject Gitea webhooks when webhook_secret is empty (was silently skipping) - Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints - Add CORS middleware with configurable origins - Mask telegram_webhook_secret in settings API response - Protect system-owned command template configs from regular user modification - Increase minimum password length to 8 characters Backend performance: - Batch queries in _resolve_command_context (3 queries instead of 3N) - Concurrent album fetching with asyncio.gather in immich commands - Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation) - TTLCache for rate limits (bounded memory, auto-eviction) - Optional aiohttp session reuse in send_reply/send_media_group Backend code quality: - Extract dispatch_helpers.py (shared link_data loading + event filtering) - Extract database/seeds.py from main.py (490 lines → dedicated module) - Split immich_handler.py (415 lines) into commands/immich/ subpackage - Replace bare except blocks with logged warnings - Add per-provider config validation (Pydantic models) - Truncate command input to 512 chars - Expose usage_* and desc_* slots in capabilities and variables API Frontend security: - CSS.escape() for user-controlled querySelector in highlight.ts - Client-side password min 8 chars validation on setup and password change Frontend code quality: - Replace any types with proper interfaces across top files - Decompose targets/+page.svelte into TargetForm + ReceiverSection - Fix $derived.by usage, $state mutation patterns - Add console.warn to empty catch blocks Frontend UX: - Auth redirect via goto() with "Redirecting..." state - Platform-aware Ctrl/Cmd K keyboard hint - Remove stat-card hover transform Frontend accessibility: - Modal: role=dialog, aria-modal, focus trap, restore focus - EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
This commit is contained in:
@@ -17,10 +17,11 @@
|
||||
import EntitySelect from '$lib/components/EntitySelect.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import type { CommandConfig } from '$lib/types';
|
||||
|
||||
function templateName(id: number | null): string {
|
||||
if (!id) return '';
|
||||
const tpl = cmdTemplateConfigs.find((c: any) => c.id === id);
|
||||
const tpl = cmdTemplateConfigs.find((c) => c.id === id);
|
||||
return tpl?.name || `#${id}`;
|
||||
}
|
||||
|
||||
@@ -33,15 +34,15 @@
|
||||
));
|
||||
let cmdTemplateConfigs = $derived(commandTemplateConfigsCache.items);
|
||||
const templateItems = $derived(cmdTemplateConfigs
|
||||
.filter((c: any) => c.provider_type === form.provider_type)
|
||||
.map((c: any) => ({ value: c.id, label: c.name + (c.user_id === 0 ? ' (System)' : ''), icon: c.icon || 'mdiCodeBracesBox', desc: c.provider_type }))
|
||||
.filter((c) => c.provider_type === form.provider_type)
|
||||
.map((c) => ({ value: c.id, label: c.name + (c.user_id === 0 ? ' (System)' : ''), icon: c.icon || 'mdiCodeBracesBox', desc: c.provider_type }))
|
||||
);
|
||||
let loaded = $state(false);
|
||||
let showForm = $state(false);
|
||||
let editing = $state<number | null>(null);
|
||||
let error = $state('');
|
||||
let submitting = $state(false);
|
||||
let confirmDelete = $state<any>(null);
|
||||
let confirmDelete = $state<{ id: number; onconfirm: () => Promise<void> } | null>(null);
|
||||
|
||||
// Immich command icons — used as fallback when capabilities don't specify icons
|
||||
const commandIcons: Record<string, string> = {
|
||||
@@ -54,7 +55,7 @@
|
||||
|
||||
let allCapabilities = $derived(capabilitiesCache.items);
|
||||
let providerCommands = $derived<{key: string, icon: string}[]>(
|
||||
(allCapabilities[form.provider_type]?.commands || []).map((c: any) => ({
|
||||
(allCapabilities[form.provider_type]?.commands || []).map((c: { name: string }) => ({
|
||||
key: c.name,
|
||||
icon: commandIcons[c.name] || 'mdiConsole',
|
||||
}))
|
||||
@@ -88,12 +89,12 @@
|
||||
function openNew() {
|
||||
form = defaultForm();
|
||||
// Auto-select first matching template for the default provider_type
|
||||
const match = cmdTemplateConfigs.find((c: any) => c.provider_type === form.provider_type);
|
||||
const match = cmdTemplateConfigs.find((c) => c.provider_type === form.provider_type);
|
||||
if (match) form.command_template_config_id = match.id;
|
||||
editing = null;
|
||||
showForm = true;
|
||||
}
|
||||
function editConfig(cfg: any) {
|
||||
function editConfig(cfg: CommandConfig) {
|
||||
form = {
|
||||
name: cfg.name,
|
||||
icon: cfg.icon || '',
|
||||
@@ -132,7 +133,7 @@
|
||||
finally { submitting = false; }
|
||||
}
|
||||
|
||||
function remove(cfg: any) {
|
||||
function remove(cfg: CommandConfig) {
|
||||
confirmDelete = {
|
||||
id: cfg.id,
|
||||
onconfirm: async () => {
|
||||
|
||||
Reference in New Issue
Block a user