2021-12-29 18:08:41 -05:00
|
|
|
import express from 'express';
|
|
|
|
|
import sessions from 'express-session';
|
|
|
|
|
import cookieParser from 'cookie-parser';
|
|
|
|
|
import bodyParser from 'body-parser';
|
|
|
|
|
import { join, dirname } from 'path';
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
import CORS from './cors.js';
|
|
|
|
|
import CSRF from './csrf.js';
|
|
|
|
|
import sharedRoutes from '../routes/shared/index.js';
|
|
|
|
|
import lndRoutes from '../routes/lnd/index.js';
|
2022-05-01 13:35:20 -04:00
|
|
|
import clnRoutes from '../routes/cln/index.js';
|
2021-12-29 18:08:41 -05:00
|
|
|
import eclRoutes from '../routes/eclair/index.js';
|
2023-09-28 20:18:14 -07:00
|
|
|
import { Database } from './database.js';
|
2021-12-29 18:08:41 -05:00
|
|
|
import { Common } from './common.js';
|
|
|
|
|
import { Logger } from './logger.js';
|
|
|
|
|
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
|
|
|
|
|
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
|
|
|
|
|
const ONE_DAY = 1000 * 60 * 60 * 24;
|
|
|
|
|
export class ExpressApplication {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.app = express();
|
|
|
|
|
this.logger = Logger;
|
|
|
|
|
this.common = Common;
|
|
|
|
|
this.eclWsClient = ECLWSClient;
|
2023-12-05 20:32:05 -08:00
|
|
|
// public clWsClient: CLWebSocketClient = CLWSClient;
|
2021-12-29 18:08:41 -05:00
|
|
|
this.lndWsClient = LNDWSClient;
|
2023-09-28 20:18:14 -07:00
|
|
|
this.databaseService = Database;
|
2021-12-29 18:08:41 -05:00
|
|
|
this.directoryName = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
this.getApp = () => this.app;
|
|
|
|
|
this.setCORS = () => { CORS.mount(this.app); };
|
|
|
|
|
this.setCSRF = () => { CSRF.mount(this.app); };
|
|
|
|
|
this.setApplicationRoutes = () => {
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' });
|
2021-12-29 18:08:41 -05:00
|
|
|
this.app.use(this.common.baseHref + '/api', sharedRoutes);
|
|
|
|
|
this.app.use(this.common.baseHref + '/api/lnd', lndRoutes);
|
2022-05-01 13:35:20 -04:00
|
|
|
this.app.use(this.common.baseHref + '/api/cln', clnRoutes);
|
2021-12-29 18:08:41 -05:00
|
|
|
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
|
|
|
|
|
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
|
|
|
|
|
this.app.use((req, res, next) => {
|
Replace deprecated csurf with csrf-csrf
csurf has been deprecated since 2022 and pins an old cookie release
with a known advisory; npm's only fix is a downgrade (issue #1634,
item 2). csrf-csrf v4 implements the same double-submit-cookie pattern
with an HMAC-signed, session-bound token keyed on the existing boot
secret (common.secret_key).
The frontend contract is unchanged: the token still arrives via the
XSRF-TOKEN cookie/header and is echoed as x-xsrf-token (all token
sources csurf accepted are still read), the signed cookie keeps the
_csrf name (now httpOnly, secure:false to match the session cookie on
plain-HTTP deployments), doubleCsrfProtection attaches req.csrfToken
so app.ts keeps working, and the error code is EBADCSRFTOKEN - already
handled in app.ts. The websocket upgrade check in authCheck.ts now
routes through the shared middleware; upgrade requests are GETs, so
its pass-through semantics are unchanged.
One fix this surfaced: app.ts called req.csrfToken() twice (cookie and
header). Under csurf every token validated against a stable secret;
under csrf-csrf each first-visit call mints a new token, desyncing the
XSRF-TOKEN cookie from the _csrf cookie it must equal. The token is
now generated once per request.
Tokens are session-bound, so a token stolen from one session no longer
validates in another - a check csurf's cookie mode did not perform.
Production npm audit drops from 6 low findings to 4, all in the
crypto-browserify/elliptic chain tracked in #1634.
Verified against the docker regtest fixture: both API suites (43
checks across LND, CLN and Eclair) plus a dedicated CSRF battery -
valid-token auth, missing token 403, garbage token 403, cross-session
replay 403, token stability across requests, the XSRF-TOKEN response
header for Quickpay, and the websocket handshake. Lint and build are
clean.
2026-07-19 17:03:53 -07:00
|
|
|
// Generate the token once per request: with csrf-csrf every call mints a
|
|
|
|
|
// new token on a first visit, so calling twice would desync the cookie
|
|
|
|
|
// from the header and the _csrf cookie it must match.
|
|
|
|
|
const csrfToken = req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : '';
|
|
|
|
|
res.cookie('XSRF-TOKEN', csrfToken); // RTL Angular Frontend
|
|
|
|
|
res.setHeader('XSRF-TOKEN', csrfToken); // RTL Quickpay JQuery
|
2021-12-29 18:08:41 -05:00
|
|
|
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
|
|
|
|
|
});
|
2022-09-23 17:35:25 -07:00
|
|
|
this.app.use((err, req, res, next) => {
|
2026-07-19 17:41:25 -07:00
|
|
|
this.handleApplicationErrors(err, req, res);
|
2022-09-23 17:35:25 -07:00
|
|
|
next();
|
|
|
|
|
});
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
|
2021-12-29 18:08:41 -05:00
|
|
|
};
|
2026-07-19 17:41:25 -07:00
|
|
|
this.handleApplicationErrors = (err, req, res) => {
|
2021-12-29 18:08:41 -05:00
|
|
|
switch (err.code) {
|
|
|
|
|
case 'EACCES':
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' });
|
2021-12-29 18:08:41 -05:00
|
|
|
res.status(406).send('Server requires elevated privileges.');
|
|
|
|
|
break;
|
|
|
|
|
case 'EADDRINUSE':
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' });
|
2021-12-29 18:08:41 -05:00
|
|
|
res.status(409).send('Server is already in use.');
|
|
|
|
|
break;
|
|
|
|
|
case 'ECONNREFUSED':
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' });
|
2021-12-29 18:08:41 -05:00
|
|
|
res.status(401).send('Server is down/locked.');
|
|
|
|
|
break;
|
|
|
|
|
case 'EBADCSRFTOKEN':
|
2026-07-19 17:41:25 -07:00
|
|
|
// Re-mint the token for the current session so a client retry succeeds
|
|
|
|
|
// (the stale one may be bound to a destroyed session or rotated secret).
|
|
|
|
|
try {
|
|
|
|
|
const csrfToken = CSRF.reMintToken(req, res);
|
|
|
|
|
res.cookie('XSRF-TOKEN', csrfToken);
|
|
|
|
|
res.setHeader('XSRF-TOKEN', csrfToken);
|
|
|
|
|
}
|
|
|
|
|
catch (csrfError) {
|
|
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'CSRF Token Re-Mint Failed', error: csrfError });
|
|
|
|
|
}
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' });
|
2021-12-29 18:08:41 -05:00
|
|
|
res.status(403).send('Invalid CSRF token, form tempered.');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err });
|
2021-12-29 18:08:41 -05:00
|
|
|
res.status(400).send(JSON.stringify(err));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' });
|
2021-12-29 18:08:41 -05:00
|
|
|
this.app.set('trust proxy', true);
|
|
|
|
|
this.app.use(sessions({ secret: this.common.secret_key, saveUninitialized: true, cookie: { secure: false, maxAge: ONE_DAY }, resave: false }));
|
|
|
|
|
this.app.use(cookieParser(this.common.secret_key));
|
|
|
|
|
this.app.use(bodyParser.json({ limit: '25mb' }));
|
|
|
|
|
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
|
|
|
|
|
this.setCORS();
|
|
|
|
|
this.setCSRF();
|
|
|
|
|
this.setApplicationRoutes();
|
2023-09-28 20:18:14 -07:00
|
|
|
this.databaseService.migrateDatabase();
|
2021-12-29 18:08:41 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
export default ExpressApplication;
|