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:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
@@ -19,6 +20,7 @@
|
||||
const theme = getTheme();
|
||||
|
||||
let showPasswordForm = $state(false);
|
||||
let redirecting = $state(false);
|
||||
let openSearch: (() => void) | undefined;
|
||||
let pwdCurrent = $state('');
|
||||
let pwdNew = $state('');
|
||||
@@ -27,6 +29,7 @@
|
||||
|
||||
async function changePassword(e: SubmitEvent) {
|
||||
e.preventDefault(); pwdMsg = ''; pwdSuccess = false;
|
||||
if (pwdNew.length < 8) { pwdMsg = t('auth.passwordTooShort'); return; }
|
||||
try {
|
||||
await api('/auth/password', { method: 'PUT', body: JSON.stringify({ current_password: pwdCurrent, new_password: pwdNew }) });
|
||||
pwdMsg = t('common.changePassword');
|
||||
@@ -38,6 +41,7 @@
|
||||
}
|
||||
|
||||
let collapsed = $state(false);
|
||||
let isMac = $derived(typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent));
|
||||
|
||||
// Nav counts for badges
|
||||
let navCounts = $state<Record<string, number>>({});
|
||||
@@ -153,15 +157,16 @@
|
||||
try {
|
||||
const saved = localStorage.getItem('nav_expanded');
|
||||
if (saved) expandedGroups = JSON.parse(saved);
|
||||
} catch {}
|
||||
} catch (e) { console.warn('Failed to parse nav_expanded:', e); }
|
||||
}
|
||||
await loadUser();
|
||||
if (!auth.user && !isAuthPage) {
|
||||
window.location.href = '/login';
|
||||
redirecting = true;
|
||||
goto('/login');
|
||||
}
|
||||
// Load nav counts
|
||||
if (auth.user) {
|
||||
try { navCounts = await api('/status/counts'); } catch {}
|
||||
try { navCounts = await api('/status/counts'); } catch (e) { console.warn('Failed to load nav counts:', e); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -270,7 +275,7 @@
|
||||
<MdiIcon name="mdiMagnify" size={16} />
|
||||
{#if !collapsed}
|
||||
<span class="flex-1 text-left text-xs">{t('searchPalette.placeholder')}</span>
|
||||
<kbd class="text-[0.6rem] font-mono px-1 py-0.5 rounded" style="background: var(--color-background); border: 1px solid var(--color-border);">⌘K</kbd>
|
||||
<kbd class="text-[0.6rem] font-mono px-1 py-0.5 rounded" style="background: var(--color-background); border: 1px solid var(--color-border);">{isMac ? '⌘' : 'Ctrl '}K</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
@@ -420,7 +425,7 @@
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-5 h-5 rounded-full border-2 border-[var(--color-primary)] border-t-transparent animate-spin"></div>
|
||||
<p class="text-sm text-[var(--color-muted-foreground)]">{t('common.loading')}</p>
|
||||
<p class="text-sm text-[var(--color-muted-foreground)]">{redirecting ? t('common.redirecting') : t('common.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user