fix: harden dashboard RBAC and cookie auth flows
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m17s
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m17s
Restrict Docker/Files writes to admins, move terminal websocket auth to cookie-first with temporary query fallback, and migrate refresh-token handling to httpOnly cookies for safer session persistence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { login, setToken, setRefreshToken, get, post } from "../lib/api.js";
|
||||
import { login, setToken } from "../lib/api.js";
|
||||
import { fade, fly } from "svelte/transition";
|
||||
|
||||
let username = $state("");
|
||||
@@ -56,7 +56,6 @@
|
||||
if (!res.ok) { const e = await res.json(); throw new Error(e.detail || "Passkey verification failed"); }
|
||||
const data = await res.json();
|
||||
setToken(data.access_token);
|
||||
setRefreshToken(data.refresh_token);
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
if (e.name === "NotAllowedError") { error = "Passkey authentication cancelled"; }
|
||||
@@ -79,7 +78,6 @@
|
||||
const res = await login(payload); // api.js helper sends JSON
|
||||
|
||||
setToken(res.access_token);
|
||||
setRefreshToken(res.refresh_token);
|
||||
// Determine if we need to reload or just update state.
|
||||
// Reload is safest to clear any stale state.
|
||||
window.location.reload();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { VoiceSession } from "../lib/voice.js";
|
||||
import { getWebSocketToken } from "../lib/api.js";
|
||||
import { tryRefreshSession } from "../lib/api.js";
|
||||
|
||||
const PERSISTENCE_STATUS_PREFIX = "__DASHBOARD_PERSISTENCE__:";
|
||||
const PERSISTENCE_STATUS_PREFIX_BYTES = new TextEncoder().encode(PERSISTENCE_STATUS_PREFIX);
|
||||
@@ -253,14 +253,16 @@
|
||||
}
|
||||
|
||||
async function connect(forceRefresh = false) {
|
||||
const token = await getWebSocketToken(forceRefresh);
|
||||
if (!token) {
|
||||
handleAuthExpired();
|
||||
return null;
|
||||
if (forceRefresh) {
|
||||
const refreshed = await tryRefreshSession();
|
||||
if (!refreshed) {
|
||||
handleAuthExpired();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const ws = new WebSocket(
|
||||
`${proto}//${location.host}/ws/terminal?host=${encodeURIComponent(tab.hostId)}&token=${encodeURIComponent(token)}`
|
||||
`${proto}//${location.host}/ws/terminal?host=${encodeURIComponent(tab.hostId)}`
|
||||
);
|
||||
ws.binaryType = "arraybuffer";
|
||||
|
||||
@@ -307,9 +309,9 @@
|
||||
if (e.code === 1008 && reason === "Unauthorized") {
|
||||
if (!closedManually && !authRecoveryInFlight) {
|
||||
authRecoveryInFlight = true;
|
||||
const recoveredToken = await getWebSocketToken(true);
|
||||
const refreshed = await tryRefreshSession();
|
||||
authRecoveryInFlight = false;
|
||||
if (recoveredToken) {
|
||||
if (refreshed) {
|
||||
reconnecting = true;
|
||||
updateTab(tabId, {
|
||||
connected: false,
|
||||
|
||||
Reference in New Issue
Block a user