feat: add Node.js Express server for IAP and backend auth
Replace Python http.server with Express to handle: - Large IAP JWT headers that caused 500 errors - API request proxying with GCP identity tokens - Static React build serving with SPA fallback Update api.ts to use relative URLs in production for proxy routing.
This commit is contained in:
parent
5eb6f9ea31
commit
5195901f01
23
Dockerfile
23
Dockerfile
|
|
@ -1,4 +1,4 @@
|
|||
# ---------- Build stage (Keep this the same) ----------
|
||||
# Build stage
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
|
|
@ -6,18 +6,11 @@ RUN npm ci
|
|||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ---------- Diagnostic Production stage ----------
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set the working directory to where the React files live
|
||||
WORKDIR /usr/share/nginx/html
|
||||
|
||||
# Copy build output from the build stage
|
||||
COPY --from=build /app/dist .
|
||||
|
||||
# Expose port 80 (Cloud Run expects this)
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY server.js .
|
||||
RUN npm install express
|
||||
EXPOSE 80
|
||||
|
||||
# Run Python's built-in simple HTTP server
|
||||
# This server is very "dumb" and will ignore/accept large IAP headers
|
||||
CMD ["python", "-m", "http.server", "80"]
|
||||
CMD ["node", "server.js"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
const express = require('express');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 80;
|
||||
const BACKEND_URL = process.env.BACKEND_URL;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Serve static React build
|
||||
app.use(express.static(path.join(__dirname, 'dist')));
|
||||
|
||||
// Proxy endpoint - fetches identity token and forwards to backend
|
||||
app.post('/api/chat/stream', async (req, res) => {
|
||||
try {
|
||||
// Fetch identity token from GCP metadata server
|
||||
const tokenResponse = await fetch(
|
||||
`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${BACKEND_URL}`,
|
||||
{ headers: { 'Metadata-Flavor': 'Google' } }
|
||||
);
|
||||
const identityToken = await tokenResponse.text();
|
||||
|
||||
// Forward request to backend with auth
|
||||
const backendResponse = await fetch(`${BACKEND_URL}/chat/stream`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${identityToken}`,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
});
|
||||
|
||||
// Stream response back to client
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const reader = backendResponse.body.getReader();
|
||||
const pump = async () => {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.write(value);
|
||||
pump();
|
||||
};
|
||||
pump();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Proxy error:', error);
|
||||
res.status(500).json({ error: 'Proxy error' });
|
||||
}
|
||||
});
|
||||
|
||||
// SPA fallback - all other routes serve index.html
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import type { ChatMode } from '@/types/chat'
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
|
||||
// In production, use relative URL to route through the proxy server
|
||||
// For local development, set VITE_API_URL=http://localhost:8000 in .env
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'
|
||||
const API_ENDPOINT = import.meta.env.VITE_API_ENDPOINT || '/chat/stream'
|
||||
const USE_MOCK_DATA = false // Set to true to use mock data for testing
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue