fix: healthcheck auth, secure cookie auto-detect, model object crash (#84)
Bug 1 (#78): Dockerfile HEALTHCHECK curled authenticated /api/status, always got 401 in production. Changed to /login which is public. Bug 2 (#78): Login hangs on HTTP deployments because secure=true cookie is silently rejected. Now auto-detects protocol from x-forwarded-proto header, only sets secure when request actually came over HTTPS. Bug 3 (#78): Agent model field from OpenClaw 2026.3.x is {primary: "name"} object instead of string, causing React error #31. Added normalizeModel() helper and applied it in all WebSocket/session mapping code paths.
This commit is contained in:
parent
d826435401
commit
f0f22129be
|
|
@ -29,5 +29,5 @@ EXPOSE 3000
|
|||
ENV PORT=3000
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/status || exit 1
|
||||
CMD curl -f http://localhost:3000/login || exit 1
|
||||
CMD ["node", "server.js"]
|
||||
|
|
|
|||
|
|
@ -92,8 +92,11 @@ export async function POST(request: Request) {
|
|||
},
|
||||
})
|
||||
|
||||
const isSecureRequest = request.headers.get('x-forwarded-proto') === 'https'
|
||||
|| new URL(request.url).protocol === 'https:'
|
||||
|
||||
response.cookies.set('mc-session', token, {
|
||||
...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }),
|
||||
...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000), isSecureRequest }),
|
||||
})
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -41,8 +41,11 @@ export async function POST(request: Request) {
|
|||
},
|
||||
})
|
||||
|
||||
const isSecureRequest = request.headers.get('x-forwarded-proto') === 'https'
|
||||
|| new URL(request.url).protocol === 'https:'
|
||||
|
||||
response.cookies.set('mc-session', token, {
|
||||
...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }),
|
||||
...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000), isSecureRequest }),
|
||||
})
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function AgentNode({ data }: { data: any }) {
|
|||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{agent.model.split('/').pop()}
|
||||
{(typeof agent.model === 'string' ? agent.model : '').split('/').pop() || 'unknown'}
|
||||
</div>
|
||||
|
||||
{agent.session && (
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ function envFlag(name: string): boolean | undefined {
|
|||
return undefined
|
||||
}
|
||||
|
||||
export function getMcSessionCookieOptions(input: { maxAgeSeconds: number }): Partial<ResponseCookie> {
|
||||
export function getMcSessionCookieOptions(input: { maxAgeSeconds: number; isSecureRequest?: boolean }): Partial<ResponseCookie> {
|
||||
const secureEnv = envFlag('MC_COOKIE_SECURE')
|
||||
const secure = secureEnv ?? process.env.NODE_ENV === 'production'
|
||||
// Explicit env wins. Otherwise auto-detect: only set secure if request came over HTTPS.
|
||||
// Falls back to NODE_ENV=production when no request hint is available.
|
||||
const secure = secureEnv ?? input.isSecureRequest ?? process.env.NODE_ENV === 'production'
|
||||
|
||||
// Strict is safest for this app (same-site UI + API), but allow override for edge cases.
|
||||
const sameSiteRaw = (process.env.MC_COOKIE_SAMESITE || 'strict').toLowerCase()
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export function getAllGatewaySessions(activeWithinMs = 60 * 60 * 1000): GatewayS
|
|||
updatedAt,
|
||||
chatType: s.chatType || 'unknown',
|
||||
channel: s.deliveryContext?.channel || s.lastChannel || s.channel || '',
|
||||
model: s.model || '',
|
||||
model: typeof s.model === 'object' && s.model?.primary ? String(s.model.primary) : String(s.model || ''),
|
||||
totalTokens: s.totalTokens || 0,
|
||||
inputTokens: s.inputTokens || 0,
|
||||
outputTokens: s.outputTokens || 0,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@ export function getStatusBadgeColor(status: AgentStatus['status']): string {
|
|||
}
|
||||
}
|
||||
|
||||
/** Normalize model field — OpenClaw 2026.3.x may send {primary: "model-name"} instead of a string */
|
||||
export function normalizeModel(model: unknown): string {
|
||||
if (typeof model === 'string') return model
|
||||
if (model && typeof model === 'object' && 'primary' in model) return String((model as any).primary)
|
||||
return ''
|
||||
}
|
||||
|
||||
export function sessionToAgent(session: Session): Agent {
|
||||
const getStatusFromSession = (session: Session): AgentStatus['status'] => {
|
||||
if (session.age === 'just now' || session.age.includes('m ago')) return 'active'
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useCallback, useRef, useEffect } from 'react'
|
||||
import { useMissionControl } from '@/store'
|
||||
import { normalizeModel } from '@/lib/utils'
|
||||
|
||||
// Gateway protocol version (v3 required by OpenClaw 2026.x)
|
||||
const PROTOCOL_VERSION = 3
|
||||
|
|
@ -172,7 +173,7 @@ export function useWebSocket() {
|
|||
key: session.key || '',
|
||||
kind: session.kind || 'unknown',
|
||||
age: session.age || '',
|
||||
model: session.model || '',
|
||||
model: normalizeModel(session.model),
|
||||
tokens: session.tokens || '',
|
||||
flags: session.flags || [],
|
||||
active: session.active || false,
|
||||
|
|
@ -219,7 +220,7 @@ export function useWebSocket() {
|
|||
// Handle various gateway events
|
||||
if (message.data?.type === 'token_usage') {
|
||||
addTokenUsage({
|
||||
model: message.data.model,
|
||||
model: normalizeModel(message.data.model),
|
||||
sessionId: message.data.sessionId,
|
||||
date: new Date().toISOString(),
|
||||
inputTokens: message.data.inputTokens || 0,
|
||||
|
|
@ -291,7 +292,7 @@ export function useWebSocket() {
|
|||
key: session.key || '',
|
||||
kind: session.kind || 'unknown',
|
||||
age: formatAge(session.updatedAt),
|
||||
model: session.model || '',
|
||||
model: normalizeModel(session.model),
|
||||
tokens: `${session.totalTokens || 0}/${session.contextTokens || 35000}`,
|
||||
flags: [],
|
||||
active: isActive(session.updatedAt),
|
||||
|
|
|
|||
Loading…
Reference in New Issue