Skip to content

Commit e1b4aa8

Browse files
authored
Update server.js
1 parent 85bfd91 commit e1b4aa8

1 file changed

Lines changed: 45 additions & 49 deletions

File tree

backend/server.js

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,29 @@ const fs = require('fs');
44
const path = require('path');
55
const https = require('https');
66
const http = require('http');
7-
const crypto = require('crypto');
87
const { rateLimit } = require('express-rate-limit');
98

10-
// ── Express app ───────────────────────────────────────
11-
const app = express();
12-
app.use(express.json({ limit: '10mb' }));
13-
14-
const FRONTEND = path.join(__dirname, '../frontend');
15-
const DATA_FILE = process.env.DATA_FILE || '/data/vault.enc';
16-
const DATA_DIR = path.dirname(DATA_FILE);
17-
const CERT_DIR = process.env.CERT_DIR || '/data/certs';
18-
const PORT = parseInt(process.env.PORT || '3000', 10);
19-
const HTTP_PORT = parseInt(process.env.HTTP_PORT || String(PORT + 1), 10);
9+
// ── Config ────────────────────────────────────────────
10+
const FRONTEND = path.join(__dirname, '../frontend');
11+
const DATA_FILE = process.env.DATA_FILE || '/data/vault.enc';
12+
const DATA_DIR = path.dirname(DATA_FILE);
13+
const CERT_DIR = process.env.CERT_DIR || '/data/certs';
14+
const PORT = parseInt(process.env.PORT || '3000', 10); // HTTPS (main)
15+
const HEALTH_PORT= parseInt(process.env.HEALTH_PORT|| '3002', 10); // HTTP (healthcheck only)
2016

17+
// Ensure directories exist
2118
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch {}
2219
try { fs.mkdirSync(CERT_DIR, { recursive: true }); } catch {}
2320

21+
// ── Express app ───────────────────────────────────────
22+
const app = express();
23+
app.use(express.json({ limit: '10mb' }));
2424
app.use(express.static(FRONTEND));
2525

26-
// ── Security headers ──────────────────────────────────
27-
// These mark the page as a "secure context" so crypto.subtle works
28-
// even when accessed via plain HTTP on a LAN IP
26+
// Headers that allow crypto.subtle on any origin (secure context hint)
2927
app.use((req, res, next) => {
30-
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
31-
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
28+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
29+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
3230
next();
3331
});
3432

@@ -39,9 +37,9 @@ const vaultLimiter = rateLimit({
3937
message: { error: 'Too many requests, try again later.' }
4038
});
4139

42-
// ── API ───────────────────────────────────────────────
40+
// ── Routes ────────────────────────────────────────────
4341
app.get('/api/health', (_, res) => {
44-
res.json({ status: 'ok', writable: canWrite(), https: true });
42+
res.json({ status: 'ok', writable: canWrite() });
4543
});
4644

