Add cookie-based SPA auth and update container plumbing

Backend now exposes /api/auth/login + /api/auth/logout setting an
httpOnly ws_token cookie, and get_current_user accepts either the
cookie (SPA) or a Bearer token (n8n/CLI). AuthContext probes the
cookie via /api/v1/auth/me. Dockerfiles and compose files updated
for the new agent service deps and CopilotKit dev sidecar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-04-29 22:02:02 -06:00
parent 7f602a67af
commit 140a75f706
8 changed files with 257 additions and 26 deletions

View File

@@ -1,33 +1,40 @@
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
import { createContext, useContext, useState, useEffect, type ReactNode } from "react";
import { logout as apiLogout } from "@/lib/api";
interface AuthCtx {
isAuthenticated: boolean;
logout: () => void;
isLoading: boolean;
logout: () => Promise<void>;
setAuthenticated: (v: boolean) => void;
}
const AuthContext = createContext<AuthCtx>({
isAuthenticated: false,
logout: () => {},
isLoading: true,
logout: async () => {},
setAuthenticated: () => {},
});
export function AuthProvider({ children }: { children: ReactNode }) {
const [isAuthenticated, setAuthenticated] = useState(!!localStorage.getItem('token'));
const [isAuthenticated, setAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const check = () => setAuthenticated(!!localStorage.getItem('token'));
window.addEventListener('storage', check);
return () => window.removeEventListener('storage', check);
// Probe auth state by hitting a protected endpoint.
// If the ws_token cookie is valid, the server returns 200; else 401.
fetch("/api/v1/auth/me", { credentials: "include" })
.then((r) => setAuthenticated(r.ok))
.catch(() => setAuthenticated(false))
.finally(() => setIsLoading(false));
}, []);
const logout = () => {
localStorage.removeItem('token');
const logout = async () => {
await apiLogout();
setAuthenticated(false);
};
return (
<AuthContext.Provider value={{ isAuthenticated, logout, setAuthenticated }}>
<AuthContext.Provider value={{ isAuthenticated, isLoading, logout, setAuthenticated }}>
{children}
</AuthContext.Provider>
);