feat(web): protect launcher dashboard with token and SPA login (#1953)

Add token-based authentication for the Launcher's embedded Web Dashboard.

- Ephemeral token generated in-memory each run (or via PICOCLAW_LAUNCHER_TOKEN env var)
- HMAC-SHA256 session cookie (HttpOnly, SameSite=Lax, Secure when HTTPS)
- Bearer token support for API/script access
- Rate limiting on login (10 attempts/IP/min)
- Referrer-Policy: no-referrer on all responses
- POST-only logout with JSON content-type (CSRF-safe)
- System tray "Copy dashboard token" action
- Login page shows contextual help (console/tray/log file path)
- Path traversal protection via path.Clean
- X-Forwarded-Host/Port/Proto support for reverse proxy deployments
- Full i18n support (English, Chinese)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zeed zhao
2026-03-29 13:11:43 +08:00
committed by GitHub
parent 27f638e909
commit 6ea364e67d
44 changed files with 1617 additions and 45 deletions
+21
View File
@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as ModelsRouteImport } from './routes/models'
import { Route as LogsRouteImport } from './routes/logs'
import { Route as LauncherLoginRouteImport } from './routes/launcher-login'
import { Route as CredentialsRouteImport } from './routes/credentials'
import { Route as ConfigRouteImport } from './routes/config'
import { Route as AgentRouteImport } from './routes/agent'
@@ -31,6 +32,11 @@ const LogsRoute = LogsRouteImport.update({
path: '/logs',
getParentRoute: () => rootRouteImport,
} as any)
const LauncherLoginRoute = LauncherLoginRouteImport.update({
id: '/launcher-login',
path: '/launcher-login',
getParentRoute: () => rootRouteImport,
} as any)
const CredentialsRoute = CredentialsRouteImport.update({
id: '/credentials',
path: '/credentials',
@@ -83,6 +89,7 @@ export interface FileRoutesByFullPath {
'/agent': typeof AgentRouteWithChildren
'/config': typeof ConfigRouteWithChildren
'/credentials': typeof CredentialsRoute
'/launcher-login': typeof LauncherLoginRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/agent/skills': typeof AgentSkillsRoute
@@ -96,6 +103,7 @@ export interface FileRoutesByTo {
'/agent': typeof AgentRouteWithChildren
'/config': typeof ConfigRouteWithChildren
'/credentials': typeof CredentialsRoute
'/launcher-login': typeof LauncherLoginRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/agent/skills': typeof AgentSkillsRoute
@@ -110,6 +118,7 @@ export interface FileRoutesById {
'/agent': typeof AgentRouteWithChildren
'/config': typeof ConfigRouteWithChildren
'/credentials': typeof CredentialsRoute
'/launcher-login': typeof LauncherLoginRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/agent/skills': typeof AgentSkillsRoute
@@ -125,6 +134,7 @@ export interface FileRouteTypes {
| '/agent'
| '/config'
| '/credentials'
| '/launcher-login'
| '/logs'
| '/models'
| '/agent/skills'
@@ -138,6 +148,7 @@ export interface FileRouteTypes {
| '/agent'
| '/config'
| '/credentials'
| '/launcher-login'
| '/logs'
| '/models'
| '/agent/skills'
@@ -151,6 +162,7 @@ export interface FileRouteTypes {
| '/agent'
| '/config'
| '/credentials'
| '/launcher-login'
| '/logs'
| '/models'
| '/agent/skills'
@@ -165,6 +177,7 @@ export interface RootRouteChildren {
AgentRoute: typeof AgentRouteWithChildren
ConfigRoute: typeof ConfigRouteWithChildren
CredentialsRoute: typeof CredentialsRoute
LauncherLoginRoute: typeof LauncherLoginRoute
LogsRoute: typeof LogsRoute
ModelsRoute: typeof ModelsRoute
}
@@ -185,6 +198,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LogsRouteImport
parentRoute: typeof rootRouteImport
}
'/launcher-login': {
id: '/launcher-login'
path: '/launcher-login'
fullPath: '/launcher-login'
preLoaderRoute: typeof LauncherLoginRouteImport
parentRoute: typeof rootRouteImport
}
'/credentials': {
id: '/credentials'
path: '/credentials'
@@ -292,6 +312,7 @@ const rootRouteChildren: RootRouteChildren = {
AgentRoute: AgentRouteWithChildren,
ConfigRoute: ConfigRouteWithChildren,
CredentialsRoute: CredentialsRoute,
LauncherLoginRoute: LauncherLoginRoute,
LogsRoute: LogsRoute,
ModelsRoute: ModelsRoute,
}