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
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user