e0bae394ee
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
62 lines
1.4 KiB
TypeScript
62 lines
1.4 KiB
TypeScript
/**
|
|
* Reactive auth state using Svelte 5 runes.
|
|
*/
|
|
|
|
import { api, setTokens, clearTokens, isAuthenticated } from './api';
|
|
import { clearAllCaches } from './stores/caches.svelte';
|
|
import type { User } from './types';
|
|
|
|
let user = $state<User | null>(null);
|
|
let loading = $state(true);
|
|
|
|
export function getAuth() {
|
|
return {
|
|
get user() { return user; },
|
|
get loading() { return loading; },
|
|
get isAdmin() { return user?.role === 'admin'; },
|
|
};
|
|
}
|
|
|
|
export async function loadUser() {
|
|
if (!isAuthenticated()) {
|
|
user = null;
|
|
loading = false;
|
|
return;
|
|
}
|
|
try {
|
|
user = await api<User>('/auth/me');
|
|
} catch {
|
|
user = null;
|
|
clearTokens();
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
export async function login(username: string, password: string) {
|
|
const data = await api<{ access_token: string; refresh_token: string }>('/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
setTokens(data.access_token, data.refresh_token);
|
|
await loadUser();
|
|
}
|
|
|
|
export async function setup(username: string, password: string) {
|
|
const data = await api<{ access_token: string; refresh_token: string }>('/auth/setup', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
setTokens(data.access_token, data.refresh_token);
|
|
await loadUser();
|
|
}
|
|
|
|
export function logout() {
|
|
clearTokens();
|
|
clearAllCaches();
|
|
user = null;
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/login';
|
|
}
|
|
}
|