Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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;"]
114 changes: 3 additions & 111 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
12 changes: 12 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<React.StrictMode>
Expand All @@ -25,5 +23,4 @@ enableMocking().then(() => {
</BrowserRouter>
</React.StrictMode>
);
});

});
7 changes: 3 additions & 4 deletions frontend/src/mocks/browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
export const worker = {
start: () => Promise.resolve()
}
2 changes: 1 addition & 1 deletion frontend/src/mocks/handlers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { http, HttpResponse } from 'msw';
export const handlers = []

export const handlers = [
// Authentication
Expand Down
8 changes: 5 additions & 3 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -11,6 +12,7 @@ export default defineConfig({
'/verify': 'http://backend:4000',
},
},

test: {
environment: 'jsdom',
coverage: {
Expand All @@ -22,4 +24,4 @@ export default defineConfig({
},
},
},
});
})