fix: audit round 2 — session cookie Secure kondisional, logout clear cookie, IDOR report owner-scoping (migrasi 0008), health force login-gate, SSE stream login-gate, FE: Gate undefined-flicker, auth-expired global redirect, X-User-Key demo dihapus, error handling alerts/routines/watchlist, chart label, encodeURIComponent, Screener onMount
CI / go test + web typecheck (push) Canceled after 0s
CI / build + deploy (Nix) — flowsight (push) Canceled after 0s

This commit is contained in:
asepharyana
2026-09-15 23:19:56 +07:00
parent 96be4d0922
commit 07ece10474
24 changed files with 160 additions and 91 deletions
+13 -7
View File
@@ -11,27 +11,32 @@ export function WatchlistDrawer() {
const [err, setErr] = createSignal("");
async function add() {
setErr("");
const t = ticker().toUpperCase().trim();
if (!t) return;
try {
await api.addWatch(ticker().toUpperCase().trim());
await api.addWatch(t);
setTicker("");
refetch();
} catch (e) { setErr(String(e)); }
}
async function del(t: string) {
await api.removeWatch(t);
refetch();
try {
await api.removeWatch(t);
refetch();
} catch (e) { setErr(String(e)); }
}
return (
<Card>
<CardHeader class="pb-2"><CardTitle class="text-base">Watchlist</CardTitle></CardHeader>
<CardContent class="space-y-2">
<div class="flex gap-2">
<TextField class="w-24"><TextFieldInput placeholder="BBCA" value={ticker()} onInput={(e) => setTicker(e.currentTarget.value)} /></TextField>
<label class="sr-only" for="wl-ticker">Tambah ticker</label>
<TextField class="w-24"><TextFieldInput id="wl-ticker" placeholder="BBCA" value={ticker()} onInput={(e) => setTicker(e.currentTarget.value)} /></TextField>
<Button size="sm" onClick={add}>Add</Button>
</div>
<Show when={err()}><p class="text-xs text-destructive">{err()}</p></Show>
<ul class="flex flex-wrap gap-1.5">
<For each={wl()?.watchlist || []}>{(t) => <li><a href={`/report/${t}`}><Badge variant="secondary">{t}</Badge></a> <button class="text-xs text-muted-foreground hover:text-foreground" onClick={() => del(t)}>×</button></li>}</For>
<For each={wl()?.watchlist || []}>{(t) => <li class="flex items-center gap-1"><a href={`/report/${encodeURIComponent(t)}`}><Badge variant="secondary">{t}</Badge></a> <button class="text-xs text-muted-foreground hover:text-foreground" aria-label={`Hapus ${t} dari watchlist`} onClick={() => del(t)}>×</button></li>}</For>
</ul>
</CardContent>
</Card>
@@ -62,10 +67,11 @@ export function ChatSidebar() {
</For>
</div>
<div class="flex gap-2">
<TextField class="flex-1"><TextFieldInput placeholder="Ask about your watchlist…" value={msg()} onInput={(e) => setMsg(e.currentTarget.value)} onKeyDown={(e: KeyboardEvent) => { if (e.key === "Enter") send(); }} /></TextField>
<label class="sr-only" for="chat-msg">Tanya AI</label>
<TextField class="flex-1"><TextFieldInput id="chat-msg" placeholder="Ask about your watchlist…" value={msg()} onInput={(e) => setMsg(e.currentTarget.value)} onKeyDown={(e: KeyboardEvent) => { if (e.key === "Enter") send(); }} /></TextField>
<Button size="sm" onClick={send}>Send</Button>
</div>
</CardContent>
</Card>
);
}
}
+2 -3
View File
@@ -28,6 +28,7 @@ export function ThemeToggle() {
}
export function useAuth() {
// me() is undefined while loading, null when logged out, AuthUser when in.
const [me, { refetch }] = createResource(async (): Promise<AuthUser | null> => {
try { return (await api.me()).user; } catch { return null; }
});
@@ -59,9 +60,7 @@ export function ButuhLogin(props: { fitur: string }) {
export function AuthButton(props: { me: AuthUser | null | undefined; onLogout: () => void }) {
return (
<Show when={props.me} fallback={
<a href="/api/auth/start"><Button>Sign in with Google</Button></a>
}>
<Show when={props.me} fallback={<a href="/login"><Button>Masuk / Daftar</Button></a>}>
{(u) => (
<span class="flex items-center gap-2">
<Avatar class="size-8">