4745
app.get('/api/vault/exists', vaultLimiter, (_, res) => {
@@ -79,64 +77,62 @@ function canWrite() {
7977
catch { return false; }
8078
}
8179

82-
// ── Self-signed certificate (generated once, persisted in /data/certs) ───────
83-
// Browser will show "Not secure" once — user clicks Advanced → Proceed.
84-
// After that crypto.subtle works on every visit.
80+
// ── TLS cert (generated once, stored in volume) ───────
8581
const CERT_FILE = path.join(CERT_DIR, 'cert.pem');
86-
const KEY_FILE = path.join(CERT_DIR, 'key.pem');
82+
const KEY_FILE = path.join(CERT_DIR, 'key.pem');
8783

88-
function genSelfSigned() {
89-
// Use openssl if available (alpine has it), otherwise fall back to pure-node
84+
function genCert() {
9085
try {
9186
const { execSync } = require('child_process');
9287
execSync(
9388
`openssl req -x509 -newkey rsa:2048 -keyout "${KEY_FILE}" -out "${CERT_FILE}" ` +
94-
`-days 3650 -nodes -subj "/CN=andromeda-vault" ` +
95-
`-addext "subjectAltName=IP:0.0.0.0,DNS:localhost" 2>/dev/null`,
96-
{ timeout: 15000 }
89+
`-days 3650 -nodes -subj "/CN=andromeda-vault" 2>/dev/null`,
90+
{ timeout: 20000 }
9791
);
98-
console.log(' ✦ Generated self-signed TLS certificate');
9992
return true;
10093
} catch (e) {
101-
console.error(' ✗ openssl not available, falling back to HTTP only:', e.message);
94+
console.error(' ✗ openssl failed:', e.message);
10295
return false;
10396
}
10497
}
10598

106-
function loadOrCreateCert() {
107-
if (fs.existsSync(CERT_FILE) && fs.existsSync(KEY_FILE)) {
99+
function loadCert() {
100+
if (fs.existsSync(CERT_FILE) && fs.existsSync(KEY_FILE))
108101
return { cert: fs.readFileSync(CERT_FILE), key: fs.readFileSync(KEY_FILE) };
109-
}
110-
if (genSelfSigned()) {
102+
console.log(' ✦ Generating self-signed TLS certificate…');
103+
if (genCert())
111104
return { cert: fs.readFileSync(CERT_FILE), key: fs.readFileSync(KEY_FILE) };
112-
}
113105
return null;
114106
}
115107

116108
// ── Start ─────────────────────────────────────────────
117-
const creds = loadOrCreateCert();
118109

110+
// 1. Plain HTTP health-check server — always starts immediately on HEALTH_PORT.
111+
// Only used by Docker HEALTHCHECK (internal only, not exposed in compose).
112+
http.createServer((req, res) => {
113+
if (req.url === '/health') {
114+
res.writeHead(200, { 'Content-Type': 'application/json' });
115+
res.end(JSON.stringify({ status: 'ok' }));
116+
} else {
117+
res.writeHead(404); res.end();
118+
}
119+
}).listen(HEALTH_PORT, '127.0.0.1', () => {
120+
console.log(` ✦ Health endpoint → http://127.0.0.1:${HEALTH_PORT}/health`);
121+
});
122+
123+
// 2. Main app server — HTTPS if cert available, plain HTTP otherwise.
124+
const creds = loadCert();
119125
if (creds) {
120-
// HTTPS — crypto.subtle works on all browsers
121126
https.createServer(creds, app).listen(PORT, '0.0.0.0', () => {
122-
console.log(`\n ✦ Andromeda → https://localhost:${PORT} (HTTPS)`);
127+
console.log(`\n ✦ Andromeda → https://localhost:${PORT}`);
123128
console.log(` ✦ Data file → ${DATA_FILE}`);
124129
console.log(` ✦ Writable → ${canWrite()}`);
125130
console.log(`\n ⚠ First visit: click "Advanced → Proceed" to accept the self-signed cert.\n`);
126131
});
127-
// HTTP redirect on PORT+1 (optional)
128-
http.createServer((req, res) => {
129-
const host = req.headers.host?.replace(/:.*/, '') || 'localhost';
130-
res.writeHead(301, { Location: `https://${host}:${PORT}${req.url}` });
131-
res.end();
132-
}).listen(HTTP_PORT, '0.0.0.0', () => {
133-
console.log(` ✦ HTTP redirect → :${HTTP_PORT} → https://:${PORT}`);
134-
});
135132
} else {
136-
// HTTP fallback — crypto.subtle only works from localhost in this mode
133+
// Fallback: plain HTTP (crypto.subtle only works from localhost)
137134
http.createServer(app).listen(PORT, '0.0.0.0', () => {
138-
console.log(`\n ✦ Andromeda → http://localhost:${PORT} (HTTP only)`);
139-
console.log(` ⚠ WARNING: crypto.subtle unavailable on non-localhost HTTP.`);
140-
console.log(` ⚠ Access via http://localhost:${PORT} or set up HTTPS.\n`);
135+
console.log(`\n ✦ Andromeda → http://localhost:${PORT} (HTTP — HTTPS cert failed)`);
136+
console.log(` ⚠ Access via http://localhost:${PORT} only — LAN access requires HTTPS.\n`);
141137
});
142138
}

0 commit comments

Comments
 (0)