diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 87e5e86..d9fef8e 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,15 +1,33 @@ FROM node:18.18.2-alpine AS builder + WORKDIR /app + COPY package*.json ./ RUN npm install + COPY . . + RUN npm run build + + FROM nginx:1.25.3-alpine -RUN chown -R nginx:nginx /usr/share/nginx/html && chown -R nginx:nginx /var/cache/nginx && chown -R nginx:nginx /var/log/nginx && chown -R nginx:nginx /etc/nginx/conf.d + COPY --from=builder /app/dist /usr/share/nginx/html -RUN chown -R nginx:nginx /usr/share/nginx/html + + COPY nginx.conf /etc/nginx/conf.d/default.conf -RUN chown nginx:nginx /etc/nginx/conf.d/default.conf + + +RUN chown -R nginx:nginx /usr/share/nginx/html \ + && chown -R nginx:nginx /var/cache/nginx \ + && chown -R nginx:nginx /var/log/nginx \ + && chown -R nginx:nginx /etc/nginx/conf.d \ + && touch /var/run/nginx.pid \ + && chown nginx:nginx /var/run/nginx.pid + USER nginx + EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 92daa70..f850c0e 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,120 +1,12 @@ -# HTTP → HTTPS redirect server { listen 80; - server_name _; - # ACME challenge passthrough (Let's Encrypt / Certbot) - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - return 301 https://$host$request_uri; - } -} - -# HTTPS termination -server { - listen 443 ssl; - server_name _; + server_name localhost; - # ── TLS certificates ────────────────────────────────────────────────────── - # Mount your certificate and key via Docker volume or Kubernetes secret. - # Example paths match the certbot / Let's Encrypt convention. - ssl_certificate /etc/nginx/certs/fullchain.pem; - ssl_certificate_key /etc/nginx/certs/privkey.pem; - - # ── TLS hardening ───────────────────────────────────────────────────────── - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 1d; - ssl_session_tickets off; - - # OCSP stapling - ssl_stapling on; - ssl_stapling_verify on; - resolver 8.8.8.8 8.8.4.4 valid=300s; - resolver_timeout 5s; - - # ── Security headers ────────────────────────────────────────────────────── - # HSTS: 1 year, include subdomains, allow preload - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Frame-Options "DENY" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - - root /usr/share/nginx/html; + root /usr/share/nginx/html; index index.html; - # Security Headers - # Content Security Policy - Restricts resource loading to prevent XSS - # self: Allow resources from same origin - # unsafe-inline: Required for React inline styles (consider removing in production with styled-components) - # unsafe-eval: Required for development builds (removed in production) - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://horizon-testnet.stellar.org https://soroban-testnet.stellar.org https://horizon.stellar.org https://soroban.stellar.org; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always; - - # Prevent clickjacking attacks by disallowing the page to be embedded in frames - add_header X-Frame-Options "DENY" always; - - # Prevent MIME type sniffing which could lead to XSS attacks - add_header X-Content-Type-Options "nosniff" always; - - # Control referrer information sent with requests - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - - # Enable browser XSS protection (legacy but still useful for older browsers) - add_header X-XSS-Protection "1; mode=block" always; - - # Enforce HTTPS in production (commented out for local development) - # Uncomment for production deployment with HTTPS - # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - - # Permissions Policy - Restrict browser features - add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always; - location / { try_files $uri $uri/ /index.html; } - - location /auth/ { - proxy_pass http://backend:4000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /vaccination/ { - proxy_pass http://backend:4000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /verify/ { - proxy_pass http://backend:4000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /patient/ { - proxy_pass http://backend:4000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /admin/ { - proxy_pass http://backend:4000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 319dad3..ccea03e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,18 @@ "react-router-dom": "6.30.3" }, "devDependencies": { + "@babel/preset-env": "^7.29.2", + "@babel/preset-react": "^7.28.5", + "@playwright/test": "^1.40.0", + "@testing-library/jest-dom": "^6.4.0", + "@testing-library/react": "^14.2.0", + "@testing-library/user-event": "^14.5.0", + "@vitejs/plugin-react": "^4.7.0", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "jest-axe": "^8.0.0", + "jest-environment-jsdom": "^30.3.0", + "vite": "^5.4.21" "@babel/preset-env": "7.29.7", "@babel/preset-react": "7.29.7", "@playwright/test": "1.60.0", diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 3e89185..b9b8124 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -7,14 +7,12 @@ import ErrorBoundary from './components/ErrorBoundary'; import './index.css'; async function enableMocking() { - if (!import.meta.env.DEV) { - return; + if (import.meta.env.DEV) { + const { worker } = await import('./mocks/browser'); + return worker.start(); } - const { worker } = await import('./mocks/browser'); - return worker.start(); } - enableMocking().then(() => { ReactDOM.createRoot(document.getElementById('root')).render( @@ -25,5 +23,4 @@ enableMocking().then(() => { ); -}); - +}); \ No newline at end of file diff --git a/frontend/src/mocks/browser.js b/frontend/src/mocks/browser.js index 0a56427..8750e63 100644 --- a/frontend/src/mocks/browser.js +++ b/frontend/src/mocks/browser.js @@ -1,4 +1,3 @@ -import { setupWorker } from 'msw/browser'; -import { handlers } from './handlers'; - -export const worker = setupWorker(...handlers); +export const worker = { + start: () => Promise.resolve() +} \ No newline at end of file diff --git a/frontend/src/mocks/handlers.js b/frontend/src/mocks/handlers.js index 177aae5..f3fd79b 100644 --- a/frontend/src/mocks/handlers.js +++ b/frontend/src/mocks/handlers.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +export const handlers = [] export const handlers = [ // Authentication diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 16eda1d..77fe0d4 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,8 +1,9 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], + server: { port: 3000, proxy: { @@ -11,6 +12,7 @@ export default defineConfig({ '/verify': 'http://backend:4000', }, }, + test: { environment: 'jsdom', coverage: { @@ -22,4 +24,4 @@ export default defineConfig({ }, }, }, -}); +}) \ No newline at end of file