Skip to content

Commit 382838e

Browse files
fix: Ensure Railway deploys full server with authentication endpoints
- Fix railway.toml to start full server (index.js) instead of minimal server - Align railway.json healthcheck path to /api/health - Fix malformed admin login/logout routes in Railway server source - Add SQLite table existence check in admin session cleanup - Verified all auth endpoints work: /auth/signup, /auth/login, /admin/* - Railway will now properly deploy with full authentication functionality
1 parent f073089 commit 382838e

3 files changed

Lines changed: 68 additions & 61 deletions

File tree

railway.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"deploy": {
1212
"restartPolicyType": "ON_FAILURE",
1313
"restartPolicyMaxRetries": 3,
14-
"healthcheckPath": "/health",
14+
"healthcheckPath": "/api/health",
1515
"healthcheckTimeout": 30
1616
}
1717
}

railway.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
builder = "nixpacks"
33

44
[deploy]
5-
startCommand = "node dist/servers/railway/minimal.js"
5+
startCommand = "node dist/servers/railway/index.js"
66
healthcheckPath = "/api/health"
77
restartPolicyType = "on_failure"
88
restartPolicyMaxRetries = 3

src/servers/railway/index.ts

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,11 @@ class RailwayMCPServer {
368368
if (this.pgPool) {
369369
await this.pgPool.query('DELETE FROM admin_sessions WHERE expires_at <= NOW()');
370370
} else if (this.db) {
371-
this.db.prepare('DELETE FROM admin_sessions WHERE datetime(expires_at) <= datetime("now")').run();
371+
// Check if table exists before cleanup
372+
const tableExists = this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='admin_sessions'`).get();
373+
if (tableExists) {
374+
this.db.prepare('DELETE FROM admin_sessions WHERE datetime(expires_at) <= datetime("now")').run();
375+
}
372376
}
373377
} catch (e) {
374378
console.warn('Admin session cleanup failed:', e);
@@ -1226,6 +1230,67 @@ loadSessions();
12261230
}
12271231
});
12281232
}
1233+
1234+
// Admin login/logout routes
1235+
this.app.get('/admin/login', (_req, res) => {
1236+
res.setHeader('Content-Type', 'text/html');
1237+
res.send(`<!doctype html><html><head><meta charset="utf-8"/><title>Admin Login</title>
1238+
<style>body{font-family:system-ui;margin:40px} input{padding:8px;margin:4px} button{padding:8px}</style></head>
1239+
<body><h3>Admin Login</h3>
1240+
<p>Paste an admin API key to manage projects and members.</p>
1241+
<form method="POST" action="/admin/login">
1242+
<input type="password" name="apiKey" placeholder="sk-..." style="min-width:360px" required/>
1243+
<div><button type="submit">Login</button></div>
1244+
<p style="color:#666">Your key is validated server-side and not stored in the browser; a short-lived session cookie is created.</p>
1245+
</form>
1246+
</body></html>`);
1247+
});
1248+
1249+
// Accept urlencoded form
1250+
this.app.post('/admin/login', express.urlencoded({ extended: false }), async (req, res) => {
1251+
try {
1252+
const apiKey = req.body?.apiKey || '';
1253+
if (!apiKey) return res.status(400).send('Missing API key');
1254+
const u = await this.validateApiKey(apiKey);
1255+
if (!u || (u as any).role !== 'admin') return res.status(403).send('Not an admin API key');
1256+
// Create DB-backed admin session and sign JWT
1257+
const jti = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
1258+
const hours = parseInt(process.env['ADMIN_SESSION_HOURS'] || '8', 10);
1259+
const expMs = Date.now() + hours * 3600 * 1000;
1260+
const expDateIso = new Date(expMs).toISOString();
1261+
const ua = req.headers['user-agent'] || '';
1262+
const ip = (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || '';
1263+
if (this.pgPool) {
1264+
await this.pgPool.query('INSERT INTO admin_sessions (id, user_id, expires_at, user_agent, ip) VALUES ($1, $2, $3, $4, $5)', [jti, (u as any).id, expDateIso, ua, ip]);
1265+
} else {
1266+
this.db.prepare('INSERT INTO admin_sessions (id, user_id, expires_at, user_agent, ip) VALUES (?, ?, ?, ?, ?)').run(jti, (u as any).id, expDateIso, ua, ip);
1267+
}
1268+
const token = jwt.sign({ sub: (u as any).id, role: 'admin', jti }, process.env['ADMIN_JWT_SECRET'] || 'dev-admin-secret', { expiresIn: hours + 'h' });
1269+
setJwtCookie(res, token);
1270+
res.redirect('/admin');
1271+
} catch (e: any) {
1272+
res.status(500).send('Login failed');
1273+
}
1274+
});
1275+
1276+
this.app.get('/admin/logout', async (req, res) => {
1277+
const cookies = parseCookies(req.headers.cookie);
1278+
const t = cookies['sm_admin_jwt'];
1279+
if (t) {
1280+
const verified = verifyAdminJwt(t);
1281+
if (verified) {
1282+
try {
1283+
if (this.pgPool) {
1284+
await this.pgPool.query('DELETE FROM admin_sessions WHERE id = $1', [verified.jti]);
1285+
} else {
1286+
this.db.prepare('DELETE FROM admin_sessions WHERE id = ?').run(verified.jti);
1287+
}
1288+
} catch {}
1289+
}
1290+
}
1291+
clearJwtCookie(res);
1292+
res.redirect('/admin/login');
1293+
});
12291294
}
12301295

12311296
private setupWebSocket(): void {
@@ -1480,61 +1545,3 @@ process.on('SIGINT', () => {
14801545
console.log('Shutting down...');
14811546
process.exit(0);
14821547
});
1483-
// Admin login/logout
1484-
this.app.get('/admin/login', (_req, res) => {
1485-
res.setHeader('Content-Type', 'text/html');
1486-
res.send(`<!doctype html><html><head><meta charset="utf-8"/><title>Admin Login</title>
1487-
<style>body{font-family:system-ui;margin:40px} input{padding:8px;margin:4px} button{padding:8px}</style></head>
1488-
<body><h3>Admin Login</h3>
1489-
<p>Paste an admin API key to manage projects and members.</p>
1490-
<form method="POST" action="/admin/login">
1491-
<input type="password" name="apiKey" placeholder="sk-..." style="min-width:360px" required/>
1492-
<div><button type="submit">Login</button></div>
1493-
<p style="color:#666">Your key is validated server-side and not stored in the browser; a short-lived session cookie is created.</p>
1494-
</form>
1495-
</body></html>`);
1496-
});
1497-
// Accept urlencoded form
1498-
this.app.post('/admin/login', express.urlencoded({ extended: false }), async (req, res) => {
1499-
try {
1500-
const apiKey = req.body?.apiKey || '';
1501-
if (!apiKey) return res.status(400).send('Missing API key');
1502-
const u = await this.validateApiKey(apiKey);
1503-
if (!u || (u as any).role !== 'admin') return res.status(403).send('Not an admin API key');
1504-
// Create DB-backed admin session and sign JWT
1505-
const jti = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
1506-
const hours = parseInt(process.env['ADMIN_SESSION_HOURS'] || '8', 10);
1507-
const expMs = Date.now() + hours * 3600 * 1000;
1508-
const expDateIso = new Date(expMs).toISOString();
1509-
const ua = req.headers['user-agent'] || '';
1510-
const ip = (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || '';
1511-
if (this.pgPool) {
1512-
await this.pgPool.query('INSERT INTO admin_sessions (id, user_id, expires_at, user_agent, ip) VALUES ($1, $2, $3, $4, $5)', [jti, (u as any).id, expDateIso, ua, ip]);
1513-
} else {
1514-
this.db.prepare('INSERT INTO admin_sessions (id, user_id, expires_at, user_agent, ip) VALUES (?, ?, ?, ?, ?)').run(jti, (u as any).id, expDateIso, ua, ip);
1515-
}
1516-
const token = jwt.sign({ sub: (u as any).id, role: 'admin', jti }, process.env['ADMIN_JWT_SECRET'] || 'dev-admin-secret', { expiresIn: hours + 'h' });
1517-
setJwtCookie(res, token);
1518-
res.redirect('/admin');
1519-
} catch (e: any) {
1520-
res.status(500).send('Login failed');
1521-
}
1522-
});
1523-
this.app.get('/admin/logout', async (req, res) => {
1524-
const cookies = parseCookies(req.headers.cookie);
1525-
const t = cookies['sm_admin_jwt'];
1526-
if (t) {
1527-
const verified = verifyAdminJwt(t);
1528-
if (verified) {
1529-
try {
1530-
if (this.pgPool) {
1531-
await this.pgPool.query('DELETE FROM admin_sessions WHERE id = $1', [verified.jti]);
1532-
} else {
1533-
this.db.prepare('DELETE FROM admin_sessions WHERE id = ?').run(verified.jti);
1534-
}
1535-
} catch {}
1536-
}
1537-
}
1538-
clearJwtCookie(res);
1539-
res.redirect('/admin/login');
1540-
});

0 commit comments

Comments
 (0)