dbernald2 commited on
Commit
eaeaf28
·
verified ·
1 Parent(s): 9748abf

Initial deploy: n8n with auth proxy and PostgreSQL

Browse files
Files changed (7) hide show
  1. .gitignore +1 -0
  2. Dockerfile +40 -0
  3. README.md +6 -5
  4. package-lock.json +40 -0
  5. package.json +8 -0
  6. proxy.js +120 -0
  7. start.sh +13 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ PROJECT.md
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM n8nio/n8n:latest
2
+
3
+ USER root
4
+
5
+ # Install proxy deps from lockfile — exact versions + integrity hashes verified
6
+ COPY package.json package-lock.json /proxy/
7
+ RUN cd /proxy && npm ci --ignore-scripts
8
+
9
+ # nginx (proxy) on 5678 (exposed), n8n internally on 5679
10
+ # n8n on 5200, Task Broker keeps its default 5679, proxy listens on 5678
11
+ ENV N8N_PORT=5200
12
+ ENV N8N_USER_FOLDER=/data
13
+ ENV N8N_PROTOCOL=https
14
+ ENV N8N_HOST=0.0.0.0
15
+ ENV WEBHOOK_URL=https://vor-tests-philosophical-breadstick.hf.space/
16
+ ENV GENERIC_TIMEZONE=UTC
17
+
18
+ # Security hardening
19
+ ENV N8N_BLOCK_ENV_ACCESS_IN_NODE=true
20
+ ENV N8N_SECURE_COOKIE=true
21
+ ENV N8N_METRICS=false
22
+ ENV N8N_HIDE_USAGE_PAGE=true
23
+ ENV N8N_DIAGNOSTICS_ENABLED=false
24
+ ENV N8N_VERSION_NOTIFICATIONS_ENABLED=false
25
+
26
+ # PostgreSQL — credentials injected at runtime via HF Space secrets
27
+ ENV DB_TYPE=postgresdb
28
+ ENV DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED=false
29
+
30
+ COPY proxy.js /proxy.js
31
+ COPY start.sh /start.sh
32
+ RUN chmod +x /start.sh
33
+
34
+ # Drop root — run as the node user the n8n image ships with
35
+ USER node
36
+
37
+ EXPOSE 5678
38
+
39
+ ENTRYPOINT []
40
+ CMD ["/bin/sh", "/start.sh"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- title: Philosophical Breadstick
3
- emoji: 🏢
4
- colorFrom: purple
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: PhilosophicalBreadstick
3
+ emoji: 🥖
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ app_port: 5678
9
  ---
10
 
11
+ # Philosophical Breadstick
package-lock.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "n8n-proxy",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 2,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "n8n-proxy",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "http-proxy": "1.18.1"
12
+ }
13
+ },
14
+ "node_modules/eventemitter3": {
15
+ "version": "4.0.7",
16
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
17
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
18
+ },
19
+ "node_modules/follow-redirects": {
20
+ "version": "1.16.0",
21
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
22
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="
23
+ },
24
+ "node_modules/http-proxy": {
25
+ "version": "1.18.1",
26
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
27
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
28
+ "dependencies": {
29
+ "eventemitter3": "^4.0.0",
30
+ "follow-redirects": "^1.0.0",
31
+ "requires-port": "^1.0.0"
32
+ }
33
+ },
34
+ "node_modules/requires-port": {
35
+ "version": "1.0.0",
36
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
37
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
38
+ }
39
+ }
40
+ }
package.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "n8n-proxy",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "http-proxy": "1.18.1"
7
+ }
8
+ }
proxy.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+ const httpProxy = require('/proxy/node_modules/http-proxy');
3
+ const http = require('http');
4
+
5
+ const N8N_PORT = parseInt(process.env.N8N_PORT || '5200');
6
+ const PROXY_PORT = 5678;
7
+ const USER = process.env.NGINX_USER;
8
+ const PASS = process.env.NGINX_PASSWORD;
9
+ const WEBHOOK_RE = /^\/webhook(-test)?\//;
10
+
11
+ // Rate limiter: max 10 failed attempts per IP within 15 minutes → block for 15 minutes
12
+ const MAX_FAILURES = 10;
13
+ const WINDOW_MS = 15 * 60 * 1000;
14
+ const BLOCK_MS = 15 * 60 * 1000;
15
+ const failures = new Map(); // ip → { count, windowStart, blockedUntil }
16
+
17
+ function getClientIp(req) {
18
+ const forwarded = req.headers['x-forwarded-for'];
19
+ return (forwarded ? forwarded.split(',')[0] : req.socket.remoteAddress).trim();
20
+ }
21
+
22
+ function isBlocked(ip) {
23
+ const entry = failures.get(ip);
24
+ if (!entry) return false;
25
+ if (entry.blockedUntil && Date.now() < entry.blockedUntil) return true;
26
+ // Block expired — reset
27
+ if (entry.blockedUntil) failures.delete(ip);
28
+ return false;
29
+ }
30
+
31
+ function recordFailure(ip) {
32
+ const now = Date.now();
33
+ const entry = failures.get(ip) || { count: 0, windowStart: now, blockedUntil: null };
34
+ // Reset window if it has expired
35
+ if (now - entry.windowStart > WINDOW_MS) {
36
+ entry.count = 0;
37
+ entry.windowStart = now;
38
+ }
39
+ entry.count += 1;
40
+ if (entry.count >= MAX_FAILURES) {
41
+ entry.blockedUntil = now + BLOCK_MS;
42
+ console.warn(`[rate-limit] Blocked ${ip} after ${entry.count} failed attempts`);
43
+ }
44
+ failures.set(ip, entry);
45
+ }
46
+
47
+ function clearFailures(ip) {
48
+ failures.delete(ip);
49
+ }
50
+
51
+ // Purge stale entries every 30 minutes to prevent memory growth
52
+ setInterval(() => {
53
+ const now = Date.now();
54
+ for (const [ip, entry] of failures) {
55
+ const expired = entry.blockedUntil
56
+ ? now > entry.blockedUntil
57
+ : now - entry.windowStart > WINDOW_MS;
58
+ if (expired) failures.delete(ip);
59
+ }
60
+ }, 30 * 60 * 1000);
61
+
62
+ const proxy = httpProxy.createProxyServer({
63
+ target: `http://127.0.0.1:${N8N_PORT}`,
64
+ ws: true,
65
+ });
66
+
67
+ proxy.on('error', (err, req, res) => {
68
+ if (res && res.writeHead) {
69
+ res.writeHead(502);
70
+ res.end('n8n starting up, please refresh in a moment');
71
+ }
72
+ });
73
+
74
+ function isAuthorized(req) {
75
+ if (WEBHOOK_RE.test(req.url)) return true;
76
+ const auth = req.headers['authorization'];
77
+ if (!auth || !auth.startsWith('Basic ')) return false;
78
+ const decoded = Buffer.from(auth.slice(6), 'base64').toString();
79
+ const colon = decoded.indexOf(':');
80
+ const user = decoded.slice(0, colon);
81
+ const pass = decoded.slice(colon + 1);
82
+ return user === USER && pass === PASS;
83
+ }
84
+
85
+ const server = http.createServer((req, res) => {
86
+ // Webhooks always pass through
87
+ if (WEBHOOK_RE.test(req.url)) return proxy.web(req, res);
88
+
89
+ const ip = getClientIp(req);
90
+
91
+ if (isBlocked(ip)) {
92
+ res.writeHead(429, { 'Content-Type': 'text/plain', 'Retry-After': '900' });
93
+ res.end('Too many failed attempts. Try again in 15 minutes.');
94
+ return;
95
+ }
96
+
97
+ if (isAuthorized(req)) {
98
+ clearFailures(ip);
99
+ proxy.web(req, res);
100
+ } else {
101
+ recordFailure(ip);
102
+ res.writeHead(401, {
103
+ 'WWW-Authenticate': 'Basic realm="n8n"',
104
+ 'Content-Type': 'text/plain',
105
+ });
106
+ res.end('Unauthorized');
107
+ }
108
+ });
109
+
110
+ server.on('upgrade', (req, socket, head) => {
111
+ if (WEBHOOK_RE.test(req.url) || isAuthorized(req)) {
112
+ proxy.ws(req, socket, head);
113
+ } else {
114
+ socket.destroy();
115
+ }
116
+ });
117
+
118
+ server.listen(PROXY_PORT, () => {
119
+ console.log(`Auth proxy ready on :${PROXY_PORT} → n8n on :${N8N_PORT}`);
120
+ });
start.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ # Fix config file permissions (mirrors what the n8n entrypoint does)
5
+ if [ -f "${N8N_USER_FOLDER}/.n8n/config" ]; then
6
+ chmod 600 "${N8N_USER_FOLDER}/.n8n/config"
7
+ fi
8
+
9
+ # Start the auth proxy in the background
10
+ /usr/local/bin/node /proxy.js &
11
+
12
+ # Start n8n in the foreground
13
+ exec /usr/local/bin/n8n