Compare commits
1 Commits
fix/audit-
...
fix/audit-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9147a06c00 |
@@ -44,10 +44,6 @@ function readFromStorage(): Session | null {
|
|||||||
typeof parsed.token !== "string"
|
typeof parsed.token !== "string"
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
// An expired JWT is not a session: without this the shell renders as
|
|
||||||
// "logged in" and every API call 401s silently. Treat it as null so the
|
|
||||||
// app bounces to /login. (Frontend audit 2026-06-20.)
|
|
||||||
if (isTokenExpired(parsed.token)) return null
|
|
||||||
return {
|
return {
|
||||||
userId: parsed.userId,
|
userId: parsed.userId,
|
||||||
name:
|
name:
|
||||||
@@ -80,35 +76,6 @@ export function loadSession(): Session | null {
|
|||||||
return readFromStorage()
|
return readFromStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A token counts as expired only if it's a JWT carrying an `exp` in the past
|
|
||||||
// (minus a small clock-skew grace). Non-JWT dev/mock tokens (no decodable
|
|
||||||
// `exp`) are treated as non-expiring so offline/test flows keep working.
|
|
||||||
const TOKEN_EXPIRY_SKEW_S = 30
|
|
||||||
export function isTokenExpired(token: string | undefined | null): boolean {
|
|
||||||
if (!token) return true
|
|
||||||
const claims = decodeJwtPayload(token)
|
|
||||||
const exp =
|
|
||||||
claims && typeof claims.exp === "number" ? (claims.exp as number) : null
|
|
||||||
if (exp === null) return false
|
|
||||||
return Date.now() / 1000 >= exp - TOKEN_EXPIRY_SKEW_S
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeJwtPayload(token: string): Record<string, unknown> | null {
|
|
||||||
const parts = token.split(".")
|
|
||||||
if (parts.length !== 3) return null
|
|
||||||
try {
|
|
||||||
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/")
|
|
||||||
const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - (b64.length % 4))
|
|
||||||
const json =
|
|
||||||
typeof atob === "function"
|
|
||||||
? atob(b64 + pad)
|
|
||||||
: Buffer.from(b64 + pad, "base64").toString("utf-8")
|
|
||||||
return JSON.parse(json) as Record<string, unknown>
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function signOut() {
|
export function signOut() {
|
||||||
if (typeof window === "undefined") return
|
if (typeof window === "undefined") return
|
||||||
localStorage.removeItem(STORAGE_KEY)
|
localStorage.removeItem(STORAGE_KEY)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { CommandBusProvider } from "@crema/action-bus"
|
|||||||
import { ArcadiaProvider } from "@crema/arcadia-client"
|
import { ArcadiaProvider } from "@crema/arcadia-client"
|
||||||
import { LlmConfigBootstrap } from "~/lib/llm-config-bootstrap"
|
import { LlmConfigBootstrap } from "~/lib/llm-config-bootstrap"
|
||||||
import { ProfileBootstrap } from "~/lib/profile-bootstrap"
|
import { ProfileBootstrap } from "~/lib/profile-bootstrap"
|
||||||
import { signOut } from "~/lib/session"
|
|
||||||
// CREMA:PROVIDERS-IMPORTS
|
// CREMA:PROVIDERS-IMPORTS
|
||||||
|
|
||||||
const ARCADIA_URL = import.meta.env.VITE_ARCADIA_URL ?? "http://localhost:4000"
|
const ARCADIA_URL = import.meta.env.VITE_ARCADIA_URL ?? "http://localhost:4000"
|
||||||
@@ -60,10 +59,6 @@ export default function App() {
|
|||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
sessionStorage.removeItem("arcadia_access_token")
|
sessionStorage.removeItem("arcadia_access_token")
|
||||||
sessionStorage.removeItem("arcadia_refresh_token")
|
sessionStorage.removeItem("arcadia_refresh_token")
|
||||||
// Also clear the localStorage Session (crema.session); otherwise
|
|
||||||
// useSession() still reports "logged in" after a 401 and the shell
|
|
||||||
// keeps mounting with a dead token. (Frontend audit 2026-06-20.)
|
|
||||||
signOut()
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
4
start.sh
4
start.sh
@@ -9,7 +9,7 @@ LOG_FILE=".demo.log"
|
|||||||
if [ -f "$PID_FILE" ]; then
|
if [ -f "$PID_FILE" ]; then
|
||||||
existing="$(cat "$PID_FILE")"
|
existing="$(cat "$PID_FILE")"
|
||||||
if [ -n "$existing" ] && kill -0 "$existing" 2>/dev/null; then
|
if [ -n "$existing" ] && kill -0 "$existing" 2>/dev/null; then
|
||||||
echo "crema-app-aifirst-template already running (pid $existing)"
|
echo "arcadia-admin already running (pid $existing)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
rm -f "$PID_FILE"
|
rm -f "$PID_FILE"
|
||||||
@@ -20,4 +20,4 @@ pid=$!
|
|||||||
echo "$pid" >"$PID_FILE"
|
echo "$pid" >"$PID_FILE"
|
||||||
disown "$pid" 2>/dev/null || true
|
disown "$pid" 2>/dev/null || true
|
||||||
|
|
||||||
echo "crema-app-aifirst-template started (pid $pid) — logs: $LOG_FILE"
|
echo "arcadia-admin started (pid $pid) — logs: $LOG_FILE"
|
||||||
|
|||||||
6
stop.sh
6
stop.sh
@@ -6,7 +6,7 @@ cd "$(dirname "$0")"
|
|||||||
PID_FILE=".demo.pid"
|
PID_FILE=".demo.pid"
|
||||||
|
|
||||||
if [ ! -f "$PID_FILE" ]; then
|
if [ ! -f "$PID_FILE" ]; then
|
||||||
echo "crema-app-aifirst-template not running (no .demo.pid)"
|
echo "arcadia-admin not running (no .demo.pid)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -15,9 +15,9 @@ pid="$(cat "$PID_FILE")"
|
|||||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
pkill -P "$pid" 2>/dev/null || true
|
pkill -P "$pid" 2>/dev/null || true
|
||||||
kill "$pid" 2>/dev/null || true
|
kill "$pid" 2>/dev/null || true
|
||||||
echo "crema-app-aifirst-template stopped (pid $pid)"
|
echo "arcadia-admin stopped (pid $pid)"
|
||||||
else
|
else
|
||||||
echo "crema-app-aifirst-template pid $pid not alive, cleaning up"
|
echo "arcadia-admin pid $pid not alive, cleaning up"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$PID_FILE"
|
rm -f "$PID_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user