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
+11 -8
View File
@@ -1,4 +1,4 @@
import { createSignal, For, Show, onMount } from "solid-js";
import { createSignal, createEffect, For, Show, on } from "solid-js";
import { useParams } from "@solidjs/router";
import { api, type ReportPayload } from "../lib/api";
import { Citations } from "../components/Citations";
@@ -19,14 +19,15 @@ function ReportInner() {
const [busy, setBusy] = createSignal(true);
const [asking, setAsking] = createSignal(false);
const [err, setErr] = createSignal("");
const authHeaders = () => ({ "X-User-Key": localStorage.getItem("fs-key") || "demo" });
async function load() {
setErr(""); setBusy(true);
try { setRep(await api.report(params.ticker)); }
catch (e) { setErr(String(e)); }
finally { setBusy(false); }
}
onMount(load);
// Refetch when the ticker param changes (ReportInner stays mounted
// because Gate wraps it, but params.ticker is reactive).
createEffect(on(() => params.ticker, () => load()));
async function exportMd() { setMd(await api.reportMd(params.ticker)); }
async function ask() {
if (!question().trim()) return;
@@ -39,6 +40,8 @@ function ReportInner() {
function dl(url: string, name: string) {
const a = document.createElement("a");
a.href = url; a.download = name; a.click();
// Revoke after a short delay to avoid blob URL leak.
setTimeout(() => URL.revokeObjectURL(url), 60_000);
}
return (
<div class="space-y-4">
@@ -72,15 +75,15 @@ function ReportInner() {
<CardContent class="flex flex-wrap gap-2">
<Button variant="outline" onClick={exportMd}>Markdown</Button>
<Button variant="outline" onClick={async () => {
const r = await fetch(`/api/report/${params.ticker}?format=html`, { method: "POST", headers: authHeaders() });
dl(URL.createObjectURL(new Blob([await r.text()], { type: "text/html" })), `${params.ticker}-report.html`);
const r = await fetch(`/api/report/${encodeURIComponent(params.ticker)}?format=html`, { method: "POST", credentials: "same-origin" });
if (r.ok) dl(URL.createObjectURL(new Blob([await r.text()], { type: "text/html" })), `${params.ticker}-report.html`);
}}>HTML</Button>
<Button variant="outline" onClick={() => {
dl(URL.createObjectURL(new Blob([JSON.stringify(rep(), null, 1)], { type: "application/json" })), `${params.ticker}-report.json`);
}}>JSON</Button>
<Button variant="outline" onClick={async () => {
const r = await fetch(`/api/report/${params.ticker}?format=pdf`, { method: "POST", headers: authHeaders() });
dl(URL.createObjectURL(await r.blob()), `${params.ticker}-report.pdf`);
const r = await fetch(`/api/report/${encodeURIComponent(params.ticker)}?format=pdf`, { method: "POST", credentials: "same-origin" });
if (r.ok) dl(URL.createObjectURL(await r.blob()), `${params.ticker}-report.pdf`);
}}>PDF</Button>
</CardContent>
</Card>
@@ -107,5 +110,5 @@ function ReportInner() {
import { Gate } from "../index";
export default function Report() {
return <Gate fitur="Report saham">{<ReportInner />}</Gate>;
return <Gate fitur="Report saham"><ReportInner /></Gate>;
}