revert
All checks were successful
Deploy Production (qr.crewsportswear.app) / deploy (push) Successful in 35s

This commit is contained in:
Frank John Begornia
2026-04-03 00:49:03 +08:00
parent 4d6fd1e348
commit a8f17fbfa5
4 changed files with 2 additions and 81 deletions

View File

@@ -75,19 +75,6 @@ jobs:
cd "$DEPLOY_DIR" cd "$DEPLOY_DIR"
echo "Checking .env file"
if [ ! -f .env ]; then
echo ".env file not found at $DEPLOY_DIR/.env"
echo "Please create it first with optional/custom variables such as:"
echo " - TRUST_PROXY"
echo " - ALLOWED_QR_IPS"
exit 1
fi
echo "Fixing .env permissions"
sudo chown $USER:$USER .env
sudo chmod 600 .env
echo "Ensure networks" echo "Ensure networks"
docker network inspect traefik-public >/dev/null 2>&1 || \ docker network inspect traefik-public >/dev/null 2>&1 || \
docker network create traefik-public docker network create traefik-public

View File

@@ -70,19 +70,3 @@ Notes:
- Internal service port is `3000` - Internal service port is `3000`
- TLS uses Traefik Let's Encrypt via `tls.certresolver=le` - TLS uses Traefik Let's Encrypt via `tls.certresolver=le`
- Includes HTTP -> HTTPS redirect via Traefik labels - Includes HTTP -> HTTPS redirect via Traefik labels
## Restrict QR generation by IP
The `/api/qr` endpoints support IP allowlisting via environment variable.
- `ALLOWED_QR_IPS`: comma-separated list of allowed client IPs
- `TRUST_PROXY`: keep this `true` behind Traefik so client IP is read from forwarded headers
Example `.env` values for production:
```env
ALLOWED_QR_IPS=203.0.113.10,198.51.100.22
TRUST_PROXY=true
```
If `ALLOWED_QR_IPS` is empty, IP filtering is disabled.

View File

@@ -9,8 +9,6 @@ services:
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3000 - PORT=3000
- TRUST_PROXY=${TRUST_PROXY:-true}
- ALLOWED_QR_IPS=${ALLOWED_QR_IPS:-}
healthcheck: healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"] test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s interval: 30s

View File

@@ -3,57 +3,9 @@ const QRCode = require("qrcode");
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
const TRUST_PROXY = process.env.TRUST_PROXY !== "false";
const ALLOWED_QR_IPS = new Set(
(process.env.ALLOWED_QR_IPS || "")
.split(",")
.map((ip) => ip.trim())
.filter(Boolean)
.map((ip) => normalizeIp(ip))
);
app.set("trust proxy", TRUST_PROXY);
app.use(express.json({ limit: "100kb" })); app.use(express.json({ limit: "100kb" }));
function normalizeIp(ip) {
if (!ip || typeof ip !== "string") {
return "";
}
const trimmed = ip.trim();
const withoutMappedV4Prefix = trimmed.startsWith("::ffff:") ? trimmed.slice(7) : trimmed;
const ipv4WithPortMatch = withoutMappedV4Prefix.match(/^(\d+\.\d+\.\d+\.\d+):(\d+)$/);
if (ipv4WithPortMatch) {
return ipv4WithPortMatch[1];
}
return withoutMappedV4Prefix;
}
function getClientIp(req) {
const forwardedFor = req.headers["x-forwarded-for"];
if (typeof forwardedFor === "string" && forwardedFor.trim()) {
return normalizeIp(forwardedFor.split(",")[0]);
}
return normalizeIp(req.ip || req.socket?.remoteAddress || "");
}
function enforceQrIpAllowlist(req, res, next) {
if (ALLOWED_QR_IPS.size === 0) {
return next();
}
const clientIp = getClientIp(req);
if (ALLOWED_QR_IPS.has(clientIp)) {
return next();
}
return res.status(403).json({ error: "Access denied for this IP address" });
}
function parseIntWithBounds(value, fallback, min, max) { function parseIntWithBounds(value, fallback, min, max) {
const parsed = Number.parseInt(value, 10); const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed)) { if (!Number.isFinite(parsed)) {
@@ -118,8 +70,8 @@ app.get("/health", (_req, res) => {
res.json({ status: "ok" }); res.json({ status: "ok" });
}); });
app.get("/api/qr", enforceQrIpAllowlist, (req, res, next) => sendQrPng(req.query, res, next)); app.get("/api/qr", (req, res, next) => sendQrPng(req.query, res, next));
app.post("/api/qr", enforceQrIpAllowlist, (req, res, next) => sendQrPng(req.body, res, next)); app.post("/api/qr", (req, res, next) => sendQrPng(req.body, res, next));
app.use((err, _req, res, _next) => { app.use((err, _req, res, _next) => {
console.error(err); console.error(err);